简介一份面向本科毕业设计及课程报告的爬虫项目论文以豆瓣影评分析系统为具体案例围绕网络爬虫基本原理、网页请求与解析、公开接口调用、数据库存储、文本清洗、情感分析和可视化展示等完整技术链路展开同时说明反爬限制应对策略适合计算机科学与技术、数据分析等专业的本科生参考。整个资源仅含一个docx文档大小约32KB内容为论文全文按章编排包含绪论、Python爬虫技术基础、豆瓣影评数据获取、数据分析与可视化、系统设计与实现、总结与展望六个部分目录清晰便于查阅。已有1246人浏览学习说明该选题具有一定的参考热度。读者可从中获得一份完整的毕业论文文稿既能借鉴绪论中研究背景、目的意义与国内外现状的撰写方式也能参考爬虫框架选型、数据抓取与清洗流程、系统需求分析、架构设计及测试等核心章节的内容组织可帮助快速理清毕业设计写作脉络。1. 基于 Python 爬虫的豆瓣影评分析系统一份值得照着复现的毕业设计做爬虫的人心里都清楚豆瓣是国内反爬强度最有代表性的网站之一请求头校验严格、IP 封禁频繁、登录态有效期短很多人第一次爬豆瓣影评都是被 418、403 劝退的。这份《基于 Python 爬虫对豆瓣影评分析系统的设计与实现》本科毕业论文价值不在于它有多前沿而在于它把「爬虫获取 → 数据清洗 → 情感分析 → 可视化展示」这一整套流程完整地走通了一遍从绪论到系统测试共六章技术栈全用 Python 生态requests 抓页面、BeautifulSoup 解析、pandas 清洗、nltk/SnowNLP 做情感判断、matplotlib 出图最后落在 MySQL 存储和结果展示上。如果你是正在做爬虫类课程设计或毕设的本科生这份资源能帮你省掉大量查资料和搭框架的时间如果你是刚入门的爬虫开发者照着它的模块拆分方式做一遍也能把「爬虫到底怎么落地成一个系统」这件事看清楚。它不是能直接跑的成品代码而是一份可以边读边复现的设计蓝图坑在哪儿、参数怎么调下面我从头拆给你看。2. 爬虫原理与库选型先想清楚用 requests 还是 Scrapy2.1 爬虫的基本流程与请求头伪装爬虫的技术本质是模拟浏览器行为程序代替人去访问 URL、接收响应、解析 HTML最后把页面里的结构化数据提取出来。整个流程在论文第二章写得很清楚五个阶段——目标确定、发送请求、解析页面、数据处理、存储数据——这也是大多数爬虫脚本的共同骨架。import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://movie.douban.com/, Accept-Language: zh-CN,zh;q0.9, } url https://movie.douban.com/subject/1292052/comments resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) comments soup.select(span.short) for c in comments: print(c.get_text().strip())这段代码是爬虫主流程的最小可用版本。headers里的三个字段是关键User-Agent必须用真实浏览器的 UA豆瓣会检查这个字段来判断访问是否来自合法客户端Referer也不能省略豆瓣对直接访问评论页的请求会倾向于拒绝Accept-Language用中文值是为了避免服务器返回繁体或英文默认页。BeautifulSoup的select方法支持 CSS 选择器span.short是豆瓣短评列表常见的评论容器标签实际使用时需要打开浏览器开发者工具确认当前页面结构。2.2 为什么这份论文选 requests BeautifulSoup 而不是 Scrapy论文第二章提到了 Scrapy但实际的数据获取模块实现用的是 requests 库。这个选择很聪明原因有三点第一课程的周期限制了框架学习成本Scrapy 的 Spiders、Middlewares、Pipelines 概念体系需要额外两周左右的时间消化而 requests 加 BeautifulSoup 半天就能写出能跑的脚本第二豆瓣影评列表页面的结构相对规整不同电影的热门短评都在同一个div容器下用 CSS 选择器就能定位不需要 Scrapy 那种面对复杂站点设计的分布式调度能力第三requests 的Session对象可以同时管理 Cookie 和请求头对于需要模拟登录或者维持访问会话的场景代码结构更直观。用 requests 的代价是并发效率低。处理这个问题论文的做法是用多线程调度爬取任务而不是引入 Scrapy 的异步机制。常见的写法是concurrent.futures.ThreadPoolExecutor控制线程数在 4 到 8 个之间既能提高抓取速度又不会因为请求频率过高触发封禁。线程数再往上加就属于高风险操作了后面避坑章节会细说。2.3 数据清洗正则去噪声与文本预处理抓下来的影评数据不能直接用。豆瓣页面的评论内容里有大量换行符、HTML 实体、特殊符号还有用户回复时嵌套的引用文本这些都会干扰后续的情感分析。论文在数据预处理环节定义了三个清洗步骤去除噪声字符、过滤非中文内容、统一评分字段格式。import re def clean_comment(text): # 去掉 HTML 标签和实体 text re.sub(r[^], , text) text re.sub(rnbsp;|amp;|lt;|gt;, , text) # 只保留中文字符、英文字母和常用标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 合并多个连续空格和换行 text re.sub(r\s, , text).strip() return text raw 这部电影太棒了br强烈推荐nbsp;nbsp; print(clean_comment(raw)) # 输出: 这部电影太棒了强烈推荐这个clean_comment函数看起来简单但正则的写法有讲究第一行re.sub(r[^], , text)用来去 HTML 标签注意这里用[^]而不是.*是因为.*会贪婪匹配到不该删的尖括号内容第二行处理常见 HTML 实体nbsp;和amp;是豆瓣页面上最常出现的两种第三行的字符范围是从 Unicode 中过滤掉表情符号、特殊符号和乱码字节只保留中文、英文、数字和中文标点。注意在 Python 正则里\u4e00-\u9fa5表示中文字符区间这是文本清洗约定俗成的写法。3. 数据获取与存储从豆瓣页面解析到 MySQL 入库3.1 页面解析策略直接抓 HTML 而不是依赖 API论文的第三章标题叫「豆瓣影评 API 调用」但实际实现里豆瓣官方开放 API 早已不对个人开发者提供匿名访问需要申请 API key 且审核严格。大多数爬虫项目采用的做法是直接解析 HTML 页面论文最终落地的也是这条路线。爬虫代码需要处理的页面类型主要有三种电影详情页获取评分和基本信息短评列表页获取用户评论和评分星级长评页获取完整影评内容。def parse_comment_page(html): soup BeautifulSoup(html, html.parser) data [] items soup.select(div.comment-item) for item in items: user item.select_one(a) vote item.select_one(span.votes) rating item.select_one(span.rating) comment item.select_one(span.short) data.append({ user: user.get_text().strip() if user else , votes: int(vote.get_text().strip()) if vote else 0, rating: rating.get(class, [])[0] if rating else , content: comment.get_text().strip() if comment else , }) return data这段解析逻辑依赖豆瓣评论列表页的 DOM 结构div.comment-item是每条短评的容器用户信息、有用数、评分、评论内容分别对应不同的子节点。其中评分字段比较特殊豆瓣的星级不是文本值而是span标签上的 class 属性比如allstar50、allstar40分别代表五星和四星所以提取时要拿class而不是get_text()。这个细节容易踩坑按文本提取会得到空字符串。3.2 分页与请求频率控制豆瓣短评列表页每页显示 20 条通过 URL 里的start参数翻页第一页是 0第二页是 20以此类推。论文中采用循环请求的方式翻页同时在两次请求之间强制休眠import time base_url https://movie.douban.com/subject/{}/comments?start{}limit20 movie_id 1292052 all_comments [] for offset in range(0, 200, 20): url base_url.format(movie_id, offset) resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: print(f请求失败: {resp.status_code}offset{offset}) break page_data parse_comment_page(resp.text) if not page_data: print(foffset{offset} 无数据可能页面结构变化或已被封禁) break all_comments.extend(page_data) time.sleep(3)这里的time.sleep(3)是反爬策略的最小成本手段。豆瓣对访问频率的容忍阈值大约在每秒 1 到 2 个请求之间超过这个频率就会触发封禁或验证码。3 秒的间隔意味着每分钟 20 条评论的获取速度看似慢但可以稳定地跑完大部分电影的热门短评。如果需要加速可以用随机间隔替代固定间隔比如time.sleep(random.uniform(2, 5))这样请求节奏更接近人工浏览行为。3.3 MySQL 建表与数据入库论文选择 MySQL 存储影评数据数据库表的设计考虑到了后续分析的查询需求核心表结构可以归纳为影评内容、用户评分、评论时间、有用数、电影 ID。建表语句和插入逻辑如下CREATE TABLE douban_review ( id INT AUTO_INCREMENT PRIMARY KEY, movie_id INT NOT NULL, user_name VARCHAR(64), rating VARCHAR(16), comment TEXT, votes INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_user_movie (user_name, movie_id, comment(100)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;import pymysql conn pymysql.connect( hostlocalhost, userroot, password123456, databasedouban, charsetutf8mb4 ) def insert_review(movie_id, item): with conn.cursor() as cursor: sql INSERT INTO douban_review (movie_id, user_name, rating, comment, votes) VALUES (%s, %s, %s, %s, %s) cursor.execute(sql, (movie_id, item[user], item[rating], item[content], item[votes])) conn.commit()建表时用UNIQUE KEY unique_user_movie (user_name, movie_id, comment(100))做去重约束很关键。爬虫脚本重复运行时或者翻页过程中遇到页面数据重叠会带来大量重复记录依赖应用层去重既慢又容易漏。数据库层的唯一索引直接把重复数据挡在外面代价是插入时偶尔会抛DuplicateEntry错误捕获异常并跳过即可。comment(100)表示对评论内容的前 100 个字符建立索引这是 MySQL 对 TEXT 字段的唯一索引限制要求也是保证查重的必要妥协。4. 数据处理与情感分析从分词到情感极性判断4.1 中文分词与停用词过滤影评数据是中文短文本与英文文本处理最本质的差别在于分词。英文单词天然以空格分隔中文必须先通过分词工具把句子切分成词序列。论文的处理流程里分词和停用词过滤是连贯的两个步骤——分词把句子拆成词语停用词过滤把「的、了、是、在」这类高频但无实际意义的词去掉。import jieba stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def tokenize(text): words jieba.lcut(text) filtered [w for w in words if w not in stopwords and len(w.strip()) 1] return filtered sample 这部电影的剧情很紧凑演员演技在线就是结局有点仓促。 print(tokenize(sample)) # 输出: [电影, 剧情, 紧凑, 演员, 演技, 在线, 结局, 仓促]jieba.lcut返回的是列表类型比cut更适合下游处理。过滤条件len(w.strip()) 1把单个字组成的词排除了这类单字词在中文本里大多是助词或语气词对情感判断贡献极小而且容易引入噪声。停用词表可以从 GitHub 上找现成的中文停用词库也可以自己根据语料统计高频词后临时补充论文的做法是两者结合。4.2 基于 SnowNLP 的情感得分计算在情感分析模块论文选用的是 Python 自然语言处理库 SnowNLP。相比 NLTK 和 TextBlobSnowNLP 对中文文本的训练结果更贴合国内用户的表达习惯它内置的情感模型基于电商评论语料训练虽然不完全匹配影评领域但胜在部署简单、调用方便一句s.sentiments就能得到情感得分。from snownlp import SnowNLP def sentiment_score(text): s SnowNLP(text) return s.sentiments # 0 到 1 之间的值越接近 1 越正面 reviews [这部电影非常震撼值得二刷, 剧情拖沓睡着了, 中规中矩吧没有惊喜] for r in reviews: score sentiment_score(r) label 正面 if score 0.6 else (负面 if score 0.4 else 中性) print(f{score:.3f} - {label} - {r})SnowNLP 的sentiments属性返回一个 0 到 1 之间的概率值越接近 1 判定为正面越接近 0 判定为负面。0.4 到 0.6 的区间当作中性处理这个阈值不是论文里写的固定标准而是我在实际跑数据时根据结果调的——豆瓣短评里大量用户给三星常规的正负二分法会把它们硬算成负面加一个缓冲区间能明显减少误判。影评数据的情感分布通常是偏态的高分电影正面评论占比极高这是领域特性不是模型偏差。4.3 评分归一化与特征统计除了评论文本用户评分也是情感分析的重要辅助特征。豆瓣页面上短评的星级以 class 形式出现转换为数值后可以做归一化处理让评分字段与情感得分一起构成综合分析的特征向量def rating_to_score(rating_class): 将豆瓣星级 class 转为 1-5 分 mapping { allstar50: 5, allstar45: 4.5, allstar40: 4, allstar35: 3.5, allstar30: 3, allstar25: 2.5, allstar20: 2, allstar15: 1.5, allstar10: 1, } return mapping.get(rating_class, 0) def normalize_score(score): return (score - 1) / (5 - 1) raw_rating allstar40 print(normalize_score(rating_to_score(raw_rating))) # 0.75归一化后的评分和情感得分可以组合成一个简单的「观众满意度指标」比如0.6 * sentiment_score 0.4 * normalized_rating。这个加权公式在论文里没有细讲但实际分析时这样做的效果不错——因为同一部电影里用户打分和文字评论之间偶尔会出现不一致比如有人给五星但评论内容偏吐槽融合两个维度比只看一个维度的结论更稳定。5. 常见问题与避坑指南豆瓣反爬的血泪经验5.1 请求频率过高导致 IP 被临时封禁现象爬虫脚本运行一段时间后突然连续出现 403 响应且页面上返回的内容不是正常评论列表而是一个包含「有异常请求」提示的验证码页面脚本随后进入无限 403 循环。原因豆瓣的反爬机制是单 IP 维度限流单位时间内的请求次数超过阈值就直接拒绝后续连接。即使设置了time.sleep(3)如果线程数开得过多比如同时跑 8 个线程每个线程都在独立请求聚合请求频率还是会超出限制。解决把并发线程数降到 4 或更低同时把休眠时间改成随机范围用random.uniform(3, 6)替代固定数值。如果 IP 已经被封最快的解决方法是重启路由器换 IP或者用代理池轮换出口 IP。论文里没有提到一点值得注意豆瓣封禁通常只封接口路径而非全站短时间 403 后可以先访问电影详情页确认 IP 是否真正被封。5.2 解析规则失效导致数据大量缺失现象爬虫运行正常、状态码返回 200但解析出的评论列表突然变成空数组日志显示本页成功 0 条数据。原因豆瓣在前端改版时会调整页面 DOM 结构曾经用div.comment-item或span.short作为选择器的写法会一次性失效。页面真实内容可能改用新的 class 名或换成>DELETE t1 FROM douban_review t1 INNER JOIN douban_review t2 WHERE t1.id t2.id AND t1.user_name t2.user_name AND t1.movie_id t2.movie_id AND t1.comment t2.comment;5.4 Cookie 登录态过期导致只能爬取公开数据现象爬取部分电影的详细短评时请求返回 200 但解析出来的是「登录后查看」的提示文本而不是评论内容或者翻到第 6、7 页时突然所有字段都为空。原因豆瓣限制未登录用户只能浏览部分短评列表的后续分页超过一定页数就强制要求登录。只带 User-Agent 的匿名请求无法满足这个条件需要在请求头里附上登录后的 Cookie。解决在代码里维护一个 Cookie 管理函数登录一次后把 Cookie 字符串持久化到本地文件过期后重新登录刷新。用requests.Session()维护会话状态把session.cookies传给每个请求避免每次请求都重新构造 Cookie。这里的常见做法是先手动在浏览器里登录豆瓣把 Cookie 复制进脚本适合课程设计这种不需要长期运行的批量任务。6. 可视化与系统展示让分析结果能直接看到6.1 基于 matplotlib 和 Flask 的结果呈现数据分析和模型计算都完成了还有一个环节决定论文的系统完整性——结果可视化。论文选择用 matplotlib 和 seaborn 绘制图表配合 Flask 搭建一个轻量级的 Web 查询页面。这个组合很适合本科毕设的定位matplotlib 负责把情感分析结果渲染成图表Flask 不需要额外引入大型前端框架服务端模板加简单 HTML 就能跑起来。from flask import Flask, render_template, request import pymysql import matplotlib.pyplot as plt import io import base64 app Flask(__name__) def get_reviews_from_db(movie_id): conn pymysql.connect(hostlocalhost, userroot, password123456, databasedouban, charsetutf8mb4) with conn.cursor() as cursor: sql SELECT rating, comment FROM douban_review WHERE movie_id %s cursor.execute(sql, (movie_id,)) rows cursor.fetchall() conn.close() return rows app.route(/, methods[GET, POST]) def index(): if request.method POST: movie_id request.form.get(movie_id) reviews get_reviews_from_db(movie_id) scores [sentiment_score(r[1]) for r in reviews] # 生成情感分布直方图 plt.figure(figsize(8, 5)) plt.hist(scores, bins20, color#2E86AB, alpha0.8) plt.title(fMovie {movie_id} Sentiment Distribution) plt.xlabel(Sentiment Score) plt.ylabel(Count) buf io.BytesIO() plt.savefig(buf, formatpng) buf.seek(0) img_base64 base64.b64encode(buf.read()).decode(utf-8) plt.close() return render_template(result.html, chartimg_base64, countlen(reviews)) return render_template(index.html) if __name__ __main__: app.run(debugTrue, port5000)这段代码演示的是最简化的 Flask 集成方式前端提交电影 ID → 后端查数据库 → 逐条算情感得分 → matplotlib 绘制分布图 → base64 编码传给前端模板显示。有个性能细节值得注意sentiment_score函数每次调用都要对文本重新做一整轮 SnowNLP 分析影评多的时候页面加载会明显变慢。论文的系统测试里没体现这一点但实际使用时我一般会加一层结果缓存把已经算过情感得分的评论 ID 存进 Redis 或直接存数据库字段下次直接读缓存结果。6.2 情感分布图与评论趋势的读法情感得分的分布直方图能直观反映一部电影的「口碑形状」。高分电影通常是左偏分布峰值集中在 0.8 以上争议电影的分布会出现明显的双峰一堆人打高分、一堆人打低分平庸电影则是中间隆起的钟形两头矮。这个图形分析维度在论文 4.2 节有过文字描述但代码层面建议加上一句对分布偏度的自动判断from scipy import stats def distribution_shape(scores): skew stats.skew(scores) if skew -0.5: return 正面主导型口碑 elif skew 0.5: return 负面主导型口碑 else: return 争议均衡型口碑scipy.stats.skew返回的是偏度系数负值表示左偏即高分评论数量占绝对优势正值表示右偏低分评论更多。这个自动分类对批量对比多部电影的口碑很有用不用一张张看图就能快速提取结论。6.3 文本主题聚类用 LDA 找出观众讨论的热点论文第 1.4 节研究方法中提到了主题建模但系统实现部分没有展开。实际上用 LDA 快速提取评论关键词能让分析系统的内容维度丰富不少。我一般会在数据清洗后把全部评论按分词结果转换成语料库然后调用gensim的 LDA 模型from gensim import corpora, models def topic_analysis(tokenized_docs, num_topics5): dictionary corpora.Dictionary(tokenized_docs) corpus [dictionary.doc2bow(doc) for doc in tokenized_docs] lda_model models.LdaModel(corpus, num_topicsnum_topics, id2worddictionary, passes15) topics lda_model.print_topics(num_words8) return topics tokenized_docs [tokenize(r[1]) for r in reviews] for topic in topic_analysis(tokenized_docs): print(topic) # 输出示例: (0, 0.038*剧情 0.026*演员 0.022*导演 ...)LDA 模型的输出是「主题编号 单词权重」的形式每个主题对应一组相关词汇。对这些主题做简单归纳比如「演员」「演技」相关词聚成一个主题「剧情」「节奏」聚成另一个主题就能看出观众对这部电影的讨论焦点分布。需要注意passes15这个参数它表示模型训练时遍历语料库的轮数设置越高模型收敛越好但时间更长15 轮对本科毕设的语料规模足够用。从那以后我做任何爬虫类项目都会把清洗、入库、分析、可视化这四个阶段拆成独立模块每个模块单独调试通过后再串联——论文里的一句话「模块化设计提高了系统的可维护性」在实际写代码时是能救命的原则。这份论文资源虽然文字部分有些地方讲得不够细但结构和技术选型都很规整按它的目录一步步复现你能得到一个功能完整、可以写进简历的影评分析系统。希望帮到你。本文还有配套的精品资源点击获取