做爬虫开发的朋友一定都遇到过这样的场景:在本地调试得好好的爬虫,部署到服务器上就各种报错------Python版本不对、依赖库缺失、系统环境差异......一套环境配下来,半天就没了。
Docker的出现正好解决了这个问题。它把爬虫代码和运行环境一起打包成一个"镜像",实现一次构建,到处运行 。换机器部署只需要一条docker run命令,不用再被环境配置折磨。
本文将以抓取电影网站(以豆瓣电影TOP250为例) 的Scrapy项目为基础,带你从零开始把爬虫容器化,并分享实践中踩过的坑和解决方案。
前置准备
开始之前,确保你已经具备:
- 一个能正常运行的Scrapy爬虫项目
- 机器上已安装Docker(参考Docker官方文档)
- 准备好
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. 定时执行
结合 crontab 或 docker 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:*-slim或python:*-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让爬虫项目的部署和运维变得前所未有的简单。