Python快速抓取豆瓣短评数据实战

Python快速抓取豆瓣短评数据实战

1. 项目背景与核心思路

去年帮朋友做影视数据分析时,需要快速获取《黑豹》的豆瓣短评数据。传统爬虫教程往往从零开始搭建完整框架,但对于这种轻量级需求,其实用Python的requests库配合几行正则就能暴力破解。这个方法虽然不够优雅,但在小规模数据抓取场景下效率极高。

核心原理很简单:豆瓣短评页面的HTML结构相对固定,我们只需要:

  1. 用requests模拟浏览器请求
  2. 从返回的HTML中提取评论文本
  3. 处理分页逻辑循环抓取

注意:实际操作前请务必检查豆瓣的robots.txt协议,控制请求频率在合理范围。我测试时设置了2秒间隔,既不会触发反爬机制,也能在1分钟内完成基础数据采集。

2. 环境准备与工具选型

2.1 基础环境配置

推荐使用Python 3.8+版本,主要依赖两个库:

pip install requests regex

选择requests而非urllib的原因:

  • 更简洁的API设计(对比urllib的urlencode等繁琐操作)
  • 自动处理连接池和Keep-Alive
  • 内置SSL验证等安全特性

2.2 反爬应对策略

豆瓣对高频请求有严格限制,实测中发现两个关键点:

  1. 需要添加合理的请求头:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9' }
  1. 必须控制请求间隔:
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)

代码优化点:

  1. 使用随机睡眠时间避免固定间隔被识别
  2. 限制总请求量在合理范围(这里设置5页)
  3. 输出时用strip()去除空白字符

5. 常见问题与解决方案

5.1 返回429错误

典型表现:

requests.exceptions.HTTPError: 429 Client Error

解决方法:

  1. 增加延迟时间到3-5秒
  2. 添加更多请求头伪装:
headers.update({ 'Accept': 'text/html,application/xhtml+xml', 'Connection': 'keep-alive' })

5.2 中文乱码问题

如果出现乱码,需要手动指定编码:

r.encoding = 'utf-8' # 强制使用UTF-8

5.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等框架构建完整爬虫,并严格遵守网站的爬取政策。