thinkphp爬虫

用ThinkPHP写爬虫,爬虫第一件事就是爬虫别用ThinkPHP的HTTP客户端去抓网页。它自带的爬虫Http类是为API交互设计的,超时设置粗糙,爬虫对重定向和Cookie的爬虫处理也不够细。我吃过亏——用Http::get()去抓一个带Cloudflare防护的爬虫站点,返回的爬虫永远是403,换成Guzzle后加个User-Agent就通了。爬虫所以结论很直接:ThinkPHP只当骨架,爬虫抓取层交给Guzzle或cURL。爬虫

安装Guzzle很简单,爬虫composer require guzzlehttp/guzzle。爬虫然后在控制器里这样写:

use GuzzleHttp\Client; $client = new Client([ 'timeout' => 10,爬虫 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer' => 'https://example.com/' ] ]); $response = $client->get('https://target.com/list'); $html = $response->getBody()->getContents(); 

拿到HTML后,别急着用正则。爬虫ThinkPHP生态里有个好东西叫QueryList,爬虫基于phpQuery,像jQuery一样选元素。比如抓取文章标题:

use QL\QueryList; $data = QueryList::html($html)->rules([ 'title' => ['h2 a', 'text'], 'link' => ['h2 a', 'href'] ])->query()->getData(); foreach ($data as $item) {  // 存入数据库 } 

这里有个细节:QueryList的text和href是内置规则,但遇到相对链接要自己补全域名。我习惯在循环里加一句if (strpos($item['link'], 'http') !== 0) { $item['link'] = 'https://target.com' . $item['link']; }。

存数据用ThinkPHP的模型最省事。建个SpiderArticle模型,字段title、url、created_at。插入前先查重,不然跑第二遍就满屏重复。用Db::name('spider_article')->where('url', $item['link'])->find()判断一下,存在就跳过。批量插入时用insertAll,比循环save快十倍不止。

反爬这块,光换User-Agent不够。有些站会检查Accept-Language和Connection头。我一般把Guzzle的headers配成和Chrome开发者工具里看到的一致。遇到需要登录的,用cookie jar:

$jar = new \GuzzleHttp\Cookie\CookieJar(); $client = new Client(['cookies' => $jar]); 

先POST登录接口,后续请求自动带Cookie。ThinkPHP的Session和这个不冲突,别混用。

并发抓取别用foreach硬跑。Guzzle的Pool或者each_limit能控制并发数。我试过同时抓50个页面,不加限制直接把目标站跑出502。后来改成each_limit(5, function($response, $index) { ... }),稳得很。回调里处理HTML,别在回调里写数据库——Guzzle是异步的,数据库连接可能断。把解析结果先塞进数组,全部完成后统一入库。

定时任务用ThinkPHP的think-cron或者系统crontab。命令行模式跑爬虫比Web模式好,没有超时限制,内存也宽松。命令写成php think spider:run,在command目录里建个SpiderRun类继承think\console\Command。日志用Log::write记到文件,别只依赖数据库——抓取失败时你总得知道是网络问题还是选择器写错了。

最后说个真实案例。我用ThinkPHP+QueryList抓某招聘网站的职位列表,一开始选择器写.job-list li,跑了一周都正常。突然某天返回空数组。打开页面一看,人家把类名改成了.job-list-item。爬虫这东西,维护成本比开发高。后来我加了个监控:如果单次抓取结果少于10条,就发邮件告警。这比任何反爬策略都实在。