1. 项目背景与核心需求
爬取豆瓣电影TOP250的评论数据是很多数据分析师和电影爱好者的常见需求。这些数据可以用于情感分析、用户行为研究、电影市场趋势预测等多种场景。但豆瓣作为国内知名电影社区,其反爬机制相对完善,传统的静态页面爬取方式往往难以奏效。
这个项目的核心挑战在于:
- 豆瓣评论数据通过动态加载实现,普通请求无法获取完整内容
- 页面元素包含复杂的JavaScript渲染逻辑
- 需要模拟真实用户行为以避免触发反爬机制
2. 技术选型与方案设计
2.1 Scrapy框架的优势
Scrapy作为Python生态中最成熟的爬虫框架,提供了完整的爬取流程管理:
- 内置的请求调度和并发控制
- 强大的选择器系统(XPath/CSS)
- 完善的数据管道处理机制
- 中间件系统可灵活扩展
python复制import scrapy
class DoubanSpider(scrapy.Spider):
name = 'douban_top250'
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
# 解析逻辑将在这里实现
pass
2.2 Selenium的补充作用
Selenium解决了Scrapy在处理JavaScript渲染页面时的局限性:
- 完整模拟浏览器环境
- 支持等待元素加载的显式等待机制
- 可以执行复杂的用户交互操作
- 支持主流浏览器驱动(Chrome/Firefox)
python复制from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://movie.douban.com/top250")
2.3 组合架构设计
我们采用Scrapy作为主框架,在Downloader Middleware中集成Selenium:
- Scrapy发起初始请求
- Selenium中间件接管请求并渲染页面
- 将渲染后的页面返回给Spider
- Spider解析数据并通过Pipeline存储
3. 核心实现细节
3.1 环境准备与配置
需要安装的Python包:
bash复制pip install scrapy selenium scrapy-selenium
ChromeDriver配置注意事项:
- 版本必须与本地Chrome浏览器匹配
- 建议将驱动放在系统PATH路径
- 可配置无头模式减少资源消耗
3.2 Selenium中间件实现
python复制from scrapy_selenium import SeleniumMiddleware
class CustomSeleniumMiddleware(SeleniumMiddleware):
def process_request(self, request, spider):
if request.meta.get('selenium'):
driver = self.driver
driver.get(request.url)
# 显式等待评论区域加载
WebDriverWait(driver, 10).until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, '.comment-item')
)
)
# 返回渲染后的页面
body = driver.page_source
return HtmlResponse(driver.current_url, body=body, encoding='utf-8')
3.3 数据解析逻辑
针对豆瓣TOP250页面的XPath选择器:
python复制def parse(self, response):
for movie in response.xpath('//div[@class="item"]'):
yield {
'title': movie.xpath('.//span[@class="title"]/text()').get(),
'rating': movie.xpath('.//span[@class="rating_num"]/text()').get(),
'comment_count': movie.xpath('.//div[@class="star"]/span[4]/text()').get()
}
评论数据的特殊处理:
python复制# 需要先点击"更多短评"加载完整数据
driver.find_element_by_css_selector('.more-comment').click()
# 等待评论加载完成
WebDriverWait(driver, 5).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, '.comment-item')
)
)
4. 反爬策略应对方案
4.1 请求频率控制
python复制# settings.py
DOWNLOAD_DELAY = 3
CONCURRENT_REQUESTS_PER_DOMAIN = 2
4.2 请求头伪装
python复制DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://movie.douban.com/'
}
4.3 IP代理轮换
python复制# middlewares.py
class ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta['proxy'] = 'http://your_proxy_server:port'
5. 数据存储方案
5.1 MongoDB存储配置
python复制# pipelines.py
import pymongo
class MongoPipeline(object):
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')
)
def process_item(self, item, spider):
self.db[spider.name].insert_one(dict(item))
return item
5.2 数据去重设计
python复制# 使用Scrapy内置的去重过滤器
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
# 或者自定义基于MongoDB的去重
def get_dupefilter():
from hashlib import sha1
key = sha1(item['content'].encode()).hexdigest()
if self.db.comments.find_one({'fingerprint': key}):
raise DropItem("Duplicate item found")
6. 性能优化技巧
6.1 浏览器资源优化
python复制chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
6.2 并发控制策略
python复制# settings.py
CONCURRENT_REQUESTS = 8
REACTOR_THREADPOOL_MAXSIZE = 20
6.3 缓存利用
python复制HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 86400 # 1天
7. 常见问题与解决方案
7.1 元素定位失败
可能原因:
- 页面未完全加载
- iframe嵌套
- 元素属性动态变化
解决方案:
python复制# 使用显式等待
WebDriverWait(driver, 10).until(
EC.frame_to_be_available_and_switch_to_it(
(By.ID, 'comment-iframe')
)
)
7.2 验证码触发
应对策略:
- 降低请求频率
- 使用更真实的User-Agent
- 考虑人工干预方案
python复制# 检测验证码页面
if "验证码" in driver.page_source:
input("请手动完成验证码后按回车继续...")
7.3 数据不完整
检查点:
- 确保点击了"加载更多"按钮
- 验证XPath选择器是否匹配最新页面结构
- 检查是否有内容被JavaScript动态加载
python复制# 滚动页面触发懒加载
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待加载
8. 项目扩展方向
8.1 情感分析集成
python复制from snownlp import SnowNLP
def process_comment(text):
s = SnowNLP(text)
return {
'text': text,
'sentiment': s.sentiments,
'keywords': s.keywords(3)
}
8.2 用户行为分析
可以收集的数据维度:
- 评论时间分布
- 评分与评论长度的关系
- 高频词汇统计
8.3 可视化展示
使用PyEcharts生成图表:
python复制from pyecharts import options as opts
from pyecharts.charts import Bar
bar = (
Bar()
.add_xaxis(rating_list)
.add_yaxis("评论数量", comment_counts)
.set_global_opts(title_opts=opts.TitleOpts(title="评分分布"))
)
bar.render("rating_distribution.html")
9. 法律与伦理考量
重要注意事项:
- 严格遵守豆瓣的robots.txt协议
- 限制爬取频率,避免对服务器造成负担
- 不爬取用户隐私数据
- 数据仅用于学习研究目的
建议做法:
- 在夜间低峰期运行爬虫
- 每个请求之间添加随机延迟
- 设置合理的并发数量
python复制# 遵守robots.txt
ROBOTSTXT_OBEY = True
# 随机延迟
DOWNLOAD_DELAY = random.uniform(2, 5)
10. 项目部署建议
10.1 定时任务配置
使用Scrapyd+ScrapyD部署:
bash复制# 安装
pip install scrapyd scrapy-client
# 部署
scrapyd-deploy default -p douban_project
10.2 日志监控
python复制# settings.py
LOG_LEVEL = 'INFO'
LOG_FILE = 'douban_spider.log'
LOG_STDOUT = True
10.3 异常处理
python复制# middlewares.py
class ExceptionMiddleware:
def process_exception(self, request, exception, spider):
if isinstance(exception, TimeoutException):
spider.logger.warning(f"Timeout on {request.url}")
return request # 重新尝试