为什么需要分布式爬虫?
单机爬虫的瓶颈显而易见:
- IP被封后无法继续
- 单机性能有限,爬取速度慢
- 一台机器宕机,整个任务中断
- 数据量大时,存储和处理压力集中
分布式爬虫通过多台机器协同工作,共享任务队列和数据,完美解决了上述问题。而Scrapy-Redis正是实现这一目标的黄金组合。
核心技术栈
- Scrapy:强大的Python爬虫框架
- Redis:作为共享的任务队列(URL去重、请求调度)
- MongoDB/MySQL:存储最终采集数据
- Redis-Server:部署在独立服务器,作为“大脑”协调所有爬虫节点
第一步:环境准备与基础配置
# requirements.txt
scrapy
scrapy-redis
redis
pymongo
# 或 mysql-connector-python
# settings.py - Scrapy核心配置
import redis
# 启用Redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 启用Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# Redis连接配置
REDIS_HOST = 'your-redis-server.com' # 独立的Redis服务器
REDIS_PORT = 6379
REDIS_PARAMS = {
'password': 'your_redis_password',
'db': 0
}
# 持久化调度队列(爬虫关闭后任务不丢失)
SCHEDULER_PERSIST = True
# 爬虫空闲时是否关闭(False表示等待新任务)
SCHEDULER_IDLE_BEFORE_CLOSE = 10
# 项目专用的Redis键前缀
REDIS_KEY = 'myspider:start_urls'
# 管道配置
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300, # 可选:将item存入Redis
'myproject.pipelines.MongoDBPipeline': 400, # 自定义管道存入MongoDB
}
# 遵守robots.txt
ROBOTSTXT_OBEY = False
# 下载延迟
DOWNLOAD_DELAY = 1
第二步:编写分布式爬虫核心
# spiders/myspider.py
import scrapy
from scrapy import Spider
from scrapy_redis.spiders import RedisSpider # 关键:继承RedisSpider
from myproject.items import ProductItem
class DistributedSpider(RedisSpider):
name = 'distributed_spider'
# 不再需要start_urls
# 爬虫启动后会自动从Redis的指定键中获取URL
# redis_key = 'myspider:start_urls' # 与settings中REDIS_KEY一致
def __init__(self, *args, **kwargs):
# 动态设置redis_key(可选)
domain = kwargs.pop('domain', '')
self.redis_key = f'myspider:start_urls:{domain}' if domain else 'myspider:start_urls'
super(DistributedSpider, self).__init__(*args, **kwargs)
def parse(self, response):
"""
解析列表页,提取详情页链接
"""
# 示例:爬取电商产品
for product in response.css('div.product-item'):
detail_url = product.css('a::attr(href)').get()
if detail_url:
yield response.follow(detail_url, callback=self.parse_detail)
# 提取下一页
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)
def parse_detail(self, response):
"""
解析详情页
"""
item = ProductItem()
item['title'] = response.css('h1.title::text').get('').strip()
item['price'] = response.css('span.price::text').get('').strip()
item['description'] = response.css('div.desc::text').getall()
item['url'] = response.url
yield item
关键点:
- 继承
RedisSpider而非CrawlSpider或Spider- 不设置
start_urls,通过Redis动态分发redis_key是所有爬虫节点共享的队列名
第三步:自定义数据管道(存入MongoDB)
# pipelines.py
import pymongo
from itemadapter import ItemAdapter
class MongoDBPipeline:
collection_name = 'scrapy_items'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
adapter = ItemAdapter(item)
self.db[self.collection_name].insert_one(adapter.asdict())
return item
# settings.py 中添加
MONGO_URI = 'mongodb://your-mongo-server:27017/'
MONGO_DATABASE = 'crawl_data'
第四步:启动分布式集群
1. 启动Redis服务器(中心节点)
# 在远程服务器上
redis-server --requirepass yourpassword
2. 启动多个爬虫工作节点
# 在不同的机器或容器中执行
scrapy crawl distributed_spider
所有节点都会监听同一个Redis队列,自动负载均衡。
3. 向Redis注入起始URL(生产者)
# push_urls.py
import redis
r = redis.StrictRedis(
host='your-redis-server.com',
port=6379,
password='your_redis_password',
db=0,
decode_responses=True
)
# 起始URL列表
start_urls = [
'https://example-shop.com/products?page=1',
'https://example-shop.com/products?page=2',
# ... 更多URL
]
# 将URL推入Redis队列
for url in start_urls:
r.lpush('myspider:start_urls', url)
print(f"已推入 {len(start_urls)} 个起始URL")
第五步:进阶优化
1. 动态代理IP集成
# middlewares.py
import random
class ProxyMiddleware:
def __init__(self):
self.proxies = [
'http://proxy1:port',
'http://proxy2:port',
# 从IP代理池获取
]
def process_request(self, request, spider):
proxy = random.choice(self.proxies)
request.meta['proxy'] = proxy
return None
# settings.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.ProxyMiddleware': 350,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}
2. 异常监控与告警
# extensions.py
from scrapy import signals
from scrapy.exceptions import NotConfigured
class AlertExtension:
@classmethod
def from_crawler(cls, crawler):
if not crawler.settings.getbool('MYEXT_ENABLED'):
raise NotConfigured
ext = cls()
crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
return ext
def spider_closed(self, spider, reason):
if reason != 'finished':
# 发送告警(邮件、钉钉、企业微信)
self.send_alert(f"爬虫 {spider.name} 异常退出,原因: {reason}")
总结:分布式爬虫的“道”与“术”
通过这本书的实战,我深刻体会到:
- Scrapy-Redis 是“胶水” :它让Scrapy天然支持分布式,核心在于共享的Redis队列。
- Redis 是“大脑” :所有爬虫节点通过它协调任务,实现去重和调度。
- 多节点是“肌肉” :横向扩展机器,轻松应对大规模采集。
- 监控是“神经系统” :没有监控的爬虫系统是脆弱的。
项目完整源码已上传:
https://github.com/yourname/scrapy-redis-distributed-crawler
(包含Docker-compose部署、Kibana日志分析、自动代理轮换模块)
作为程序员,我们不仅要会“爬”,更要会“架构”。掌握分布式爬虫,意味着你拥有了从海量互联网中高效、稳定地提取价值数据的能力。这不仅是技术实力的体现,更是数据驱动时代的“核心竞争力”。