网络爬虫
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》
最近在写一个程序,去爬热门事件和热门关键词网站上的数据。在这里介绍一下网络爬虫的种种。
基本组件
网络爬虫也叫做网络蜘蛛,是一种互联网机器人,把需要的网页撷取下来,组织成适当格式存储。它是搜索引擎的重要组成部分,虽然从技术实现上来说,它的难度往往要小于对于得到的网页信息的处理。
上面这张图来自 维基百科,scheduler调度多个多线程的下载器下载网页,并把信息和元数据存储起来。而通过解析下载网页的数据,找到链接,又把链接加入到工作队列中去准备下载。这看起来是一个迭代的过程。
网络爬虫相关的几项重要策略:
- 选择策略:哪些网页是需要被抓取的;
- 重访问策略:怎样的方式去检测网页是否被修改过;
- 礼貌性策略:抓取网页的时候,需要方式网站过载;
- 并行化策略:怎样组织分布式的网络爬虫。
选择策略
限定跟随链接。通常只需要html文本信息,所以根据MIME类型,如果不是文本信息,会被丢弃掉。所以,如果URL无法得知资源的二进制类型,爬虫可能会先发起一个head请求获知目标是不是文本,如果是的话,才发送一个get请求获取页面。
URL标准化。它用来避免多次爬到相同的页面。有些URL包含“../”这样的相对路径信息,这也需要爬虫处理使之成为完整正确的URL,而有些URL则需要在最后面加上斜杠。
路径升序。有些爬虫想尽可能爬多的信息,资源是有层级关系的,比如 http://llama.org/hamster/monkey/page.html这样一个链接,它会尝试爬“/hamster/monkey/”、“/hamster/”和“/”这几个页面。
学术网页爬虫。专注于学术领域,比如Google Scholar等等。
重访问策略
网页总是在动态变化的,爬完整一个网站可能会花掉数周甚至一个月的时间,在爬完一个网页之后,网页可能就不在了,或者更新了。什么时候再爬这个网站呢?通常对于经常变化的网页,这个间隔时间应该短一些。
新鲜度:在一定时间t内,页面是否有变化 。
Age: 本地网页拷贝有多么过时。
有一种常见的重访问策略是,先以一个默认的频度访问页面,如果发现页面持续不更新,就逐步降低这个频度;反之亦然。
礼貌性策略
爬虫当然可以尽可能快地爬取数据,但是我们需要考虑网站的性能压力,已经对网络资源的消耗。
有一个 robots exclusion协议,指定了爬虫应该怎样访问网站的资源,哪些可以访问,哪些不可以访问。这个协议并不是强制的,但是已经是事实上的标准。比如,Crawl-delay参数,就可以定义每连续两次的请求,至少需要间隔多少秒。
以本网站的robots.txt为例:
User-agent: *
Disallow: /wp-admin/
Disallow: /wp-includes/
其中的User-agent表示对爬虫类型没有限制,Disallow两行指定了哪些URL是不允许搜索引擎抓取的;Sitemap则是一个xml格式的网站地图:
<url> <loc>http://www.raychase.net/</loc> <lastmod>2013-05-25T16:32:13+00:00</lastmod> <changefreq>daily</changefreq> <priority>1.0</priority> </url> <url> <loc>http://www.raychase.net/1374</loc> <lastmod>2013-05-19T14:46:19+00:00</lastmod> <changefreq>monthly</changefreq> <priority>0.2</priority> </url>
指定了网站有哪些页面,更新频率和权重各是多少。如果有些页面没有明显的直接链接抵达,通过sitemap的形式可以告知爬虫去抓取。如果你很讨厌某个搜索引擎,你可以这样指定(我只是举例而已,百度除了假药假广告侵权信息管制主观性过滤以外也是做了一些好事的 -_-~):
User-agent: Baiduspider
Disallow: /
网页深度
通常,越是深的链接,重要性越低,数量也越大。对于爬虫来说,越深的链接往往价值越小。在大多数情况下,我们不需要所有的信息,这时候需要控制合理的网页深度,价值高的网站可以深度适当大一些。
互联网即数据库
以前 我曾经介绍过YQL,一种像SQL查询数据库一样查询互联网网页数据的语言,你也可以在这个 YQL控制台自己试试:
select * from flickr.photos.search where text="Cat" and api_key="your key here" limit 10
这是一个“get 10 flickr “cat” photos”的例子。
再比如:
select * from html where url="http://finance.yahoo.com/q?s=yhoo" and xpath='//div[@id="yfi_headlines"]/div[2]/ul/li/a'
如果你看得懂XPATH表达式的话,这一定很清楚。它可以以普通HTTP API的方式暴露出来:
http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22http%3A%2F%2Ffinance.yahoo.com%2Fq%3Fs%3Dyhoo%22%20and%20xpath%3D'%2F%2Fdiv%5B%40id%3D%22yfi_headlines%22%5D%2Fdiv%5B2%5D%2Ful%2Fli%2Fa'&format=json&diagnostics=true&callback=cbfunc
在很多情况下,我们需要的并不是宽泛的信息,而是明确地知道自己需要什么信息,譬如某网站总是显示自己关心的信息,那么就可以借用它来定时爬取特定的页面(比如我以前干过这样的事:一场NBA比赛结束了,我没有看,但是我很想尽快地看到比赛录像,于是每隔几分钟就去爬取一个论坛比赛下载的帖子汇总页面,当“马刺”那个关键词出现的时候,我就去看一下是不是有比赛下载了。当然,你可以做得更好,写一个脚本让整个下载过程自动化完成,这样你就可以安心上班了,等回到家的时候,比赛已经下载完毕等你去看了)。现在这样的事情可以通过上面的HTTP API链接更方便地完成了。
完成整个过程以后,你一定会有这样的体会,互联网其实就是一个硕大的数据库(不管URI的定义是否符合RESTful风格,它最多只能说是数据是不是能够以符合某种统一格式的方式来获取)。
- HTML、RSS、PDF……这些只是不同的数据格式而已,和内容无关,获取这些不同格式不同地址数据的组件可以叫做requester;
- XPATH、CSSPath……这些是针对不同数据,去解析这些数据,指定有用信息的路径表达式,解析这些数据的组件被称为parser;
- 存储每一内容条目使用哪个requester和哪个parser的配置,汇总在一个configuration的组件内;
- 现在,还需要一个调度器scheduler管理若干个线程(或进程)根据configuration去爬取数据了;
- 数据爬取完成后持久化到存储组件storage中。
有了上面这5个组件,一个特定信息的爬取程序就完成了,和一个传统意义上的网络爬虫相比,它更加简单,也不需要解析HTML链接含义的过程。
另外,值得注意的是,有时候网站会做反爬虫机制,与其去猜它反爬虫的规则,还不如通过脚本启动一个浏览器去请求页面。还有,页面之间的关联关系有时并不能够从<a href=”xxx”>这样的链接中发现,有时是通过JavaScript的Ajax请求等等实现,这种情况下的链接关系对网络爬虫并不友好,也可以通过启动一个真实的浏览器请求去获取。
Java有名的开源网络爬虫包括 Heritrix和 Nutch,后续我再详细介绍一下。
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》