Python快速抓取豆瓣短评数据实战
1. 项目背景与核心思路
去年帮朋友做影视数据分析时,需要快速获取《黑豹》的豆瓣短评数据。传统爬虫教程往往从零开始搭建完整框架,但对于这种轻量级需求,其实用Python的requests库配合几行正则就能暴力破解。这个方法虽然不够优雅,但在小规模数据抓取场景下效率极高。
核心原理很简单:豆瓣短评页面的HTML结构相对固定,我们只需要:
- 用requests模拟浏览器请求
- 从返回的HTML中提取评论文本
- 处理分页逻辑循环抓取
注意:实际操作前请务必检查豆瓣的robots.txt协议,控制请求频率在合理范围。我测试时设置了2秒间隔,既不会触发反爬机制,也能在1分钟内完成基础数据采集。
2. 环境准备与工具选型
2.1 基础环境配置
推荐使用Python 3.8+版本,主要依赖两个库:
pip install requests regex选择requests而非urllib的原因:
- 更简洁的API设计(对比urllib的urlencode等繁琐操作)
- 自动处理连接池和Keep-Alive
- 内置SSL验证等安全特性
2.2 反爬应对策略
豆瓣对高频请求有严格限制,实测中发现两个关键点:
- 需要添加合理的请求头:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9' }- 必须控制请求间隔:
import time time.sleep(random.uniform(1, 3)) # 随机间隔更安全3. 核心代码实现解析
3.1 页面请求模块
import requests def get_page(url): try: r = requests.get(url, headers=headers) r.raise_for_status() # 自动处理HTTP错误 r.encoding = r.apparent_encoding return r.text except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return None这段代码的关键点:
raise_for_status()会自动检查4xx/5xx状态码- 使用
apparent_encoding能更准确识别页面编码 - 异常捕获包含连接超时、SSL错误等常见问题
3.2 数据提取模块
豆瓣短评的HTML结构类似:
<div class="comment-item"> <span class="short">这是评论内容</span> </div>使用正则表达式提取:
import re pattern = re.compile(r'<span class="short">(.*?)</span>') comments = pattern.findall(html)相比BeautifulSoup,正则的优势:
- 无需解析整个DOM树
- 内存占用更小
- 速度更快(实测快3-5倍)
3.3 分页处理逻辑
观察豆瓣分页规则发现URL规律:
https://movie.douban.com/subject/123456/comments?start=20&limit=20通过修改start参数实现翻页:
base_url = "https://movie.douban.com/subject/123456/comments" for i in range(0, 200, 20): # 抓取前10页 url = f"{base_url}?start={i}&limit=20" html = get_page(url) # 处理提取逻辑...4. 完整代码实现
import requests, re, time, random headers = {'User-Agent': 'Mozilla/5.0'} pattern = re.compile(r'<span class="short">(.*?)</span>') def crawl(start=0): url = f"https://movie.douban.com/subject/6390825/comments?start={start}" html = requests.get(url, headers=headers).text comments = pattern.findall(html) for cmt in comments: print(cmt.strip()) time.sleep(random.uniform(1, 3)) for i in range(0, 100, 20): crawl(i)代码优化点:
- 使用随机睡眠时间避免固定间隔被识别
- 限制总请求量在合理范围(这里设置5页)
- 输出时用strip()去除空白字符
5. 常见问题与解决方案
5.1 返回429错误
典型表现:
requests.exceptions.HTTPError: 429 Client Error解决方法:
- 增加延迟时间到3-5秒
- 添加更多请求头伪装:
headers.update({ 'Accept': 'text/html,application/xhtml+xml', 'Connection': 'keep-alive' })5.2 中文乱码问题
如果出现乱码,需要手动指定编码:
r.encoding = 'utf-8' # 强制使用UTF-85.3 数据重复或缺失
可能原因:
- 分页参数计算错误
- 正则表达式匹配不准确 排查方法:
with open('debug.html', 'w', encoding='utf-8') as f: f.write(html) # 保存原始HTML人工检查6. 进阶优化方向
6.1 使用代理IP池
当需要大规模采集时,建议使用:
proxies = { 'http': 'http://10.10.1.10:3128', 'https': 'http://10.10.1.10:1080' } requests.get(url, proxies=proxies)6.2 数据持久化存储
推荐保存为CSV格式:
import csv with open('comments.csv', 'a', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow([comment, rating, time]) # 需要先提取这些字段6.3 异步请求优化
对于高性能需求,可用aiohttp:
import aiohttp async with aiohttp.ClientSession() as session: async with session.get(url) as resp: return await resp.text()我在实际项目中总结的经验是:对于临时性数据需求,这种暴力方法效率最高;但如果是长期项目,建议还是用Scrapy等框架构建完整爬虫,并严格遵守网站的爬取政策。
