1. 为什么选择Scrapy爬取豆瓣电影Top 250?
作为Python生态中最专业的爬虫框架,Scrapy在结构化数据抓取场景下具有不可替代的优势。我曾在多个数据采集项目中对比过Requests、BeautifulSoup等方案,当面对豆瓣这类反爬机制完善的网站时,Scrapy的中间件系统和异步处理能力能让开发效率提升3倍以上。
具体到豆瓣电影Top 250这个经典案例,我们需要处理:
- 分页逻辑与URL规律分析
- 动态加载内容的应对策略
- 反爬虫机制的识别与绕过
- 数据清洗与持久化方案
传统脚本式爬虫在这些环节需要手动处理每个细节,而Scrapy通过内置的:
- Item Pipeline(数据清洗)
- Downloader Middleware(反爬对抗)
- Spider Middleware(逻辑扩展)
三大核心组件,让开发者可以专注业务逻辑而非底层细节。
提示:2023年豆瓣更新了反爬策略,传统基于随机User-Agent的方式已部分失效,需要配合IP轮询和请求限速才能稳定采集
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目初始化
2.1 基础环境配置
推荐使用Python 3.8+环境,这是目前与Scrapy兼容性最好的版本。通过conda创建独立环境:
bash复制conda create -n douban_scrapy python=3.8
conda activate douban_scrapy
安装核心依赖包时特别注意版本匹配:
bash复制pip install scrapy==2.8.0
pip install scrapy-user-agents==0.1.1 # 随机User-Agent中间件
pip install scrapy-proxies==0.3.0 # 代理IP中间件
2.2 项目脚手架生成
执行标准初始化命令:
bash复制scrapy startproject douban_top250
cd douban_top250
scrapy genspider movie "movie.douban.com/top250"
生成的项目结构关键文件说明:
code复制douban_top250/
├── scrapy.cfg # 部署配置文件
└── douban_top250/ # 项目主目录
├── items.py # 数据模型定义
├── middlewares.py # 中间件配置
├── pipelines.py # 数据处理管道
├── settings.py # 爬虫配置
└── spiders/ # 爬虫逻辑
└── movie.py # 自动生成的爬虫文件
3. 反爬策略深度解析与应对方案
3.1 豆瓣最新的反爬机制分析
根据2023年实测,豆瓣主要采用以下防御手段:
| 反爬类型 | 表现特征 | 解决方案 |
|---|---|---|
| 请求频率检测 | 返回418状态码 | 自动限速+随机延迟 |
| TLS指纹识别 | 特定客户端指纹拦截 | 使用scrapy-playwright |
| 行为分析 | 鼠标轨迹检测 | 模拟人类操作间隔 |
| IP封禁 | 返回403状态码 | 代理IP池轮换 |
3.2 配置反爬中间件
在settings.py中启用关键组件:
python复制DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy_user_agents.middlewares.RandomUserAgentMiddleware': 400,
'scrapy_proxies.RandomProxy': 100,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
}
# 代理IP池配置(需自行准备有效代理)
PROXY_LIST = '/path/to/proxy_list.txt'
PROXY_MODE = 0 # 0-随机, 1-轮询
# 自动限速设置
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
4. 爬虫核心逻辑实现
4.1 数据模型定义(items.py)
python复制import scrapy
class DoubanMovieItem(scrapy.Item):
rank = scrapy.Field() # 排名
title = scrapy.Field() # 中文片名
original_title = scrapy.Field() # 原名
rating = scrapy.Field() # 评分
votes = scrapy.Field() # 评价人数
directors = scrapy.Field() # 导演列表
actors = scrapy.Field() # 主演列表
types = scrapy.Field() # 类型标签
release_date = scrapy.Field() # 上映日期
runtime = scrapy.Field() # 片长
summary = scrapy.Field() # 剧情简介
cover_url = scrapy.Field() # 封面URL
4.2 页面解析逻辑(spiders/movie.py)
python复制import scrapy
from douban_top250.items import DoubanMovieItem
from urllib.parse import urljoin
class MovieSpider(scrapy.Spider):
name = 'movie'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
# 提取当前页电影条目
for movie in response.css('.item'):
item = DoubanMovieItem()
item['rank'] = movie.css('.pic em::text').get()
item['title'] = movie.css('.title::text').get()
item['rating'] = movie.css('.rating_num::text').get()
yield item
# 处理分页
next_page = response.css('.next a::attr(href)').get()
if next_page:
yield response.follow(urljoin(response.url, next_page), self.parse)
4.3 应对动态加载内容
当遇到JavaScript渲染的内容时,可采用以下两种方案:
方案一:分析AJAX接口(推荐)
python复制# 在parse方法中追加
yield scrapy.Request(
url="https://movie.douban.com/j/subject_abstract",
callback=self.parse_detail,
meta={'item': item},
headers={'X-Requested-With': 'XMLHttpRequest'}
)
def parse_detail(self, response):
item = response.meta['item']
data = json.loads(response.text)
item['summary'] = data.get('summary')
yield item
方案二:启用Playwright(资源消耗大但通用)
python复制# 安装额外依赖
pip install scrapy-playwright playwright
# 修改settings.py
DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
5. 数据存储与后处理
5.1 数据清洗管道(pipelines.py)
python复制import pymongo
from itemadapter import ItemAdapter
class DoubanTop250Pipeline:
def process_item(self, item, spider):
# 统一评分格式
item['rating'] = float(item['rating']) if item['rating'] else 0.0
# 处理导演/演员列表
item['directors'] = [d.strip() for d in item['directors'].split('/')]
return item
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', 'douban')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db['top250'].update_one(
{'title': item['title']},
{'$set': dict(item)},
upsert=True
)
return item
5.2 启用管道配置
在settings.py中激活管道并配置MongoDB:
python复制ITEM_PIPELINES = {
'douban_top250.pipelines.DoubanTop250Pipeline': 300,
'douban_top250.pipelines.MongoPipeline': 800,
}
MONGO_URI = 'mongodb://localhost:27017'
MONGO_DATABASE = 'douban_data'
6. 部署与调度执行
6.1 本地运行测试
bash复制scrapy crawl movie -o top250.json
6.2 生产环境部署建议
使用Scrapyd服务进行分布式调度:
bash复制# 安装服务端
pip install scrapyd
# 安装客户端工具
pip install scrapyd-client
# 部署项目
scrapyd-deploy default -p douban_top250
设置定时任务(Linux crontab示例):
bash复制0 3 * * * cd /path/to/project && scrapy crawl movie -o /data/top250_$(date +\%Y\%m\%d).json
我在实际项目中发现,豆瓣对高频访问极其敏感。建议将爬虫设置为每天凌晨3-5点执行,单次运行间隔不低于10秒,这样可持续运行数月不被封禁。当遇到封禁时,立即切换备用IP池并降低请求频率至30秒/页
