PHP爬虫需用microtime()精准控制请求间隔而非sleep(),并配合UA轮换、Referer模拟、禁用连接复用等措施,否则易被风控识别封禁。

PHP爬虫不加延迟,基本等于主动触发反爬机制——绝大多数目标站的风控系统会在几秒内识别并封禁IP。
为什么 sleep() 不够用
单纯在每次请求后 sleep(1) 只能控制本进程的节奏,但无法应对并发请求、DNS缓存复用、TCP连接复用等真实网络行为。更关键的是:很多站点会统计单位时间内的请求数(如 10 秒内超 20 次即限流),而 sleep() 无法保证请求时间点均匀分布,容易形成“请求簇”,反而更易被识别。
- 多个
cURL请求若共享同一个CURLOPT_TCP_KEEPALIVE连接池,实际发出时间可能集中在毫秒级窗口内 - 使用
file_get_contents()时,PHP 默认不复用连接,但 DNS 解析可能被系统缓存,导致后续请求瞬间爆发 - 没考虑服务器响应时间波动——如果某次请求耗时 800ms,
sleep(1)后下一次请求实际间隔仅 200ms
用 microtime() 实现精准间隔控制
真正可控的方式是按“上一次请求发起时间 + 固定间隔”来调度下一次请求,而不是依赖上一次请求结束时间。核心是记录 microtime(true) 时间戳,再计算等待时长。
$min_interval = 1.5; // 最小间隔 1.5 秒 $last_request_time = 0;function request_with_delay($url) { global $last_request_time, $min_interval;
$now = microtime(true); $delay = $last_request_time + $min_interval - $now; if ($delay > 0) { usleep((int)($delay * 1000000)); } $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 5000); curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10000); $result = curl_exec($ch); curl_close($ch); $last_request_time = microtime(true); // 记录发起时间,非完成时间 return $result;}
立即学习“PHP免费学习笔记(深入)”;
注意:这里用的是
curl_init()发起时刻作为锚点,不是curl_exec()返回时刻,才能避免响应延迟导致的节奏偏移。配合 User-Agent 轮换与 Referer 模拟才真正有效
只控频不伪装,照样会被标记为爬虫。频率控制只是基础层,必须叠加请求头真实性:
- 每次请求前从数组中随机选一个
User-Agent字符串,避免固定值被指纹识别 - 设置
Referer为该目标站的首页或上一级页面(如爬/list/123,Referer 设为https://example.com/list/) - 禁用
Accept-Encoding: gzip或手动解压,某些 WAF 会检测压缩头异常 - 避免连续使用同一 Cookie,必要时调用
session_start()并清理$_COOKIE
别忽略 DNS 和连接复用带来的隐性并发
PHP 的 cURL 默认启用 CURLOPT_TCP_KEEPALIVE(7.25.0+),且 DNS 缓存由系统或 cURL 自身管理。这意味着即使你单线程跑,也可能因复用连接造成服务端看到“短时高频”流量。
稳妥做法是显式关闭复用:
curl_setopt($ch, CURLOPT_FORBID_REUSE, true); curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 0);
同时,如果用 curl_multi_exec() 做并发采集,必须配合信号量或队列控制总并发数 ≤ 1,否则所有前面的延迟逻辑都失效。
真正难的不是写个 sleep(),而是让每一次请求在网络层、应用层、语义层都像真实用户——延迟只是其中一环,漏掉任意一层,封禁只是时间问题。











