Python爬虫实战:抓取豆瓣电影与书籍评分与评论(含反爬应对)

发布时间:2026/9/9 8:52:19
Python爬虫实战:抓取豆瓣电影与书籍评分与评论(含反爬应对) 如果你在用 Python 做数据采集第一次想抓的网站多半是豆瓣。它结构清晰、数据价值高、反爬强度适中是练手和做数据分析项目首选的目标之一。这篇文章我会从零开始拆解如何抓取豆瓣电影和书籍的评分与评论包含完整代码、反爬应对思路还有我自己踩过的坑。1. 项目概述与整体设计思路1.1 为什么拿豆瓣练手最合适豆瓣平台的数据有几个非常适合入门的特征电影和书籍的详情页、列表页结构规整HTML 标签语义化程度较高短时间内就能摸清页面规律评分、评论、标题、作者这些核心字段都有稳定的 CSS 类名或 ID 锚点解析逻辑一旦写好扩展到一千部作品也不在话下。另一个关键点是豆瓣的访问策略虽然有一定限制但不像电商平台那样需要复杂的风控对抗。只要请求频率控制得当、UA 伪装到位基本不会触发封锁。这种“有挑战但可解”的难度恰好是新手从静态页面爬取迈向动态页面爬取之间的重要桥梁。从需求角度来说抓取内容通常有两种目的一种是做数据分析比如分析高分电影的特征、评价数量与评分之间的关系另一种是建立个人图书或电影数据库做推荐系统。不同目的对字段完整性要求不同因此项目初期就要想清楚需要哪些字段避免后期返工。1.2 技术方案选型与理由整套方案我推荐用 Requests BeautifulSoup 4 组合这是 Python 爬虫领域最稳妥的入门配置。Requests 负责发送 HTTP 请求、维持会话、处理 CookiesBS4 负责解析 HTML 文档树。两者都不依赖浏览器环境运行速度快、内存占用低非常适合批量抓取任务。比 Scrapy 框架轻量得多比 Selenium 快得多。对于豆瓣这种服务端渲染为主的网站完全不需要动用无头浏览器那样反而会大幅提高被识别的风险。后续如果要做大规模采集可以直接把 Requests 的代码逻辑迁移到 Scrapy 的 Spider 里改动成本很低。数据存储方面第一版建议直接用 CSV 文件选 UTF-8 编码配合 BOM 头。这能解决 Excel 打开中文乱码的问题又省去搭建数据库的麻烦。如果项目数据量预期超过一万条再切换 SQLite迁移过程也不复杂。2. 核心细节解析与实操要点2.1 请求头伪装与 Session 管理豆瓣对请求头校验不算严格但完全裸奔的请求大概率会被拦截。核心需要处理的字段有三个User-Agent模拟真实浏览器标识不要用 Python 默认的 urllib 标识Referer尤其翻页请求时需要带上模拟用户从上一页跳转的路径Cookie如果抓取需要登录后才可见的评论必须处理登录态代码层面有个容易忽略的细节建议用 Session 对象而非裸 requests.get。Session 会自动保存服务端返回的 Cookie配合后续请求发送能减少很多身份校验问题。import requests from fake_useragent import UserAgent session requests.Session() ua UserAgent() session.headers.update({ User-Agent: ua.random, Accept-Language: zh-CN,zh;q0.9,en;q0.8 })这里用到了 fake_useragent 库它可以随机生成不同浏览器、不同版本的 UA 字符串。实测下来连续请求几十次后UA 重复率很低降低了被单一 UA 特征锁定的可能性。2.2 解析策略CSS 选择器与数据结构映射BS4 的 find 和 select 方法各有适用场景。单元素精确查找用 find批量列表提取用 select。豆瓣列表页中每个条目都包裹在 li 或 div 容器里这类结构非常适合先用 select 定位容器再逐个提取子元素。建议写一个字段映射表把需要的数据和对应的 CSS 路径对应起来字段选择器备用选择器片名.hd span.titlea span:first-child评分.star .rating_num.rating_nums评价人数.star span:last-child.pl简介.bd p:first-child.inq链接.hd aitempropurl多准备一个备用选择器非常实用。豆瓣改版时某些类名会调整两个选择器可以显著减少维护成本。解析时建议用 get_text(stripTrue) 方法去除多余空白字符否则后期数据清洗时要花不少时间处理换行和空格。2.3 延时与限速策略豆瓣的限速策略不透明但根据我长期抓取的经验两个请求之间的间隔尽量保持 2 到 4 秒。这个频率既能保证任务在合理时间内完成又不会触发封禁。import time import random time.sleep(random.uniform(2, 4))random.uniform 比固定延时更好用。固定间隔的数据模式容易被识别为脚本行为随机间隔更贴近真人操作。另外连续请求出错时要启用指数退避策略连续失败时逐步拉长等待时间。3. 实操过程与核心代码实现3.1 环境准备与依赖安装项目运行环境建议使用 Python 3.9 或更高版本避免老版本在新特性上的兼容问题。最低依赖只有三个库安装命令如下pip install requests beautifulsoup4 fake-useragent如果希望把数据存成更规整的表格可以追加安装 pandas它对 CSV 的读写处理比标准库方便很多。不建议一开始就安装 Scrapy 或 Selenium保持环境轻量专注把核心逻辑跑通。3.2 爬取豆瓣电影 Top250 评分数据先设计一个单页抓取函数输入 URL 输出解析后的电影数据列表。这个函数后续可以反复调用完成多页循环。from bs4 import BeautifulSoup def fetch_movie_page(url): resp session.get(url, timeout10) resp.raise_for_status() resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) items soup.select(.grid_view .item) page_data [] for item in items: title item.select_one(.hd .title).get_text(stripTrue) rating item.select_one(.star .rating_num).get_text(stripTrue) votes item.select_one(.star span:last-child).get_text(stripTrue) quote item.select_one(.bd .inq) quote_text quote.get_text(stripTrue) if quote else page_data.append({ title: title, rating: float(rating), votes: int(votes.replace(人评价, ).strip()), quote: quote_text }) return page_data这里的 URL 使用 Top250 列表页每页 25 条数据翻页参数是 start步长为 25。主循环逻辑如下all_movies [] for page in range(10): start page * 25 url fhttps://movie.douban.com/top250?start{start} all_movies.extend(fetch_movie_page(url)) time.sleep(random.uniform(2, 4)) print(f抓取完成共 {len(all_movies)} 条数据)运行这个脚本大约 30 秒就能抓完 250 条电影数据。注意首次请求时建议手动访问一次豆瓣首页确认网络环境正常避免代理或防火墙导致请求失败。3.3 爬取豆瓣图书评分与短评图书数据的结构和电影类似但列表页的字段略有差异。这里用豆瓣图书 Top 250 作为演示图书详情页中的 ISBN、出版社、作者信息更适合做书籍分析。def fetch_book_page(url): resp session.get(url, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) books [] for item in soup.select(.subject-list .subject-item): title_tag item.select_one(h2 a) title title_tag.get_text(stripTrue).replace( , ) link title_tag.get(href) rating item.select_one(.rating_nums) rating_text rating.get_text(stripTrue) if rating else 无 intro item.select_one(.pub) intro_text intro.get_text(stripTrue) if intro else books.append({ title: title, url: link, rating: rating_text, intro: intro_text }) return books注意书名的提取结果常有空格我在代码里用了 replace 处理。图书页面的 .pub 字段包含作者、出版社、出版年等信息但具体格式不固定后续需要正则或分割处理才能拆成结构化字段。短评的抓取路径在详情页内部URL 模式为https://book.douban.com/subject/{图书ID}/comments/短评列表页同样分页每页 20 条。抓取短评时建议把评分、评论内容和评论时间三个字段一起保存这是后续做情感分析的基础数据。3.4 数据存储与可视化抓取的数据如果直接 print 查看不便于统计分析。建议先用 pandas 加载并输出摘要信息确认数据质量import pandas as pd df pd.DataFrame(all_movies) print(df.info()) print(df.describe())数据完整的情况下可以顺手做一个可视化看看 Top250 评分分布import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] df[rating].hist(bins20) plt.title(豆瓣电影 Top250 评分分布) plt.xlabel(评分) plt.ylabel(数量) plt.show()这里的字体设置很关键macOS 用户需要把 SimHei 换成 Arial Unicode MS否则图表中文会乱码。4. 常见问题与排查技巧实录4.1 请求被重定向到登录页这是最常见的坑。豆瓣对高频请求或异常 UA 会直接 302 到 accounts.douban.com 登录页。排查方法是打印最终响应 URLresp session.get(url, timeout10) print(resp.url)如果 resp.url 里出现了 passport 或 accounts 字样说明被重定向了。解决办法是降低请求频率、检查 UA 是否真实有效。如果账号有需要登录才能看的字段带上 Cookie 即可。4.2 抓取结果为 418 或 403 状态码418 状态码是豆瓣的经典反爬标识代表服务器识别出非人类行为。出现这种情况时需要确认 Cookie 是否过期、请求头是否完整。还有一个很容易被忽略的点某些代理 IP 被豆瓣拉黑后访问任何页面都会返回 418。这时需要更换代理而不是反复重试。403 状态码通常表示请求被服务器拒绝常见原因是 UA 无效或请求频率过快。可以先在浏览器里打开目标页面确认数据存在再用当前请求头测试。4.3 解析结果为空或字段缺失解析结果为空时先打印整个页面文本确认结构是否和你预期一致。豆瓣页面改版不频繁但 CSS 类名偶尔微调类名变化会导致 select 找不到元素静默返回空列表。调试建议分三步走先用浏览器开发者工具检查目标元素的真实类名在 Python 里单独测试 select 语句确认返回节点数量逐个字段打印定位解析失败的环节4.4 数据写入 CSV 后中文乱码这个问题源于编码不一致。写入文件时指定 UTF-8 with BOM 编码Excel 才能正确识别中文df.to_csv(douban_top250.csv, indexFalse, encodingutf-8-sig)如果已经生成乱码文件用文本编辑器打开后另存为 UTF-8 with BOM 格式即可修复。5. 合规提示与延伸建议5.1 爬虫使用的合规红线爬虫技术本身是中性的但使用方式必须符合法律法规和平台规则。豆瓣的 robots.txt 文件明确规定了哪些路径允许爬取部分页面禁止抓取。实际操作中要注意三点只抓取公开数据不碰用户非公开信息请求频率保持克制不干扰目标网站正常运行抓取数据只用于学习和个人研究不进行商业用途或大规模传播我在项目里一直保持 2 秒以上的请求间隔并且设置了异常退出机制连续报错 5 次就自动停止任务避免给目标服务器造成压力。5.2 如何扩展成更大规模的数据采集当前代码只覆盖了静态页面的基础抓取。如果想把项目扩展到整个电影库或图书库核心瓶颈会出现在两个层面一是 IP 池不够大二是增量更新策略缺失。IP 方面可以从免费代理池开始但稳定性和速度都不能保证商用代理则根据请求量计费适合中大型项目。增量更新方面可以定期抓取排行榜提取新出现的电影或书籍 ID只抓详情页到本地数据库从而最小化对目标网站的访问压力。我个人后续的扩展方向是加入数据库存储和定时任务让爬虫每天自动检查榜单更新同时结合 NLP 技术对评论做情感分析。这样整个项目的价值就不仅是数据采集而是建立了可持续更新的个人数据仓库。如果你是从零开始的小白建议按这篇文章的顺序先把 Top250 电影跑通再尝试书籍和评论逐个环节理解后再扩展。爬虫的核心并不是堆代码而是理解 HTTP 交互和数据解析的思路这个思路才是可以复用到任何平台的底层能力。