V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gouchaoer
V2EX  ›  PHP

php 的爬虫经验分享

  •  7
     
  •   gouchaoer · 2016-11-30 12:08:16 +08:00 · 28802 次点击
    这是一个创建于 2916 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最好的语言 PHP + 最好的前端测试框架 Selenium = 最好的爬虫(上)

    入职冰鉴科技做爬虫开发已经半年多了,陆续开发维护了几个爬虫以后终于在 web 端爬虫这一块有了登堂入室的感觉。中间踩了许多坑,也对爬虫的许多细节有了自己的认识,所以今天希望能分享一些爬虫经验。虽然爬虫的很多东西不好说太细,因为说太细了别人马上有针对性的反爬虫了,而且很多技巧业界没用通用的解决方案(别人就算做出来了也不太愿意分享),都是我自己慢慢摸索出来的。但是我认为适当的业界 /友商之间的技术交流是必要的,不能闭门造车,我也渴望能和业界 /友商有更多私下的深入交流,大家多切磋才能进步嘛。最近我在研究 app 反编译爬虫相关的,所以对这块特别感兴趣。个人博客: qsalg.com

    为什么是 PHP

    其实就目前业界来说, 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 很受关注也一直在维护。我也研究了一下他的代码,质量很高,但是有一个缺点就是没有使用现有的第三方库而选择自己封装。我们应该把精力花在爬虫业务上而不是去从新造轮子,我平时直接无脑的使用现有的 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 中读取控制参数,如果发现 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 相关的经验就留到下次再说了。

    以上

    第 1 条附言  ·  2016-11-30 12:43:00 +08:00
    忘了说了,我这个办法在 windows 下和 linux 下都可以工作的很好。。。我的一部分爬虫是跑在 windows 下,除了运维简单以外,还因为我的浏览器只在 win 下打通了。。。不过 phantomjs 倒是随便 windows 还是 linux ,在 linux 下我是用的 docker
    75 条回复    2019-09-05 09:12:21 +08:00
    nsxuan
        1
    nsxuan  
       2016-11-30 12:21:53 +08:00
    好文,赞
    php71
        2
    php71  
       2016-11-30 12:39:08 +08:00
    我就看看我不说话
    nigelvon
        3
    nigelvon  
       2016-11-30 12:39:38 +08:00
    不错不错,赞
    willhunger
        4
    willhunger  
       2016-11-30 12:50:17 +08:00
    我就看看我不说话
    willhunger
        5
    willhunger  
       2016-11-30 12:50:48 +08:00
    表示之前是用的 PHPquery ,我还以为没有 php 的爬虫框架了呢
    jugelizi
        6
    jugelizi  
       2016-11-30 13:03:16 +08:00
    再牛逼的框架 别人出个铁道部的验证码一样干掉你
    exalex
        7
    exalex  
       2016-11-30 13:15:03 +08:00
    大神文章先马后看
    gouchaoer
        8
    gouchaoer  
    OP
       2016-11-30 13:19:42 +08:00 via Android
    @jugelizi 我想说的就是这个,比如处理验证码的情况,框架里面要处理也可以不过扩展比较麻烦。。。如果你不用框架,自己检测验证码,简单的用现有的大码工具,复杂的上打码平台,这样就容易很多了。。。
    gouchaoer
        9
    gouchaoer  
    OP
       2016-11-30 13:21:27 +08:00 via Android
    @willhunger dom 选择器 php 有好几个库,捡顺手的就完了。。。其实 selenium 的 dom 选择器也是非常成熟的
    helloccav
        10
    helloccav  
       2016-11-30 13:28:39 +08:00
    一直觉得 dom 选择器很难用,不如直接用正则提取内容方法
    gouchaoer
        11
    gouchaoer  
    OP
       2016-11-30 13:38:53 +08:00 via Android   ❤️ 2
    @helloccav 其实 html 的元素是为前端准备的,前端用 jquery 之类的 dom 选择器那爬虫用同一套工具肯定是最优的。。。正则我更多的是用在拿到元素文本内容后提取关键字啥的。。。而且在比较复杂的爬虫里面,光是不同的 dom 选择操作就几十上百个,每天还得盯着人家 html 变化没,变了赶紧更新代码,用正则会绝望的
    scnace
        12
    scnace  
       2016-11-30 13:42:08 +08:00 via Android
    我明明是来看 selenium 和 phantomjs 的。。。
    nilai
        13
    nilai  
       2016-11-30 13:44:29 +08:00
    其实应该来份 DEMO 也不错哦。
    gouchaoer
        14
    gouchaoer  
    OP
       2016-11-30 13:48:12 +08:00 via Android   ❤️ 1
    @scnace selenium 和 phantomjs 打算下次分享,里面有的几个坑很有趣的(其中有一个坑让我差点不干了),这块我透漏一下我 i7 的 cpu 能跑 100 个 phantomjs 进程(长期稳定)
    gouchaoer
        15
    gouchaoer  
    OP
       2016-11-30 13:51:08 +08:00 via Android
    @nilai 贴代码多没意思啊,这个东西也不复杂我也就写了个基类 1k 行也没啥技术含量,简单粗暴的
    scnace
        16
    scnace  
       2016-11-30 14:08:34 +08:00
    @gouchaoer 爬虫这块其实是不限语言的 因为不同语言的实现原理都类似 从开始实习到现在也一直在搞爬虫的事 `phantomjs`这块我是调命令行实现的 但是解析动态页面(AJAX 生成的 URL 这些) 还是不太给力 所以很期待楼主的分享啊 记得更新了圈下我啊 233
    barood
        17
    barood  
       2016-11-30 14:31:39 +08:00
    谢谢分享
    vus520
        18
    vus520  
       2016-11-30 14:37:32 +08:00
    分布式的轮子有没有开源出来?
    mahone3297
        19
    mahone3297  
       2016-11-30 14:46:24 +08:00
    分享不错
    因为你说了,用第三方库,能否分享几个好的库?比如 dom 的话,哪个库?其他呢?
    changwei
        20
    changwei  
       2016-11-30 15:33:18 +08:00
    楼主你好,看了你的文章收获很多,请问能否分享一下你的 github ?

    我也是 phper ,但是由于我开始也和你说的很多人一样以为 php 的多线程能力很弱,虽然知道 php 用 pthreads 扩展可以实现多线程,但是编译和安装扩展的时候出现了各种坑,因此只好转 python 了。

    关于 php 的 nts 和 zts 我有一个疑问,就是你说的 nts 是为了兼容 iis 下的 ISAPI ,那么请问为什么 linux 下的 php 版本也区分 nts 和 zts 呢?还有就是我看 python 和 java 里面仅仅是在模块级别上区分是否为线程安全版本(比如说 py 的 queue 队列这种数据结构就是线程安全版本,还有 java 下的 Vector 和 arraylist ),为什么 php 是整个版本就开始做区分呢?

    还有关于全局锁的问题,我发现我 python 写的爬虫如果开 10 个线程,那么命令行输出就是一次性输出 10 条抓取日志然后突然停顿 0.5s 左右,请问这 0.5s 是不是就是你说的由于全局锁的关系,导致它内部在做线程调度从而产生这 0.5s 的卡顿时间?

    谢谢楼主的分享。
    changwei
        21
    changwei  
       2016-11-30 15:35:26 +08:00
    对了,还有就是我现在写的爬虫大多数都是这种需求,就是有一个获取 list 的接口(文章列表),先去这个接口获取一批 id ,再去一个通过 id 获取详情的接口(文章内容)来获取具体要抓取的数据。

    我现在在 python 下是用 threading 和 queue 队列来实现的抓取,请问 php 下是如何做类似需求呢?
    gouchaoer
        22
    gouchaoer  
    OP
       2016-11-30 15:47:15 +08:00
    @changwei 为了服务于 php-cli 的多线程吧,毕竟多线程支持都做了……一般大家都选 NTS ,没有多线程支持的性能要好点

    至于 java ,你要知道它是一门工业级的静态类型语言, jvm 是非常强大的 runtime ,原生支持多线程是必然的。 php 整个版本区分是因为 ZTS 版本性能更低,为了性能。。。

    python 的多线程。。。我用 py 不是很多。。。。


    @changwei 其实爬虫的难点在于别人反爬虫,具体去爬的业务没啥难度也没啥好说的。。。你的这个需求 php 下单进程异步应该可以吧? http://docs.guzzlephp.org/en/latest/quickstart.html#concurrent-requests
    你对并发感兴趣的话我有 2 篇博客你可以参考一下,写的比较乱:
    http://qsalg.com/?p=423
    http://blog.icekredit.com/?p=40
    ylsc633
        23
    ylsc633  
       2016-11-30 16:40:40 +08:00
    恩! 文章很赞! 最近正有想法 想研究研究这个!

    楼上也有提出 PHPquery 的 有用过.. snoopy 也用过! 还有 github 上的这个 https://doc.querylist.cc/site/index/doc/9

    都用过,不过不是很适合自己!

    一旦遇到验证码 我就懵逼了...

    甚至 模拟登陆都有点坑....

    膜拜楼主
    cenxun
        24
    cenxun  
       2016-11-30 16:51:17 +08:00
    关注下,关于多进程执行调度可以看看 resque 库
    Dlad
        25
    Dlad  
       2016-11-30 17:10:34 +08:00
    我也写过一个基于 perl 语言的多线程爬虫框架,一直没有机会跟大家讨论。<br />
    地址: https://github.com/qdladoooo/b2c_crawler <br />
    我把抓取过程分为四个阶段:网址入库,抓取,解析,数据入库。其中 1 、 3 步需要根据情况编码。<br />
    多线程分为两级,第一级在 DB ,每个线程领取任务,更新状态,完成任务;第二级在运行时的代码,维护一个地址池,是个”生产者-消费者“模型。<br />
    <br />
    我觉得组织一个通用的“用户级”的框架是很难的,所谓爬虫框架都是 coder 级的产品。
    sagaxu
        26
    sagaxu  
       2016-11-30 17:11:09 +08:00   ❤️ 2
    针对 4 个 php 第三方库,可能
    1. python 的 requests 库封装的非常好
    2. beautifulsoup 和 lxml 也非常好
    3. python 标准库的 multiprocessing 和 threading 很好用
    4. python 是 selenium 官方支持的 5 种语言之一

    我用 15 寸球面 CRT 也能写代码,然后对外宣称
    最好的显示器 15 寸 CRT + 最好的 cherry 键盘 = 最好的开发设备
    gouchaoer
        27
    gouchaoer  
    OP
       2016-11-30 17:21:30 +08:00 via Android
    @cenxun resque 的代码我研究过,我没有用的原因还是过度封装,直接用 redis list 的 blocking pop 足够了
    gouchaoer
        28
    gouchaoer  
    OP
       2016-11-30 17:39:03 +08:00 via Android
    @sagaxu httpclient 中非常重要的异步并发请求功能 requests 没有, py 的几个支持异步的 httpclient 都不太好用(我们这边有个 py 爬虫需要异步并发,我研究了一段时间的这个东西)。。。

    至于 selenium 客户端,用啥语言都差不多, py 调用浏览器不需要单独开一个命令行的 stand-alone 的 java 的 selenium 服务端这点比较方便。。。。 php 需要单独开一个命令行的 stand alone 。。。不过论代码质量, facebook 的 client 质量不错,很强调强类型,不过没有文档需要自己去看源码。。。。

    说 php 是最好的语言只是想起个比较吸引眼球的题目而已, php 的优缺点和 py 的优缺点场景不一样的话体现也不一样,其实最好的语言就是你最熟悉的。

    我喜欢 windows ,喜欢 eclipse ,讨厌 vim ,对屏幕 /键盘完全无所谓。。。。
    cenxun
        29
    cenxun  
       2016-11-30 17:39:51 +08:00
    @gouchaoer 哈哈~,确实有点
    sagaxu
        30
    sagaxu  
       2016-11-30 18:20:43 +08:00
    @gouchaoer 爬虫这种 IO 密集型的工作,用多线程再合适不过了,如果实在想要异步, requests 的作者还写了个 grequests ,本质上是用 gevent 打的鸡血补丁。
    gouchaoer
        31
    gouchaoer  
    OP
       2016-11-30 18:26:32 +08:00
    @sagaxu IO 密集型的工作用异步是常识啊……
    grequests 我之前就研究过,你去读读它的只有 150 行的源代码, https://github.com/kennethreitz/grequests ,质量如何以及这个 httpclient 好不好用,我觉得和它那么多的 star 不相配
    sagaxu
        32
    sagaxu  
       2016-11-30 19:00:17 +08:00
    @gouchaoer 常识不是先开个几百个线程试试吗?多线程扛不住的时候,也不该用脚本语言了。最次也要考虑用 Java 或者 Go ,研发团队够强就上 C++。
    gouchaoer
        33
    gouchaoer  
    OP
       2016-11-30 19:12:42 +08:00   ❤️ 1
    @sagaxu 关于这个常识没啥好说的了……其实只要是按照异步(毕竟底层都用了各种差不多的事件库)来写各个语言的性能跑出来差别不会太大,而且也没必要迷信用静态类型语言,论异步的 http 性能,在某些场景下 php 都是可以打 Go 的:

    韩天峰-Rango
    11 月 7 日 10:17 来自 微博 weibo.com
    PHP7+Swoole 的 http 服务器和 Go 的 Http 服务器,在我的电脑上进行基准测试, Swoole 的性能总是 Go 的 2 倍。
    https://github.com/swoole/swoole-src/blob/master/benchmark/http.php
    https://github.com/swoole/swoole-src/blob/master/benchmark/http.go

    至于为啥不用静态类型写,我个人干活也是比较喜欢静态类型的,因为强类型写出来更鲁棒性能更好,用脚本语言纯粹是写起来快,尤其是爬虫这种全是内容处理+细节处理而且需要常常改的东西
    sagaxu
        34
    sagaxu  
       2016-11-30 20:39:51 +08:00   ❤️ 1
    @gouchaoer micro benchmark 性能确实差不太多,不过 swoole 并没有比 go 快,在 go1.6 和 php7+swoole 下面,并发 10 个的时候,两者性能差别在 1%左右,当并发提高到 1000 的时候, go 比 swoole 快不到 10%,换成 go1.7 ,比 swoole 快 10%。比 go 快 1 倍,是跟 go1.4 比的吗?用 C 扩展还跑不过没有一行 C 代码的 Go 。如果稍微加一点儿逻辑进去, php 自身慢一个数量级的缺陷就暴露了。
    mrgeneral
        35
    mrgeneral  
       2016-11-30 22:02:51 +08:00
    本人也是 phper 。

    楼主用的到轮子,我也折腾过,其实在请求、解析、清洗这个层面, php 和 python 都差不多,都有很好的轮子。

    说到效率, python 可能还不如 php 。(忘记在哪里看到的非官方资料)

    小规模的 PHP 都能支持,没啥大问题。但是规模大起来后,就尴尬了。

    php 调度一直都没有一个很好的解决方案。楼主的也比较粗糙,我自己实现的是用队列+数据库,也没办法很好的解决失败重连、暂停继续等问题。

    这个是个痛点,有机会可以一起折腾下,说不定就有惊喜了。
    gouchaoer
        36
    gouchaoer  
    OP
       2016-11-30 22:28:14 +08:00 via Android
    @mrgeneral 我完全是为了满足厂里的业务需要,现在这样也够用了,其实单纯的怎么更好的爬数据不是我的工作的重点,我精力在于和反爬虫较量上面,这里面很多东西就不好说太细了。所以目前暂时没有折腾轮子的想法,感谢邀请。

    至于说 php 的性能,在脚本语言里许多场景下下, php 数一数二的信心还是有的, php 本来就把性能和兼容看的很重: https://pages.zend.com/rs/zendtechnologies/images/PHP7-Performance%20Infographic.pdf
    moell
        37
    moell  
       2016-11-30 22:51:58 +08:00
    好文,学到了很多,大赞
    mrzhao
        38
    mrzhao  
       2016-11-30 22:57:32 +08:00
    撸主,我只是听说过爬虫,能带我入门不? 而且我现在有一个相关的问题需要解决,能不能聊一下 @gouchaoer
    Moker
        39
    Moker  
       2016-11-30 23:01:43 +08:00
    最近做的业务也是跟爬虫有关,只是选择器用的是 phpquery ,觉得也是很不错,和 jq 语法基本一致,关于分布式和多进程的问题用的是 php-resque
    huyi
        40
    huyi  
       2016-11-30 23:25:44 +08:00
    表示 已经用 php 每天爬几 T 的数据了,哈哈,要想效率高,还是自己写框架比较好,好多第三方都不靠谱,例如 simple html dom
    a67793581
        41
    a67793581  
       2016-11-30 23:52:56 +08:00 via Android
    好文 感谢分享
    IMRES
        42
    IMRES  
       2016-12-01 00:41:08 +08:00 via iPhone
    写得不错
    setonfocus
        43
    setonfocus  
       2016-12-01 08:43:47 +08:00
    楼上撕的也漂亮!!!
    Clarencep
        44
    Clarencep  
       2016-12-01 09:05:52 +08:00
    速度怎么样? phantomjs + webdrive 好像效率蛮低的说。

    phpquery 稳定吗? 与 python 的 beatifulsoup 相比怎么样?
    ety001
        45
    ety001  
       2016-12-01 09:48:23 +08:00
    我也是 phper ,虽然基本不写爬虫,但是还是觉得用 python 写爬虫最大的优势就是在于 python 是 linux 的预装软件。如果只是想在一台机器上跑 worker ,这样的优势是巨大的。
    ctftemp
        46
    ctftemp  
       2016-12-01 09:59:13 +08:00
    谢谢分享,有几个问题。
    您这种框架是针对每一个要抓的站点写一个 worker 程序吗?
    对一个站点起多个 worker 同时抓取时,多个 worker 是怎么通信避免重复抓取的?
    Marser
        47
    Marser  
       2016-12-01 09:59:40 +08:00
    写的挺详细的。。给楼主点赞~ 已收藏 ~
    RihcardLu
        48
    RihcardLu  
       2016-12-01 10:23:52 +08:00
    @ctftemp 针对同一个站点,多个 worker 可以设置不同的范围,这样就避免重复了。
    yanzixuan
        49
    yanzixuan  
       2016-12-01 10:37:52 +08:00
    感觉最大的问题是流量问题而不是语言的效率问题。如果是反爬措施牛逼的网站,只能通过降低访问的频率+换代理的解决。这就是流量最大的瓶颈。用 PHP 还是 python 的效率还是内存占用,在这个瓶颈面前几乎可以忽略不计。
    python 的分布式爬虫可以用 supervisord 来管理的。
    reuqests 听好用了的,最大的问题就是跟 gevent 目前搭配还不好。虽然作者写了例子教大家怎么玩。
    或者用 genvent+urllib2 来玩高效率了
    ctftemp
        50
    ctftemp  
       2016-12-01 10:45:44 +08:00
    @RihcardLu 划分范围也是需要通信的。比如 worker1 抓到一个属于 worker2 范围的 url ,他需要传递给 worker2 。
    gouchaoer
        51
    gouchaoer  
    OP
       2016-12-01 11:11:14 +08:00
    @mrzhao 有啥问题可以直接这里说或者到我博客下留言,我比较倾向于把问题放到大家都看的到底地方


    @Clarencep 其实 dom 选择器捡个顺手的用就 ok 了,没啥区别。。。至于速度,驱动浏览器当然更慢,不过有一些技巧可以使之做到和 httpclient 速度差不多。。。这个我以后可能回分享一下自己的经验


    @ctftemp 这种就是需要高度定制的地方了,比如有 url 作为区分的、有关键字作为区分的,把区分字段加上索引每次爬的时候去查一下看爬过没,这种比较简单粗暴,我目前有个长期爬虫库里 7kw 用这种方法也没啥问题。。。。。
    eoo
        52
    eoo  
       2016-12-01 11:20:44 +08:00 via Android
    要异步 直接上 node.js
    sagaxu
        53
    sagaxu  
       2016-12-01 11:24:25 +08:00
    @yanzixuan IP 池的确是个大问题,我们弄了几百个才勉强够用。除了网络资源外,真正的门槛是 APP 包的破解和验证码的破解,其它都是体力活。
    yanzixuan
        54
    yanzixuan  
       2016-12-01 11:35:49 +08:00
    @gouchaoer url 搞得好不好都能看出一个人认真程度。比如遇到 302 的跳转,最终的 URL 不是最初的 URL ,这个时候就需要开发者来以最终的 URL 来定。
    问题是,有工程师居然不检查这个然后说我们需要去重算法。这一点我 TM 都服了。
    至于去重问题,可以通过 simhash 来玩。如 mongo 的时候加上 simhash 字段。
    最后去重的时候,可以设置汉明距离在设置去重的尺度。最后基本上就能做到实时去重。
    抛砖引玉,不知道有没有达人有更好的玩法。
    towser
        55
    towser  
       2016-12-01 11:38:58 +08:00
    非常高质量的文章。我也分享几点:
    1 、 Master 进程常驻的时候要注意内存占用和泄漏问题,及时 unset 一些没用的大变量
    2 、 Selenium 用来应对如淘宝一类的登录模拟很好用,取到 cookie 后就可以结束了,这方面我用 Python 来做,只是有时会捕获一些奇怪的异常( ChromeDriver )
    3 、 IO 密集的场景下异步的复杂度显然是低于多线程的,而且 curl_muti 系列函数的性能已经够用
    4 、抽取 HTML 内容我用 XPath ,直接从 Chrome 开发者工具复制路径就可以用
    5 、复杂验证码就交给打码平台,大把廉价劳动力日夜兼程
    6 、 IP 不够可以先用爬虫扫一扫网上的免费代理,再不够就去租
    7 、 35 L 提到了一些现在还不太好处理的问题, PHP 对爬虫集群的控制依赖于 Redis 确实比较粗糙,如果有别的思路可以交流交流
    Felldeadbird
        56
    Felldeadbird  
       2016-12-01 11:44:00 +08:00
    我也写了一个爬虫帮公司爬对手网站得信息。我没做楼主这么细。
    我的方案比较简单:
    CURL 做一个 模拟多线程请求。 将要爬的 URL 每一进程划分为 10 批次。
    结合 linux cron 去 php-fpm 执行 脚本。
    脚本确保每 10 分钟跑一次和强制结束。
    Felldeadbird
        57
    Felldeadbird  
       2016-12-01 11:46:01 +08:00
    这里应该线程: 将要爬的 URL 每一线程划分为 10 批次。 也就是每次运行爬 10 条 URL 。防止对手发现异常封 IP (当然,我做了动态的代理设置)。 10 分钟内 基本可以爬对手 100 个 URL 。基本 1 天时间内可以把 几个对手的内容都全爬了。
    iRiven
        58
    iRiven  
       2016-12-01 11:46:34 +08:00 via Android
    很好很强势 但是 PHP 和其他语言不同的地方就是 PHP 开头要写<?PHP 什么时候才能去掉这个标签(-_-)
    eoo
        59
    eoo  
       2016-12-01 13:34:22 +08:00 via Android
    @iRiven 历史原因 必须要
    zaishanfeng
        60
    zaishanfeng  
       2016-12-01 13:46:35 +08:00 via Android
    爬虫的难点在于反爬
    gouchaoer
        61
    gouchaoer  
    OP
       2016-12-01 14:27:22 +08:00
    @towser 关于你的第一点,我认为这是不对的,在长期运行的 php-cli 中老是有人说用 unset 及时释放变量来回收内存,这是典型的 micro-optimization 是邪恶的,变量超出了生存范围会被自动回收的。我们知道 php 变量回收是简单的基于引用计数, php 自从 5.3 开始解决了循环引用,也带来了一些负面影响( https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799),但是鸟哥也说了 php 的默认垃圾回收已经基本上能处理非极端情况,所以我个人认为在 php-fpm 中和 php-cli 中都没必要去 unset 。比如老有人说`$len=count($arr);for($i;$i<$len;$i++){balabla}`比`for($i;$i<count($arr);$i++){balabla}`强,的确要强一点,但是我们知道 php 的数组回记录其大小,也就是说 count($arr)是 O ( 1 )的,两种代码的复杂度都是 O ( N ),没有本质区别,后者还要紧凑一点, micro-optimization 没必要。

    另外看来 chrome 作为一个有界面浏览器比 firefox 好,不知道你 chrome 有没有单机并发以及缓存定制之类的? firefox 下又许多坑现在我都没法很好的解决。
    towser
        62
    towser  
       2016-12-01 15:53:39 +08:00
    @gouchaoer 你的观点是对的,使用 5.3 以后的版本不用考虑那么多内存问题。不过复杂场景下还是要根据具体情况区别对待。比如非标量类型的回收可能还是不及时等。

    你说的 chrome 单机并发我没明白是指什么。
    gouchaoer
        63
    gouchaoer  
    OP
       2016-12-01 15:58:53 +08:00
    @towser 单机 selenium 驱动多个有界面浏览器,并且不同的浏览器实例可以定制特性(比如代理、 user-agent 、图片加载,静态资源缓存之类的)
    yytsjq
        64
    yytsjq  
       2016-12-01 16:00:50 +08:00
    现在用的还是 PHP Simple HTML DOM Parser ...
    towser
        65
    towser  
       2016-12-01 16:40:28 +08:00
    @gouchaoer 那倒没有。我只用来取登录后的 cookie ,最多开多个 tab ,没有定制到那么细致。
    fyibmsd
        66
    fyibmsd  
       2016-12-01 19:27:18 +08:00
    写了个豆瓣图书的爬虫, https://git.wwxa.cc/fyibmsd/spider ,未完待续..
    183387594
        67
    183387594  
       2016-12-02 08:12:38 +08:00 via Android
    马克,我一直都是瞎🐔写的
    shijingshijing
        68
    shijingshijing  
       2016-12-09 00:44:46 +08:00
    @gouchaoer 好文,赞一个!听说 PhantomJS 有内存溢出的问题, i7 跑 100 多个 PhantomJS 长期稳定,内存是多大呢?碰到过内存溢出问题么?
    gouchaoer
        69
    gouchaoer  
    OP
       2016-12-09 11:51:14 +08:00   ❤️ 1
    @shijingshijing 你说的 phantomjs 内存溢出是指单个 phantomjs 的内存溢出还是指 phantomjs 进程没法回收呢?如果是前者, phantomjs 内存消耗在 90M 上下,我的经验的(其实爬消耗比较大的网站 100 多 M 也可能);如果是后者,你看一下你的 Selenium 驱动是不是 2.x ,然后去看看是不是我再下篇中的那个 bug : https://www.v2ex.com/t/325540
    zp450594157
        70
    zp450594157  
       2016-12-10 20:20:26 +08:00
    IO 密集?异步?我怎么感觉是 node.js 的菜呢??
    shijingshijing
        71
    shijingshijing  
       2016-12-12 12:14:43 +08:00
    @gouchaoer 谢谢解答,已加关注,我们目前还没碰到过 100+PhantomJS 同时在内存跑的实例,不过以后肯定会用到的,有机会多交流。
    mingyun
        72
    mingyun  
       2016-12-25 16:58:47 +08:00
    @ylsc633 这个国产的不错
    hadoop
        73
    hadoop  
       2017-01-04 16:47:35 +08:00
    赞,还请介绍使用 phantomjs 的坑
    hpu423
        74
    hpu423  
       2019-09-04 14:18:44 +08:00
    @yanzixuan 大量爬取的数据,怎么用 simhash,汉明距离来处理,有具体方案吗?
    yanzixuan
        75
    yanzixuan  
       2019-09-05 09:12:21 +08:00
    @sagaxu bs 遇到大文件就会内存溢出,建议直接 xpath 或 parsel
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   934 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 22:14 · PVG 06:14 · LAX 14:14 · JFK 17:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.