用 swoole 协程替代 curl_exec 可大幅提升 php 爬虫性能,co\http\client 在协程内自动挂起恢复 io,100 请求 2–5 秒并发完成;需在 co\run 中运行,避免混用 curl,合理设超时与代理池轮换,并流式清洗数据。

用 Swoole 协程发请求,别再用 curl_exec
同步阻塞式请求是 PHP 爬虫性能的头号杀手。curl_exec 或 file_get_contents 一发请求就卡住整个进程,100 个链接串行跑完要几分钟;换成 Co\Http\Client,同样 100 个请求在单进程内并发跑,通常 2–5 秒完成。
关键不是“能不能并发”,而是“协程挂起是否自动”。Swoole 的 Co\Http\Client 在底层自动处理 DNS 查询、TCP 握手、SSL 协商、响应读取等 IO 阶段的让出与恢复,你只需写顺序代码:
$client = new Co\Http\Client('example.com', 443, true);
$client->set(['timeout' => 5]);
$client->get('/api/data');
$response = $client->getBody(); // 这行不阻塞主线程
注意三点:
- 必须在
Co\run()或Swoole\Coroutine::create()内启动协程,裸跑会报错 - 不要混用
curl_init和Co\Http\Client,前者会破坏协程调度 - 超时设太短(如
0.5)会导致大量连接被丢弃,建议3–5秒并配合重试
代理 IP 轮换不是加个 CURLOPT_PROXY 就完事
传统 curl_setopt($ch, CURLOPT_PROXY, ...) 是静态绑定,一次设置全程复用。高并发下所有协程共用一个代理出口,等于把 1000 个请求全打到同一个代理 IP 上——几秒内就被目标站封掉。
真正可用的轮换必须满足:每个请求独享代理、失败自动剔除、新请求即时分配未用 IP。推荐做法是用 Channel 做代理池分发:
$proxyChan = new Channel(100);
// 预载 50 个有效代理
foreach ($proxies as $proxy) {
$proxyChan->push($proxy);
}
Co\run(function () use ($proxyChan) {
for ($i = 0; $i pop(); // 取一个
$client = new Co\Http\Client(...);
$client->setProxy($proxy['host'], $proxy['port']);
// ... 发请求
if ($client->getStatusCode() === 200) {
$proxyChan->push($proxy); // 成功则放回
}
});
}
});
常见陷阱:
- 代理认证格式错误:必须是
user:pass@ip:port,不是ip:port:user:pass - HTTP 代理误用于 HTTPS 目标:部分代理不支持 CONNECT 隧道,导致 TLS 握手失败
- 没做代理健康检测:上线前用
Co\Http\Client对每个代理发 HEAD 请求验证连通性
数据清洗不能等到 all() 收集完再开始
爬虫最常犯的错,是把全部响应存进数组,最后统一 array_map 清洗。内存爆满不说,还掩盖了字段缺失、编码错乱、结构突变等实时问题。
正确做法是「流式清洗」:每个响应解析后立即标准化,失败项直接丢弃或打日志,不污染后续流程。比如用 DOMDocument 提取标题时:
$dom = new DOMDocument();
@$dom->loadHTML($html, LIBXML_NOERROR | LIBXML_NOWARNING);
$titleNode = $dom->getElementsByTagName('title')->item(0);
$title = $titleNode ? trim($titleNode->textContent) : '';
if (!$title) {
error_log("Empty title from {$url}");
return; // 不塞进结果集
}
// 统一转 UTF-8,过滤控制字符
$title = mb_convert_encoding($title, 'UTF-8', 'auto');
$title = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $title);
重点在三处:
- 用
@抑制DOMDocument的警告,但必须手动检查节点是否存在 -
mb_convert_encoding(..., 'auto')比硬写gbk或utf-8更可靠,尤其面对混合编码页面 - 清洗逻辑必须和解析耦合,不能分离成两个阶段——否则某页多出一个
就会让整批数据对齐失败
php-spider 框架的配置陷阱:不是所有钩子都适合加反爬逻辑
php-spider 的事件系统看着灵活,但 onAfterDownload 里加 UA 切换或代理切换,实际无效——因为下载动作已完成,改头换面也晚了。
真正该动手的位置只有两个:
-
onBeforeDownload:在这里动态设置$item->setOption('headers', [...])和$item->setOption('proxy', ...) -
onAfterParse:只做清洗和校验,比如检查$item->getTitle()是否为空,空则调用$item->skip()
另一个坑是 domains 配置。写成 ['example.com'] 没问题,但若目标站用 CDN 域名(如 cdn.example.com),而你没加进去,框架会直接跳过这些链接——它默认只爬 domains 白名单内的主机。
复杂点在于:有些反爬策略依赖 JS 执行环境,而 php-spider 是纯服务端解析,无法运行 document.cookie 或 localStorage 生成的 token。这种场景下,框架再好也得退场,老实用 Playwright 启个轻量浏览器实例,别硬扛。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











