肉砣砣(2) – Crawler

网络上Python的crawler(爬虫,也叫robots)实在是满坑满谷。最有名的大概要算是Harvest Man。但是本座看过的crawler都有类似的缺点:首先是文档写得非常烂,大概这是开源小项目不可避免的特色。然后就是一般都是为了专门的用途开发,很难进行自由自在的扩展。

所以,肉砣砣的爬虫是本座自己动手写的。写一个爬虫主要的困难点有:

1. 如何使得你爬网页的规则容易制定和扩展。

2. 如何在访问的时候让自己不被服务器ban掉。

3. 怎么处理访问时延和负载平衡的问题。

另外,还有一个蛮关键的选择就是用单线程还是多线程。因为怕有的url打开时超时造成整个程序阻塞,肉砣砣是用了多线程来进行抓取的。

1. 具体实现

简介里面说过,肉砣砣是由Crawler、Analyzer和Exporter组成的。这个Crawler其实又是由一个控制器(controller)和多线程的抓取器(fetcher)构成。

1.1 Controller

controller是单线程的,它负责把Analyzer那边送出的接下来要抓取的URL喂给一个空闲的fetcher thread。

1.1 Controller pseudo-code

While (pages crawled<limit and not getStopSign)

url = analyzer.selectNextUrl

for f in fetchers:

if f is free:

fetcher =f

assign url to fecher

fecher.run

1.2 Fetcher

Fetcher是负责从URL抓取内容的一组线程。它调用两砣东西:UrlHandler(负责取回URL的内容)和Extractor(负责从取回的内容中得到原始内容和HTML树)。最后这棵树会用一个Site对象来保存。Site对象会被插入到sites这个queue里面去,然后激活Analyzer。

1.2 Fecher pseudo-code

content= urlHandler(url)

extractor.setSite(content)

processedInfo =extractor.extract()

site = Site(processedInfo)

add site to site queue

analyzer.run

这里获取HTML树和原始内容的代码,都是基于lxml的,说明一下。

processUrl

获取一个指定的url的内容:

def processUrl(self, stringUrl):
        """
        Sets the url that the parser is working on.
        Raises an exception if we can't open it.
        """
        self.robotParser = robotparser.RobotFileParser()
        # check access rights, if not ok raise exception
        if not self.__canVisitSite(stringUrl):
            logging.info
            raise Exception
        # create the HTTP request
        req = self.__createRequest(stringUrl)
        # open the url and set our site to the opened url
        site = urllib2.urlopen(req)
 
        if (not (site.headers.type == 'text/html')) and
           (not (site.headers.type == 'application/x-javascript')):
               logging.info
               raise Exception
        logging.info('successfully opened %s'; % stringUrl)
        self.currentSite = site

lxml解析HTML

本座就是那么喜欢lxml:

def setSite(self, stringUrl, content):
        """
        Sets the current site url and content for the extractor.
        @param stringUrl: The url of the site being analyzed.
        @param content: The html content of the site.
        """
        self.stringUrl = stringUrl #.rstrip('/ ')
        preDomain = urlparse.urlparse(self.stringUrl)
        self.domain = urlparse.urlunparse((preDomain[0],
                      preDomain[1],'', '', '', ''))
 
        fullContent = content.read()
        doc = H.document_fromstring(fullContent.decode('utf-8'))
        doc.make_links_absolute(extractBaseUrl(stringUrl))
        self.parsedContent = doc
        self.rawContent = H.tostring(doc, pretty_print=True,
                         encoding=unicode,method='html')

这里有个很重要的地方就是拿到http返回的内容content后,第一时间就把它decode成unicode,然后在整个程序里面,都用unicode进行操作,直到我们要导出wrx文件的时候,才encode。这样尽早解码,尽晚编码的策略可以保证不出现乱码。



  1. Leo 1.5.09 / 5下午

    最后一句话可谓是金玉良言

  2. weil 7.11.09 / 2上午

    :tongue: 向云中的高手学习

    lenciel

    云中高手啥意思?云中鹤么,哈哈。

  3. weil 7.17.09 / 8下午

    最近研究lxml建立树时发现会丢掉一部分取来的html
    就这点来看,php的dom更好一些.
    丢掉的数据可以是你刚好要抓取的.郁闷

    lenciel

    为什么我用的时候没有出现过呢?我一抓就是几千几百页的数据….

  4. ke 6.15.11 / 10下午

    请问哪里可以下载您的crawler啊?我是菜鸟一个但我现在我要做一个关于thematic crawler的,也就是带方向(主题)的网页爬虫 (所以可以的话还麻烦您发一份到我的邮箱里,十分感谢啊!!):
    主要思想是从一些关键字入手在搜索引擎搜索,然后把搜索到的网页提供给爬虫进行挖掘,爬虫会找到所有的链接以及链接周围的文字(一般如何提取这样的文字呢?),把所有链接放入一个列表,然后对比和关键字最相配的链接进行向下挖掘(找到其所有的子链接),然后把列表中的母链接换成其所有子链接重新找和关键字最相近的链接,如此循环,直到满足必要条件结束。如果要在您的crawler中实现起来简单吗?可以说说大概如何实现吗?非常感谢!!!

Have your say

:want: :w00t: :tongue: :thrwon: :smile: :sick: :shock: :sad: :ninja: :getlost: :ermm: :dizzy: :devil: :cool: :boring: :blush: :blink: :angry: :angel:



Safari hates me


Douban Update

Get the Flash Player to see the slideshow.
去看看吧