当前位置:   article > 正文

菜鸟写Python-Scrapy:Spider源码分析扩展-CrawlSpider使用分析(详解)_scrapy basicspider

scrapy basicspider

本系列文章在上章节: 菜鸟写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自己独有的特性:

  • Rules: 是Rule对象的集合,用于匹配目标网站并排除干扰,简单来说就是定义爬取urls的规则
  • parse_start_url: 用于爬取起始响应,必须要返回ItemRequest中的一个。

Rule参数:link_extractor、callback=None、cb_kwargs=None、follow=None、process_links=None、process_request=None

如果这一步理解拗口,建议要挑到第三节看怎么使用crawlspider,然后又感觉再回来看rules的定义。

参数理解:

1.link_extractor:该值(方法)是一个Link Extractor实例,主要定义的就是链接的解析规则。规则如下:

  • allow:值是正则表达式,满足括号中“正则表达式(列表,dict列表)”的urls会被提取;如果为空,则全部匹配。
  • deny:不提取与这个正则表达式(列表,dict()列表)匹配的URL。
  • allow_domains:会被提取的链接的domains。
  • deny_domains:一定不会被提取链接的domains。
  • restrict_xpaths/restrict_css:使用xpath/css表达式,来指定爬取哪一块的urls和allow共同过滤爬取链接。

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为例:

  1. 2.1 抓取urls的规则--目的是找到招聘职位的详情页面,如https://www.lagou.com/jobs/3959551.html,这是一个详细python招聘职位的描述,然后爬取这个岗位的详细数据
  2. 2.2 那怎么才能从lagou这个站中那么多urls找到类似上述的url呢?所以要给定抓取urls规则或者范围
  3. 2.3 分析lagou发现从以下两个地方可以进入招聘职位页面:
  4. # 1、招聘分类:这里面有各类招聘的所有职位; https://www.lagou.com/zhaopin/***** 招聘页面urls共性
  5. # 2、招聘需要公司发布,我们从公司页面也可以抓取到所有的职位; https://www.lagou.com/gongsi/** 公司页面共性
  6. # 3、从上面两个大urls可以进入到一个具体岗位的url,如https://www.lagou.com/jobs/3959551.html,而.*/jobs/.*html是共性
  7. 所以我们可以写一个Rules,来界定我们抓取的规则:1圈定范围 2从范围中找到要解析的页面
  8. rules = (
  9. # 根据上面分析,可以写一些规则也圈定抓取的urls
  10. # 爬取urls中有 zhaopin/的urls,并却对这类页面中的所有的url进行跟进follow
  11. Rule(LinkExtractor(allow=('.*/zhaopin/.*',)),follow=True),
  12. # 也可以再加规则,如果发现公司页面也进行follow然后找到公司中的发布的职位,也可以爬取到
  13. Rule(LinkExtractor(allow=(r'.*/gongsi/.*')),follow=True),
  14. # 对页面进行根据时,如果发现/jobs/.*.html的url则说明找到了要爬取的页面,那么调用parse_item进行解析
  15. Rule(LinkExtractor(allow=r'/jobs/.*.html'), callback='parse_item', follow=True),
  16. )

3.编写一个用于解析页面的parse_item,这个方法的作用跟以前的parse一样,但区别在于,在crawlspider中这个处理方法,不能取名为parse。

(你可以取任何名字,就是一定不能取名为这个,原因是在crawlspider类中该parse方法有它自己的作用,如何命名为parse则为重新了类方法,那么会改变crawlspider正常执行,后面源码中会介绍到。)

  1. # 符合规则的进行页面爬取解析response
  2. def parse_item(self, response):
  3. item = {}
  4. #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
  5. #i['name'] = response.xpath('//div[@id="name"]').extract()
  6. #i['description'] = response.xpath('//div[@id="description"]').extract()
  7. return item

这个方法就是用来分析页面,和拿到页面数据了,具体的实现跟以前spider通过正则表达提取一样,然后把数据给item并传给pipeline处理。这里就不在详细说了。唯独还要提醒一点的就是命名:慎用(别用)parse做为回调方法,因为这边的parse已经不像spider类中的那样没有具体操作。

看到了这里,是不是发现crawlspider其实代码实现很简单,而且很方便抓取整个网站某一类的urls。

lagou完整代码如下:

  1. class LagouSpider(CrawlSpider):
  2. name = 'lagou'
  3. allowed_domains = ['www.lagou.com']
  4. start_urls = ['http://www.lagou.com/']
  5. # 抓取urls的规则--目的是找到招聘职位的详情页面,然后爬取职位的详细数据
  6. # 如https://www.lagou.com/jobs/3959551.html,这是一个详细python招聘职位的描述
  7. # 那怎么才能从lagou这个站中那么多urls找到类似上述的url呢?所以要给定抓取urls规则或者范围
  8. # 分析lagou发现从以下两个地方可以进入招聘职位页面:
  9. # 1、招聘分类:这里面有各类招聘的所有职位; https://www.lagou.com/zhaopin/***** 招聘页面urls共性
  10. # 2、招聘需要公司发布,我们从公司页面也可以抓取到所有的职位; https://www.lagou.com/gongsi/** 公司页面共性
  11. rules = (
  12. # 根据上面分析,可以写一些规则也圈定抓取的urls
  13. # 爬取urls中有 zhaopin/的urls,并却对这类页面中的所有的url进行跟进follow
  14. Rule(LinkExtractor(allow=('.*/zhaopin/.*',)),follow=True),
  15. # 也可以再加规则,如果发现公司页面也进行follow然后找到公司中的发布的职位,也可以爬取到
  16. Rule(LinkExtractor(allow=(r'.*/gongsi/.*')),follow=True),
  17. # 对页面进行根据时,如果发现/jobs/.*.html的url则说明找到了要爬取的页面,那么调用parse_item进行解析
  18. Rule(LinkExtractor(allow=r'/jobs/.*.html'), callback='parse_item', follow=True),
  19. )
  20. # 符合规则的进行页面爬取解析response
  21. def parse_item(self, response):
  22. item = {}
  23. #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
  24. #i['name'] = response.xpath('//div[@id="name"]').extract()
  25. #i['description'] = response.xpath('//div[@id="description"]').extract()
  26. return item

四、CrawlSpider源码分析

  1. class CrawlSpider(Spider):
  2. rules = ()
  3. def __init__(self, *a, **kw):
  4. super(CrawlSpider, self).__init__(*a, **kw)
  5. self._compile_rules()
  6. #1、首先调用parse()方法来处理start_urls中返回的response对象。
  7. #2、parse()将这些response对象传递给了_parse_response()函数处理,并设置回调函数为parse_start_url()。
  8. #3、设置了跟进标志位True,即follow=True。
  9. #4、返回response。
  10. def parse(self, response):
  11. return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
  12. #处理start_url中返回的response,需要重写。
  13. def parse_start_url(self, response):
  14. return []
  15. def process_results(self, response, results):
  16. return results
  17. def _build_request(self, rule, link):
  18. #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数。这个‘_build_request’函数在下面调用。
  19. r = Request(url=link.url, callback=self._response_downloaded)
  20. r.meta.update(rule=rule, link_text=link.text)
  21. return r
  22. #从response中抽取符合任一用户定义'规则'的链接,并构造成Resquest对象返回。
  23. def _requests_to_follow(self, response):
  24. if not isinstance(response, HtmlResponse):
  25. return
  26. seen = set()
  27. #抽取所有链接,只要通过任意一个'规则',即表示合法。
  28. for n, rule in enumerate(self._rules):
  29. links = [lnk for lnk in rule.link_extractor.extract_links(response)
  30. if lnk not in seen]
  31. if links and rule.process_links:
  32. links = rule.process_links(links)
  33. #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded()。
  34. for link in links:
  35. seen.add(link)
  36. #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数。这个‘_build_request’函数在上面定义。
  37. r = self._build_request(n, link)
  38. #对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request。
  39. yield rule.process_request(r)
  40. #处理通过rule提取出的连接,并返回item以及request。
  41. def _response_downloaded(self, response):
  42. rule = self._rules[response.meta['rule']]
  43. return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
  44. #解析response对象,使用callback解析处理他,并返回request或Item对象。
  45. def _parse_response(self, response, callback, cb_kwargs, follow=True):
  46. #1、首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数)
  47. #2、如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象,
  48. #3、然后再交给process_results处理。返回cb_res的一个列表。
  49. if callback:
  50. cb_res = callback(response, **cb_kwargs) or ()
  51. cb_res = self.process_results(response, cb_res)
  52. for requests_or_item in iterate_spider_output(cb_res):
  53. yield requests_or_item
  54. #如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象。
  55. if follow and self._follow_links:
  56. #返回每个Request对象。
  57. for request_or_item in self._requests_to_follow(response):
  58. yield request_or_item
  59. def _compile_rules(self):
  60. def get_method(method):
  61. if callable(method):
  62. return method
  63. elif isinstance(method, six.string_types):
  64. return getattr(self, method, None)
  65. self._rules = [copy.copy(r) for r in self.rules]
  66. for rule in self._rules:
  67. rule.callback = get_method(rule.callback)
  68. rule.process_links = get_method(rule.process_links)
  69. rule.process_request = get_method(rule.process_request)
  70. @classmethod
  71. def from_crawler(cls, crawler, *args, **kwargs):
  72. spider = super(CrawlSpider, cls).from_crawler(crawler, *args, **kwargs)
  73. spider._follow_links = crawler.settings.getbool(
  74. 'CRAWLSPIDER_FOLLOW_LINKS', True)
  75. return spider
  76. def set_crawler(self, crawler):
  77. super(CrawlSpider, self).set_crawler(crawler)
  78. self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
  79. 作者:小怪聊职场
  80. 链接:https://www.jianshu.com/p/d492adf17312
  81. 來源:简书
  82. 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

我们对上述代码做了分析,发现执行流程其实是:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/764186
推荐阅读
相关标签
  

闽ICP备14008679号