1. 项目概述:爬取豆瓣电影数据的实用价值
豆瓣电影Top 250榜单是中文互联网最具公信力的电影评分排行榜,包含了从经典老片到当代佳作的精华片单。作为数据分析师、电影爱好者或是需要构建推荐系统的开发者,获取这份数据有着多重实用价值:
- 电影市场分析:通过评分、评价人数、年份等字段分析观众偏好变迁
- 推荐系统冷启动:为电影推荐算法提供高质量初始数据集
- 舆情监控基线:建立优质电影的评分基准线
- 技术练手项目:完整的爬虫流程包含反爬应对、数据清洗等典型环节
提示:虽然豆瓣数据开放获取,但请控制请求频率(建议2-3秒/次),避免对服务器造成压力。商业用途需获得官方授权。
2. 技术选型:为什么选择Scrapy框架
2.1 Scrapy的核心优势
相比Requests+BeautifulSoup的传统组合,Scrapy作为专业爬虫框架具有明显优势:
- 内置异步处理:基于Twisted的异步网络库,单机即可实现高并发
- 结构化管道:内置的Item Pipeline机制完美支持数据清洗与存储
- 中间件扩展:通过Downloader Middleware可灵活处理各种反爬策略
- 健壮性保障:自动重试、深度限制等机制减少异常中断风险
python复制# 典型Scrapy项目结构
douban_top250/
├── scrapy.cfg
└── douban_top250
├── __init__.py
├── items.py # 定义数据结构
├── middlewares.py # 反爬处理
├── pipelines.py # 数据存储
├── settings.py # 爬虫配置
└── spiders/ # 爬虫核心逻辑
└── top250.py
2.2 环境准备
推荐使用Python 3.8+环境,通过virtualenv创建隔离环境:
bash复制python -m venv scrapy_env
source scrapy_env/bin/activate # Linux/Mac
pip install scrapy fake-useragent
3. 爬虫核心实现详解
3.1 页面分析关键点
打开豆瓣Top250页面(https://movie.douban.com/top250)可以看到:
- 分页机制:每页25条,共10页,URL格式为
?start=参数 - 数据位置:电影信息位于
<div class="item">标签内 - 反爬措施:
- 未登录用户频繁访问会触发验证码
- 请求头检测(需模拟浏览器)
- IP频次限制
3.2 定义数据模型(items.py)
明确需要抓取的字段:
python复制import scrapy
class DoubanMovieItem(scrapy.Item):
rank = scrapy.Field() # 排名
title = scrapy.Field() # 中文名
orig_title = scrapy.Field() # 原名
rating = scrapy.Field() # 评分
votes = scrapy.Field() # 评价人数
year = scrapy.Field() # 年份
directors = scrapy.Field() # 导演
actors = scrapy.Field() # 主演
genres = scrapy.Field() # 类型
quote = scrapy.Field() # 经典台词
3.3 核心爬虫逻辑(spiders/top250.py)
python复制import scrapy
from douban_top250.items import DoubanMovieItem
from fake_useragent import UserAgent
class Top250Spider(scrapy.Spider):
name = 'douban_top250'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
custom_settings = {
'DOWNLOAD_DELAY': 3,
'CONCURRENT_REQUESTS_PER_DOMAIN': 1
}
def parse(self, response):
for movie in response.css('div.item'):
item = DoubanMovieItem()
item['rank'] = movie.css('div.pic em::text').get()
item['title'] = movie.css('span.title::text').get()
# 其他字段解析...
yield item
# 处理分页
next_page = response.css('span.next a::attr(href)').get()
if next_page:
yield response.follow(next_page,
headers={'User-Agent': UserAgent().random},
callback=self.parse)
3.4 反爬策略实战方案
3.4.1 请求头伪装
在middlewares.py中配置:
python复制from fake_useragent import UserAgent
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = UserAgent().random
request.headers['Referer'] = 'https://movie.douban.com/'
3.4.2 IP代理轮换(需自备代理池)
python复制class ProxyMiddleware:
def process_request(self, request, spider):
request.meta['proxy'] = "http://your_proxy:port"
3.4.3 验证码处理方案
当触发验证码时,建议:
- 立即暂停爬虫(约1小时)
- 手动处理验证码后继续
- 或者使用付费打码平台API
4. 数据存储与清洗
4.1 数据管道配置(pipelines.py)
推荐同时存储为JSON和CSV格式:
python复制import json
import csv
from itemadapter import ItemAdapter
class DoubanTop250Pipeline:
def open_spider(self, spider):
self.file_json = open('top250.json', 'w', encoding='utf-8')
self.file_csv = open('top250.csv', 'w', encoding='utf-8', newline='')
self.writer = csv.writer(self.file_csv)
self.writer.writerow(['排名','片名','评分','年份'])
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file_json.write(line)
self.writer.writerow([
item['rank'],
item['title'],
item['rating'],
item['year']
])
return item
def close_spider(self, spider):
self.file_json.close()
self.file_csv.close()
4.2 数据清洗要点
在Pipeline中添加清洗逻辑:
python复制def process_item(self, item, spider):
# 处理空值
if not item.get('year'):
item['year'] = '未知'
# 统一评分格式
item['rating'] = float(item['rating']) if item['rating'] else 0.0
# 处理多值字段(如导演)
if isinstance(item['directors'], str):
item['directors'] = [d.strip() for d in item['directors'].split('/')]
return item
5. 部署与优化建议
5.1 生产环境部署
使用Scrapyd服务部署爬虫:
bash复制pip install scrapyd
scrapyd &
scrapyd-deploy default -p douban_top250
5.2 定时任务配置
通过APScheduler实现定时运行:
python复制from apscheduler.schedulers.twisted import TwistedScheduler
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
process = CrawlerProcess(get_project_settings())
scheduler = TwistedScheduler()
scheduler.add_job(process.crawl, 'cron', args=['douban_top250'], day_of_week='mon', hour=3)
scheduler.start()
process.start(False)
5.3 性能优化技巧
-
并发控制:在settings.py中调整
python复制CONCURRENT_REQUESTS = 8 # 总并发数 DOWNLOAD_DELAY = 2 # 请求间隔(秒) -
缓存启用:减少重复请求
python复制HTTPCACHE_ENABLED = True HTTPCACHE_EXPIRATION_SECS = 86400 # 缓存1天 -
失败重试:
python复制RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 408]
6. 常见问题与解决方案
6.1 被封IP怎么办?
- 立即停止爬虫至少1小时
- 检查是否触发了以下行为:
- 请求频率高于3秒/次
- 未设置User-Agent
- 未处理Cookies
- 解决方案:
- 使用代理IP池轮换
- 降低请求频率至5秒/次
- 模拟完整浏览器行为
6.2 数据字段缺失如何处理?
在解析逻辑中添加默认值:
python复制item['quote'] = movie.css('span.inq::text').get() or '无经典台词'
6.3 如何增量爬取?
- 使用Scrapy的扩展机制记录已爬取URL
- 或者将已存储的ID列表存入Redis:
python复制import redis
r = redis.Redis(host='localhost', port=6379)
def parse(self, response):
movie_id = response.url.split('/')[-2]
if r.sismember('douban:top250', movie_id):
return
r.sadd('douban:top250', movie_id)
# 继续解析逻辑...
7. 数据应用示例
获取到的数据可以进行多种分析:
python复制import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('top250.csv')
# 评分分布分析
df['rating'].hist(bins=10)
plt.title('豆瓣Top250评分分布')
plt.show()
# 导演作品统计
director_counts = df['directors'].explode().value_counts().head(10)
director_counts.plot(kind='barh')
plt.title('作品数最多的十大导演')
plt.show()
8. 法律与道德注意事项
- 遵守robots.txt:豆瓣的robots.txt允许/top250访问
- 限制请求频率:建议≥3秒/次的间隔
- 数据使用范围:个人学习研究用途合法
- 商业使用授权:如需商用需联系豆瓣API合作
- 数据存储安全:不要公开传播原始数据
重要:虽然技术上可以实现暴力爬取,但请始终遵循最小必要原则,保护网站正常运行。
