赞
踩
本系列文章在上章节: 菜鸟写Python-Scrapy:Spider源码分析,分析了Scrapy的Spider爬虫源码,并且也提到了spider分为四种模版,常见是的basic和crawl两种,那么本文将详细阐述一下CrawlSpider基本情况。
一、前言
我们发现很多网站中下属url都是有一定规则的(如django在item中定义的urls规则就是正则表达的),那么我们如果能利用这些规则,似乎就可以省去我们仔细去解析网页面中url的苦力,有没有方法呢?答案是肯定的,要满足这个要求就可以使用spider提供的第二个模块crawlspider,它非常适合爬取整个网站数据。
二、CrawlSpider分析
CrawlSpider是爬取一般网站常用的spider。它定义了一些规则(rule)来提供跟进link的方便的机制。它同样要继承Spider,然后在基础上提供了一些Rule规则来指定爬取规则。
生成CrawlSpider项目的cmd命令:scrapy genspider -t crawl lagou www.lagou.com (scrapy genspider -t 模版 项目名 域名)
相对之前的改变在于通过-t指定了spider的模版,如果不指定则默认为basic模版,这个就是我们之前使用的。
crawlspider要做的是全站点urls的匹配,当找到fllow我们要页面时才做页面解析,因此要实现这一步,肯定要告诉我们的spider哪一种的url规则才是我们要爬取的,所以crawlspider相比basicspider先提供了一个Rule规则类,来筛选符合要求我们的urls。
CrawlSpider基于Spider自己独有的特性:
Rule参数:link_extractor、callback=None、cb_kwargs=None、follow=None、process_links=None、process_request=None
如果这一步理解拗口,建议要挑到第三节看怎么使用crawlspider,然后又感觉再回来看rules的定义。
参数理解:
1.link_extractor:该值(方法)是一个Link Extractor实例,主要定义的就是链接的解析规则。规则如下:
2.callback:回调方法,用来解析response爬取页面数据的。但是回调方法函数名不能用parse,因为parse在crawlspider定义了并且有自己不一样的作用,它已经不像spider类中的那样没有具体操作。
3.cb_kwargs:给callback方法传递参数。
4.follow:布尔对象,用来表示是当前页面(response)中的url是否继续采集。如果callback是None,那么它就默认为True,否则为False。
5.process_links:该方法在crawlspider中的_requests_to_follow方法中被调用,它接收一个元素为Link的列表作为参数,返回值也是一个元素为Link的列表。可以用该方法对采集的Link对象进行修改,比如修改Link.url。这里的如果你的目标url是相对的链接,那么scrapy会将其扩展成绝对的。
6.process_request:处理request的。(其实后面两个参数,我也还没有实际用上,先当理解吧,觉得拗口就先跳过吧)
三、CrawlSpider使用
我们这一步先尝试根据官方给出的模版先写一个crawlspider,因为我发现我先去看了源码,发现源码理解可比使用要难的多,但是crawlspider的基本使用确实简单易解,一看就会使用。
(这里已爬取拉勾招聘职位数据为案例)
1.生成一个crawlspider:scrapy genspider -t crawl lagou www.lagou.com
2.编写Rule规则,筛选要爬取的url,以lagou为例:
2.1 抓取urls的规则--目的是找到招聘职位的详情页面,如https://www.lagou.com/jobs/3959551.html,这是一个详细python招聘职位的描述,然后爬取这个岗位的详细数据 2.2 那怎么才能从lagou这个站中那么多urls找到类似上述的url呢?所以要给定抓取urls规则或者范围 2.3 分析lagou发现从以下两个地方可以进入招聘职位页面: # 1、招聘分类:这里面有各类招聘的所有职位; https://www.lagou.com/zhaopin/***** 招聘页面urls共性 # 2、招聘需要公司发布,我们从公司页面也可以抓取到所有的职位; https://www.lagou.com/gongsi/** 公司页面共性 # 3、从上面两个大urls可以进入到一个具体岗位的url,如https://www.lagou.com/jobs/3959551.html,而.*/jobs/.*html是共性 所以我们可以写一个Rules,来界定我们抓取的规则:1圈定范围 2从范围中找到要解析的页面 rules = ( # 根据上面分析,可以写一些规则也圈定抓取的urls # 爬取urls中有 zhaopin/的urls,并却对这类页面中的所有的url进行跟进follow Rule(LinkExtractor(allow=('.*/zhaopin/.*',)),follow=True), # 也可以再加规则,如果发现公司页面也进行follow然后找到公司中的发布的职位,也可以爬取到 Rule(LinkExtractor(allow=(r'.*/gongsi/.*')),follow=True), # 对页面进行根据时,如果发现/jobs/.*.html的url则说明找到了要爬取的页面,那么调用parse_item进行解析 Rule(LinkExtractor(allow=r'/jobs/.*.html'), callback='parse_item', follow=True), )
3.编写一个用于解析页面的parse_item,这个方法的作用跟以前的parse一样,但区别在于,在crawlspider中这个处理方法,不能取名为parse。
(你可以取任何名字,就是一定不能取名为这个,原因是在crawlspider类中该parse方法有它自己的作用,如何命名为parse则为重新了类方法,那么会改变crawlspider正常执行,后面源码中会介绍到。)
- # 符合规则的进行页面爬取解析response
- def parse_item(self, response):
- item = {}
- #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
- #i['name'] = response.xpath('//div[@id="name"]').extract()
- #i['description'] = response.xpath('//div[@id="description"]').extract()
- return item
这个方法就是用来分析页面,和拿到页面数据了,具体的实现跟以前spider通过正则表达提取一样,然后把数据给item并传给pipeline处理。这里就不在详细说了。唯独还要提醒一点的就是命名:慎用(别用)parse做为回调方法,因为这边的parse已经不像spider类中的那样没有具体操作。
看到了这里,是不是发现crawlspider其实代码实现很简单,而且很方便抓取整个网站某一类的urls。
lagou完整代码如下:
- class LagouSpider(CrawlSpider):
- name = 'lagou'
- allowed_domains = ['www.lagou.com']
- start_urls = ['http://www.lagou.com/']
-
- # 抓取urls的规则--目的是找到招聘职位的详情页面,然后爬取职位的详细数据
- # 如https://www.lagou.com/jobs/3959551.html,这是一个详细python招聘职位的描述
- # 那怎么才能从lagou这个站中那么多urls找到类似上述的url呢?所以要给定抓取urls规则或者范围
- # 分析lagou发现从以下两个地方可以进入招聘职位页面:
- # 1、招聘分类:这里面有各类招聘的所有职位; https://www.lagou.com/zhaopin/***** 招聘页面urls共性
- # 2、招聘需要公司发布,我们从公司页面也可以抓取到所有的职位; https://www.lagou.com/gongsi/** 公司页面共性
- rules = (
- # 根据上面分析,可以写一些规则也圈定抓取的urls
- # 爬取urls中有 zhaopin/的urls,并却对这类页面中的所有的url进行跟进follow
- Rule(LinkExtractor(allow=('.*/zhaopin/.*',)),follow=True),
- # 也可以再加规则,如果发现公司页面也进行follow然后找到公司中的发布的职位,也可以爬取到
- Rule(LinkExtractor(allow=(r'.*/gongsi/.*')),follow=True),
- # 对页面进行根据时,如果发现/jobs/.*.html的url则说明找到了要爬取的页面,那么调用parse_item进行解析
- Rule(LinkExtractor(allow=r'/jobs/.*.html'), callback='parse_item', follow=True),
- )
-
- # 符合规则的进行页面爬取解析response
- def parse_item(self, response):
- item = {}
- #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
- #i['name'] = response.xpath('//div[@id="name"]').extract()
- #i['description'] = response.xpath('//div[@id="description"]').extract()
- return item

四、CrawlSpider源码分析
- class CrawlSpider(Spider):
-
- rules = ()
-
- def __init__(self, *a, **kw):
- super(CrawlSpider, self).__init__(*a, **kw)
- self._compile_rules()
-
- #1、首先调用parse()方法来处理start_urls中返回的response对象。
- #2、parse()将这些response对象传递给了_parse_response()函数处理,并设置回调函数为parse_start_url()。
- #3、设置了跟进标志位True,即follow=True。
- #4、返回response。
- def parse(self, response):
- return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
-
- #处理start_url中返回的response,需要重写。
- def parse_start_url(self, response):
- return []
-
- def process_results(self, response, results):
- return results
-
- def _build_request(self, rule, link):
- #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数。这个‘_build_request’函数在下面调用。
- r = Request(url=link.url, callback=self._response_downloaded)
- r.meta.update(rule=rule, link_text=link.text)
- return r
-
- #从response中抽取符合任一用户定义'规则'的链接,并构造成Resquest对象返回。
- 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)
- #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded()。
- for link in links:
- seen.add(link)
- #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数。这个‘_build_request’函数在上面定义。
- r = self._build_request(n, link)
- #对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request。
- yield rule.process_request(r)
-
- #处理通过rule提取出的连接,并返回item以及request。
- def _response_downloaded(self, response):
- rule = self._rules[response.meta['rule']]
- return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
-
- #解析response对象,使用callback解析处理他,并返回request或Item对象。
- def _parse_response(self, response, callback, cb_kwargs, follow=True):
- #1、首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数)
- #2、如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象,
- #3、然后再交给process_results处理。返回cb_res的一个列表。
- 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
-
- #如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象。
- if follow and self._follow_links:
- #返回每个Request对象。
- for request_or_item in self._requests_to_follow(response):
- yield request_or_item
-
- 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)
-
- @classmethod
- def from_crawler(cls, crawler, *args, **kwargs):
- spider = super(CrawlSpider, cls).from_crawler(crawler, *args, **kwargs)
- spider._follow_links = crawler.settings.getbool(
- 'CRAWLSPIDER_FOLLOW_LINKS', True)
- return spider
-
- def set_crawler(self, crawler):
- super(CrawlSpider, self).set_crawler(crawler)
- self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
-
- 作者:小怪聊职场
- 链接:https://www.jianshu.com/p/d492adf17312
- 來源:简书
- 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

我们对上述代码做了分析,发现执行流程其实是:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。