Python3.9爬虫项目部署:Scrapy+Redis分布式实战案例

你是不是也遇到过这样的问题?单机爬虫抓取速度太慢,遇到反爬就卡住,数据量一大机器就扛不住。别担心,今天我就带你用Python3.9,结合Scrapy和Redis,搭建一个真正能投入生产的分布式爬虫系统。

这个方案的核心思路很简单:把爬虫任务拆分成多个部分,让多台机器同时干活,通过Redis这个“中央指挥部”来协调工作。这样不仅速度能提升好几倍,还能自动处理反爬、断点续爬这些头疼的问题。

我用的环境是Miniconda-Python3.9镜像,这个环境管理工具特别省心,能帮你创建独立的开发环境,避免各种包版本冲突。下面我就手把手带你从零开始,搭建一个完整的分布式爬虫项目。

1. 环境准备与快速部署

1.1 创建并激活Conda环境

首先,我们需要一个干净的Python3.9环境。打开终端,执行以下命令:

# 创建一个名为scrapy_redis的新环境,指定Python版本为3.9
conda create -n scrapy_redis python=3.9

# 激活这个环境
conda activate scrapy_redis

激活后,你的命令行提示符前面应该会显示(scrapy_redis),这表示你已经在这个环境里了。

1.2 安装必要的包

接下来安装我们需要的所有包。这里我建议用清华的镜像源,速度会快很多:

# 设置pip使用清华镜像源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

# 安装核心包
pip install scrapy scrapy-redis redis

简单解释一下这几个包是干什么的:

  • scrapy:爬虫框架,相当于盖房子的脚手架
  • scrapy-redis:让Scrapy能和Redis配合工作的插件
  • redis:Python操作Redis的客户端库

安装完成后,可以检查一下版本:

python -c "import scrapy; print(f'Scrapy版本: {scrapy.__version__}')"

2. 项目创建与基础配置

2.1 创建Scrapy项目

现在我们来创建一个Scrapy项目,名字就叫distributed_spider

# 创建项目
scrapy startproject distributed_spider

# 进入项目目录
cd distributed_spider

创建成功后,你会看到这样的目录结构:

distributed_spider/
├── scrapy.cfg          # 项目配置文件
└── distributed_spider/
    ├── __init__.py
    ├── items.py        # 定义要抓取的数据结构
    ├── middlewares.py  # 中间件(处理请求和响应)
    ├── pipelines.py    # 数据处理管道
    ├── settings.py     # 项目设置
    └── spiders/        # 爬虫文件存放目录
        └── __init__.py

2.2 配置Redis连接

打开settings.py文件,我们需要修改几个关键配置。找到文件末尾,添加以下内容:

# 启用scrapy-redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 启用去重过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 允许暂停/恢复爬取
SCHEDULER_PERSIST = True

# Redis连接设置(根据你的实际情况修改)
REDIS_HOST = 'localhost'  # Redis服务器地址
REDIS_PORT = 6379         # Redis端口
REDIS_PASSWORD = None     # 如果有密码就填上

# 使用scrapy-redis的管道
ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 300,
}

# 其他重要设置
CONCURRENT_REQUESTS = 32      # 并发请求数(根据机器性能调整)
DOWNLOAD_DELAY = 1            # 下载延迟(秒),避免被封
ROBOTSTXT_OBEY = False        # 不遵守robots协议(根据实际情况决定)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'  # 设置User-Agent

3. 编写分布式爬虫代码

3.1 创建第一个分布式爬虫

spiders目录下创建一个新文件example_spider.py,我们来写一个抓取书籍信息的示例爬虫:

import scrapy
from scrapy_redis.spiders import RedisSpider
from distributed_spider.items import BookItem


class BookSpider(RedisSpider):
    """分布式书籍爬虫"""
    name = 'book_spider'  # 爬虫名称,必须唯一
    
    # Redis中的起始URL键名
    redis_key = 'book_spider:start_urls'
    
    def __init__(self, *args, **kwargs):
        # 动态设置允许的域名
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(BookSpider, self).__init__(*args, **kwargs)
    
    def parse(self, response):
        """解析书籍列表页"""
        # 提取当前页的所有书籍链接
        book_links = response.css('.book-item a::attr(href)').getall()
        
        for link in book_links:
            # 构建完整的URL
            full_url = response.urljoin(link)
            # 将详情页请求加入队列
            yield scrapy.Request(
                url=full_url,
                callback=self.parse_book_detail,
                meta={'original_url': full_url}
            )
        
        # 提取下一页链接(如果有的话)
        next_page = response.css('.next-page::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)
    
    def parse_book_detail(self, response):
        """解析书籍详情页"""
        item = BookItem()
        
        # 提取书籍信息
        item['title'] = response.css('.book-title::text').get('').strip()
        item['author'] = response.css('.author::text').get('').strip()
        item['price'] = response.css('.price::text').re_first(r'[\d.]+')
        item['description'] = ''.join(response.css('.description *::text').getall()).strip()
        item['url'] = response.meta.get('original_url', response.url)
        item['crawled_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        
        # 记录日志
        self.logger.info(f"成功抓取书籍: {item['title']}")
        
        yield item

3.2 定义数据结构

打开items.py文件,定义我们要抓取的数据结构:

import scrapy


class BookItem(scrapy.Item):
    """书籍信息Item"""
    title = scrapy.Field()        # 书名
    author = scrapy.Field()       # 作者
    price = scrapy.Field()        # 价格
    description = scrapy.Field()  # 描述
    url = scrapy.Field()          # 来源URL
    crawled_time = scrapy.Field() # 抓取时间

3.3 配置数据处理管道

pipelines.py中,我们可以添加数据处理逻辑。这里我写一个简单的管道,将数据保存到JSON文件:

import json
import os
from datetime import datetime


class JsonWriterPipeline:
    """将数据保存为JSON文件的管道"""
    
    def __init__(self):
        # 创建输出目录
        self.output_dir = 'output'
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
        
        # 生成带时间戳的文件名
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        self.filename = os.path.join(self.output_dir, f'books_{timestamp}.json')
        self.file = open(self.filename, 'w', encoding='utf-8')
        self.file.write('[\n')  # 开始JSON数组
        self.first_item = True
    
    def process_item(self, item, spider):
        """处理每个抓取到的item"""
        line = json.dumps(dict(item), ensure_ascii=False)
        
        if not self.first_item:
            self.file.write(',\n')
        self.file.write('  ' + line)
        
        self.first_item = False
        return item
    
    def close_spider(self, spider):
        """爬虫关闭时调用"""
        self.file.write('\n]')
        self.file.close()
        spider.logger.info(f'数据已保存到: {self.filename}')

记得在settings.py中启用这个管道:

ITEM_PIPELINES = {
    'distributed_spider.pipelines.JsonWriterPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

4. 启动Redis并运行爬虫

4.1 启动Redis服务器

如果你还没有安装Redis,可以快速安装一个:

# 使用Docker快速启动Redis(推荐)
docker run -d --name redis-server -p 6379:6379 redis

# 或者使用conda安装(Windows/Mac/Linux通用)
conda install -c conda-forge redis
# 然后启动Redis服务
redis-server

4.2 向Redis添加起始URL

启动Redis后,我们需要向其中添加起始URL。创建一个Python脚本add_start_urls.py

import redis
import sys

# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)

# 爬虫名称(必须和爬虫中的redis_key对应)
spider_name = 'book_spider'
redis_key = f'{spider_name}:start_urls'

# 添加起始URL(这里以豆瓣读书为例)
start_urls = [
    'https://book.douban.com/tag/小说',
    'https://book.douban.com/tag/编程',
    'https://book.douban.com/tag/历史',
]

# 清空旧的URL(可选)
r.delete(redis_key)

# 添加新的起始URL
for url in start_urls:
    r.lpush(redis_key, url)
    print(f'已添加: {url}')

print(f'共添加了 {len(start_urls)} 个起始URL到 {redis_key}')

运行这个脚本:

python add_start_urls.py

4.3 启动分布式爬虫

现在可以启动爬虫了。打开多个终端窗口,在每个窗口中执行:

# 激活环境
conda activate scrapy_redis

# 进入项目目录
cd distributed_spider

# 启动爬虫(可以同时启动多个)
scrapy crawl book_spider

你会看到类似这样的输出:

2024-01-15 10:30:15 [scrapy.utils.log] INFO: Scrapy 2.11.0 started
2024-01-15 10:30:15 [scrapy.utils.log] INFO: Versions: lxml 5.1.0...
2024-01-15 10:30:15 [scrapy.crawler] INFO: Overridden settings:
...
2024-01-15 10:30:15 [scrapy_redis.scheduler] INFO: Reading start URLs from redis key 'book_spider:start_urls'

4.4 监控爬虫状态

你可以通过Redis命令行工具监控爬虫状态:

# 连接Redis
redis-cli

# 查看队列中的URL数量
127.0.0.1:6379> LLEN book_spider:start_urls

# 查看去重集合的大小(已抓取的URL数量)
127.0.0.1:6379> ZCARD book_spider:dupefilter

# 查看待处理的请求队列
127.0.0.1:6379> LLEN book_spider:requests

5. 实战技巧与问题解决

5.1 处理反爬策略

在实际爬取中,你可能会遇到反爬。这里分享几个实用的技巧:

# 在settings.py中添加这些配置
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 500,
}

# 使用随机的User-Agent
FAKEUSERAGENT_PROVIDERS = [
    'scrapy_fake_useragent.providers.FakeUserAgentProvider',
    'scrapy_fake_useragent.providers.FakerProvider',
    'scrapy_fake_useragent.providers.FixedUserAgentProvider',
]

# 设置重试策略
RETRY_ENABLED = True
RETRY_TIMES = 3  # 重试次数
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]

# 使用代理IP(如果需要)
# PROXY_LIST = 'path/to/proxy_list.txt'

5.2 分布式部署实战

当你的爬虫需要多台机器协同工作时,可以这样部署:

  1. 主节点设置

    # 在主节点的settings.py中
    REDIS_HOST = '你的Redis服务器IP'
    REDIS_PORT = 6379
    # 所有爬虫节点都连接这个Redis
    
  2. 启动多个爬虫节点

    # 在机器A上
    scrapy crawl book_spider -s JOBDIR=jobs/book_spider_1
    
    # 在机器B上  
    scrapy crawl book_spider -s JOBDIR=jobs/book_spider_2
    
    # 在机器C上
    scrapy crawl book_spider -s JOBDIR=jobs/book_spider_3
    
  3. 动态添加任务: 你可以随时向Redis中添加新的URL,所有爬虫节点会自动获取并处理:

    import redis
    
    r = redis.Redis(host='Redis服务器IP', port=6379)
    r.lpush('book_spider:start_urls', '新的URL')
    

5.3 数据存储优化

除了保存到JSON文件,你还可以将数据存储到数据库。这里以MongoDB为例:

# 安装pymongo
pip install pymongo

# 创建MongoDB管道
import pymongo

class MongoPipeline:
    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', 'scrapy_data')
        )
    
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
    
    def process_item(self, item, spider):
        collection_name = item.__class__.__name__
        self.db[collection_name].insert_one(dict(item))
        spider.logger.debug(f"数据已保存到MongoDB: {item['title']}")
        return item
    
    def close_spider(self, spider):
        self.client.close()

在settings.py中添加MongoDB配置:

MONGO_URI = 'mongodb://localhost:27017'
MONGO_DATABASE = 'book_data'

5.4 常见问题解决

问题1:爬虫启动后不工作

  • 检查Redis是否正常运行:redis-cli ping 应该返回 PONG
  • 检查起始URL是否已添加到Redis:redis-cli LLEN book_spider:start_urls
  • 检查爬虫名称是否匹配

问题2:抓取速度太慢

  • 调整并发数:CONCURRENT_REQUESTS = 100
  • 关闭下载延迟:DOWNLOAD_DELAY = 0
  • 增加超时时间:DOWNLOAD_TIMEOUT = 30

问题3:遇到反爬被封IP

  • 增加下载延迟:DOWNLOAD_DELAY = 2
  • 使用代理IP池
  • 设置更真实的User-Agent
  • 启用自动重试

6. 总结

通过这个实战案例,我们完成了一个完整的Scrapy+Redis分布式爬虫系统。让我帮你回顾一下关键要点:

核心收获

  1. 环境搭建:使用Miniconda-Python3.9创建独立环境,避免包冲突
  2. 项目配置:正确配置Scrapy和scrapy-redis,让它们能协同工作
  3. 分布式架构:通过Redis实现任务分发和状态同步,多台机器可以同时爬取
  4. 实战技巧:学会了处理反爬、数据存储、监控调试等实际问题

这个方案的优势

  • 扩展性强:随时可以增加爬虫节点,提升抓取速度
  • 容错性好:单个节点故障不影响整体运行
  • 管理方便:通过Redis可以集中监控和控制所有爬虫
  • 断点续爬:支持暂停和恢复,不怕中途中断

下一步建议

  1. 尝试抓取其他网站,修改爬虫解析逻辑
  2. 添加更多数据处理管道,比如存到MySQL、Elasticsearch等
  3. 实现动态代理IP,应对更严格的反爬
  4. 添加监控告警,当爬虫异常时及时通知
  5. 考虑使用Docker容器化部署,更方便管理

分布式爬虫听起来复杂,但拆解开来就是几个核心组件:Scrapy负责抓取和解析,Redis负责协调和调度,你的代码负责业务逻辑。掌握了这个框架,你就能应对大多数爬虫需求了。

记住,爬虫不仅要能跑起来,还要跑得稳、跑得快、不被封。多实践、多调试,你会越来越熟练的。如果在实践中遇到问题,欢迎随时交流讨论!


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐