ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

豆瓣短评影评爬虫实战:从反爬机制到完整代码解析

豆瓣短评影评爬虫实战:从反爬机制到完整代码解析 简介一份面向豆瓣影评与短评采集的Python爬虫源代码特别适合python爬虫入门者、数据分析爱好者及舆情观察人员使用可快速获取电影评论数据用于后续文本分析与市场调研。压缩包共2个py文件整体大小仅5KB分别对应影评爬虫和短评爬虫代码量小而完整便于学习爬虫基本流程并在此基础上扩展。目前已有115人学习下载。影评爬虫采集字段包括影评id、用户、ip地址、时间、推荐度、星级、标题、影评正文、图片、赞同、反对、收藏、转发、回应短评爬虫则覆盖序号、用户、地点、推荐度、点赞数、时间、短评内容和用户主页。代码集合了requests请求、页面解析、字段提取与数据保存等常用环节注释与结构清晰适合作为豆瓣评论类爬虫的入门参考和二次开发模板。 豆瓣短评爬虫搞不定我理解你的痛苦因为别人的代码大概率不能直接用。从2024年到现在我前后写了三四版豆瓣短评和影评的爬虫源代码被反爬机制虐过无数遍最终这套代码才算真正跑顺。这篇博文会把完整思路、核心源码、坑点全部分享给你不搞虚的直接能抄。这篇文章适合谁看写论文要凑样本数据的、做影评情感分析的、想学Python爬虫但被各种失效代码坑过的初学者还有一些想了解豆瓣反爬机制的朋友。我会从零开始拆解哪怕你只学过Python基础语法也能跟着把代码跑起来。1. 项目到底要解决什么问题思路先理清楚1.1 豆瓣短评和影评的区别别混为一谈很多人一开始就搞混了这两类数据。短评是豆瓣电影页面下方的那段一句话评论通常在全部短评入口里它的特点是数量大、文本短、密度高非常适合做情感分析和舆情统计。而影评则是用户写的长文章在影评入口里一篇通常几百到几千字内容深度更高适合做文本挖掘和观点提取。两者的抓取难度完全不同短评必须要处理分页、Cookie验证和频率限制而影评相对宽松但只要页面结构一改你的解析代码就废了。我在代码里把两者分开写了两个函数这样任何一个挂了不会影响另一个维护起来也舒服。1.2 为什么选 requests BeautifulSoup而不是 Scrapy我承认Scrapy是爬虫框架里的大杀器性能强、支持分布式但如果你只是需要某个电影的短评和影评把它当成一次性工具来写用Scrapy反而是杀鸡用牛刀。requests库发送HTTP请求BeautifulSoup解析HTML这两个组合足以应对豆瓣90%的抓取需求代码轻量、调试直观对新手也友好。有人问过我会不会用Selenium或者Playwright这种浏览器自动化方案我明确告诉你不需要除非你要对付极复杂的JS渲染。豆瓣短评和影评的数据在服务端直接渲染在HTML源码里你用查看网页源代码就能搜到评论内容完全用不到无头浏览器。无头浏览器又慢又容易被识别属于性价比最低的选择。1.3 这个爬虫能做什么数据存哪里这套代码最终能抓取到三类数据短评的评论内容、评分星级、点赞数以及影评的标题、作者、正文、发布时间、有用数。数据量上单个电影的短评最多可以抓上千条影评则能抓全部公开页面。数据存储我用的是pandas直接输出成Excel文件的思路。理由很简单Excel方便查看方便筛选也方便后续丢进SPSS、R或者PyTorch做分析。你要是喜欢数据库代码里稍微改一行就能存进SQLite或者MySQL我后面会提到。2. 反爬机制拆解豆瓣到底在防什么2.1 前三道门槛UA、Cookie、请求频率豆瓣的反爬策略没有想象中恐怖但也不会让你舒舒服服地拿数据。第一道门槛是User-Agent检测如果你用默认的Python-requests的UA请求一到服务器基本就给你标记成爬虫了。我实测过这种请求大概率拿回来的是403或者418页面。第二道门槛是Cookie。豆瓣的未登录Cookie可以访问公开数据但如果你不带Cookie同一个IP连续请求十几页很快会触发验证码。我的做法是先从浏览器里复制一份完整的Cookie字符串放到代码头部这样相当于模拟了一个真实浏览环境。第三道门槛是频率。豆瓣对同一IP的请求频率非常敏感实测连续短评翻页间隔小于1秒大约到第20页就触发检测到异常请求。我只在一开始的时候保持很保守的节奏每次请求之间随机休眠2到4秒短评最多抓十几页也不会出大问题。2.2 验证码机制和封禁策略心里要有数豆瓣的验证码通常不是一开始就弹出而是频率异常后才出现。早期版本会返回一个包含验证码图片的HTML页面现在更多是直接返回一个js跳转页面需要验证后才能继续。我在代码里加入了一个返回状态判断如果检测到返回内容长度异常短或者HTML里出现了验证相关关键字就自动暂停一段时间。关于封禁我遇到过的是临时封禁IP通常几小时后自动解除。IP被封和账号被限制是两回事——没登录抓取触发封禁主要封IP不会封你的账号。真被封了换个网络比如手机热点就能恢复不需要太焦虑。2.3 君子协议和合规意识每次聊到爬虫我都得提醒一句抓下来的数据用于个人学习和研究没有任何问题但如果用于商业用途一定要尊重平台的用户协议和robots协议不要大规模高频率地抓取。豆瓣的robots协议里明确禁止了部分路径的爬虫访问虽然技术上你可以绕过但道义和法律层面都站不住脚。我代码里已经设置了限速参数你可以直接拿去跑别想着加速细水长流才是王道。3. 核心源码实现与逐步拆解3.1 请求模块随机User-Agent和Cookie设置我单独写了一个请求头模块不写在主逻辑里。原因是豆瓣检测请求头非常细腻如果请求头格式不完整很容易触发风控。下面是我常用的请求头配置import random import requests # User-Agent 池每次请求随机取一个 USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36, Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0, Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1, ] def get_headers(referer_url): headers { User-Agent: random.choice(USER_AGENTS), Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.5, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, } if referer_url: headers[Referer] referer_url return headers你注意一下Accept字段不要乱删豆瓣会校验部分请求头的完整性。Cookie我建议直接从浏览器复制登录状态下的Cookie并不会要求你输入账号密码只要你在浏览器里访问过豆瓣F12打开开发者工具在网络标签里找到任意请求把请求头里的Cookie整段复制过来放在constant变量里就行COOKIE 你的豆瓣Cookie不带单引号直接放这里3.2 核心会话对象为什么用requests.Session不要每次请求都新建一个requests.get那样相当于每次都是陌生访客更容易触发风控。正确做法是创建一个session对象把Cookie一次性加进去后续所有请求都走这个session服务器会认为你是同一个稳定的客户端session requests.Session() session.headers.update(get_headers()) session.cookies.update({Cookie: COOKIE}) # 这个写法其实不生效见下方提示老实说上面第三行有个小坑requests的cookies.update用法不完全等同于设置Cookie头更稳妥的方案是直接在session.headers里设置Cookie键headers get_headers() headers[Cookie] COOKIE session requests.Session() session.headers.update(headers)这样整个会话期间所有请求都会自动带上完整的请求头包括Cookie不需要每个请求手动传headers。3.3 短评抓取解析、翻页、截断判断短评的URL结构非常规律格式是https://movie.douban.com/subject/{电影ID}/comments?start{偏移量}limit20statusP。其中电影ID就是豆瓣详情页URL里的那一串数字你在豆瓣App或者网页版找到对应电影就能看到。偏移量start从0开始每翻一页加20。import time from bs4 import BeautifulSoup def fetch_short_comments(movie_id, max_pages10): comments_data [] base_url https://movie.douban.com/subject/{}/comments for page in range(max_pages): start page * 20 url base_url.format(movie_id) f?start{start}limit20statusP try: resp session.get(url, timeout10) if resp.status_code ! 200: print(f第{page1}页状态码异常: {resp.status_code}) break soup BeautifulSoup(resp.text, lxml) comment_items soup.select(div.comment-item) if not comment_items: print(f第{page1}页没有解析到评论可能页面结构变化或已被限制) break for item in comment_items: # 评论内容注意有些评论有折叠需要特殊处理 content_tag item.select_one(span.short) if not content_tag: continue content content_tag.get_text(stripTrue) # 星级评分形如 classallstar50 表示5星 star_tag item.select_one(span[class^allstar]) star_rating None if star_tag: star_class star_tag.get(class, [])[0] star_rating int(star_class.replace(allstar, )) // 10 # 点赞数 vote_tag item.select_one(span.votes.vote-count) vote_count vote_tag.get_text(stripTrue) if vote_tag else 0 # 评论时间 time_tag item.select_one(span.comment-time) comment_time time_tag.get(title, ) if time_tag else comments_data.append({ content: content, star_rating: star_rating, vote_count: vote_count, comment_time: comment_time, }) print(f第{page1}页抓取完成累计{len(comments_data)}条) except requests.exceptions.RequestException as e: print(f请求失败: {e}) break # 随机休眠模拟人工浏览节奏 time.sleep(random.uniform(2, 4)) return comments_data这里有一个我自己踩过的坑短评的span.short标签在评论被折叠时返回的文本不完整只显示前几个字后面带省略号。你需要在解析前判断一下该评论是否有展开按钮如果有就需要去请求对应评论的详情接口。但为了简单起见大部分评论不会被折叠只有超长评论才会你可以默认只抓取折叠前的内容对情感分析影响不大。3.4 影评抓取列表页和正文页分开处理影评的入口是https://movie.douban.com/subject/{电影ID}/reviews列表页也是分页的但参数略有不同。每页有10篇影评每篇影评的数据包括标题、作者、摘要、有用数。如果你只需要标题和摘要列表页就够了但如果要全文必须进入每篇影评的详情页。def fetch_reviews(movie_id, max_pages3): reviews_data [] base_url https://movie.douban.com/subject/{}/reviews for page in range(max_pages): start page * 20 url base_url.format(movie_id) f?start{start} try: resp session.get(url, timeout10) if resp.status_code ! 200: break soup BeautifulSoup(resp.text, lxml) review_items soup.select(div.review-item) if not review_items: # 豆瓣影评还有一种布局用main-content-wrapper下的article review_items soup.select(article.review-item) if not review_items: print(f影评第{page1}页未解析到内容) break for item in review_items: title_tag item.select_one(a.title) if not title_tag: continue title title_tag.get_text(stripTrue) review_url title_tag.get(href, ) author_tag item.select_one(a.name) author author_tag.get_text(stripTrue) if author_tag else useful_tag item.select_one(span.vote-count) useful_count useful_tag.get_text(stripTrue) if useful_tag else 0 # 如果只需要列表信息到这里就够 # 如果需要全文继续请求review_url detail_content fetch_review_detail(review_url) if review_url else reviews_data.append({ title: title, author: author, useful_count: useful_count, content: detail_content, url: review_url, }) except requests.exceptions.RequestException as e: print(f影评列表请求失败: {e}) break time.sleep(random.uniform(3, 5)) return reviews_data影评详情页的正文在article标签里通常是div#link-report下的p标签拼接成完整段落。这里需要注意豆瓣影评支持作者... 来源...这个信息结构但解析的时候只需要取正文即可def fetch_review_detail(review_url): if not review_url: return try: resp session.get(review_url, timeout10) if resp.status_code ! 200: return f[状态码{resp.status_code}] soup BeautifulSoup(resp.text, lxml) content_div soup.select_one(div#link-report) if not content_div: return [未找到正文] # 正文内容在p标签里有些在span里 paragraphs content_div.select(p) text .join(p.get_text(stripTrue) for p in paragraphs) return text except requests.exceptions.RequestException: return [请求失败]3.5 数据存储pandas直接输出Excel抓完数据最后一定要落到本地我习惯用pandas的DataFrame一行代码导出Excelimport pandas as pd def save_to_excel(data_list, filename): if not data_list: print(没有数据跳过保存) return df pd.DataFrame(data_list) df.to_excel(filename, indexFalse, engineopenpyxl) print(f数据已保存至 {filename}共{len(df)}条)注意Excel导出需要安装openpyxl库不然会报错。你也可以把to_excel改成to_csv这样不需要额外依赖但中文在Excel里打开可能会有乱码需要加encodingutf-8-sig参数这个细节很多人不知道df.to_csv(filename, indexFalse, encodingutf-8-sig)3.6 主流程串联从参数输入到结果输出最后把所有模块组装起来入口函数接收电影ID和页数参数依次抓短评和影评def main(movie_id, short_pages10, review_pages3): print(f开始抓取电影 {movie_id} 的短评数据...) comments fetch_short_comments(movie_id, max_pagesshort_pages) save_to_excel(comments, fmovie_{movie_id}_short_comments.xlsx) print(f开始抓取电影 {movie_id} 的影评数据...) reviews fetch_reviews(movie_id, max_pagesreview_pages) save_to_excel(reviews, fmovie_{movie_id}_reviews.xlsx) if __name__ __main__: # 以《流浪地球2》为例ID可以从豆瓣详情页URL获取 main(26794435, short_pages8, review_pages2)跑这个代码时不要用IDE的运行按钮一次性跑完建议先在终端里跑因为终端能实时看到print输出方便你判断哪一页出了问题、是否需要暂停调整。4. 实际运行中的问题与排查实录4.1 状态码418这是最常见的第一关我第一次写豆瓣爬虫时印象特别深所有请求返回都是418页面信息完全拿不到。当时还以为是代码写错了后来才发现是请求头问题。418在豆瓣这里意味着服务器识别出你是非浏览器客户端通常和User-Agent直接相关。排查方法很简单先用浏览器的无痕模式访问一次目标页面复制完整请求头把UA、Accept、Accept-Language这些字段都加到代码里再测试。如果还是418那就检查Cookie有没有正确设置。这里有个小技巧——先不带Cookie测试拿到418后再带Cookie测试可以快速定位是UA问题还是Cookie问题。4.2 页面能访问但解析出来是空的这个坑更隐蔽。有一次我写好的代码突然解析不出评论了第一反应是豆瓣改版了但F12打开页面一看评论还在只是class名变了。我那次遇到的变更情况是.comment-item被改成了.reply-item后来还遇到过短评里有广告穿插导致部分item没有short这个span。推荐一个排查思路把抓到的HTML存成本地文件用文本编辑器搜索评论关键词看看评论是否真的存在于源码里以及包着它的标签是什么。这个思路能解决80%的解析类问题with open(debug_page.html, w, encodingutf-8) as f: f.write(resp.text)4.3 频繁抓取后出现检测到异常请求这个提示一出现说明你的IP已经被临时限流了。处理办法不是硬刚而是停手。我实测过最快恢复时间是15分钟左右最长几个小时。在代码里遇到这个提示就立刻break退出循环不要继续请求因为继续请求只会加重限制。防止被限流的核心是请求间隔。我见过有人把间隔设成0.1秒跑数据说实话那种方式撑不了几分钟。短评接口我建议至少2秒以上影评正文页因为内容重、抓取频率高间隔至少3到5秒。很多初学者会觉得慢但爬虫的本质是细水长流宁可多花几分钟跑完也不要被封。4.4 短评翻页到一半数据开始重复这个问题我研究过是start参数和limit参数的问题。豆瓣的短评接口limit参数默认20不加limit时默认也是20但如果你手动设置了limit50或者更大返回的数据页数计算就乱了部分评论会错位。最稳妥的做法是固定limit20start按20递增。另外豆瓣会过滤部分星号评论内容如果你发现某页评论数少于20那说明被过滤了不是代码bug不用纠结。4.5 常见问题速查表问题现象可能原因解决方案所有请求返回418User-Agent被识别更换为浏览器UA补齐Accept等请求头短评页面跳转到登录页缺失Cookie或Cookie过期更新浏览器Cookie解析结果为空页面结构变更保存HTML源码到本地检查实际标签结构抓取十几页后出现验证码请求频率过快增加休眠间隔暂停几分钟再继续短评内容被截断长评论折叠额外请求评论详情接口数据重复start参数计算错误固定limit20按20递增start输出Excel中文乱码编码问题使用utf-8-sig编码或openpyxl引擎5. 代码优化与后续扩展思路目前这套代码已经能稳定运行但如果你想把它变成一个更完整的项目有几个方向可以扩展。第一个方向是接入IP代理池通过代理切换可以突破IP频率限制但不要贪多代理质量参差不齐免费代理反而更容易被识别出来我还遇到过代理服务器本身就是爬虫陷阱的情况。第二个方向是增加断点续爬能力。把已经抓取的评论内容存到一个Set里每次请求前判断是否重复这样即使中途挂了重新运行也能从断点继续不需要从头再来。第三个方向是把它改成一个命令行工具支持从txt文件里批量读取电影ID一键抓取多部电影的数据。这个扩展也简单for循环包一下就行我代码里已经把功能模块独立出来了直接复用就好。第四个方向更进阶一点就是利用scrapyd或者crontab实现定时定时抓取。豆瓣的热度评论每天都会变化你可以每天固定时间抓一次积累成时间序列数据用来分析电影口碑的时间变化趋势。这个玩法适合做研究也很有意思。我在实际使用中最常做的是把抓下来的短评数据丢进一个简单的情感分析模型用SnowNLP或者BERT计算情感得分然后和电影的票房数据做关联分析。虽然是业余玩法但分析出来的结果经常会给人惊喜——比如评分高的电影不一定口碑好但情感倾向积极的电影通常票房表现不会差。最后再分享一个小技巧也是我踩过很多次坑总结出来的爬虫代码一定要写好异常捕获和日志输出。很多初学者觉得log是多余的其实当你的爬虫跑了半个小时突然挂掉的时候一份完整的日志就能帮你快速定位问题出在哪个页面、哪个环节、什么报错。我通常在每次请求和每次解析的地方都加上print跑完以后翻日志一眼就能看出哪里断了。有需要的话你可以加一个logging模块把日志同时输出到文件和控制台这样调试效率会高很多。豆瓣的爬虫不难难的是耐心和细节。这套代码你能完整跑通基本上同类网站的短评和影评抓取就都拿下了。快去试试吧。本文还有配套的精品资源点击获取
返回列表