在之前的文章我们通过 scrapy 框架 及 scrapy.Spider 类做了一个《糗事百科》的糗百爬虫, 本章我们再来看一下相较于 scrapy.Spider 类更为强大的 CrawlSpider 类.
CrawlSpider 是 Spider 的派生类, Spider 类的设计原则是只爬取 start_url 列表中的网页, 而 CrawlSpider 类定义了一些规则 (rule) 来提供跟进 link 的方便的机制, 从爬取的网页中获取 link 并继续爬取的工作更适合.
源码参考
- class CrawlSpider(Spider):
- rules = ()
- def __init__(self, *a, **kw):
- super(CrawlSpider, self).__init__(*a, **kw)
- self._compile_rules()
- #首先调用 parse()来处理 start_urls 中返回的 response 对象
- #parse()则将这些 response 对象传递给了_parse_response()函数处理, 并设置回调函数为 parse_start_url()
- #设置了跟进标志位 True
- #parse 将返回 item 和跟进了的 Request 对象
- 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
- #从 response 中抽取符合任一用户定义'规则'的链接, 并构造成 Resquest 对象返回
- def _requests_to_follow(self, response):
- if not isinstance(response, htmlResponse):
- return
- seen = set()
- #抽取之内的所有链接, 只要通过任意一个'规则', 即表示合法
- for n, rule in enumerate(self._rules):
- links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
- #使用用户指定的 process_links 处理每个连接
- 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 对象的回调函数
- r = Request(url=link.url, callback=self._response_downloaded)
- r.meta.update(rule=n, link_text=link.text)
- #对每个 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):
- #首先判断是否设置了回调函数.(该回调函数可能是 rule 中的解析函数, 也可能是 parse_start_url 函数)
- #如果设置了回调函数 (parse_start_url()), 那么首先用 parse_start_url() 处理 response 对象,
- #然后再交给 process_results 处理. 返回 cb_res 的一个列表
- if callback:
- #如果是 parse 调用的, 则会解析成 Request 对象
- #如果是 rule callback, 则会解析成 Item
- 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, basestring):
- 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)
- def set_crawler(self, crawler):
- super(CrawlSpider, self).set_crawler(crawler)
- self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
CrawlSpider 继承于 Spider 类, 除了继承过来的属性外(name,allow_domains), 还提供了新的属性和方法:
- LinkExtractors
- from scrapy.linkextractors import LinkExtractor
Link Extractors 的目的很简单: 提取链接。
每个 LinkExtractor 有唯一的公共方法是 extract_links(), 它接收一个 Response 对象, 并返回一个 scrapy.link.Link 对象.
Link Extractors 要实例化一次, 并且 extract_links 方法会根据不同的 response 调用多次提取链接。
- class scrapy.linkextractors.LinkExtractor(
- allow = (),
- deny = (),
- allow_domains = (),
- deny_domains = (),
- deny_extensions = None,
- restrict_xpaths = (),
- tags = ('a','area'),
- attrs = ('href'),
- canonicalize = True,
- unique = True,
- process_value = None
- )
主要参数:
allow: 满足括号中 "正则表达式" 的值会被提取, 如果为空, 则全部匹配.
deny: 与这个正则表达式 (或正则表达式列表) 不匹配的 URL 一定不提取.
allow_domains: 会被提取的链接的 domains.
deny_domains: 一定不会被提取链接的 domains.
restrict_xpaths: 使用 xpath 表达式, 和 allow 共同作用过滤链接.
rules
在 rules 中包含一个或多个 Rule 对象, 每个 Rule 对爬取网站的动作定义了特定操作. 如果多个 rule 匹配了相同的链接, 则根据规则在本集合中被定义的顺序, 第一个会被使用.
- class scrapy.spiders.Rule(
- link_extractor,
- callback = None,
- cb_kwargs = None,
- follow = None,
- process_links = None,
- process_request = None
- )
link_extractor: 是一个 Link Extractor 对象, 用于定义需要提取的链接.
callback: 从 link_extractor 中每获取到链接时, 参数所指定的值作为回调函数, 该回调函数接受一个 response 作为其第一个参数.
注意: 当编写爬虫规则时, 避免使用 parse 作为回调函数. 由于 CrawlSpider 使用 parse 方法来实现其逻辑, 如果覆盖了 parse 方法, crawl spider 将会运行失败.
follow: 是一个布尔 (boolean) 值, 指定了根据该规则从 response 提取的链接是否需要跟进. 如果 callback 为 None,follow 默认设置为 True , 否则默认为 False.
process_links: 指定该 spider 中哪个的函数将会被调用, 从 link_extractor 中获取到链接列表时将会调用该函数. 该方法主要用来过滤.
process_request: 指定该 spider 中哪个的函数将会被调用, 该规则提取到每个 request 时都会调用该函数. (用来过滤 request)
接下来我们就按上面所说的内容将之前的糗百爬虫做一下修改, 我们将 qiubaiSpider.py 的代码改为如下:
- import scrapy
- # 导入 CrawlSpider 类和 Rule
- from scrapy.spiders import CrawlSpider, Rule
- # 导入链接规则匹配类, 用来提取符合规则的连接
- from scrapy.linkextractors import LinkExtractor
- from ..items import QiushiItem
- class QiushiSpider(CrawlSpider):
- # 爬虫名
- name = "qiubai"
- # 允许爬虫作用的范围, 不能越界
- allowd_domains = ["https://www.qiushibaike.com/"]
- # 爬虫起始 url
- start_urls = ["https://www.qiushibaike.com/text/page/1/"]
- # Response 里链接的提取规则, 返回的符合匹配规则的链接匹配对象的列表
- pageLink = LinkExtractor(allow=("/page/\d+"))
- # 获取这个列表里的链接, 依次发送请求, 并且继续跟进, 调用指定回调函数处理
- rules = [
- Rule(pageLink, callback="parseContent", follow=True)
- ]
- # 指定的回调函数
- def parseContent(self, response):
- # 通过 scrayy 自带的 xpath 匹配想要的信息
- qiushi_list = response.xpath('//div[contains(@id,"qiushi_tag")]')
- for site in qiushi_list:
- # 实例化从 items.py 导入的 QiushiItem 类
- item = QiushiItem()
- # 根据查询发现匿名用户和非匿名用户的标签不一样
- try:
- # 非匿名用户
- username = site.xpath('./div/a/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/a/img/@src')[0].extract() # 头像
- except Exception:
- # 匿名用户
- username = site.xpath('./div/span/img/@alt')[0].extract() # 作者
- imgUrl = site.xpath('./div/span/img/@src')[0].extract() # 头像
- content = site.xpath('.//div[@class="content"]/span[1]/text()').extract()
- item['username'] = username
- item['imgUrl'] = "https:" + imgUrl
- item['content'] = content
- # 将获取的数据交给 pipeline 管道文件
- yield item
在控制台或终端输入 scrapy crawl qiubai 即可运行程序并获取糗百数据.
需要注意的是在 rule 规则中的 callback 千万不能写 parse, 因为 CrawlSpider 使用 parse 方法来实现其逻辑, 如果覆盖了 parse 方法, crawl spider 将会运行失败.
来源: https://www.cnblogs.com/weijiutao/p/10901105.html