Cron调度PHP循环刷新问题及PHP爬虫执行逻辑改造咨询
解决方案:两个PHP调度与爬虫优化问题的处理指南
一、解决Cron调度的PHP循环需页面刷新的问题
首先,你需要页面刷新才能让循环继续,大概率是因为原来的脚本靠浏览器访问、依赖Web服务器运行——这种模式下脚本的执行受限于HTTP请求的生命周期,必须刷新页面才能触发下一轮循环。要彻底解决这个问题,核心是切换到CLI(命令行)模式运行脚本,让Cron直接调用PHP解释器执行,完全脱离Web环境:
- 适配CLI环境修改脚本:去掉脚本中依赖Web的全局变量(比如
$_SERVER、$_POST),所有文件路径改用绝对路径(比如/var/www/html/your_script.php而非./script.php),避免CLI环境下路径解析出错。 - 配置Cron任务:直接在Crontab中添加调用命令,比如每分钟执行一次脚本:
这里* * * * * /usr/bin/php /path/to/your/loop_script.php >> /var/log/loop_script.log 2>&1>> /var/log/loop_script.log 2>&1是把脚本的输出和错误都写入日志文件,方便后续排查问题。 - 取消Web触发逻辑:删掉原来需要页面刷新的循环触发方式,让Cron完全接管调度,脚本会在后台按计划持续执行,不需要任何人工或浏览器干预。
二、PHP爬虫改为每次处理单个站点并自动触发下一次执行的实现方案
把原来的批量循环改成单次处理+自动续跑,核心是用数据库跟踪处理状态,避免重复处理,同时实现自动触发下一轮任务。下面是具体步骤和示例代码:
1. 数据库表结构调整
先给你的站点表(假设表名为sites)加两个字段,用来跟踪处理状态:
ALTER TABLE sites ADD COLUMN processing_status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending'; ALTER TABLE sites ADD COLUMN last_processed_at DATETIME NULL;
processing_status:标记站点的处理状态,防止多个进程同时处理同一个站点;last_processed_at:记录最后一次处理的时间,方便后续排查或重试。
2. 修改爬虫脚本逻辑(crawler.php)
脚本核心流程变为:锁定一个未处理的站点 → 执行检测 → 更新状态 → 触发下一次执行。我写了个简化版示例,你可以根据实际情况调整:
<?php set_time_limit(0); // 取消脚本超时限制,确保curl检测能完成 // 1. 连接数据库(这里用PDO示例,你可以换成自己的连接方式) $dsn = 'mysql:host=localhost;dbname=your_db;charset=utf8mb4'; $pdo = new PDO($dsn, 'username', 'password', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]); try { // 2. 用行锁获取一个未处理的站点,防止并发冲突 $pdo->beginTransaction(); $stmt = $pdo->prepare("SELECT * FROM sites WHERE processing_status = 'pending' LIMIT 1 FOR UPDATE"); $stmt->execute(); $site = $stmt->fetch(PDO::FETCH_ASSOC); if (!$site) { // 没有未处理的站点,直接退出 $pdo->commit(); exit("No pending sites to process.\n"); } // 3. 标记该站点为处理中 $stmt = $pdo->prepare("UPDATE sites SET processing_status = 'processing' WHERE id = ?"); $stmt->execute([$site['id']]); $pdo->commit(); // 4. 执行curl检测逻辑(替换成你原有的检测代码即可) $ch = curl_init($site['url']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置30秒超时 $html = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 5. 根据检测结果更新站点状态 $status = ($httpCode >= 200 && $httpCode < 300) ? 'completed' : 'failed'; $stmt = $pdo->prepare("UPDATE sites SET processing_status = ?, last_processed_at = NOW() WHERE id = ?"); $stmt->execute([$status, $site['id']]); echo "Processed site {$site['url']} - Status: {$status}\n"; // 6. 自动触发下一次脚本执行(后台运行,不阻塞当前进程) $scriptPath = __FILE__; exec("/usr/bin/php {$scriptPath} > /dev/null 2>&1 &"); } catch (Exception $e) { // 异常处理:把站点状态改回pending,避免死锁 if (isset($site['id'])) { $pdo->rollBack(); $stmt = $pdo->prepare("UPDATE sites SET processing_status = 'pending' WHERE id = ?"); $stmt->execute([$site['id']]); } error_log("Error processing site: " . $e->getMessage()); exit("Error occurred: " . $e->getMessage() . "\n"); } ?>
3. 更稳妥的触发方式:用Cron定期触发
如果担心exec()调用导致进程堆积,也可以用Cron每分钟触发一次脚本,脚本执行时如果有未处理的站点就处理,没有就直接退出。这种方式更可控,避免意外的进程爆炸:
* * * * * /usr/bin/php /path/to/crawler.php >> /var/log/crawler.log 2>&1
4. 额外优化建议
- 重试机制:对于
failed状态的站点,可以加个定时任务定期重置为pending,比如每天凌晨执行:UPDATE sites SET processing_status = 'pending' WHERE processing_status = 'failed' AND last_processed_at < DATE_SUB(NOW(), INTERVAL 1 DAY); - 分布式锁:如果后续需要多进程处理,建议用Redis做分布式锁,比数据库行锁性能更好;
- 资源监控:可以在脚本中添加内存使用监控,避免内存泄漏;或者用
ulimit在Cron命令中限制进程资源。
内容的提问来源于stack exchange,提问作者David Sword
相关产品推荐
相关产品推荐

