爬虫--爬虫镜像化--docker部署scrapy

做爬虫开发的朋友一定都遇到过这样的场景:在本地调试得好好的爬虫,部署到服务器上就各种报错------Python版本不对、依赖库缺失、系统环境差异......一套环境配下来,半天就没了。

Docker的出现正好解决了这个问题。它把爬虫代码和运行环境一起打包成一个"镜像",实现一次构建,到处运行 。换机器部署只需要一条docker run命令,不用再被环境配置折磨。

本文将以抓取电影网站(以豆瓣电影TOP250为例) 的Scrapy项目为基础,带你从零开始把爬虫容器化,并分享实践中踩过的坑和解决方案。


前置准备

开始之前,确保你已经具备:

  1. 一个能正常运行的Scrapy爬虫项目
  2. 机器上已安装Docker(参考Docker官方文档
  3. 准备好requirements.txt依赖清单

实战示例:豆瓣电影TOP250爬虫

我们先用Scrapy创建一个完整的豆瓣电影爬虫作为示例项目。

1. 创建Scrapy项目

bash 复制代码
scrapy startproject movie_spider
cd movie_spider
scrapy genspider douban movie.douban.com

2. 编写爬虫代码

编辑 movie_spider/spiders/douban.py

python 复制代码
import scrapy
from scrapy import Request
from movie_spider.items import MovieItem

class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['movie.douban.com']
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        # 提取每一部电影的信息
        movies = response.css('div.item')
        for movie in movies:
            item = MovieItem()
            item['rank'] = movie.css('em::text').get()
            item['title'] = movie.css('span.title::text').get()
            item['rating'] = movie.css('span.rating_num::text').get()
            item['quote'] = movie.css('span.inq::text').get()
            yield item

        # 翻页:继续爬取下一页
        next_page = response.css('span.next a::attr(href)').get()
        if next_page:
            yield Request(response.urljoin(next_page), callback=self.parse)

3. 定义Item结构

编辑 movie_spider/items.py

python 复制代码
import scrapy

class MovieItem(scrapy.Item):
    rank = scrapy.Field()
    title = scrapy.Field()
    rating = scrapy.Field()
    quote = scrapy.Field()

4. 配置设置(可选)

编辑 movie_spider/settings.py,启用一些常用配置:

python 复制代码
# 遵守robots协议(豆瓣允许爬虫,但建议设置延迟以示礼貌)
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 1.5

# 设置User-Agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'

# 日志级别
LOG_LEVEL = 'INFO'

# 输出格式:数据保存为JSON文件
FEEDS = {
    'output/movies.json': {
        'format': 'json',
        'encoding': 'utf8',
        'indent': 2,
    },
}

5. 准备依赖文件

创建 requirements.txt

复制代码
scrapy>=2.11.0

第一步:编写Dockerfile

Dockerfile是构建镜像的"说明书"。以下是针对本电影爬虫项目优化的Dockerfile:

dockerfile 复制代码
# 使用轻量级Python镜像
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 先复制依赖文件(利用Docker层缓存,提高构建效率)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目代码
COPY . .

# 创建输出目录(用于保存爬取结果)
RUN mkdir -p /app/output

# 容器启动时执行的命令:爬取豆瓣TOP250
CMD ["scrapy", "crawl", "douban", "-o", "output/movies.json"]

关键点说明:

  • 电影爬虫不需要复杂的系统依赖(不需要编译lxml),所以直接用 slim 基础镜像即可
  • 依赖安装放在代码复制之前,这样代码改动后重新构建时可以复用缓存层,速度更快
  • 在容器内预创建输出目录,便于挂载数据卷

第二步:构建镜像

在项目根目录(Dockerfile所在目录)执行:

bash 复制代码
# 查看项目结构确认
ls -la
# 应该看到:Dockerfile  requirements.txt  movie_spider/

# 构建镜像
docker build -t douban-spider:v1 .

-t给镜像打上标签,方便管理。构建完成后用 docker images 可以查看生成的镜像。

复制代码
$ docker images | grep douban
douban-spider    v1    abc123def456    2 minutes ago    189MB

第三步:运行容器

基础运行方式

bash 复制代码
docker run --rm douban-spider:v1

--rm表示容器运行结束后自动删除,避免残留无用容器。

数据持久化:保存爬取结果

容器内产生的数据会随容器删除而丢失。使用数据卷(Volume)可以把结果保存到宿主机:

bash 复制代码
docker run --rm -v $(pwd)/output:/app/output douban-spider:v1

这样爬虫保存到 /app/output/movies.json 的数据会出现在当前目录的 output 文件夹中。

覆盖默认命令:抓取其他内容

如果需要覆盖默认命令,例如只抓取评分最高的10部电影:

bash 复制代码
docker run --rm douban-spider:v1 scrapy crawl douban -a top=10 -o output/top10.json

需要在爬虫中增加对应的参数支持(扩展):

python 复制代码
def __init__(self, top=None, *args, **kwargs):
    super(DoubanSpider, self).__init__(*args, **kwargs)
    self.top = int(top) if top else None

进阶配置

1. 环境变量:动态配置爬虫

通过环境变量传递配置(如数据源、开关等):

bash 复制代码
docker run --rm \
  -e OUTPUT_FORMAT=csv \
  -e ENABLE_PROXY=true \
  -v $(pwd)/output:/app/output \
  douban-spider:v1

settings.py 中读取环境变量:

python 复制代码
import os

# 动态配置输出格式
FEEDS = {
    f'output/movies.{os.getenv("OUTPUT_FORMAT", "json")}': {
        'format': os.getenv("OUTPUT_FORMAT", "json"),
        'encoding': 'utf8',
        'indent': 2,
    },
}

2. 代理配置

如果目标网站有反爬限制,可以在Docker运行时挂载代理:

bash 复制代码
docker run --rm \
  -e HTTP_PROXY=http://proxy.example.com:8080 \
  -e HTTPS_PROXY=http://proxy.example.com:8080 \
  douban-spider:v1

3. 定时执行

结合 crontabdocker run 配合调度工具(如Apache Airflow),实现定时抓取:

bash 复制代码
# 每天凌晨2点执行一次
0 2 * * * docker run --rm -v /data/output:/app/output douban-spider:v1

实战踩坑记录

坑1:Scrapy的Robots.txt被屏蔽

豆瓣等网站可能对爬虫做限制。解决方法:

  • 方案A :在 settings.py 中设置 ROBOTSTXT_OBEY = False
  • 方案B:自定义User-Agent,模拟真实浏览器(见前文配置)

坑2:中文乱码问题

JSON输出中文显示为 \uXXXX 编码。解决方法:

  • settings.py 中设置 FEED_EXPORT_ENCODING = 'utf-8'
  • 在Item导出时指定 ensure_ascii=False(对于自定义导出器)

坑3:容器内时区问题

容器默认UTC时区,爬取时间戳可能不正确。解决方案:

dockerfile 复制代码
# 在Dockerfile中设置时区
RUN apt-get update && apt-get install -y tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone

或在运行时挂载宿主机时区:

bash 复制代码
docker run --rm -v /etc/localtime:/etc/localtime:ro douban-spider:v1

坑4:镜像体积过大

保持镜像轻量化的策略:

  • 使用 python:*-slimpython:*-alpine 基础镜像
  • 利用Docker层缓存(依赖先复制)
  • 清理 apt 缓存:apt-get clean && rm -rf /var/lib/apt/lists/*
  • 使用多阶段构建(如需要编译的库)

多容器协作:Scrapy + Redis + MongoDB

真实生产环境往往需要多个服务配合。以分布式电影爬虫为例:Redis做URL去重队列,MongoDB存储电影数据。

使用 docker-compose 一键启动整套环境:

yaml 复制代码
version: '3.8'

services:
  # Redis - URL队列和去重
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # MongoDB - 存储电影数据
  mongodb:
    image: mongo:7
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: secret
      MONGO_INITDB_DATABASE: movies
    ports:
      - "27017:27017"
    volumes:
      - mongo_data:/data/db
    restart: unless-stopped

  # Scrapy爬虫 - 支持多实例
  spider:
    build: .
    depends_on:
      - redis
      - mongodb
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
      MONGO_HOST: mongodb
      MONGO_USER: admin
      MONGO_PASSWORD: secret
    volumes:
      - ./output:/app/output
    # 启动多个爬虫实例实现并发抓取
    # deploy:
    #   replicas: 3

volumes:
  redis_data:
  mongo_data:

启动整个集群:

bash 复制代码
# 启动所有服务
docker-compose up -d

# 查看容器状态
docker-compose ps

# 启动3个爬虫实例并发抓取
docker-compose up -d --scale spider=3

# 查看爬虫日志
docker-compose logs -f spider

这样Redis做URL队列去重,MongoDB存储电影详情,多个爬虫容器共享任务,一套分布式爬虫系统就搭建起来了。


完整项目结构

最终的项目文件结构:

复制代码
movie_spider/
├── Dockerfile
├── requirements.txt
├── docker-compose.yml
├── output/                    # 运行后生成
│   └── movies.json
└── movie_spider/
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders/
        ├── __init__.py
        └── douban.py

总结

把Scrapy爬虫Docker化,核心价值在于:

价值 说明
环境一致性 本地跑通,线上必然跑通,告别"环境问题"
快速部署 新机器只需要Docker,一条命令启动
资源隔离 不同爬虫项目互不干扰
弹性伸缩 配合docker-compose或K8s,轻松横向扩展

从单容器到分布式集群,Docker让爬虫项目的部署和运维变得前所未有的简单。