爬虫--爬虫镜像化--docker部署scrapy
做爬虫开发的朋友一定都遇到过这样的场景:在本地调试得好好的爬虫,部署到服务器上就各种报错——Python版本不对、依赖库缺失、系统环境差异……一套环境配下来,半天就没了。
Docker的出现正好解决了这个问题。它把爬虫代码和运行环境一起打包成一个“镜像”,实现一次构建,到处运行。换机器部署只需要一条docker run命令,不用再被环境配置折磨。
本文将以抓取电影网站(以豆瓣电影TOP250为例)的Scrapy项目为基础,带你从零开始把爬虫容器化,并分享实践中踩过的坑和解决方案。
前置准备
开始之前,确保你已经具备:
- 一个能正常运行的Scrapy爬虫项目
- 机器上已安装Docker(参考Docker官方文档)
- 准备好
requirements.txt依赖清单
实战示例:豆瓣电影TOP250爬虫
我们先用Scrapy创建一个完整的豆瓣电影爬虫作为示例项目。
1. 创建Scrapy项目
scrapy startproject movie_spidercdmovie_spider scrapy genspider douban movie.douban.com2. 编写爬虫代码
编辑movie_spider/spiders/douban.py:
importscrapyfromscrapyimportRequestfrommovie_spider.itemsimportMovieItemclassDoubanSpider(scrapy.Spider):name='douban'allowed_domains=['movie.douban.com']start_urls=['https://movie.douban.com/top250']defparse(self,response):# 提取每一部电影的信息movies=response.css('div.item')formovieinmovies: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()yielditem# 翻页:继续爬取下一页next_page=response.css('span.next a::attr(href)').get()ifnext_page:yieldRequest(response.urljoin(next_page),callback=self.parse)3. 定义Item结构
编辑movie_spider/items.py:
importscrapyclassMovieItem(scrapy.Item):rank=scrapy.Field()title=scrapy.Field()rating=scrapy.Field()quote=scrapy.Field()4. 配置设置(可选)
编辑movie_spider/settings.py,启用一些常用配置:
# 遵守robots协议(豆瓣允许爬虫,但建议设置延迟以示礼貌)ROBOTSTXT_OBEY=TrueDOWNLOAD_DELAY=1.5# 设置User-AgentUSER_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:
# 使用轻量级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所在目录)执行:
# 查看项目结构确认ls-la# 应该看到:Dockerfile requirements.txt movie_spider/# 构建镜像dockerbuild-tdouban-spider:v1.-t给镜像打上标签,方便管理。构建完成后用docker images可以查看生成的镜像。
$ docker images | grep douban douban-spider v1 abc123def456 2 minutes ago 189MB第三步:运行容器
基础运行方式
dockerrun--rmdouban-spider:v1--rm表示容器运行结束后自动删除,避免残留无用容器。
数据持久化:保存爬取结果
容器内产生的数据会随容器删除而丢失。使用数据卷(Volume)可以把结果保存到宿主机:
dockerrun--rm-v$(pwd)/output:/app/output douban-spider:v1这样爬虫保存到/app/output/movies.json的数据会出现在当前目录的output文件夹中。
覆盖默认命令:抓取其他内容
如果需要覆盖默认命令,例如只抓取评分最高的10部电影:
dockerrun--rmdouban-spider:v1 scrapy crawl douban-atop=10-ooutput/top10.json需要在爬虫中增加对应的参数支持(扩展):
def__init__(self,top=None,*args,**kwargs):super(DoubanSpider,self).__init__(*args,**kwargs)self.top=int(top)iftopelseNone进阶配置
1. 环境变量:动态配置爬虫
通过环境变量传递配置(如数据源、开关等):
dockerrun--rm\-eOUTPUT_FORMAT=csv\-eENABLE_PROXY=true\-v$(pwd)/output:/app/output\douban-spider:v1在settings.py中读取环境变量:
importos# 动态配置输出格式FEEDS={f'output/movies.{os.getenv("OUTPUT_FORMAT","json")}':{'format':os.getenv("OUTPUT_FORMAT","json"),'encoding':'utf8','indent':2,},}2. 代理配置
如果目标网站有反爬限制,可以在Docker运行时挂载代理:
dockerrun--rm\-eHTTP_PROXY=http://proxy.example.com:8080\-eHTTPS_PROXY=http://proxy.example.com:8080\douban-spider:v13. 定时执行
结合crontab或docker run配合调度工具(如Apache Airflow),实现定时抓取:
# 每天凌晨2点执行一次02* * *dockerrun--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中设置时区 RUN apt-get update && apt-get install -y tzdata \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone或在运行时挂载宿主机时区:
dockerrun--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一键启动整套环境:
version:'3.8'services:# Redis - URL队列和去重redis:image:redis:7-alpineports:-"6379:6379"volumes:-redis_data:/datarestart:unless-stopped# MongoDB - 存储电影数据mongodb:image:mongo:7environment:MONGO_INITDB_ROOT_USERNAME:adminMONGO_INITDB_ROOT_PASSWORD:secretMONGO_INITDB_DATABASE:moviesports:-"27017:27017"volumes:-mongo_data:/data/dbrestart:unless-stopped# Scrapy爬虫 - 支持多实例spider:build:.depends_on:-redis-mongodbenvironment:REDIS_HOST:redisREDIS_PORT:6379MONGO_HOST:mongodbMONGO_USER:adminMONGO_PASSWORD:secretvolumes:-./output:/app/output# 启动多个爬虫实例实现并发抓取# deploy:# replicas: 3volumes:redis_data:mongo_data:启动整个集群:
# 启动所有服务docker-composeup-d# 查看容器状态docker-composeps# 启动3个爬虫实例并发抓取docker-composeup-d--scalespider=3# 查看爬虫日志docker-composelogs-fspider这样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让爬虫项目的部署和运维变得前所未有的简单。
