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

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

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

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

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


前置准备

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

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

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

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

1. 创建Scrapy项目

scrapy startproject movie_spidercdmovie_spider scrapy genspider douban movie.douban.com

2. 编写爬虫代码

编辑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:v1

3. 定时执行

结合crontabdocker 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:*-slimpython:*-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让爬虫项目的部署和运维变得前所未有的简单。