通用爬蟲(Broad Crawls)介紹
[傳送:中文文檔介紹],里面除了介紹還有很多配置選項(xiàng)尝抖。
通用爬蟲一般有以下通用特性:
其爬取大量(一般來說是無限)的網(wǎng)站而不是特定的一些網(wǎng)站懊昨。
其不會(huì)將整個(gè)網(wǎng)站都爬取完畢窄潭,因?yàn)檫@十分不實(shí)際(或者說是不可能)完成的。相反酵颁,其會(huì)限制爬取的時(shí)間及數(shù)量嫉你。
其在邏輯上十分簡(jiǎn)單(相較于具有很多提取規(guī)則的復(fù)雜的spider),數(shù)據(jù)會(huì)在另外的階段進(jìn)行后處理(post-processed)
其并行爬取大量網(wǎng)站以避免被某個(gè)網(wǎng)站的限制所限制爬取的速度(為表示尊重躏惋,每個(gè)站點(diǎn)爬取速度很慢但同時(shí)爬取很多站點(diǎn))幽污。
- 正如上面所述,Scrapy默認(rèn)設(shè)置是對(duì)特定爬蟲做了優(yōu)化其掂,而不是通用爬蟲油挥。不過, 鑒于其使用了異步架構(gòu)款熬,Scrapy對(duì)通用爬蟲也十分適用。 本篇文章總結(jié)了一些將Scrapy作為通用爬蟲所需要的技巧攘乒, 以及相應(yīng)針對(duì)通用爬蟲的Scrapy設(shè)定的一些建議贤牛。
創(chuàng)建默認(rèn)工程
scrapy創(chuàng)建工程是通過命令進(jìn)行,創(chuàng)建一個(gè)名為proname的scrapy工程:
scrapy startproject proname
然后根據(jù)提示:
cd proname
然后生成爬蟲模板:
scrapy genspider lagou www.lagou.com
創(chuàng)建Broad Crawls工程
通用爬蟲的創(chuàng)建過程與默認(rèn)爬蟲創(chuàng)建過程一樣则酝,只是在生成爬蟲模板的時(shí)候命令不同殉簸,生成不同的爬蟲模板:
scrapy genspider -t crawl lagou www.lagou.com
只需要增加 -t crawl即可。
源碼邏輯解析
主要邏輯:
1.爬蟲模板主要使用的類是CrawlSpider沽讹,而它繼承的是Spider般卑。
2.Spider的入口函數(shù)是start_requests()。
3.Spider的默認(rèn)返回處理函數(shù)是parse(),它調(diào)用_parse_response(),則允許我們重載parse_start_url()和process_results()方法來對(duì)response進(jìn)行邏輯處理爽雄。
4._parse_response()會(huì)去調(diào)用爬蟲模板設(shè)置的rules=(Rule(LinkExtractor…)),將response交給LinkExtrator,LinkExtrator會(huì)根據(jù)我們傳進(jìn)來的參數(shù):
allow=(), deny=(), allow_domains=(), deny_domains=(), restrict_xpaths=(),
tags=('a', 'area'), attrs=('href',), canonicalize=False,
unique=True, process_value=None, deny_extensions=None, restrict_css=(),
strip=True
進(jìn)行處理摧茴,其中deny的意思是除了它以外兴猩,反向取值,比如deny=('jobs/')則在處理的時(shí)候就會(huì)略過jobs乘盖,只爬取jobs以外的規(guī)則焰檩。
在項(xiàng)目目錄的spiders文件夾下默認(rèn)生成proname.py:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class GxrcSpider(CrawlSpider):
name = 'proname'
allowed_domains = ['www.proname.com']
start_urls = ['http://www.proname.com/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
i = {}
return i
1.通過Ctrl + 鼠標(biāo)左鍵的方式跟蹤C(jī)rawlSpider類,發(fā)現(xiàn)它是繼承Spider的订框,往下看到CrawlSpider中有個(gè)parse方法析苫,那么就意味著后面寫代碼的時(shí)候不能跟之前一樣在代碼里自定義parse函數(shù)了,最好像模板給出的parse_item這種寫法。
2.解析parse函數(shù)衩侥,它調(diào)用了_parse_response方法:
def parse(self, response):
return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
其中的_parse_response可以說是核心函數(shù)国旷,里面的參數(shù)cb_kwargs代表著參數(shù)。通過Ctrl+左鍵跟進(jìn)_parse_response:
def _parse_response(self, response, callback, cb_kwargs, follow=True):
if callback:
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res)
for requests_or_item in iterate_spider_output(cb_res):
yield requests_or_item
if follow and self._follow_links:
for request_or_item in self._requests_to_follow(response):
yield request_or_item
首先它判斷是否有callback,就是parse函數(shù)中的parse_start_url方法顿乒,這個(gè)方法是可以讓我們重載的议街,可以在里面加入想要的邏輯;
然后它還會(huì)將參數(shù)cb_kwargs傳入到callback中璧榄,往下看它還調(diào)用了process_result方法:
def process_results(self, response, results):
return results
這個(gè)方法什么都沒做特漩,把從parse_start_url接收到的result直接return回去,所以process_result方法也是可以重載的骨杂。
接著看:
if follow and self._follow_links:
for request_or_item in self._requests_to_follow(response):
yield request_or_item
如果存在follow涂身,它就進(jìn)行循環(huán),跟進(jìn)_requests_to_follow看一看:
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [lnk for lnk in rule.link_extractor.extract_links(response)
if lnk not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = self._build_request(n, link)
yield rule.process_request(r)
在 _requests_to_follow中首先判斷是否是response搓蚪,如果不是就直接返回了蛤售,如果是就設(shè)置一個(gè)set,通過set去重妒潭;然后把_rules變成一個(gè)可迭代的對(duì)象悴能,跟進(jìn)_rules:
def _compile_rules(self):
def get_method(method):
if callable(method):
return method
elif isinstance(method, six.string_types):
return getattr(self, method, None)
self._rules = [copy.copy(r) for r in self.rules]
for rule in self._rules:
rule.callback = get_method(rule.callback)
rule.process_links = get_method(rule.process_links)
rule.process_request = get_method(rule.process_request)
看到:
rule.callback = get_method(rule.callback)
rule.process_links = get_method(rule.process_links)
rule.process_request = get_method(rule.process_request)
這幾個(gè)都是前面可以傳遞過來的,其中rule.process_links是從Rule類中傳遞過來的:
class Rule(object):
def __init__(self, link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=identity):
self.link_extractor = link_extractor
self.callback = callback
self.cb_kwargs = cb_kwargs or {}
self.process_links = process_links
self.process_request = process_request
if follow is None:
self.follow = False if callback else True
else:
self.follow = follow
雖然process_links默認(rèn)為None雳灾,但是實(shí)際上我們?cè)谛枰臅r(shí)候可以設(shè)置的漠酿,通常出現(xiàn)在前面爬蟲模板代碼里面的
Rule(LinkExtractor(allow=r'WebPage/JobDetail.*'), callback='parse_item', follow=True,process_links='links_handle')
然后可以在url那里增加各種各樣的邏輯,這里只簡(jiǎn)單的打印輸出:
def links_handle(self, links):
for link in links:
url = link.url
print(url)
return links
可以將url進(jìn)行其他的預(yù)處理谎亩,比如可以將url拼接到一起炒嘲、設(shè)置不同的url或者對(duì)url進(jìn)行字符切割等操作。(使用舉例:常用于大型分城市的站點(diǎn)匈庭,比如58的域名nn.58.com夫凸、wh.58.com,就可以通過這個(gè)對(duì)各個(gè)站點(diǎn)的域名進(jìn)行預(yù)匹配)
再來到_requests_to_follow方法中看處理邏輯
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [lnk for lnk in rule.link_extractor.extract_links(response)
if lnk not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = self._build_request(n, link)
yield rule.process_request(r)
set去重后就yield交給了_build_request處理阱持,build_request則調(diào)用_response_downloaded進(jìn)行頁面的下載夭拌,下載后的頁面交給_parse_response