ARTICLE DETAIL

资讯详情

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

Python爬虫实战:豆瓣影评数据采集与情感分析完整项目

Python爬虫实战:豆瓣影评数据采集与情感分析完整项目 简介面向高校课程设计与毕业设计的Python实战项目基于豆瓣影评的爬取与情感分析完整覆盖数据采集、清洗、模型训练到可视化展示的工程闭环尤其适合计算机、AI、电子信息等专业学生用于作业、竞赛或毕业设计。资源共69个文件以46个Python脚本为主涵盖爬虫采集、数据预处理、训练评估、UI界面与辅助工具等模块另附设计报告docx、Markdown说明文档、环境配置yaml及若干图片样张压缩包仅1.54MB轻量且便于部署适合教学演示。目前已有四十二人学习下载。内容包括可直接运行的源码与测试脚本并配有依赖清单和图文说明其中TextRNN、BERT等文本分类模型可作为二次开发基线结合完整设计报告与清晰目录结构能帮助学习者快速理解项目脉络、复现实验并扩展功能。1. 课程设计里的Python爬虫项目实际在检验什么当一个课程设计题目里同时出现“Python爬虫”“豆瓣影评分析”“爬取项目”和“含全部资料报告”这串关键词时它真正检验的并不是你能否找到一个现成压缩包而是你能不能把一条完整数据链路从头走到尾用requests拿到豆瓣评论页HTML用BeautifulSoup解析出用户、评分和评论内容再用pandas、jieba和matplotlib把文本变成统计图最后写出一份能解释每个选择的报告。这篇文章按这个主线展开覆盖请求参数、反爬边界、解析排错、情感分析和并发取舍过程中会给出可直接运行的代码和参数表。适合正在做课程设计、毕设准备期、或想补全爬虫工程细节的读者。2. 豆瓣影评爬取前的反爬分析与请求参数设计2.1 先拆开豆瓣影评页面的数据来源长评是review短评是comments课程设计里的“影评分析”通常指短评因为短评数量大、字段规整。短评页面URL结构是https://movie.douban.com/subject/{movie_id}/comments?start{offset}limit20statusPsortnew_score有人会从电影首页的“短评”tab点进去那个页面URL也是这个模式但首页HTML里包含大量推荐位和异步片段直接对首页做解析会把少量短评混在一堆无关节点里不好处理。直接请求comments页面HTML结构更稳定字段位置基本固定。参数取值示例作用start0、20、40评论偏移量翻页时每次20limit20每页条数豆瓣服务端固定为20传其他值不生效statusP只看看过这部电影的观众短评sortnew_score、time、useful排序方式最新、按时间、按热度课设一般用sortnew_score因为最新评论连续爬取时重复率低。statusP是为了过滤“想看”用户的评论避免数据里混入没看过电影的人。抓取范围按需求设一部电影取10页200条足够分析评分和情感倾向。2.2 用requests构造请求头先确认返回状态码写后端或脚本的同学容易忽略请求头。豆瓣在服务端会检查User-Agent没有UA的请求很容易返回403甚至有些教室出口IP在短时间连续请求后会触发验证码。先给一个最小可用的requests请求写法import requests session requests.Session() headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://movie.douban.com/, Connection: keep-alive, } url https://movie.douban.com/subject/1292052/comments?start0limit20statusPsortnew_score resp session.get(url, headersheaders, timeout(5, 10)) print(resp.status_code, len(resp.text))这段代码里session为什么不用裸requests.getSession复用TCP连接在翻页循环里能省掉重复握手也让Cookie在多次请求间保持一致。timeout(5,10)元组表示连接超时5秒、读超时10秒比只写一个数字合适。Referer指向豆瓣域名防止被简单防盗链规则拒绝。如果返回418或403先做两步把浏览器里登录豆瓣后的Cookie复制到headers里或者换一个更靠近浏览器的UA。课程设计不建议把Cookie写死在公开代码里可以用环境变量读取。这里有个需要接受的事实豆瓣对高频率请求比较敏感一旦触发验证码请停下请求别让课设代码变成对站点的压力测试。提示把请求间隔设置为1.5到3秒单次请求失败后至少休息5秒再重试这在课设答辩中也是“考虑服务端负载”的加分项。2.3 限速与重试把爬虫写成“礼貌”的客户端很多课程设计代码里只有requests.get失败就崩溃。真实环境里网络抖动、服务端限流都很常见所以要给请求加超时、重试和退避。requests库本身支持挂载HTTPAdapter来设置重试策略from requests.adapters import HTTPAdapter import time session.mount(https://, HTTPAdapter(max_retries3)) for offset in range(0, 60, 20): try: resp session.get( fhttps://movie.douban.com/subject/1292052/comments?start{offset}limit20statusPsortnew_score, headersheaders, timeout(5, 10) ) if resp.status_code 200: print(offset, ok) else: print(offset, resp.status_code) except requests.RequestException as exc: print(offset, error, exc) time.sleep(2.0)HTTPAdapter(max_retries3)处理的是连接级别的重试不是请求被拒后的业务重试status_code不是200的情况需要自己判断。time.sleep(2.0)是页面间隔2秒在课设场景下是可行的。如果是爬多部电影总请求量超过50页建议把间隔提高到2.5到3秒。还有一个细节sleep放的位置。有人写在打印后有人写在循环体开头。正确的位置是在一次请求结束后、下一次请求开始前这样才能保证两次请求之间的距离稳定。3. 写好核心爬虫从豆瓣评论页到结构化影评数据3.1 定位每一条评论的DOM结构拿到HTML后先别急着写选择器先打印一段看看结构。用BeautifulSoup解析短评页时每条评论对应一个div.comment节点。常见字段和选择器如下字段所在节点类型说明user.comment-info astr用户昵称rating.comment-info [title]str中文评分标签如“力荐”time.comment-info .comment-timestr评论时间也可用datetime解析votes.votesstr“有用”数可能带逗号content.shortstr评论文本这里要注意的是rating字段不是数字而是“力荐/推荐/还行/较差/很差”这五个中文词后续分析要么转成1到5分要么直接做占比统计。下面这段代码完成单页解析from bs4 import BeautifulSoup import pandas as pd def parse_comments(html): soup BeautifulSoup(html, html.parser) rows [] for item in soup.select(div.comment): info item.select_one(.comment-info) user info.select_one(a).text.strip() if info and info.select_one(a) else rating info.select_one([title])[title] if info and info.select_one([title]) else votes item.select_one(.votes).text.strip().replace(,, ).replace(有用, ) content item.select_one(.short).text.strip() if item.select_one(.short) else rows.append({user: user, rating: rating, votes: votes, comment: content}) return rows选择器写法的几个关键判断.select_one返回None或不存在的节点时直接用if判断可以避免解析中断.votes节点里的文本可能是“128有用”或“0有用”用replace把非数字内容去掉但保留空字符串也行后续pandas处理。3.2 翻页循环与数据落地单页解析函数写好后再做翻页。这里用前面构造的session去取10页数据每页20条累计200条all_rows [] base_url https://movie.douban.com/subject/1292052/comments for start in range(0, 200, 20): params {start: start, limit: 20, status: P, sort: new_score} resp session.get(base_url, paramsparams, headersheaders, timeout(5, 10)) if resp.status_code ! 200: print(fail, start, resp.status_code) continue rows parse_comments(resp.text) all_rows.extend(rows) time.sleep(2.0) df pd.DataFrame(all_rows) df.to_csv(douban_comments.csv, indexFalse, encodingutf-8-sig)这次用params字典而不是拼接字符串requests会自动做URL编码也方便日后改参数。base_url里不写?避免和params拼接时出现两个问号。存储编码选utf-8-sig否则用Excel直接打开CSV会出现中文乱码如果你后续都用pandas读utf-8也够。翻页范围要注意豆瓣对未登录用户最多只能看到约200条短评start超过某个阈值会一直返回最后一批数据甚至返回空页面。所以课程设计里200条是个安全的采样范围报告里写“采集样本200条”也说得通不用强求全量。3.3 空列表与字段错位的排查思路课设里最常见的两个问题parse_comments返回空列表或字段错位。空列表先检查resp.status_code和resp.text长度如果status_code是200但长度很短大概率页面里没有div.comment可能是被跳转到了验证码页面。这时打印soup.select(.comment)看看结果别急着调选择器。字段错位通常来自select_one返回的节点不是预期节点。例如.comment-info [title]在有些页面会选到a标签上的hint属性而不是评分标签所以评分为空或错位。建议先用print(item.prettify())看一条评论真实结构再写选择器。另一个检查方法是解析完成后统计rating的取值范围如果出现“力荐/推荐”以外的值说明选择器选错位置了。4. 影评数据清洗与情感分析让爬取结果变成报告素材4.1 用pandas清洗评分和时间字段CSV存下来后进入分析阶段。先读回数据把评分字段从中文映射成数字再检查缺失值和重复值import pandas as pd df pd.read_csv(douban_comments.csv, encodingutf-8-sig) print(df.shape) print(df[rating].value_counts()) rating_map {力荐: 5, 推荐: 4, 还行: 3, 较差: 2, 很差: 1} df[score] df[rating].map(rating_map) df df.dropna(subset[comment]) df df.drop_duplicates(subset[comment]) df[comment_len] df[comment].str.len()dropna(subset[comment])处理的是评论内容为空的情况drop_duplicates按评论文本去重因为翻页可能出现重复评论。这里不直接删除评分缺失的行而是先去重再决定怎么处理。评分映射表在报告里可以做成表格展示数据字典这也是格式化的习惯。日期字段建议直接分析不要急着转时间戳。如果要看评论时间趋势可以用pd.to_datetime(df[time])注意豆瓣返回的时间有些是相对时间比如“3天前”课设中遇到这种值可以在清洗时统一丢弃或做特殊映射。4.2 使用jieba分词并统计高频词文本分析最直接的方法是分词后看词频。用jieba的lcut按文本切词再用停用词表过滤掉“的、了、是、我”这类没有情绪的词。停用词表自己准备一个stopwords.txt每行一个词。import jieba from collections import Counter stopwords set() with open(stopwords.txt, encodingutf-8) as f: for line in f: word line.strip() if word: stopwords.add(word) words [] for comment in df[comment]: for w in jieba.lcut(comment): if w not in stopwords and len(w.strip()) 1 and w.strip().isalnum(): words.append(w) counter Counter(words) print(counter.most_common(20))len(w.strip()) 1过滤单字符isalnum过滤标点和特殊符号。注意jieba默认词典对电影名、演员名人名的分词不一定准确比如“肖申克的救赎”可能被切成“肖申克”和“救赎”课设报告里说明这个局限即可不要投入过多时间做自定义词典。如果词频里出现明显无意义的词就把它们加进停用词表再跑一遍。4.3 情感倾向计算与可视化情感分析在课设阶段用情感词典就够。准备一个正负面词集合给每个评论打分类型示例词正面词好看、喜欢、感动、经典、精彩、致敬、震撼、推荐负面词烂、难看、失望、无聊、差评、狗血、低分、浪费时间positive_words {好看, 喜欢, 感动, 经典, 精彩, 致敬, 震撼, 推荐} negative_words {烂, 难看, 失望, 无聊, 差评, 狗血, 低分, 浪费时间} def sentiment_score(comment): pos sum(1 for w in jieba.lcut(comment) if w in positive_words) neg sum(1 for w in jieba.lcut(comment) if w in negative_words) return pos - neg df[sentiment] df[comment].apply(sentiment_score) df[sentiment_type] pd.cut(df[sentiment], bins[-float(inf), 0, float(inf)], labels[负面, 正面])pd.cut在这里把得分小于等于0的归为负面大于0的归为正面。为什么要允许得分为0算负面因为0分评论往往吐槽或中性表达课设中你可以把边界说明白。更严谨的做法是设一个阈值比如正向得分大于等于2才算正面这样报告里会更有说服力。可视化有两个常用方向评分分布直方图和情感占比饼图。用matplotlib画评分分布import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False df[score].plot.hist(bins5, edgecolorwhite) plt.xlabel(评分1-5) plt.ylabel(评论数量) plt.savefig(score_hist.png, dpi150)SimHei字体在Windows环境可用Linux服务器上没有黑体就需要换成WenQuanYi Micro Hei否则中文会显示成方块。评分直方图是报告里最容易解释的一张图它直接对应豆瓣的星级分布也方便和豆瓣页面上显示的占比做对比验证。5. 并发提速与异常兜底从课设代码到能跑的爬虫5.1 评估请求量先决定要不要并发“并发设计到底哪个好”是爬虫社区常被问的问题但在课程设计里答案取决于你的样本量。一部电影爬10页单线程加2秒间隔只需要20多秒根本不用并发。如果换成爬10部电影每部200条总计约100个请求单线程需要200秒以上这时再考虑并发。在动手写多线程前先算两笔账总请求数约等于样本数/20总耗时下限 请求数 * 平均响应时间 (请求数-1) * 间隔。如果这个耗时超过3分钟才值得引入并发。课设评分看的是稳定性和结果不是“用了ThreadPoolExecutor就加分”。5.2 线程数、间隔与Session的组合用concurrent.futures写可控并发核心是限制线程数而不是无脑开。我一般会把请求封装成独立函数每个线程使用自己的Session避免共享会话在高并发下资源竞争from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_comments(start): with requests.Session() as sess: params {start: start, limit: 20, status: P, sort: new_score} resp sess.get(base_url, paramsparams, headersheaders, timeout(5, 10)) if resp.status_code ! 200: raise ValueError(fstatus {resp.status_code}) return resp.text offsets list(range(0, 200, 20)) with ThreadPoolExecutor(max_workers3) as pool: future_map {pool.submit(fetch_comments, offset): offset for offset in offsets} for future in as_completed(future_map): offset future_map[future] try: html future.result() rows parse_comments(html) all_rows.extend(rows) except Exception as exc: print(offset, failed, exc)这里with requests.Session()作为上下文每个线程一个Session关闭连接由上下文管理。max_workers3是经过权衡的2到3个线程在豆瓣场景已经能明显提速再多容易触发风控。3个线程配合2秒间隔100个请求大约70秒能结束单线程要200多秒。配置项建议值说明max_workers2-3超过5个并发在豆瓣上很容易被限流请求间隔1.5-3秒间隔并不只挂在worker内也要考虑队列整体的请求密集度失败重试最多2次连续失败3次就停止整个任务超时(5,10)连接5秒读10秒5.3 失败记录与续爬异常处理要能定位并发时as_completed会按完成顺序返回某个页面失败时不能只打印一个异常要把offset记录下来后面根据offset续爬。简单做法是维护failed_offsets列表并在结束后再跑一轮失败重试failed_offsets [] for offset in offsets: try: html fetch_comments(offset) all_rows.extend(parse_comments(html)) except Exception as exc: failed_offsets.append(offset) with open(error.log, a, encodingutf-8) as f: f.write(f{offset} {exc}\n) time.sleep(2) for offset in failed_offsets: try: html fetch_comments(offset) all_rows.extend(parse_comments(html)) time.sleep(3) except Exception as exc: print(retry fail, offset, exc)注意all_rows在并发环境下需要加锁但上面这个重试版本是串行的不需要处理线程安全。如果非要在并发版本里共享列表用threading.Lock或改用queue.Queue否则会出现丢数据。这个细节在答辩时经常被问到。另一个容易被忽视的点失败日志要写offset和异常类型不要只写“error”。有了日志报告里可以放一张采集统计表写明多少页请求成功、多少页重试成功这比“爬虫非常稳定”有说服力。6. 报告与资料整理验证爬虫思路和结果一致性的技巧6.1 用数据量自检核对采集完整性拿到“含全部资料报告”的压缩包后第一步先对照数据量和页面实际条数。例如从豆瓣页面上可看到当前短评总数如果只有200条采样那么自检逻辑是start最多取到180最终CSV行数应等于实际解析成功条数如果CSV行数小于去重前记录数说明解析函数漏掉了某些节点。快速核对代码import pandas as pd df pd.read_csv(douban_comments.csv, encodingutf-8-sig) print(总行数:, len(df)) print(去重后:, df[comment].nunique()) print(重复率:, (len(df) - df[comment].nunique()) / len(df))然后把这三行数据写进报告“数据采集”章节比任何描述都直接。6.2 用图表反向检查情感分析结果如果报告里写了“负面评论占比”就要回看具体评论。抽取出情感得分为负的10条人工读一下判断是否符合直觉。这是一个验证技巧把情感分析结果和评分分布相互对比比如说电影评分4.5分但情感分析负面评论占30%说明情感词典需要扩充。在报告里放这个对比能体现分析不是“跑完就交”。6.3 把环境依赖整理成可直接复用的文件不管收到的是zip源码还是自己写的代码都补上一个requirements.txt内容至少包含requests、beautifulsoup4、pandas、jieba、matplotlib。课程设计报告里附上“运行环境”小表把Python版本、主要库版本和用途写清楚这样答辩时别人能快速复现。最后一页还可以加一个“复现命令”pip install -r requirements.txt python crawler.py python analysis.py用一行命令表达出整条链路。本文还有配套的精品资源点击获取
返回列表