如何提前清理从未被使用的PHP会话?
解决PHP爬虫无用会话堆积的方案
针对爬虫请求创建会话后不复用导致的无效会话堆积问题,核心思路是区分首次创建后未复用的会话与正常用户持续使用的会话,从根源减少无效会话或实现差异化过期回收,以下是具体方案:
方案一:拦截爬虫创建会话(从根源减少无效会话)
大部分爬虫会带有特征性的User-Agent,可提前识别并跳过会话创建:
- 在
session_start()执行前,检查请求的User-Agent,匹配常见爬虫标识(如curl/、Scrapy/、Bot/等),直接跳过会话初始化。 - 代码示例:
// 在session_start()之前执行 $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $crawlerKeywords = ['curl/', 'Scrapy/', 'Bot/', 'Spider/', 'Crawl/']; foreach ($crawlerKeywords as $keyword) { if (stripos($userAgent, $keyword) !== false) { // 跳过会话创建,直接处理爬虫请求 goto skipSessionInit; } } session_start(); // 正常用户的会话逻辑 // ... skipSessionInit: // 爬虫请求的处理逻辑 // ...
- 注意:这种方式可能误判伪装成浏览器的爬虫,但能过滤绝大多数常规爬虫,从源头降低无效会话的产生。
方案二:自定义会话GC逻辑,实现差异化过期
通过给会话添加状态标记,在垃圾回收(GC)时区分处理:
- 标记会话状态:
首次创建会话时记录创建时间与初始状态,会话被复用时更新标记:session_start(); if (!isset($_SESSION['created_at'])) { $_SESSION['created_at'] = time(); $_SESSION['is_reused'] = false; } else { // 会话被复用,标记为有效会话 $_SESSION['is_reused'] = true; } - 自定义GC回收规则:
替换PHP默认的会话存储处理器,根据is_reused标记判断回收时间:// 调整GC触发概率(每100次请求触发1次,可按需修改) ini_set('session.gc_probability', 1); ini_set('session.gc_divisor', 100); ini_set('session.save_handler', 'user'); // 自定义会话存储回调函数(以文件存储为例) function open($savePath, $sessionName) { return true; } function close() { return true; } function read($sessionId) { $file = session_save_path() . '/sess_' . $sessionId; return file_exists($file) ? file_get_contents($file) : ''; } function write($sessionId, $data) { $file = session_save_path() . '/sess_' . $sessionId; return file_put_contents($file, $data, LOCK_EX) !== false; } function destroy($sessionId) { $file = session_save_path() . '/sess_' . $sessionId; return unlink($file); } function gc($maxLifetime) { $currentTime = time(); $normalExpire = ini_get('session.gc_maxlifetime'); // 正常用户会话过期时间 $unusedExpire = 60; // 未复用会话的过期时间(1分钟) $files = glob(session_save_path() . '/sess_*'); foreach ($files as $file) { $fileMtime = filemtime($file); // 超过正常过期时间的会话直接删除 if ($fileMtime < $currentTime - $normalExpire) { unlink($file); continue; } // 读取会话数据,判断是否未被复用且超过短过期时间 $data = file_get_contents($file); if (strpos($data, 'is_reused|b:0;') !== false && $fileMtime < $currentTime - $unusedExpire) { unlink($file); } } return true; } session_set_save_handler('open', 'close', 'read', 'write', 'destroy', 'gc'); register_shutdown_function('session_write_close');- 优化:如果使用数据库存储会话,GC逻辑可直接查询
is_reused=0且created_at < 当前时间-60的记录删除,效率更高。
- 优化:如果使用数据库存储会话,GC逻辑可直接查询
方案三:临时调整新会话的过期时间
通过动态修改session.gc_maxlifetime,让未复用的新会话快速过期:
$isNewSession = empty(session_id()); session_start(); if ($isNewSession && !isset($_SESSION['is_reused'])) { // 新会话未被复用,设置短过期时间 ini_set('session.gc_maxlifetime', 60); $_SESSION['created_at'] = time(); } else { // 正常复用的会话,恢复常规过期时间 ini_set('session.gc_maxlifetime', 3600); // 示例1小时 $_SESSION['is_reused'] = true; }
- 局限性:依赖PHP默认GC的随机触发机制,回收及时性不如自定义GC,建议配合方案一使用。
额外优化建议
- 限制会话创建频率:对同一IP短时间内频繁创建会话的请求,返回429状态码或跳过会话创建,防止恶意爬虫刷会话。
- 跳过无必要的会话:静态资源、公开内容等不需要会话的请求,直接不执行
session_start(),减少无效会话的产生。
内容的提问来源于stack exchange,提问作者Jānis Elmeris
相关产品推荐
相关产品推荐

