最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
PHP函数curl_multi_族实现非阻塞并发抓取:爬虫进阶
时间:2026-07-11 09:17:52 编辑:袖梨 来源:一聚教程网
必须调用curl_multi_add_handle才能发起请求,因curl_multi_init仅创建任务容器,未添加句柄则curl_multi_exec无网络行为;选项须在添加前设置,重复添加或顺序错误将导致配置丢失或未定义行为。
curl_multi_init 之后必须调用 curl_multi_add_handle 才能真正发起请求
很多人以为 curl_multi_init() 创建句柄后请求就自动发出去了,其实它只是初始化一个“任务容器”,所有具体请求都得手动塞进去。漏掉 curl_multi_add_handle(),curl_multi_exec() 就永远返回 CURLM_CALL_MULTI_PERFORM 或直接卡住,没有任何实际网络行为。
常见错误场景:循环创建多个 curl_init() 句柄,但只对其中一部分调用 curl_multi_add_handle();或者误把 curl_setopt() 放在 curl_multi_add_handle() 之后——此时设置会丢失,因为选项必须在加入 multi 句柄前完成。
- 每个独立的 cURL 句柄(
$ch)必须先配置好CURLOPT_URL、CURLOPT_RETURNTRANSFER等参数,再传给curl_multi_add_handle() - 添加后不能再调用
curl_setopt($ch, ...)修改参数;如需动态控制,应在添加前用数组缓存配置,或改用curl_multi_setopt()(仅限少数全局选项) - 重复添加同一个句柄会导致未定义行为,务必确保每个
$ch只加一次
curl_multi_exec 的 while 循环不是“多线程”,而是轮询驱动状态机
curl_multi_exec() 本身不阻塞,但它返回的状态码(如 CURLM_CALL_MULTI_PERFORM)常被误解为“需要反复调用直到完成”。实际上,这个返回值只表示“还有内部操作要立即执行”,不是“还没跑完”。真正的并发推进靠的是 curl_multi_select() 配合循环等待。
典型翻车点:写成 while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running)) {},这会疯狂空转 CPU,且无法感知 socket 就绪事件,导致超时、连接堆积甚至 PHP 进程卡死。
立即学习“PHP免费学习笔记(深入)”;
- 正确模式是:先调一次
curl_multi_exec(),然后进入while ($running) { curl_multi_select($mh); curl_multi_exec($mh, $running); } -
curl_multi_select()最多等待 1 秒(PHP 7.2.22+ 默认值),避免长时间挂起;若需更短等待,可传入毫秒级超时参数(如0.01) - 如果所有请求都很快完成(比如本地 mock 接口),
$running可能在第一次exec后就变为 0,select根本不会被调用——这是正常现象,不代表逻辑出错
获取结果时 curl_multi_getcontent 返回 null?检查 CURLOPT_RETURNTRANSFER 是否启用
用 curl_multi_getcontent($ch) 拿不到响应体,大概率是因为对应句柄没设 CURLOPT_RETURNTRANSFER => true。这个选项必须在 curl_init() 后、curl_multi_add_handle() 前设置,否则 getcontent 永远返回 null,而 curl_exec() 也早已失效(因为 multi 模式下不能调 curl_exec)。
另一个隐藏坑:如果设置了 CURLOPT_HEADER => true,curl_multi_getcontent() 会把响应头和响应体一起返回,但很多人没意识到 header 是原始字符串格式(含 rn),直接 json_decode() 会失败。
- 务必确认每个
$ch都显式设置了curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) - 不要依赖
curl_setopt_array()的顺序——数组键名无序,应单独调用或使用索引数组保证执行顺序 - 调试时可用
curl_getinfo($ch, CURLINFO_HTTP_CODE)和curl_error($ch)辅助判断是否真成功,而非只看getcontent是否非空
并发数超过 100 时容易触发系统资源限制,别硬扛
Linux 默认单进程最多打开 1024 个文件描述符,而每个 cURL 连接至少占 1 个 fd(TCP socket + 可能的 DNS socket)。设 200 并发,还没跑就可能报 curl_easy_perform() failed: Couldn't resolve host name 或 Too many open files ——这不是 PHP 错,是系统拦住了。
更隐蔽的问题是:即使调大 ulimit,高并发下 DNS 解析、TIME_WAIT 连接堆积、远程服务限流都会让成功率断崖下跌。盲目堆数量不如稳住节奏。
- 上线前用
ulimit -n查当前限制,用ulimit -n 65536临时提升(需 root 权限或 shell 配置) - 用
curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1)复用连接,配合CURLOPT_FORBID_REUSE => false减少握手开销 - 实际生产建议控制在 20–50 并发,配合
usleep(10000)在每次curl_multi_exec()后微调节奏,比硬上 200 更稳
multi 句柄本身不解决 DNS 阻塞,如果大量域名不同,记得加 CURLOPT_DNS_CACHE_TIMEOUT 或换用 c-ares 异步 DNS(需编译支持)。这些细节不处理,再多的并发也只是在等超时。