
入职冰鉴科技做爬虫开发尚未半年多了,陆续开发维护了几个爬虫以后已经在 web 端爬虫这一块有了登堂入室的觉得。中间踩了许多坑,也对爬虫的许多细节有了自己的了解,所以今晚希望可分享一些爬虫经验。虽然爬虫的好多东西不好说很细,因为说挺细了对方立刻有针对性的反爬虫了,而且这些方法业界没用通用的解决方案(别人就算做起来了也不太愿意分享),都是我自己慢慢摸索回来的。但是我认为适度的业界 /友商之间的技术交流是必要的,不能闭门造车,我也期待可跟业界 /友商有更多私下的深入交流,大家多切磋才能进步嘛。最近我在研究 app 反编译爬虫相关的,所以对这块特别感兴趣。个人博客: qsalg.com
其实就现在业界来说, python 下的爬虫轮子是最多的,我厂大多数同学都用 python 搞爬虫。我因为以前搞 web 后端用 PHP 比较多,对 PHP 下的生态和第三方库啥的如数家珍,厂里对使用的语言也不做强制规定,所以我就用更拿手的 PHP 开搞了。有朋友可能会觉得 PHP 下爬虫轮子仍然不多,甚至有个别做惯了 PHP 后台的同学在必须完成爬虫任务时也拿出了 python ,难道 PHP 就不合适搞爬虫么?我觉得恰恰相反, PHP 在 web 领域累积了长期成熟的第三方库,而且其强悍的内容处理能力让之在必须琐碎处理的爬虫任务中如鱼得水。爬虫从运行时间上大概可以分为两种: 1 、实时的爬虫:一个请求来了我就开一个爬虫去爬取结果,一般状况下这些爬虫直接对外提供 API ; 2 、长期爬虫:这种爬虫通常会经常运行以及定期运行,把数据更新入库。一般来说这 2 种爬虫都必须非常经常的维护升级, PHP 作为一门部署简单的脚本语言,可以实施热更新爬虫代码,非常方便。
用 PHP 搞爬虫请利用好 composer 下的第三方库。 PHP 在 web 领域累积了长期成熟的第三方库,基本上你想得到的库都可在 github 上都能找到,如果你不用第三方库的话,那么你就等于舍弃了 PHP 在 web 领域的很大优势。爬虫相关的 PHP 第三方库我用的非常多的有:
1 、 Guzzle :功能更加强的 httpclient ,带异步并发功能,别的脚本语言找不到这么好的 httpclient

2 、 Goutte :对 symfony 的 dom-crawler 和 css-selector 的简洁封装,你也可以直接用 symfony 的 css-selector 来抽取 html 的 dom 元素
3 、 symfony/process : symfony 出品的 php 开进程的库(封装的 proc_open ),兼容 windows ,要知道 pcntl 扩展不支持 windows 的
4 、 php-webdriver : Facebook 官方维护的 selenium 的 php 客户端
前段时间有一个《我用爬虫一天时间“偷了”知乎一百万用户,只为证明 PHP 是世界上最好的语言》,这个 repo 很受关注也经常在维护。我也研究了一下他的代码,质量很大,但是有一个缺点就是没有使用现有的第三方库而选取自己封装。我们必须把精力花在爬虫业务上而不是去重新造轮子php 爬虫代码,我以前直接无脑的使用现有的 composer 下的各类第三方库。我从去年 4 月份入职至目前 8 个月时间只写了 3 个爬虫(除了爬虫业务外,基于 redis 的分布式爬虫调度、单机多爬虫并发、报警+监控+参数控制、 selenium 多浏览器匹配+特性定制、代理策略定制 and so on )一套下来,所有代码都加出来只有 6000 行 PHP 代码。已经有现成的成熟稳定的第三方库不用,自己造轮子是得不偿失的。

爬虫不能不说到并发,爬虫作为一个 IO 密集型而不是 CPU 密集型的任务,一个好的并发的爬虫需要满足: 1 、尽量可能高的下载带宽(下载带宽越高,爬的数据越多); 2 、尽可能小的 CPU 消耗和尽可能小的存储消耗。多线程似乎是推动并发的不错的方法,经常有人说“ PHP 没有多线程”让广大 PHPer 直不起腰。作为 web 后端的之后 PHP 没法使用多线程,但是作为命令行运行的话 PHP 是支持多线程的。我们明白 PHP 分为线程安全( ZTS )和非线程安全版本( NTS ),后者虽然是为了兼容 win 下 IIS 的 ISAPI ,这也就逼着 PHP 下的扩展基本上都提供的线程安全跟非线程安全版本。也就是说从理论上来说命令行的 PHP 多线程是真的多线程,没有像 py 或者 ruby 那样的全局锁(实际上同一时刻只有一个线程在走),但是实际上 PHP 命令行多线程不太稳定(毕竟它的多线程不是为 php-cli 设计的),所以我建议命令行应用还是使用多进程来做并发。而异步也是推动并发的重要手段,爬虫需要并发的大多数情况是我想是同时去爬多个 url ,这种情况无须使用多进程 /多线程,直接在单进程中使用异步就可以了。比如 PHP 的 Guzzle 异步支持非常好用, Guzzle 默认异步是包装的 curl 的 curl_multi 的几个函数来做的,如果你想用性能更好的异步事件库可以设定 Guzzle 的 adapter 为 react-guzzle-psr7 (当然了你得安装 Event 之类的轮询 pecl 扩展)。我个人试用下来感觉 Guzzle 默认的异步就够用了,单进程并发几十上百的 http 请求跑满小水管那是不成问题的, cpu 和存储消耗还更小。总之,把 php 的多进程跟异步合起来用,实现良好的并发不是问题。
开箱即用封装好的爬虫框架不是银弹。我一开始也研究了 java 和 py 下的一些非常著名的框架,企图先把这种框架学会之后把自己的爬虫任务整合出来,后来看到如此做更困难。诚然用爬虫框架基本上改两行就可以走出来了,对简单的爬虫任务来说很不错。但是用对方封装好的框架会导致爬虫的定制性变差(要知道爬虫是必须灵活处理各类情况的),而我们了解爬虫的本质就是开着 httpclient 取回 html 然后 dom 抽取数据就完了(并发的话再加个多进程管理),就这样简单的任务为了尽可能满足所有人需要被封装成了一个复杂系统的框架,并不必定适合所有的状况。有一次 V2EX 上还有人回来质疑说我直接用 requests 也很简单啊, scrapy 的优势在那里呢?我的理解是爬虫框架的优势就在于把爬虫的并发调度都做了,而我们直接单进程来写爬虫的话没法是一个单进程爬虫没有并发调度。其实爬虫的多进程并发调度没这么复杂,也不需要搞很复杂,我看看我的 php 爬虫是如何做并发调度的( python 下一回事)。
我的 PHP 爬虫多进程调度非常简洁粗暴,爬虫分为管理爬虫进程的 Master 进程跟负责详细爬取业务的 worker 进程,而 redis 负责对爬虫进行控制或者显示爬虫的状况。


比如我有一个爬取 A 站点的爬虫任务,我开发好爬虫 Worker A 以后,我可以在 redis 中修改在服务器 Node1 上我开 2 个 Worker A 来爬,而 Node1 上的 master1 进程会定期去 redis 中调用控制参数php 爬虫代码,如果看到 Node1 上的 Worker A 进程不足 2 个的话经常新开 Worker A 进程补充。当然了,控制参数必须包括那些你可以自己定制,比如我就定制了每个节点的 Worker 上限、使用的代理策略、是否禁止加载图片、浏览器特性定制等等。 Master 进程新开 Worker 进程有 2 种方法,一种是借助类 exec (比如在 Master 进程中 proc_open(‘ php Worker.php balabala ’, $descriptorspec, $pipes)这样)调用来开一个新的命令行 php 的 Worker 进程,另外就是通过 fork 机制来做。我采取了类 exec 调用的方式(其实是 symfony/process 库,它封装的 proc_open 函数来开的进程)来开 Worker 进程(如果要传命令行参数给 Worker 进程注意使用 base64 编码一下,因为命令行可能会过滤这些参数),这么做的好处就是解耦。
需要注意的是,现在 Worker 进程都是 Master 进程的子进程,所以 Master 进程退出的话所有 Worker 进程也会退出,所以 Master 进程注意异常的 catch ,尤其是 redis 、和别的有网络 io 的地方。如果你期望 Worker 进程 damonize 的话请按这篇文章的方式来( php 下也有一样的,不过不兼容 windows )。我不建议 Master 进程通过 IPC 机制对 Worker 进程进行控制,因为如此做一下子就让 Master 进程跟 Worker 进程耦合起来了, Master 进程需要仅仅简单的负责开 Worker 进程而已。对 Worker 进程的控制可以借助 Redis 来完成,也就是说 Worker 进程每隔一段时间(可以是完成了一次 http 请求,或者每隔几秒)可以去 Redis 读一次控制参数(如果必须的话,也可以到汇报一下自己状态,参数非常多的话用好 redis 的 pipeline ),在实践中这些方式工作的很高。我的 PHP 爬虫中都采用了这个简单粗暴的方案,我觉得它的好处有 4 个:
1 、支持分布式且依赖简单,参数控制+状态汇报直接通过单一的 redis 节点。我推荐你用一个好的 redis 的 GUI 工具来管理 redis , redis 的 5 种数据结构用来做爬虫参数控制+爬虫状态显示特别方便
2 、 Master 进程和 Worker 进程解耦,而且可以解决爬虫较多出现的内存泄漏问题( Worker 进程跑完直接退出),也可以热更新代码

3 、实时爬虫可以借助 Master 进程抢占 push 到 redis list 中的请求来做,而大量任务的爬虫在 Worker 进程意外退出后 Master 进程立刻补充,能适应各类爬虫任务
4 、开发爬虫只用去写 Worker 进程就 ok 了,开发便利,不用关心调度问题
缺点显然就是这一套机制都需要你自己写,高度能定制性的损失就是自己动手。
把我的 PHP 下爬虫经验的几个方面拿出来讲了一下,由于篇幅有限 Selenium 相关的心得就留到这次再说了。
以上
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/ruanjian/article-142241-1.html
我的6是9