简介面向Python爬虫与自然语言处理学习者这套微博情感分析系统源码完整实现了从数据采集到结果可视化的全流程。项目基于Scrapy框架爬取微博数据结合繁简转换、URL去除等清洗脚本并采用BERT与LSTM混合模型完成情感分类最终通过柱状图直观展示各情感标签下的模型表现覆盖爬虫、数据预处理、深度学习建模及前端展示等关键环节。资源共73个文件压缩包约517KB主要包含Python源码、前端Vue组件、配置文件及说明文档目录清晰区分爬虫、模型训练与预测等模块。目前已有113人学习下载适合作为课程设计或入门情感分析任务的参考工程可直接运行前端与后端联动调试也可重点研读模型构建与数据清洗部分的实现思路。1. 基于 Python 的微博情感分析系统一套能跑通全流程的源码而不是 PPT做文本情感分析的同学大多经历过这种尴尬理论看了不少BERT 和 LSTM 的原理都能默写但真要处理中文社交媒体文本还是被繁简混杂、URL 乱入、emoji 干扰整到怀疑人生。这套基于 Python 的微博情感分析系统源码好就好在它不是单点算法演示而是一条完整的流水线——Scrapy 爬取微博数据自定义规则清洗BERTLSTM 混合模型做情感分类最后用 Vue 前端把结果可视化出来。换句话说从微博上的一条原始文本到柱状图上的情感分布中间每一步都有代码对应。适合两类人一是做课程设计需要完整系统而非零散脚本的在校生二是想快速搭一套中文情感分析基线、准备往业务方向迭代的从业者。2. 系统架构与数据流先搞清楚数据是怎么“走”完整个链路的2.1 前后端分离的结构以及每个目录的实际职责拆开这份源码第一眼看到的是 webapps 和 back-end 两个顶层目录这代表前端和后端是完全分离的。前端基于 Vue包含src/App.vue、src/main.js、src/components、src/router标准的单页应用骨架后端是 Python核心目录是back-end/spiders、back-end/models和back-end/predictor。这个分层方式很常规但有一个好处你可以只跑通后端脚本完成情感分析前端纯粹是展示层不影响核心功能。back-end/models目录下的MyModel.py、Trainer.py、Test.py、MyDataSet.py是模型的训练和测试代码CleanData.py是数据清洗脚本单独拆出来而不是混在训练代码里这个设计很有必要——微博文本的脏数据问题严重清洗逻辑值得独立维护。predictor/Predictor.py负责加载训练好的模型做推理app.py是后端服务入口。整个链路的依赖关系不复杂spiders 爬数据 → CleanData 清洗 → MyDataSet 构造数据集 → Trainer 训练 → Predictor 推理 → app.py 暴露接口给前端。2.2 数据流视角下的完整链路理论上这套系统可以这样走通Scrapy 爬虫抓取微博用户的时间线或关键词搜索结果得到原始文本清洗脚本处理掉 URL、繁体字、emoji 等噪声清洗后的数据送入 BERTLSTM 混合模型得到每条文本的情感倾向最终以柱状图形式呈现模型在各类情感标签下的分类表现。微博原始文本 → spiders/ 爬虫采集 → CleanData.py 清洗 → MyDataSet.py 构造数据集 → MyModel.py (BERT LSTM) 训练 → Predictor.py 推理 → app.py API → Vue 前端可视化这是全链路的数据流但实际使用中不必一次跑通全部环节。如果你想先验证模型效果可以跳过爬虫用自己的文本数据集走清洗和训练如果你只想拿现成的模型做推理直接调用 Predictor 就行。源码的模块化做得足够每条路径都走得通。3. Scrapy 爬虫模块从微博抓数据重点看字段设计和中间件3.1 爬虫文件的分工单条推文、用户页、公共接口back-end/spiders目录下有三个核心爬虫文件single_tweet.py、user.py和tweet.py。single_tweet.py按单条推文 ID 抓取适合补充指定数据user.py按用户维度爬取可以抓某个用户的所有推文tweet.py是通用的推文爬虫适合关键词搜索或话题抓取。common.py大概率是公共的工具函数比如请求头构造、Cookie 处理这类重复逻辑。微博的反爬策略比较严格所以源码里专门有middlewares.py文件。Scrapy 的中间件机制在这里的作用是两条一是设置合理的请求间隔避免高频请求触发封禁二是处理请求头模拟浏览器环境。建议拿到源码后先看这个文件里的DOWNLOAD_DELAY和User-Agent设置这是爬虫能否稳定运行的关键。3.2 爬虫运行方式与 settings.py 的关键参数Scrapy 项目不能直接python xxx.py运行而要用scrapy crawl命令。源码里run_spider.py就是干这个的封装说明作者已经把命令行参数封装成了可执行脚本。cd back-end scrapy crawl tweet -a keyword人工智能 -a pages5-a参数用于给爬虫传递自定义参数keyword是搜索关键词pages是抓取页数。注意实际参数名以tweet.py里的__init__方法定义为准这里只是常见写法。settings.py里有几个参数值得优先关注# settings.py 关键配置项 DOWNLOAD_DELAY 2.0 # 请求间隔单位秒过小容易触发反爬 CONCURRENT_REQUESTS 4 # 并发请求数建议保守设置 USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) ITEM_PIPELINES { pipelines.py 中定义的管道类: 300, }DOWNLOAD_DELAY和CONCURRENT_REQUESTS是一对需要权衡的参数。调大间隔能降低封禁概率但抓取速度会明显变慢并发数调高能加速但微博的风控会更快注意到异常流量。我的经验是拿自己账号的 Cookie 跑间隔不低于 1.5 秒并发不超过 8单次任务控制在几百条以内基本不会触发验证码。如果你只需要做情感分析实验几千条数据足够训练一个基线模型了。4. 数据清洗与预处理繁简转换、URL 剔除、emoji 映射的工程实现4.1 微博文本的脏数据到底有多“脏”做过中文社交文本处理的人都有体会微博文本的噪声种类比其他语料多得多。首先是繁体字港台用户和部分营销号习惯用繁体直接送进模型会稀释词向量的表达能力其次是 URL短链接在文本中频繁出现对情感判断完全没有贡献再就是 emoji微博的 emoji 有两种存在形式——Unicode 原生字符和形如[笑哭]的文本表情后者如果不做映射分词器会把它切成无意义的碎片。CleanData.py就是专门解决这些问题的。4.2 清洗模块的代码逻辑与参数说明# CleanData.py 核心清洗逻辑简化版 import re def clean_text(text: str) - str: # 1. 去除 URL text re.sub(rhttp[s]?://(?:[a-zA-Z]|[0-9]|[$-_.]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F])), , text) # 2. 繁体转简体 # 常见做法是使用 zhconv 或 opencc 库 from zhconv import convert text convert(text, zh-cn) # 3. emoji 文本表情映射例如 [笑哭] - [笑哭] emoji_map {[笑哭]: [笑哭], [doge]: [doge], [微笑]: [微笑]} # 实际项目中这里会维护一个较大的映射表或使用 emoji 库统一转换 # 4. 去除多余空白字符 text re.sub(r\s, , text).strip() return text清洗顺序是有讲究的。先去 URL 再转繁体是因为 URL 里可能包含编码后的字符先去除能减少后续转换的干扰emoji 映射放在繁简转换之后是因为部分 emoji 文本表情在繁体环境下可能有不同的写法。原代码里还包含表情包转换的逻辑也就是把表情统一转为文字标签这一步能让 BERT 的 Tokenizer 更好地理解情感信号。convert(text, zh-cn)是 zhconv 库的调用方式它基于 OpenCC 的词典做转换精度在社交媒体场景下够用。需要注意这个函数依赖zhconv包源码的requirements.txt里应该已经列了但如果你只拷贝了CleanData.py而不装依赖运行时会直接报ModuleNotFoundError。这是拿到源码后最容易踩的第一个坑。5. BERTLSTM 混合模型为什么这么搭训练时有哪些关键参数5.1 混合模型的动机BERT 做特征抽取LSTM 做序列建模BERT 本身已经是强大的文本编码器为什么还要接一层 LSTM这要从训练成本和数据量两个角度解释。微博文本通常较短140 字以内直接用 BERT 做分类没问题但对于短文本BERT 的注意力机制会平等地看待每个 token丢失部分局部序列信息LSTM 天然擅长捕捉相邻 token 之间的顺序依赖。两者结合实际上是让 BERT 先把文本映射成语义丰富的向量序列LSTM 再在这个序列上做一次时序建模最后接全连接层输出情感类别。# MyModel.py 模型结构核心代码基于 PyTorch 的实现逻辑 import torch import torch.nn as nn from transformers import BertModel class BertLSTMModel(nn.Module): def __init__(self, bert_model_namebert-base-chinese, hidden_size256, num_classes3): super(BertLSTMModel, self).__init__() self.bert BertModel.from_pretrained(bert_model_name) self.lstm nn.LSTM(input_size768, hidden_sizehidden_size, num_layers2, batch_firstTrue, dropout0.3) self.classifier nn.Linear(hidden_size * 2, num_classes) def forward(self, input_ids, attention_mask): # BERT 输出最后一层隐藏状态 bert_outputs self.bert(input_idsinput_ids, attention_maskattention_mask) sequence_output bert_outputs.last_hidden_state # shape: (batch, seq_len, 768) # 送入双向 LSTM取最后时间步的隐状态 lstm_output, (h_n, c_n) self.lstm(sequence_output) # 双向 LSTM 拼接最后隐状态 last_hidden torch.cat((h_n[-2], h_n[-1]), dim1) # shape: (batch, hidden_size*2) logits self.classifier(last_hidden) return logits这段代码展现的是混合模型的核心结构。hidden_size256控制 LSTM 隐层维度影响模型的参数量和拟合能力num_layers2是 LSTM 层数太深容易过拟合微博短文本场景 2 层足够dropout0.3是 LSTM 层内部的随机失活比例用于抑制过拟合num_classes3对应情感类别数常见划分是正面、中性、负面三分类如果要更细粒度可以自行调整。5.2 训练流程与 Trainer.py 的关键设置Trainer.py是训练的主控脚本MyDataSet.py负责把清洗后的文本转成 BERT 需要的格式——包括分词、构造input_ids、生成attention_mask这个过程依赖 transformers 库的BertTokenizer。训练时的关键参数一般在 Trainer 或单独配置中定义# Trainer.py 训练关键参数常见配置 BATCH_SIZE 16 # 批大小BERT 系模型显存占用大根据 GPU 显存调整 EPOCHS 5 # 训练轮数小数据集 3-5 轮即可多了容易过拟合 LEARNING_RATE 2e-5 # BERT 微调推荐使用较小的学习率 WARMUP_PROPORTION 0.1 # 前 10% 的 step 用于学习率预热稳定训练过程LEARNING_RATE 2e-5这个值值得特别注意。BERT 模型的参数已经在海量语料上预训练过微调阶段如果用较大的学习率会把学到的通用语义知识破坏掉。2e-5 是 BERT 微调的经验安全值换数据集后优先保持这个量级效果差再在 1e-5 到 5e-5 之间搜索调整。Test.py负责在测试集上评估模型效果评估指标通常包括准确率、精确率、召回率和 F1 值。训练完成后模型权重会保存为文件Predictor.py在推理阶段加载这个权重。项目根目录下有一张「模型在各标签下的表现.svg」的图这其实就是训练完成后导出的可视化结果是模型表现可视化的直观呈现也正是摘要里提到的柱状图可视化环节。6. 避坑手册与常见问题排查从爬虫到模型训练的高频翻车现场6.1 爬虫阶段能运行但抓到的是空数据现象运行爬虫没有任何报错日志也显示抓到了 item但最后数据库或导出文件里没有数据。原因最常见的是 item 只经过爬虫的parse方法但没有被 pipeline 处理。Scrapy 中爬虫返回的 item 要先经过Item Pipelines才会入库或导出如果 pipeline 没有启用或者 pipeline 类里没有实现process_item方法数据就会在管道中断掉。解决检查settings.py里的ITEM_PIPELINES字典是否包含pipelines.py中定义的管道类确认process_item方法的返回值是 item 而不是 None。另外在pipelines.py的process_item开头加一行日志打印 item 内容确认数据是否真的流到了这一层。6.2 清洗阶段繁简转换直接报错现象运行CleanData.py时from zhconv import convert这行报ModuleNotFoundError。原因source 包的requirements.txt可能没有把zhconv列为依赖或你只拷贝了部分源码文件没有同步安装依赖。解决手动执行pip install zhconv opencc-python-reimplemented这两个库都能实现繁简转换。如果对转换精度要求高推荐opencc它支持更多转换风格但安装包更大。我一般用 zhconv因为轻量且对社交媒体文本足够。6.3 模型训练显存溢出CUDA out of memory现象训练刚开始或第一个 epoch 进行中控制台报CUDA out of memory进程被 kill 掉。原因BERT 模型本身就占用大量显存加上 LSTM 层和 batch 内所有样本同时计算显存峰值超标。微博文本如果没做截断padding 后序列过长会进一步放大显存占用。解决优先降低BATCH_SIZE从 16 降到 8 或 4同时在MyDataSet.py里设置max_len128超过部分直接截断——微博文本绝大多数不超过 128 个字做截断不会明显损失信息。如果显存还是不够考虑加载bert-base-chinese的量化版或蒸馏版比如distilbert-base-chinese但效果会有一定损失。6.4 模型训练损失不降准确率在随机水平徘徊现象训练多个 epoch 后 loss 基本不变测试准确率在 50% 左右三分类任务相当于随机水平。原因最常见的是标签和预处理不匹配。检查MyDataSet.py的标签映射是否和CleanData.py清洗后的文本对齐另一个可能是attention_mask没有正确传入模型导致模型把所有 padding token 也当成了有效语义。解决先做一次过拟合测试——取训练集里 100 条样本训练 10 个 epoch如果 loss 能降下去说明模型没问题问题在数据如果 loss 还是不动检查forward函数里attention_mask是否正确传入 BERT 层。按我的经验8 成是input_ids和attention_mask的 device 不一致导致的静默错误。6.5 推理阶段Predictor 加载模型报错现象训练完成后运行Predictor.py报模型权重维度不匹配size mismatch。原因训练时的num_classes类别数和推理时实例化模型的参数不一致。比如训练用的三分类模型Predictor 里实例化时却写了num_classes2全连接层维度对不上。解决检查Predictor.py里BertLSTMModel初始化的参数是否和训练时完全一致。这里有一个好习惯保存模型时不要把整个模型对象序列化而是只保存state_dict并在加载时显式传入和训练时完全一致的模型结构参数。7. 把模型表现可视化并接入前端从训练曲线到情感分布图源码里有一张模型在各标签下的表现.svg这张图对应摘要里的柱状图可视化环节。它的生成逻辑通常是在训练或测试完成后统计模型在正面、中性、负面或其他标签上的准确率/召回率/F1然后用 Python 端的 matplotlib 绘成图表输出为 SVG 矢量格式因为 SVG 缩放到任意尺寸都不会失真。# 生成模型在各标签下表现的柱状图简化版示例 import matplotlib.pyplot as plt labels [positive, neutral, negative] f1_scores [0.86, 0.74, 0.78] # 示例数据实际以训练结果为 plt.figure(figsize(8, 5)) plt.bar(labels, f1_scores, color[#2ecc71, #f39c12, #e74c3c]) plt.ylabel(F1 Score) plt.title(Model Performance by Sentiment Label) plt.ylim(0, 1.0) # 在柱状图上显示具体数值 for i, score in enumerate(f1_scores): plt.text(i, score 0.02, f{score:.2f}, hacenter) plt.savefig(模型在各标签下的表现.svg, formatsvg)前端的展示逻辑在src/components和src/views下的 Vue 组件里后端app.py通过 Flask 或其他 Web 框架暴露 API前端用 Axios 请求接口拿数据。如果要启动完整的前后端联调流程是后端python app.py启动服务前端npm install装依赖后npm run serve启动开发服务器浏览器访问 Vue 默认端口通常是 localhost:8080。这里要提醒一个联调最容易忽略的点跨域。Vue 开发服务器默认跑在 8080后端 Flask 跑在 5000 或某个自定义端口前端请求后端接口时会被浏览器的同源策略拦截。解决办法是在 Vue 项目的vue.config.js里配置代理// vue.config.js devServer 代理配置 module.exports { devServer: { proxy: { /api: { target: http://localhost:5000, // 指向后端服务地址 changeOrigin: true } } } }配置代理后前端请求/api/xxx时会被自动转发到后端的 5000 端口绕开跨域问题。changeOrigin: true的作用是重写请求头中的 Host 字段让后端认为请求来自同源——这个参数不写会导致某些后端框架对请求来源做校验时报 403。说到最后我自己的使用习惯是拿到源码后先不急着跑全部流程而是把CleanData.py单独抽出来拿自己积累的文本试一圈清洗效果确认繁简转换、URL 剔除、emoji 映射的表现符合预期后再启动爬虫去抓数据。那之后我每次做文本分类任务都会强制走一遍「先清洗 → 再抽样看效果 → 最后训练」的流程从不跳过。这套源码的价值不在于它用了多前沿的模型而在于它把微博情感分析这条路完整地铺了一遍你只需要在这个基础上换数据、调参数、改前端样式就能有自己的可用系统。希望帮到你。本文还有配套的精品资源点击获取