3.7 分布式爬取 Scrapy 高级主题:3.7 分布式爬取 Scrapy 是一个强大的爬虫框架,但单个 Scrapy 爬虫的处理能力有限。当面对大规模数据抓取需求时,单机爬虫往往成为瓶颈。分布式爬取能够将爬取任务分散到多台机器上并行执行,从而显著提高爬取效率。 分布式爬取的核心挑战 分布式爬取的核心挑战在于: 请求分发与调度: 如何将大量的爬取请求均匀地分发到各个爬虫节点。 数据共享: 如何在多个爬虫节点之间共享数据,例如待爬取的 URL 队列、已爬取的 URL 指纹等。 结果汇总: 如何将各个爬虫节点抓取到的数据汇总到一起。 避免重复爬取: 如何避免多个爬虫节点重复爬取相同的页面。
Scrapy 是一个强大的爬虫框架,但单个 Scrapy 爬虫的处理能力有限。当面对大规模数据抓取需求时,单机爬虫往往成为瓶颈。分布式爬取能够将爬取任务分散到多台机器上并行执行,从而显著提高爬取效率。
分布式爬取的核心挑战在于:
请求分发与调度: 如何将大量的爬取请求均匀地分发到各个爬虫节点。
数据共享: 如何在多个爬虫节点之间共享数据,例如待爬取的 URL 队列、已爬取的 URL 指纹等。
结果汇总: 如何将各个爬虫节点抓取到的数据汇总到一起。
避免重复爬取: 如何避免多个爬虫节点重复爬取相同的页面。
Redis 是一个高性能的键值存储数据库,非常适合用于实现分布式爬取的请求队列和数据共享。
我们可以使用 Redis 作为中心化的请求队列和数据共享中心,多个 Scrapy 爬虫节点从 Redis 获取待爬取的 URL,并将抓取到的数据存储到 Redis 或其他数据库中。
2.2.1 安装依赖
首先,需要安装 scrapy-redis 库:
pip install scrapy-redis
2.2.2 配置 Scrapy 项目
在 Scrapy 项目的 settings.py 文件中进行如下配置:
# settings.py # 启用 Redis 调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 确保所有爬虫通过 Redis 去重 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # Redis 连接配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 # 爬虫空闲时等待的最大时间(秒) SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 使用优先级队列 (可选) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 使用 FIFO 队列 (默认) SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' # 使用 LIFO 队列 (可选) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 存储爬取结果的 Pipeline ITEM_PIPELINES = { 'myproject.pipelines.MyPipeline': 300, # 将 'myproject' 替换为你的项目名称 } # 开启 Redis 客户端自动清理 REDIS_START_URLS_AS_SET = True
2.2.3 编写 Spider
修改 Spider,使其从 Redis 读取起始 URL,并使用 Redis 的指纹去重:
# myproject/spiders/myspider.py (将 'myproject' 替换为你的项目名称) import scrapy from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): """Spider that reads urls from redis queue (myspider:start_urls).""" name = 'myspider' #redis_key = 'myspider:start_urls' #注释掉start_urls 和 allowed_domains #start_urls = ['http://quotes.toscrape.com/'] #allowed_domains = ['quotes.toscrape.com'] def __init__(self, *args, **kwargs): # Dynamically define the redis_key domain = kwargs.pop('domain', '') self.allowed_domains = [domain] super(MySpider, self).__init__(*args, **kwargs) self.redis_key = 'myspider:start_urls' def parse(self, response): # 示例:提取网页标题 yield { 'title': response.xpath('//title/text()').get(), 'url': response.url, }
2.2.4 编写 Pipeline
编写 Pipeline,将抓取到的数据存储到 Redis 或其他数据库中:
# myproject/pipelines.py (将 'myproject' 替换为你的项目名称) import redis from scrapy.exceptions import DropItem class MyPipeline(object): def __init__(self): self.redis_client = redis.StrictRedis(host='localhost', port=6379, db=0) def process_item(self, item, spider): # 将数据存储到 Redis self.redis_client.hmset(item['url'], item) return item
2.2.5 添加起始 URL 到 Redis
可以使用 Redis 客户端向 Redis 中添加起始 URL:
import redis redis_client = redis.StrictRedis(host='localhost', port=6379, db=0) redis_client.sadd('myspider:start_urls', 'http://quotes.toscrape.com/') # 替换为你需要爬取的URL
2.2.6 启动爬虫
使用以下命令启动爬虫:
scrapy crawl myspider
可以在多台机器上启动相同的爬虫,它们会共享 Redis 中的请求队列,从而实现分布式爬取。
SCHEDULER: 指定 Scrapy 使用 scrapy_redis.scheduler.Scheduler 作为调度器,负责从 Redis 中获取请求。
DUPEFILTER_CLASS: 指定 Scrapy 使用 scrapy_redis.dupefilter.RFPDupeFilter 作为去重过滤器,基于 Redis 的集合来判断 URL 是否已经被爬取过。
REDIS_HOST 和 REDIS_PORT: 配置 Redis 连接信息。
redis_key: 指定 Spider 从 Redis 中读取 URL 的键名。
RedisSpider: scrapy-redis 提供的 Spider 基类,会自动从 Redis 读取起始 URL。
Pipeline: 用于将抓取到的数据存储到 Redis 或其他数据库中。
除了使用 Redis,还有其他方法可以实现分布式爬取:
使用消息队列 (如 RabbitMQ, Kafka): 可以将爬取请求放入消息队列,多个爬虫节点从队列中获取请求。
使用 Celery: Celery 是一个分布式任务队列,可以将爬取任务作为 Celery 任务分发到多个 worker 节点。
使用 Scrapy Cluster: Scrapy Cluster 是一个基于 Docker 的 Scrapy 分布式爬取框架,提供了一整套解决方案。
反爬虫策略: 分布式爬取可能会增加被网站反爬虫的风险,需要注意控制爬取频率,并使用代理 IP。
数据一致性: 在分布式环境下,需要保证数据的一致性,可以使用事务或乐观锁等机制。
监控与日志: 需要对分布式爬虫进行监控,并记录详细的日志,以便排查问题。
资源管理: 合理分配各个爬虫节点的资源,避免资源浪费或资源竞争。
分布式爬取是提高 Scrapy 爬虫效率的有效方法。使用 Redis 可以快速搭建一个简单的分布式爬取系统。在实际应用中,需要根据具体需求选择合适的分布式爬取方案,并注意反爬虫、数据一致性、监控与日志等方面的问题。
通过合理的设计和配置,可以构建一个高效、稳定的分布式爬虫系统,从而应对大规模数据抓取需求。