定向抓取漫谈
源:http://www.searchtb.com/2011/01/an-introduction-to-crawler.html
评:作者淘宝长孙秦
网络爬虫(webcrawler)又称为网络蜘蛛(webspider)是一段计算机程序,它从互联网上按照一定的逻辑和算法抓取和下载互联网的网页,是搜索引擎的一个重要组成部分。一般的爬虫从一部分starturl开始,按照一定的策略开始爬取,爬取到的新的url在放入到爬取队列之中,然后进行新一轮的爬取,直到抓取完毕为止。
我们看一下crawler一般会遇到什么样的问题吧:
抓取的网页量很大
网页更新量也很大,一般的网站,比如新闻,电子商务网站,页面基本是实时更新的
大部分的网页都是动态的,多媒体,或者封闭的(facebook)
海量网页的存在就意味着在一定时间之内,抓取只能的抓取其中的一部分,因此需要定义清楚抓取的优先级;网页更新的频繁,也就意味着需要抓取最新的网页和保证链接的有效性,因此一个更有可能带来新网页的列表页显得尤为重要;对于新闻网站,新的网站一般出现在首页,或者在指定的分类网页,但是对于淘宝来说,商品的更新就很难估计了;动态网页怎么办呢?现在的网页大都有JS和AJAX,抓取已经不是简单的执行wget下载,现代的网页结构需要我们的爬虫更加智能,需要更灵活的应对网页的各种情况。
因此,对一个通用的爬虫个,我们要定义
抓取策略,那些网页是我们需要去下载的,那些是无需下载的,那些网页是我们优先下载的,定义清楚之后,能节省很多无谓的爬取
更新策略,监控列表页来发现新的页面;定期check页面是否过期等等
抽取策略,我们应该如何的从网页中抽取我们想要的内容,不仅仅包含最终的目标内容,还有下一步要抓取的url
抓取频率,我们需要合理的去下载一个网站,却又不失效率
抓取策略
使用URL的正则特征是一个简单但却很高效的模式;对于定向抓取,一般的网站的URL有一定的特征,比如可能仅仅关心.html,.htm,.asp,.aspx,.php,.jsp,.jspx类型的网页;或者是如果可以得到目标网站的正则,则可以大大的降低抓取的数量;又或者我们无需关心某一类网页,比如我们不抓取bbs.taobao.com下面的内容;仅仅需要抓取淘宝的商品页面(http://item.taobao.com/item.htm?id=\d+)。通过URL的正则能极大的降低抓取数量;
也可以通过网页的文本特征来确定;不过要复杂得多了,一般需要一定数量已知页面的训练集合,然后提取页面的文本特征,然后通过向量空间模型或者其其他基于主题词提取的模型计算目标网页和训练集网页的距离,决定是否是目标网页。
更新策略
Freshness:表示抓取到的网页是否已经被修改
Age:表示抓取的网页过期的时间
对于更新来说,目标是让平均age时间越小,freshness越高;一般的更新策略有两种:定期批量更新和按更新周期更新;定期批量更新指对一批URL,按照失效时间定期去刷新,按周期更新指的是按照页面更新变化频率而修正是更新频率,一般来说,更新越频繁的网页更新也就越快。
抽取策略:
XPATH是一个简单直观,但是很有效的一个方案,XPATH能精准的定位网页的任意一个位置,意味着我们可以很精准的抽取页面上的任意位置,当面临很多网站的时候,当然配置XPATH就是一个很艰巨的任务,也许存在一个自适应的XPATH识别的方法。
JS和AJAX
在java下面,HtmlUnit是一个不错的解决方案,HtmlUnit是Junit的扩展测试框架之一,该框架模拟浏览器的行为,开发者可以使用其提供的API对页面的元素进行操作,套用官方网站的话HtmlUnit“是Java程序的浏览器”。HtmlUnit支持HTTP,HTTPS,COOKIE,表单的POST和GET方法,能够对HTML文档进行包装,页面的各种元素都可以被当作对象进行调用,另外对JavaScript的支持也比较好。一般来说,HtmlUnit是在java环境下解决JS的很好的选择
WebKit包含一个网页引擎WebCore和一个脚本引擎JavaScriptCore,它们分别对应的是KDE的KHTML和KJS;目前比较主流的浏览器GoogleChrome和Apple的safari,都是基于WebKit的内核写的。使用浏览器作为抓取能更好的模拟用户浏览的行为,能够天然的解决JS和AJAX等问题,问题可能就是性能是一个瓶颈,
抓取频率
同时开启N个线程抓取一个网站,相信很快就会被对方网站封掉;因此抓取的频率也很重要;抓取网站同时不对对方网站造成压力;在robot.txt协议里面定义Crawl-delay来确定抓取的频率也是一种网站的通用的做法,对于一般的抓取而言,10到20秒抓取一次是一个比较保险的频率,也有提出10*t的抓取间隔(t是download时间)比较合理
定向抓取的框架
通用抓取架构,如下图
多线程下载模块(Multi-threadeddownloader)
该模块一般包含:
下载模块,下载网页,并应对一些web的一些错误,包括redirect等等
DNS解析模块,网页数量很多的时候,我们需要一个本地的DNS解析模块来维护domain到IP的映射
链接抽取模块,抽取下一步要抓取的链接(followlink)
调度模块(schedule)
调度模块是抓取系统的核心,调度模块从url队列里面选择一批url喂到下载模块下载;其中会涉及到
url调度,调度模块按照一定的策略,选取url进入抓取系统
url除重,一定时期之内已经抓取的网页,不再抓取
实例:使用开源的scrapy爬虫抓取B2C站点的商品
Scrapy(http://scrapy.org/)是基于Twisted的异步处理框架,纯python实现的爬虫框架,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便,现在我们以scrapy为例子,简单配置一个抓取商品的例子。
Scrapy的安装请参考http://doc.scrapy.org/intro/install.html#intro-install
定义起始页面:http://www.xxxx.com
定义详情页,例如:http://product.xxxx.com/product.htm?id=\d+
执行scrapystartprojectmycrawler,新建一个mycrawler的工程
在mycrawler/mycrawler/spiders目录下,写抓取的python代码:mycrawlerspider.py
fromscrapy.selectorimportHtmlXPathSelector
fromscrapy.spiderimportBaseSpider
fromscrapy.contrib.linkextractors.sgmlimportSgmlLinkExtractor
fromscrapy.utils.urlimporturljoin_rfc
fromscrapy.httpimportRequest
classMySpider(BaseSpider):
name='test'
allowed_domains=['xxxx.com']
start_urls=[
'http://www.xxxx.com',
]
download_delay=10
defparse(self,response):
forlinkinSgmlLinkExtractor(allow="product.htm\?id=\d+").extract_links(response):
yieldRequest(link.url,callback=self.parse_detail)
hxs=HtmlXPathSelector(response)
forurlinhxs.select('//a/@href').extract():
url=self._urljoin(response,url)
#printurl
yieldRequest(url,callback=self.parse)
defparse_detail(self,response):
hxs=HtmlXPathSelector(response)
what_u_want=hxs.select("/xpath/text()").extract()[0]
print'url=',response.url,what_u_want.strip()
return
def_urljoin(self,response,url):
"""Helpertoconvertrelativeurlstoabsolute"""
returnurljoin_rfc(response.url,url,response.encoding)
fromscrapy.selectorimportHtmlXPathSelector
fromscrapy.spiderimportBaseSpider
fromscrapy.contrib.linkextractors.sgmlimportSgmlLinkExtractor
fromscrapy.utils.urlimporturljoin_rfc
fromscrapy.httpimportRequest
classMySpider(BaseSpider):
name='test'
allowed_domains=['xxxx.com']
start_urls=[
'http://www.xxxx.com',
]
download_delay=10
defparse(self,response):
forlinkinSgmlLinkExtractor(allow="product.htm\?id=\d+").extract_links(response):
yieldRequest(link.url,callback=self.parse_detail)
hxs=HtmlXPathSelector(response)
forurlinhxs.select('//a/@href').extract():
url=self._urljoin(response,url)
#printurl
yieldRequest(url,callback=self.parse)
defparse_detail(self,response):
hxs=HtmlXPathSelector(response)
what_u_want=hxs.select("/xpath/text()").extract()[0]
print'url=',response.url,what_u_want.strip()
return
def_urljoin(self,response,url):
"""Helpertoconvertrelativeurlstoabsolute"""
returnurljoin_rfc(response.url,url,response.encoding)
最后,在工程目录里面执行scrapycrawlmycrawler
简单几步,我们就可以抓取得到一个站点想要的页面了,并且可以抽取指定xpath的内容。
一个简单的定向爬虫就搭建起来了,关键是一个能够大规模的分布式的爬虫可能是一个挑战,后续再进一步介绍如何在分布式环境进行大规模的抓取,以及抓取遇到的一些更为棘手的问题,seeu