当前位置:   article > 正文

知识笔记 - 将scrapy改造为通用分布式全站爬虫(基于RedisCrawlSpider)

rediscrawlspider


前言

目标:处理批量zf网站 并自动抓取全站数据

解决方式:实现基于 RedisCrawlSpider 的通用分布式爬虫

技术选型思路:

  1. 抓取全站数据 ——> 选择scrapy框架的crawlspider爬虫;

  2. 提高抓取效率 ——> 选择基于redis的分布式,即RedisCrawlSpider;

  3. 自动处理每一个进来的网站 ——> 实现全网站通用爬虫。

本篇文章将介绍如何在CrawlSpider的基础上改造为RedisCrawlSpider分布式爬虫

以及给 RedisCrawlSpider改造为通用型爬虫的实现 提供一种思路。

网上相关的文章比较少,希望这篇文章可以给到大家一些帮助。


一、如何实现RedisCrawlSpider?

其实并不复杂,只需要在基于scrapy的CrawlSpider上,修改spider.py和settings.py这两个文件就可以了。

1.改写 spider.py

第一步,修改scrapy核心文件 spider.py;

  1. 修改 spider 继承的的类
  2. 由于我们是从 redis 取请求任务,所以start_urls就不需要了;
  3. 新增字段 redis_key,这是 redis 任务队列的 key,程序从该 key 取请求任务;
  4. 保留 crawlspider 的 rules 链接提取规则。
# spider.py

# from scrapy.spiders import CrawlSpider
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy.spiders import Rule

class SlaveSpider(RedisCrawlSpider):
    name = 'slavespider'
    allowed_domains = ['baidu.com']
    # start_urls = ['https://www.baidu.com/']
    redis_key = 'slavespider:start_urls'
    # rules根据相关抓取网站来定
    rules = (Rule(LinkExtractor(allow=r'.*baidu.com.*'),callback='parse_item',follow=False))
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.改写 settings.py

第二步,修改settings配置;

  1. 去重组件,设置scrapy_redis的去重组件,统一使用redis指纹集合
# settings.py

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  • 1
  • 2
  • 3
  1. 调度器组件,设置scrapy_redis的调度器组件,统一使用redis任务队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  • 1
  1. redis连接,设置 redis 的 IP、端口、密码、库 等,初始化时自动赋值到 self.sever
redis_host = 'localhost'
redis_port = 6379
redis_pwd = 'xxx'
redis_db = 0
REDIS_URL = 'redis://:{}@{}:{}/{}'.format(redis_pwd, redis_host, redis_port, redis_db)
  • 1
  • 2
  • 3
  • 4
  • 5
  1. redis pipeline组件 (可选),打开该配置可将抓取到的item保存到redis数据库里
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
}
  • 1
  • 2
  • 3

3.启动 RedisCrawlSpider

我们可以新建一个 db.py 文件,代码如下:

# db.py

import redis

server = redis.StrictRedis(host='localhost', password='xxx', port=6379, db=0,
                           decode_responses=True)
server.lpush('slavespider:start_urls', 'https://www.baidu.com/')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

run 运行该文件,将需要 start_urls 推进 redis 中的任务队列里,爬虫即可启动。

二、将 RedisCrawlSpider 改为通用爬虫(修改源码)

1.传递meta

CrawlSpider有一个缺点

crawlspider没有提供start_requests入口,在redis取到任务后发出请求前,我们无法加入其它操作,最重要的是无法进行meta参数的构造与传递。

这个缺点是可以补足的,我们可以通过修改源码来让请求携带 meta,并让其能在请求中传递,但主要有以下两个难点:

① meta 如何从起始的请求 传递到 rules提取的新请求
depth = 1 --> depth = 2

② meta 如何从 rules提取的请求 传递到 下一次rules提取的请求
depth = 2 --> depth = n

解决方法是:

  1. 重写 scrapy_redis 的 next_request 方法,使请求携带meta;
  2. 重写 crawlspider 中的 _requests_to_follow 方法,使meta能够在起始请求的节点其他从节点中进行传递。

下面为相关源码展示,改写部分已用 [*] 注释标注:

  1. 重写 next_request 方法
# spider.py

    def next_requests(self):
        use_set = self.settings.getbool('REDIS_START_URLS_AS_SET', False)
        fetch_one = self.server.spop if use_set else self.server.lpop
        found = 0
        while found < self.redis_batch_size:
            data = fetch_one(self.redis_key)
            if not data:
                break
			"""[*] 组装出需要传递的meta"""
			meta = self.loads_data(data)
			"""[*] 构造请求并携带上meta"""
            req = self.make_request_from_data(meta)
            if req:
                yield req
                found += 1

    def loads_data(self, data):
		# 根据业务需求自行组装
		return data

    def make_request_from_data(self, meta):
    	url = meta.get('url')
        # 根据业务需求自行处理逻辑
        return scrapy.Request(url=url, meta=meta, dont_filter=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  1. 重写_requests_to_follow方法
# spider.py

    def _requests_to_follow(self, response):
    	"""[*] 取出响应的meta"""
        meta = response.meta
        if not isinstance(response, HtmlResponse):
            return
        seen = set()
        for rule_index, rule in enumerate(self._rules):
            links = [lnk for lnk in rule.link_extractor.extract_links(response)
                     if lnk not in seen]
            for link in rule.process_links(links):
                seen.add(link)
                request = self._build_request(rule_index, link)
                """[*] 将新request的 规则 和 链接文本 赋值出来, 避免被旧的覆盖"""
                meta['rule'], meta['link_text'] = request.meta['rule'], request.meta['link_text']
                """[*] 将meta传递给 rules 新提取出来的链接请求"""
                request.meta.update(meta)
                yield rule.process_request(request, response)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

一定要将新生成请求中的meta里面的rule取出保留,不然继承了response的meta后,会导致旧reponse的rule对应的callback错误覆盖新请求的callback。

以上两步实现了meta请求的传递
起始请求 ——> rules提取的新请求
rules提取的请求 ——> 下一次rules提取的请求

2.定制化rules

实现通用爬虫的功能,需要根据不同的网站任务定制使用不同的rules。

这边提供一个思路。

我们可以先加载一个默认的通用的 rules 配置,并将 rules 保存到redis里。当我们发现默认的 rules 没办法匹配某个网站的链接时,通过redis我们可以达到不终止程序运行的情况下修改、定制和更新 rules 。

但是问题来了,rules 一般在crawlspider初始化的时候就赋值了,而我们只能在scrapy流程初始化稍后一点的next_request方法里从redis中取出rules,即使赋值为self.rules,程序依旧读取不到rules,因为已经过了初始化rules的流程。

我们依然可以通过重写对应的源码来解决该问题,还是需要重写上面提到的 crawl.py 文件中的 _requests_to_follow 方法,通过源码实现的self._compile_rules()方法来让 rules 进行更新并再次重置一下。

  1. 重写 _requests_to_follow 方法,新增self._compile_rules()
# spider.py

    def _requests_to_follow(self, response):
    	# 取出响应的meta
        meta = response.meta
        if not isinstance(response, HtmlResponse):
            return
        seen = set()
        """[*] 重置self.rules"""
        self._compile_rules()
        for rule_index, rule in enumerate(self._rules):
            links = [lnk for lnk in rule.link_extractor.extract_links(response)
                     if lnk not in seen]
            for link in rule.process_links(links):
                seen.add(link)
                request = self._build_request(rule_index, link)
                # 将新的request的 规则 和 链接文本 赋值出来, 避免错误覆盖
                meta['rule'], meta['link_text'] = request.meta['rule'], request.meta['link_text']
                # 将meta传递给提取出来的链接
                request.meta.update(meta)
                yield rule.process_request(request, response)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

由于我们配置了 redis 作为分布式统一的任务队列,除了获取了起始请求任务的 spider 有初始化和绑定了自己的 self.rules 外,

我们还得考虑其他从节点的spider,它们拿到一个request后,是直接进行请求然后去触发prase_item解析函数的,它们没有经过初始化 rules ,这时爬虫会报错,因为self.rules是未定义的。

解决方法:

我们在前面构造self.loads_data方法时,可以将self.rules传到 meta 里面,

然后通过重写 crawl.py 文件中的 _callback 方法,

在方法通过 response.meta 里拿出 rules 并再一次赋值和重置该 spider 的self.rules

# spider.py

    def _callback(self, response):
        """[*] 从上一个响应的meta获取rules,赋值为self.rules"""
        self.rules = response.meta['rules']
        """[*] 重置self.rules"""
        self._compile_rules()
        rule = self._rules[response.meta['rule']]
        return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当实现完上文提到的步骤后,

我们就初步完成了 RedisCrawlSpider 通用分布式爬虫,

其他组件以及流程逻辑可以根据大家不同的业务需求进行实现。


总结

本文旨在为有相似需求的同学提供一种RedisCrawlSpider通用分布式爬虫的实现思路,其他一些细节还需各位自行实现。

总结一下,文章主要分享了以下几个知识点:

实现一个 RedisCrawlSpider 分布式爬虫
1.改写 spider.py
2.改写 settings.py
3.启动 RedisCrawlSpider

将 RedisCrawlSpider 改为通用爬虫
1.解决CrawlSpider的meta传递问题 (修改源码)
2.根据不同网站定制不同的rules (修改源码)

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

闽ICP备14008679号