关于php的Goutte爬虫框架和selenium2的php-webdriver的理解

最近几个月有用爬虫去爬一些内容的需求,所以调查了许多爬虫框架然后实现了一些爬虫内容,这里做个笔记。

首先是语言和框架的选择,最开始尝试了java的jsoup拿来做DOM以及htmlunit拿来执行js之类的,试了一下发现htmlunit对js的支持不完整。举个例子,普通的discuz的bbs的登录界面有部分js处理,这里htmlunit就无法正确处理js导致登录失败。其实我目前的任务并不需要做登录操作的,因为我直接在chrome登录好,然后导出cookie拿给爬虫用就OK了。不过这只是比较简单的情况,如果任务多了就可能被反爬虫而且需要自动登录,所以我想尝试一下。我认为爬虫的根本在于完全模拟浏览器的操作,一部分人去分析反爬虫机制中的关键js代码或者post的数据,我认为这是得不偿失的。既然htmlunit对js的支持很差,于是我又尝试了一下selenium2,用了一下效果很好,这里记录一下。

selenium2有各种语言的绑定以及浏览器绑定,我由于可能最终代码需要部署在vps上,所以选择了facebook维护的php的绑定,也就是php-webdriver。php-webdriver老实说文档比较匮乏,不过好在里面的API和selenium2原生的比较像,而且eclipse有自动补全所以用起来还是比较顺利的。浏览器端我主要试了firefox和phantomjs,用下来一个感受就是selenium2的确是反反爬虫的利器,不用去分析js机制,浏览器怎么操作我就怎么模拟,而且很方便bebug,因为发生了什么你浏览器看的一清二楚。但是缺点就是因为加载了包括html、图片、js、css在内的所有资源,所以速度比较慢、占带块、吃cpu吃内存,不过好在有headless的浏览器phantomjs在。我试了一下phantomjs,内存和cpu消耗小了很多,而且headless的浏览器完全可以部署在linux系统的vps上,特别适合生产环境。

那么接下来再谈谈Goutte这个php框架吧,其实这个框架是对HttpGuzzle和symphony框架下dom相关组件的轻封装,同样缺乏文档,但是在stackoverflow上是很流行的php爬虫框架。当然了作为纯的php框架肯定是不支持js执行的,我要爬的内容并没有做太多反爬虫所以就已经够用了。因为很多内容需要登录但是用php去实现登录是比较困难的,所以我选择了在chrome里面登录然后导出cookie给爬虫来直接登录。具体用php写爬虫业务没什么好写的,这里我只写一些我遇到的坑:

首先,就是所有的爬虫的过程都要用try包含起来处理异常,因为爬得过程也好,处理返回的html的dom操作获取html元素内容也好,总是会遇到各种异常,往往我们都不希望出现这种情况。

第二个因为php执行有命令行和浏览器访问2种方式,一般写爬虫都会做一个页面然后填一些参数post到对应的php爬虫页面执行,而爬的过程又是很长的,所以基本大家都会在任务开始的时候加上:set_time_limit ( 0 );我也是这样的,但是慢慢的就发现有时候我的爬虫任务还是莫名其妙的停止执行了。去php-fpm的log里面也没有看到停止执行的error log,查了半天发现原来如果用户浏览器和服务器连接中断或者停止访问了,那么正在执行的php脚本也会退出,所以加上:ignore_user_abort(true);这样就可以完全无视用户独立爬了。

第三个就是,php脚本输出的内容不会立即刷新到页面,而是会缓存的自己的缓冲区,我们可以通过ob_implicit_flush ( true );ob_start ();ob_flush ();几个函数来刷新缓冲区,实际上我本地XAMPP开发环境工作的很好,但是我部署到vps上的时候就不行了。因为nginx服务器也有自己的缓冲区,但是你php脚本是无法去控制nginx的缓冲区的。怎么办呢,只要输出很多空白字符来填满nginx的缓冲区,试了一下我的vps大约没5000个字节的输出就会被nginx返回到浏览器。

第四个就是,很多时候浏览器和服务器的连接可能断开,这个时候爬虫还是在执行可以你却无法得到输出,当然了可以把输出放到数据库里面再写个脚本去读数据库内容,不过我只是简单的把输出定向到一个txt,通过访问txt就能知道目前爬虫的进度如何。嘛,不是办法的办法。

第五个就是Deprecate问题了,php在升级版本的过程中许多函数弃用了,比如php5.5开始就把preg_replace的/e特性作为Deprecate的了,我原本的代码用太多的这个特性,改了一段时间发现实在改不过来,于是直接关闭error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);

第六个就是Goutte去爬的数据的时候的假死问题,有时候浏览器去打开一张图如果网络不好可能下载了一半就死了。这个对于爬虫来说就相当于永远的卡在这里了,所以Goutte里面的HttpGuzzle客户端去下载资源的时候最好设置一个timeout,因为超时会抛出异常所以注意catch起来。另外去下载资源的时候,加上原网站的referer是个好习惯,很多资源是cdn发布的只认原站点的referer才行,我也是stackoverflow上问一下才搞明白。

最后一个就是我有一些数据需要持久化,但是又没必要放进数据库里面,所以把这部分数据做成json存入文件是最好的。但是php的json_encode函数接口有个特性就是,这个接口默认会把数组里面用数字做key的转化成json里的list,然后字符串做key的转化成json里的object,这并没有问题。但是json_decode就会把object转化成php里的对象也就是StdClass,把list转化成数组。我一个php数组json_encode再json_decode回来就有了StdClass,导致原来处理数组的逻辑去处理StdClass出错。我原本是很小心让数组的key不是字符串,然后发现json_decode第二个参数就是全部decode成数组的,本来好好读文档就解决的问题浪费了许多时间。

关于php的Goutte爬虫框架和selenium2的php-webdriver的理解》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注