天天看点

网络爬虫实现原理

何谓网络爬虫

网络爬虫是一种用来抓取网页资源的程序工具。像谷歌,百度等知名搜索引擎就是采用网络爬虫把全网的网页资源收集起来,建立索引,用于搜索。

网络爬虫实现原理

互联网网页可以看成是一张超大图,每个网页是一个节点,网页中指向其他网页的链接是边。那么,可以这样实现全网网页收集:以某一个网页为起点,下载并处理该网页,解析里面的链接,所得的URL加入下载队列。这个过程其实就是图的遍历过程,可以是深度优先或者广度优先遍历,取决于下载队列如何维护。简单地,网络爬虫可以由以下部分组成:

下载模块

对于一个给定的URL,下载该网页。如果从零开始实现,工作量还是挺大的:①解析URL里面的域名并通过DNS查询域名对应的IP;②建立一个到该IP的TCP连接;③发送一个HTTP请求;④接收并解析HTTP响应;⑤保存该网页资源。说白了,就是给定一个URL,用HTTP协议请求一个网页资源的过程。

下载队列

下载队列保存从网页中解析出来将用于获取网页资源的URL:每次从下载队列里面取出一个URL,通过下载模块下载该网页,解析该网页里面的URL并加入下载队列,这样就可以源源不断地进行网页抓取。如果用FIFO实现下载队列,那么对网页资源的遍历就是广度优先的;如果用LIFO实现下载队列,那么对网页资源的遍历就是深度优先的。

重复检查

如果某个网页里面包含了已经下载的网页的URL怎么办呢?肯定不能再重复下载一次。如何避免就是重复检查模块要做的事。可以用一个set把所有遇到的URL记录下来,每次下载模块获取一个网页,将其URL放到该set;解析网页所获得的URL,如果在该set中已经存在了,就不要加入下载队列了。当然了,这只是最直白的实现方式,工程上为了应对大量的URL,一般采用布隆过滤器。

Python实现举例

#!/usr/bin/env python
# -*- encoding=utf8 -*-

import re, urllib2, md5, urlparse

class Crawler(object):
    def __init__(self, *starts):
        self.seen = set()   # 已获取网页的URL集合
        self.queue = []     # 待下载队列
        self.URLMOD = re.compile('href="(http://[^" target="_blank" rel="external nofollow" ]*)"')   # 用于匹配URL的正则

        # 将起点URL加入下载队列
        for start in starts:
            self.queue.append(start)

    def process(self, url, res):
        # 正则解析网页里面的URL
        for new_url in self.URLMOD.findall(res):
            if new_url.find('\n') == -1 and new_url not in self.seen:
                # 如果URL不在已获取集合,则加入下载队列
                self.queue.append(new_url)

        # 在这里写个性化的网页处理逻辑:保存到文件?写到数据库?

    def run(self):
        while self.queue:
            # 从下载队列取出一个URL
            url = self.queue.pop()

            try:
                # 下载该网页:用Python的urllib2
                res = urllib2.urlopen(url, timeout=10).read()
                print 'Get %s %s' % (url, '')
            except:
                # 下载出错
                print 'Err %s %s' % (url, '')
                continue

            # 将URL加入已获取集合
            self.seen.add(url)

            # 处理该网页
            self.process(url, res)

if __name__ == '__main__':
    Crawler('http://www.baidu.com').run()
           

注意这只是一个非常丑陋的实例,所谓麻雀虽小,五脏俱全。猜猜这里的遍历是深度优先还是广度优先!