爬虫与反爬虫就好像是安全领域的破解与反破解一样,相互矛盾,相互克制,同时也相互促进。
网站的构建技术从简单的静态网站发展到动态网站,信息的传递从用户单向接收发展到双向交互,内容的产生从站长集中生成发展到全民参与生成。
Web技术的发展对网络爬虫构成了极大的挑战,我们以Nutch为例来说明难在哪里:
1、静态网站(简单)
2、动态网站(无陷阱)(难)
3、动态网站(有陷阱)(非常难)
对于静态网站,页面数量有限,无论页面之间如何构造链接,无论页面内容是什么,都能在一个有限的时间内抓取完毕。对于静态网站来说,我们假设网站无陷阱(不会有程序来动态生成无穷无尽的静态页面),内容质量高(不会为了提高搜索结果排名进行关键词堆砌,不会大量静态页面都是一样的内容或近似的内容等)。这样的静态网站,就是爬虫理想的抓取对象!
![](/qrcode.jpg)
对于无陷阱的动态网站,用户需要和服务器交互,服务器根据用户指定的参数动态返回结果。爬虫要抓取这样的网站,就需要枚举完所有可用的参数,而很多时候,爬虫是无法枚举完所有可用的参数的。
假如我们要想抓取淘宝上面的所有商品,我们通过他的搜索入口去抓是无法抓完的,因为我们无法枚举所有的商品;那么我们还可以通过分类栏目作为入口,一页一页地往后抓,这虽然可行,但是无法抓全,淘宝会对分页进行限制,如100页,每页96个商品,也才不到一万件商品,而淘宝一个分类栏目下可能有几百万甚至几千万的商品,召回率只有百分之一甚至千分之一。
对于有陷阱的动态网站,那就更复杂了,下面举一个例子,一个简单的动态页面就可以让爬虫永远也爬不完,弱智的爬虫就会进入死循环,Nutch当然是不会了。
构造一个动态JSP页面,内容如下:
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>APDPlat应用级产品开发平台</title> </head> <body> <p>APDPlat是Application Product Development Platform(应用级产品开发平台)的缩写,作者是杨尚川。APDPlat提供了应用容器、多模块架构、代码生成、安装程序、认证授权、备份恢复、数据字典、web service、系统监控、操作审计、统计图、报表、机器绑定、防止破解、数据安全、内置搜索、数据转换、maven支持、WEB组件、内容管理、工作流、Web资源优化等功能。</p> <a href="circle.jsp?time=<%=System.nanoTime()%>">APDPlat</a> <a href="circle.jsp">APDPlat Home</a> </body> </html>
这个页面有2个指向自己的链接,一个是固定的,一个是会变化的,爬虫在访问这个页面的时候,会解析这2个链接,由于其中1个链接带了1个查询参数,这个参数的值每次访问页面的时候都会变化,因此每一次解析出来的都是一个新链接,尽管都是指向自己。虽然页面的可视文字内容相同,但由于链接参数的不同,所以页面的“指纹”也不同,对于不同网页之间重复内容去重来说,就不能够简单地比较页面的“指纹”来判重。
这个页面引出了爬虫抓取页面时候的两个挑战:
1、链接陷阱
2、内容去重
链接陷阱不一定会导致死循环(就看你如何设计爬虫),但是一定会导致爬虫永远也爬不完。
内容去重分三种情况,一是内容完全一样,这种情况很少;二是相似度极高,像上面的例子,只有一个链接的参数time的值不一样,其他内容完全一样;三是有一定的相似度,在同样的文档(1)和完全不同的文档(0)之间分布;因此,一个适合实际应用的网页内容相似度算法和判断重复的阈值就很关键了。
下面配置Nutch来抓取上面构造的页面,看看情况如何:
1、下载Nutch1.7的二进制版本解压并切换到本地运行目录:
wget http://pan.baidu.com/s/1nt2oJBN unzip apache-nutch-1.7-bin.zip -d nutch1.7 cd nutch1.7/runtime/local
2、准备注入URL文件:
mkdir urls echo 'http://localhost:8080/circle.jsp' > urls/url
3、修改URL过滤文件conf/regex-urlfilter.txt限制抓取范围
#-[?*!@=] +^http://localhost:8080/ -.
4、运行爬虫,参数为:
bin/nutch crawl urls -dir data -solr http://localhost:8983/solr/collection1 -depth 30 &
5、查看抓取下来的URL及其状态,通过分析发现,这个页面是抓不完的,抓完一个URL又会产生一个新的URL,无休无止,永远有一个新的未抓取的URL。同时,通过查看segments里面的内容发现,每个页面的签名不一样,因为每个页面的输出链接的参数不一样,哪怕其他的都一模一样:
http://localhost:8080/circle.jsp (db_fetched) http://localhost:8080/circle.jsp?time=123907953954240 (db_fetched) http://localhost:8080/circle.jsp?time=123930321590561 (db_fetched) http://localhost:8080/circle.jsp?time=123944319760709 (db_fetched) http://localhost:8080/circle.jsp?time=123951830890558 (db_fetched) http://localhost:8080/circle.jsp?time=123959814878130 (db_fetched) http://localhost:8080/circle.jsp?time=123967551109864 (db_fetched) http://localhost:8080/circle.jsp?time=123976440281803 (db_fetched) http://localhost:8080/circle.jsp?time=123984079586038 (db_fetched) http://localhost:8080/circle.jsp?time=123991682817130 (db_fetched) http://localhost:8080/circle.jsp?time=123999276416373 (db_fetched) http://localhost:8080/circle.jsp?time=124006908945776 (db_fetched) http://localhost:8080/circle.jsp?time=124015015509583 (db_fetched) http://localhost:8080/circle.jsp?time=124024925839499 (db_fetched) http://localhost:8080/circle.jsp?time=124034102389505 (db_fetched) http://localhost:8080/circle.jsp?time=124041764064461 (db_fetched) http://localhost:8080/circle.jsp?time=124049395271718 (db_fetched) http://localhost:8080/circle.jsp?time=124056979467640 (db_fetched) http://localhost:8080/circle.jsp?time=124064870330856 (db_fetched) http://localhost:8080/circle.jsp?time=124072499230132 (db_fetched) http://localhost:8080/circle.jsp?time=124080080885493 (db_fetched) http://localhost:8080/circle.jsp?time=124087617811162 (db_fetched) http://localhost:8080/circle.jsp?time=124095117258073 (db_fetched) http://localhost:8080/circle.jsp?time=124102663093132 (db_fetched) http://localhost:8080/circle.jsp?time=124110288582706 (db_fetched) http://localhost:8080/circle.jsp?time=124117843855932 (db_fetched) http://localhost:8080/circle.jsp?time=124125433893931 (db_fetched) http://localhost:8080/circle.jsp?time=124133061938250 (db_fetched) http://localhost:8080/circle.jsp?time=124140496939575 (db_fetched) http://localhost:8080/circle.jsp?time=124148039993387 (db_fetched) http://localhost:8080/circle.jsp?time=124155897999884 (db_unfetched)
6、查看提交给Solr的索引,发现索引文档数为30,言下之意就是Nutch并没有内容去重(不同的URL有相同或99.99%近似的内容)功能,当然了,Nutch有对同一个URL的去重功能,因为同一个URL会有多个版本嘛。
Num Docs:30 Max Doc:30
7、也许你可能会觉得很奇怪,没事搞这种指向自身的链接循环抓取干什么,难道有什么好处?没错,确实有好处!我们知道PageRank对搜索引擎结果排名的重要性,下面我们看看Nutch抓了30次之后每个页面的分值情况,circle.jsp的分值已经从注入时默认的1.0变为2.0了,随着抓取的次数越来越大,circle.jsp的分值就会越来越高!不过这种方法的分值提高还是很有限的,因为新发现的链接的分值=当前页面的分值除以当前页面的输出链接数,所以新发现的URL的分值会越来越小(折半)。为了让效果更好,我们可以构造很多链接,让他们之间相互引用,这样效果就会好得多,这也叫做“链接农场”。
http://localhost:8080/circle.jsp (Score: 2.0) http://localhost:8080/circle.jsp?time=134316762995966 (Score: 1.0) http://localhost:8080/circle.jsp?time=134326310205035 (Score: 0.5) http://localhost:8080/circle.jsp?time=134333900156323 (Score: 0.25) http://localhost:8080/circle.jsp?time=134341614881614 (Score: 0.125) http://localhost:8080/circle.jsp?time=134349238025525 (Score: 0.0625) http://localhost:8080/circle.jsp?time=134356824616950 (Score: 0.03125) http://localhost:8080/circle.jsp?time=134364502496801 (Score: 0.015625) http://localhost:8080/circle.jsp?time=134372113078909 (Score: 0.0078125) http://localhost:8080/circle.jsp?time=134379790766293 (Score: 0.00390625) http://localhost:8080/circle.jsp?time=134387649892340 (Score: 0.001953125) http://localhost:8080/circle.jsp?time=134395940369325 (Score: 9.765625E-4) http://localhost:8080/circle.jsp?time=134405766166198 (Score: 4.8828125E-4) http://localhost:8080/circle.jsp?time=134415907784492 (Score: 2.4414062E-4) http://localhost:8080/circle.jsp?time=134423612474766 (Score: 1.2207031E-4) http://localhost:8080/circle.jsp?time=134433400896206 (Score: 6.1035156E-5) http://localhost:8080/circle.jsp?time=134440922627127 (Score: 3.0517578E-5) http://localhost:8080/circle.jsp?time=134448441822753 (Score: 1.5258789E-5) http://localhost:8080/circle.jsp?time=134456076637041 (Score: 7.6293945E-6) http://localhost:8080/circle.jsp?time=134463676417913 (Score: 3.8146973E-6) http://localhost:8080/circle.jsp?time=134471189704200 (Score: 1.9073486E-6) http://localhost:8080/circle.jsp?time=134479013725922 (Score: 9.536743E-7) http://localhost:8080/circle.jsp?time=134486694863121 (Score: 4.7683716E-7) http://localhost:8080/circle.jsp?time=134494120468014 (Score: 2.3841858E-7) http://localhost:8080/circle.jsp?time=134501673767138 (Score: 1.1920929E-7) http://localhost:8080/circle.jsp?time=134509163792495 (Score: 5.9604645E-8) http://localhost:8080/circle.jsp?time=134516640328157 (Score: 2.9802322E-8) http://localhost:8080/circle.jsp?time=134524359639021 (Score: 1.4901161E-8) http://localhost:8080/circle.jsp?time=134531631412690 (Score: 7.4505806E-9) http://localhost:8080/circle.jsp?time=134539151699502 (Score: 3.7252903E-9) http://localhost:8080/circle.jsp?time=134546846151757 (Score: 1.8626451E-9)
参考资料:
1、The Anatomy of a Large-Scale Hypertextual Web Search Engine