肉砣砣(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。这样尽早解码,尽晚编码的策略可以保证不出现乱码。


Fatal error: Call to undefined function wp_related_posts() in /www/lenciel.cn/wp-content/themes/ubox-rc3/comments.php on line 22