21天Python分布式爬虫教程|零基础入门

50 阅读4分钟

为什么需要分布式爬虫?

单机爬虫的瓶颈显而易见:

  • 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 而非 CrawlSpiderSpider
  • 不设置 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}")

总结:分布式爬虫的“道”与“术”

通过这本书的实战,我深刻体会到:

  1. Scrapy-Redis 是“胶水” :它让Scrapy天然支持分布式,核心在于共享的Redis队列。
  2. Redis 是“大脑” :所有爬虫节点通过它协调任务,实现去重和调度。
  3. 多节点是“肌肉” :横向扩展机器,轻松应对大规模采集。
  4. 监控是“神经系统” :没有监控的爬虫系统是脆弱的。

项目完整源码已上传https://github.com/yourname/scrapy-redis-distributed-crawler
(包含Docker-compose部署、Kibana日志分析、自动代理轮换模块)

作为程序员,我们不仅要会“爬”,更要会“架构”。掌握分布式爬虫,意味着你拥有了从海量互联网中高效、稳定地提取价值数据的能力。这不仅是技术实力的体现,更是数据驱动时代的“核心竞争力”。