如何通过Cron Job可靠运行长时PHP爬虫脚本并实现断点续爬
爬虫稳定性与断点续爬解决方案
一、断点续爬实现
- 状态持久化存储:创建数据库表(如
crawl_tasks),字段包含url、status(待爬/爬取中/已爬/失败)、last_attempt_time、price_data。每次脚本启动时,优先选取status='待爬'或status='失败'且距上次尝试超1小时的URL,避免重复或频繁重试失败链接。 - 原子状态更新:请求前将URL标记为
爬取中,防止多进程重复处理;请求成功则更新为已爬并存储价格,失败则标记为失败,后续可重试。 - 核心代码片段:
// 初始化PDO连接 $pdo = new PDO('mysql:host=localhost;dbname=crawl_db', 'user', 'pass'); // 获取单个待爬URL $stmt = $pdo->prepare("SELECT url FROM crawl_tasks WHERE status = 'pending' LIMIT 1 FOR UPDATE"); $stmt->execute(); $url = $stmt->fetchColumn(); if ($url) { // 标记为爬取中 $pdo->prepare("UPDATE crawl_tasks SET status = 'processing' WHERE url = ?")->execute([$url]); // 执行爬取逻辑 $price = fetchProductPrice($url); if ($price !== false) { $pdo->prepare("UPDATE crawl_tasks SET status = 'completed', price_data = ?, last_attempt_time = NOW() WHERE url = ?")->execute([$price, $url]); } else { $pdo->prepare("UPDATE crawl_tasks SET status = 'failed', last_attempt_time = NOW() WHERE url = ?")->execute([$url]); } } function fetchProductPrice($url) { try { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) return false; // 此处替换为实际的价格解析逻辑 preg_match('/<span class="price">\$(\d+\.\d+)<\/span>/', $response, $matches); return $matches[1] ?? false; } catch (Exception $e) { error_log("爬取失败 $url: " . $e->getMessage()); return false; } }
二、脚本稳定性保障
- 拆分单次任务:放弃每周一次触发全量任务的方案,改为Cron Job每隔200秒执行一次,每次仅处理1个URL。即使服务器停机,也仅丢失当前未完成的单个任务,重启后自动从下一个待爬URL继续。
- 错误捕获与重试:用
try-catch包裹请求逻辑,捕获网络异常、解析错误;对标记为失败的URL,可设置每日定时重试3次,避免永久遗漏。 - 资源限制:移除
set_time_limit(0),改为设置合理的超时时间(如30秒),避免单个请求阻塞脚本;同时限制脚本内存使用,防止内存泄漏。
三、一周运行时长的合理性
3000个URL分散到一周,平均每201秒请求一次,这个频率远低于大多数网站的反爬阈值,完全合理,几乎不会触发防火墙或过载对方服务器。如果担心效率,可将间隔缩短至100-150秒,同时监控对方网站的响应状态(如是否返回429限流码)。
四、缩短至数小时的处理方案
若要压缩到6小时内完成,需将请求间隔调整至约7秒,此时需注意:
- 动态间隔调整:根据对方响应状态自动调整间隔,若返回429则临时延长至30秒,连续3次失败则标记为
待重试,1小时后再处理。 - 并发控制:保持串行请求,禁止多进程并发,避免瞬间请求量过大被封禁。
- 批次化管理:将URL拆分为多个批次(如每100个一批),数据库新增
batch_id字段,脚本每次处理一个批次的剩余URL,便于跟踪进度。
内容的提问来源于stack exchange,提问作者Jethro Hazelhurst
相关产品推荐
相关产品推荐

