肉砣砣(2) – Crawler
网络上Python的crawler(爬虫,也叫robots)实在是满坑满谷。最有名的大概要算是Harvest Man。但是本座看过的crawler都有类似的缺点:首先是文档写得非常烂,大概这是开源小项目不可避免的特色。然后就是一般都是为了专门的用途开发,很难进行自由自在的扩展。
所以,肉砣砣的爬虫是本座自己动手写的。写一个爬虫主要的困难点有:
1. 如何使得你爬网页的规则容易制定和扩展。
2. 如何在访问的时候让自己不被服务器ban掉。
3. 怎么处理访问时延和负载平衡的问题。
另外,还有一个蛮关键的选择就是用单线程还是多线程。因为怕有的url打开时超时造成整个程序阻塞,肉砣砣是用了多线程来进行抓取的。
在简介里面说过,肉砣砣是由Crawler、Analyzer和Exporter组成的。这个Crawler其实又是由一个控制器(controller)和多线程的抓取器(fetcher)构成。
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
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的,说明一下。
获取一个指定的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:
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。这样尽早解码,尽晚编码的策略可以保证不出现乱码。

6 Comments
Jump to comment form | comments rss [?] | trackback uri [?]