前段日子帮同学写一个网络爬虫,同学做教育类创业,需要从其他网站上抓取题库,这个事情肯定是要用爬虫来做。一般的步骤,无非是抓取网页、解析内容,但在实际操作过程中,发现不那么好使。主要有两个问题,一是登录,该题库网站是有账号系统的;二是ajax,简单来说,要解析的内容(章节目录、题目和答案)都是ajax请求的。这让自动化的抓取变得很麻烦。
初步分析网页,发现了要解决的问题,所以先调研了一些网页抓取工具。基本思路是采用可以模拟浏览器操作的库来获取网页内容,如果实现了浏览器的功能,那么登录和ajax自然都不是问题。网络上有各种工具,基于Java的有httpclient,htmlunit等,基于python的scrapy,基于js的phantomjs/casperjs等。五花八门的,咋一看,这事不小case么,这么多网络爬虫,分分钟搞定。事实证明,永远不要把事情想得太简单!你觉得简单,很可能是因为你还不了解里面的坑!
博主是写Java的,因而首先选了htmlunit。htmlunit是基于Java的页面分析工具,它模拟了浏览器的运行,被誊为浏览器的Java实现。至关重要的一点,它支持js,性能也不错。从描述上看,简直是理想的爬虫。不过实际的表现让博主彻底的灰心丧气了。它连第一步的登录都搞不定,经过无数次的尝试,始终卡在了提交表单这一步骤上。博主至今也没搞清楚问题到底出在哪里,看它用来做别的登录挺好的,就是在这次的网站上,提交后的结果始终是登录不上。博主猜测,要么是htmlunit对网页操作的模拟有问题,或者是要抓取的网站太不规范了。
第二次尝试转到selenium上了。selenium是一款用于web程序测试的程序,它通过插件直接运行在浏览器上,真实的模板了页面在浏览器上的反映。另一方面,也可以通过程序实现对页面的各种操作,程序最终会通过一个server作用在浏览器上。又是一个完美的方案。结果仍然夭折。selenium在浏览器上有一个插件,可以通过脚本编写操作页面的程序,但我要抓取大量的网页,脚本功能太弱,很难实现。必须采用后一种办法,即selenium web driver,编写Java程序操作网页,java请求通过server转发给浏览器。令人郁闷的是,我调试好的登录脚本,使用selenium转化为java程序后,怎么也登录不上。同一个工具,都不和谐。博主心中万马奔腾,只好手工调整。经过大量尝试,终于觉得这一套java库还是有缺陷的,遂放弃。谈一点selenium的经验,无论是脚本还是程序,都会启动一个浏览器,将所有操作在浏览器中展示出来。从效率上来说,要差一些。
接下来尝试了plantomjs,一款基于Webkit的无界面浏览器,具有完整的Javascript解析、页面渲染功能。js实现的工具,编程上也更加方便。结果又卡在登录上了,表单提交后并没有返回登录后的页面,博主心里直骂娘了!
借助于已有工具来操作页面,总会因为规范而出现各种各样的问题。登录主要涉及到了用户名、密码的输入,及登录button的点击。对这些操作不到位,最后肯定造成登录失败。
想了想,博主最后还是回到了最原始的搞法,即模拟HTTP请求,这也是最靠谱的做法。如果提交和在浏览器里点击时一模一样的请求呢?仅仅靠Chrome的”审查元素“还是不太够,这时候fiddle就大显身手了。fiddle是一个网络请求抓包神器。基本原理是fiddle在本地起了一个服务器,拦截了所有HTTP请求,然后再转发出去。因而,可以从fiddle中发现所有网页点击时发生的HTTP请求和参数,并且可以直接配置参数来模拟这个过程。登录完成后,服务器会发回一个cookie,作为后续请求的依据。这样,可以顺利的解决登录的问题。
Java里面httpclient是一个比较好的支持http请求的库,刚开始使用的时候,只设置http头的几个参数,结果发现得不到响应。最后,无奈了,对照fiddler抓包的结果,完整地设置了http头的几乎所有字段,顺利返回结果。这算是博主取得了第一个重大进展了。有了一次请求成功的经验后,就很容易上手了。ajax本质都是http请求,只要分析清楚请求对应的头字段,一样搞定。
值得一提的是cookie问题,cookie是服务器为了识别不同用户而放在http请求当中的一个字段。一般是登录后,服务器返回该字段作为后续请求的凭据。在模拟提交请求的过程中,必须填充该字段。否则,服务器会返回要求登录页面的信息。幸运的是,httpclient可以自动的维护cookie信息。事实上,一般类似的模拟工具都可以自动维护cookie。因而,只需要保证登录这样的流程就可以了。
到此,第一步算是彻底完成了。程序可以去登录和自动下载所有包含我们需要内容的网页了,这个时候得到了仅仅只是html源码。接下来,要从中去解析出具体内容了。正则表达式可以搞定这个问题,会显得比较麻烦。因为,网页组织较复杂,而且通常不规范,没有统一的格式。这个时候,如何能够解析DOM就完美了。jsoup刚好就是这样的一款Java库,直接根据html源码解析出DOM结构来。得到了网页的DOM,剩下来的事情,就非常简单了。
从开始做到完成确实费了我好多功夫,主要是博主走了太多弯路,用现成工具当然是最好的了,只是没想到实际用起来有太多的不匹配的东西。最后还是回归到基础,模拟HTTP请求。感觉现成的工具不一定就好用,因为你不清楚它是如何实现的,出了问题也很难搞明白原因在哪。从原理着手,虽然搞清楚原理比较费功夫,但是一旦清楚了,就可以举一反三,用起来也得心应手,效率不一定比现成工具差。有了这次经验,博主起码对于抓网页算是熟悉了。感觉使用fiddle+httpclient+jsoup,可以搞定大部分的网络爬虫需求了。
将这段实际工作经验写出来,和大家共享,也方便有类似需求的人。