
简介一份面向Python课程设计、毕业设计与爬虫项目的实战资源专注东方财富股吧与新浪财经数据采集、情感分析及舆情报告自动生成。项目代码完整、结构清晰包含统一爬虫入口、模拟浏览器UA池、金融情感词典、SnowNLP与自定义词典分析模块以及舆情报告自动生成和邮件发送脚本适合金融舆情分析入门或作为课题拓展。压缩包共31个文件以Python脚本为主辅以txt情感词典、sql数据库、docx说明文档和png架构图整体仅8.01MB便于下载与快速部署。详细项目说明文档和舆情分析报告涵盖配置、运行与实现思路可帮助读者复现并从零搭建类似系统。已有64人学习适合需要完成课设、毕设或想掌握爬虫情感分析全流程的开发者。1. 东财股吧新浪财经舆情分析从采集到报告是一条可落地的流水线一个投资者打开某只股票的股吧看到的往往是两种极端要么铺天盖地的“加仓”喊单要么下跌后满屏的“割肉”咒骂。真正的情绪往往藏在评论的措辞里——明明在骂公司却在骂完后补一句“但我还是没卖”这种自相矛盾恰恰是最有价值的信号。这个项目标题最值得关注的部分不是爬虫本身而是“舆情报告自动生成”这后半段把东财股吧的散户评论和新浪财经的新闻标题拉到一起做情感分析再将统计结果自动拼装成一份周报。对量化研究员、财经自媒体和独立投资者来说这套流程的核心价值不在某个技术点而在于把“采集、清洗、打标签、出报告”串成了每天能自动跑完的流水线。2. 先打通数据管道东财股吧与新浪财经的爬虫层设计与增量采集2.1 认清两个数据源的结构差异东财股吧和新浪财经是两种完全不同的采集对象。股吧是 UGC 内容帖子标题、正文、评论都来自散户文本长度参差不齐、错别字极多但好处是数据接口相对直接——股吧页面背后有一套 JSON 接口在驱动列表和分页。新浪财经则是新闻门户内容以上市公司公告、行业快讯、分析文章为主文本规范得多但新闻页面的反爬策略比股吧更严格而且同一篇新闻会出现在列表页、频道页、正文页多个位置容易造成重复采集。常见做法是给两个源分别写采集器但共享同一个去重表。东财股吧的帖子有稳定的 post_id新浪新闻有 URL 中的 doc_id这两个 ID 天然适合作为去重主键。采集上使用 requests 固定请求头就能跑通不必一开始就上 Scrapy——数据量没有大到需要分布式的时候Scrapy 的调度和中间件反而会拖慢迭代速度。2.2 用 requests 抓取东财股吧评论的核心接口东财股吧的帖子列表可以直接从移动端接口读取返回 JSON 而不是 HTML解析成本低很多。下面这段代码抓取指定股票的最新帖子列表import requests import json def fetch_guba_posts(stock_code600519, page1, page_size50): url https://gbpush.eastmoney.com/api/post/list params { code: stock_code, # 股票代码 sort: 1, # 按最后评论时间排序 p: str(page), # 页码 ps: str(page_size), # 每页条数 ut: 7eea3edcaed734bea9cbfc24409ed989, # 公共token } headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X), Referer: fhttps://guba.eastmoney.com/list,{stock_code}.html, Accept: application/json, } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.raise_for_status() data resp.json() return data.get(post_list, [])这段代码核心在两个地方sort1代表按最后评论时间排序用于抓取“正在被讨论”的热帖ut是东财接口的公共 token正常浏览器访问时会自动带上的那个固定值。实际开发中先把返回的 JSON 原文打印出来确认字段名再写解析逻辑因为东财偶尔会调整字段命名。拿到帖子列表后还需要进一步抓取单帖下的评论。常见做法是再调一层评论接口没有评论区的帖子直接跳过减少无效请求。2.3 新浪财经新闻的简易采集与页面解析新浪财经没有统一的开放 API最常见做法是抓取搜索结果页再逐条解析标题和摘要。搜索接口返回的是半结构化 HTML用 BeautifulSoup 就能处理import requests from bs4 import BeautifulSoup def fetch_sina_news(stock_name贵州茅台, page1): url https://search.sina.com.cn/ params { q: stock_name, c: news, beg: 0 if page 1 else str((page - 1) * 20), sort: time, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) results [] for item in soup.select(.box-result): title_tag item.select_one(h2 a) if title_tag: results.append({ title: title_tag.get_text(stripTrue), url: title_tag.get(href), summary: item.select_one(.box-text).get_text(stripTrue)[:200], source: sina, }) return results这个方案的关键是resp.encoding utf-8——新浪搜索页如果不手动指定编码requests 很可能按 headers 里的旧编码解出乱码。搜索结果页按时间排序时页码跨度是 20 条一页beg参数传的是偏移量而不是页号这点容易写错。如果发现返回条目为空优先检查参数名和选择器是否匹配当前页面结构新浪改版频率不高但每次改版都会影响选择器。2.4 增量去重与采集状态表的设计两个数据源都抓下来之后增量去重是决定能不能每天稳定跑下去的关键。为每个数据源维护一张采集状态表记录已经见过的内容 ID 和时间戳字段说明示例source数据来源标识eastmoney / sinaobj_id帖子 ID 或新闻 URLpost_123456789title_hash标题的 md5 值用于内容级去重9f2c...first_seen首次采集时间2026-04-26 09:15:00last_seen最后一次见到该内容的时间2026-04-26 10:00:00数据库用 SQLite 就够写入时用INSERT OR IGNORE做自然去重CREATE TABLE IF NOT EXISTS collected( source TEXT, obj_id TEXT, title_hash TEXT, first_seen DATETIME, last_seen DATETIME, PRIMARY KEY(source, obj_id) );东财帖子有稳定 ID直接作为主键。新浪新闻的 URL 中有 doc_id截取后入库不要存完整 URL 当主键因为新浪会给同一个文档生成不同带参数的访问链接。增量采集的时间窗口建议设为最近 24 小时配合定时任务每小时跑一次既不会漏掉当天讨论度突然上升的帖子也不会重复请求太久远的历史数据。3. 情感分析给几千条评论打上可信的情绪标签3.1 通用情感模型为什么在股市评论区失灵把通用的中文情感分析模型直接套在股吧评论上效果通常不会太好。原因在于股市评论的语言习惯和电商评论相差太大——“跌”本身是中性词但“跌停”是强负面“被套”是负面“解套”却是正面。“加仓”和“清仓”动作相反情感方向截然不同但通用模型很难捕捉这种金融语境。更麻烦的是反讽“庄家真会玩”这句话字面积极实际表达的是愤怒。所以这个项目里情感分析的落地顺序是先跑通用模型做基线再用金融词典和否定词规则做校准最后有条件再上微调模型。这样分层的意义在于每一层都能看到增益而不是一上来就把结果交给一个像黑盒一样的神经网络。3.2 基线与增强SnowNLP 打分 金融词典规则修正SnowNLP 是使用成本最低的情感分析库一行代码就能拿到 0 到 1 的情感分值0.5 以下偏负面。先拿它跑一遍全部评论再叠加规则修正from snownlp import SnowNLP import re NEG_WORDS [不, 没, 别, 无, 未, 休, 勿, 莫] DEGREE_WORDS [非常, 极其, 彻底, 完全, 稍微, 有点] FIN_NEG [跌停, 爆仓, 被套, 割肉, 利空, 减持, 立案, 问询函] FIN_POS [涨停, 加仓, 利好, 回购, 增持, 突破, 放量上涨, 中签] def fin_sentiment(text, base_scoreNone): if base_score is None: base_score SnowNLP(text).sentiments score base_score # 金融词典强规则命中强负面词条直接压低分值 for w in FIN_NEG: if w in text: score - 0.2 for w in FIN_POS: if w in text: score 0.15 # 否定词反转积极词前出现否定情感方向反转 for w in NEG_WORDS: for p in FIN_POS: pattern w p if pattern in text: score - 0.25 break return max(0.0, min(1.0, score))这段规则的背后逻辑是SnowNLP 给出的是一个通用语义基础分金融词典把领域关键词带来的方向性偏差拉回来否定词处理则解决“不看好”“没有利好”这类常见表达。FIN_NEG和FIN_POS两份词表需要根据实际抓取到的评论内容持续扩充每跑一周就把情感分值明显异常比如被模型打成 0.7 以上的利空消息的样本捡出来看看漏掉了哪些词。这个方案的局限也很明显词典无法处理“高开低走”“冲高回落”这类连续动作描述。这类表达需要整句分析不是词典能覆盖的所以下一步要考虑模型。3.3 上升到 FinBERT什么时候才值得引入深度学习模型当规则词典的修正已经不能带来明显的情感分类准确率提升时就到了引入 FinBERT 类模型的时机。FinBERT 是在金融语料上预训练的中文模型对“看空”“增持”“业绩预增”这类专业表达的理解远强于通用模型。使用 HuggingFace 生态加载即可from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch def load_finbert(): model_name yiyanghkust/finbert-tone-chinese tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name) return tokenizer, model def predict_sentiment(text, tokenizer, model): inputs tokenizer(text, return_tensorspt, truncationTrue, max_length128) with torch.no_grad(): outputs model(**inputs) probs torch.nn.functional.softmax(outputs.logits, dim-1) labels [negative, neutral, positive] return labels[probs.argmax().item()], probs.max().item()引入模型不只是换一个分词器的问题开销体现在三方面标注数据、推理时间和硬件成本。如果要自己微调而不是直接用现成权重至少要人工标注三千条以上股吧评论否则模型学到的只是通用金融文本的分布对股吧这种充满口语和错别字的场景照样水土不服。如果只是做每日舆情监控而不是实时交易决策跑一个中小模型的推理成本完全可以用定时任务覆盖没必要上 GPU 服务器。3.4 批量处理与置信度输出批量处理时把原始文本、基础分、修正分、最终标签一次性落表方便后续对比不同方案的效果import pandas as pd from snownlp import SnowNLP def batch_analyze(df, text_colcontent): df[snownlp_score] df[text_col].apply(lambda x: SnowNLP(x).sentiments) df[fin_score] df.apply( lambda row: fin_sentiment(row[text_col], row[snownlp_score]), axis1 ) df[label] pd.cut(df[fin_score], bins[0, 0.4, 0.6, 1.0], labels[negative, neutral, positive]) df[confidence] df.apply( lambda row: abs(row[fin_score] - 0.5) * 2, axis1 ) return dfconfidence这个字段很容易被忽略但它比情感分值本身更实用。0.1 分附近的评论说明模型信心不足在报告里应该归入中性0.8 以上的评论才值得作为强信号写进结论。另外注意pd.cut的边界——0.6 分已经落在 positive 区间这个阈值可以根据实际业务场景调整如果发现报告里看多结论过多先查权重倾向而不是急着改模型。4. 舆情报告自动生成从 CSV 到可视化 Word 报告4.1 报告内容框架与指标设计报告生成是这个项目标题里“自动生成”的落点。爬虫和情感分析产出的是一张带标签的宽表报告生成要做的是把这张宽表浓缩成一页纸——哪些票在讨论热度上升、情绪是偏多还是偏空、有没有异常爆发。实际写报告时建议先定模板再定指标。一份可用的日报至少包含五块内容舆情概览当日评论总量、情感分布占比、热度排行讨论量 TOP10 个股或话题、情感异动情感指数较前一日变化超过阈值的对象、重点新闻摘要新浪财经来源的标题列表、风险提示负面命中词频最高的评论原文。情感指数的计算方式是加权平均给东财股吧评论权重 0.6、新浪新闻标题权重 0.4理由是新浪新闻表达更克制但更权威两者需要互相制衡。4.2 用 Jinja2 模板驱动段落与结论生成报告不能只是数据表格的堆砌需要在数字后面跟随自动生成的结论性文字。这里使用 Jinja2 模板把数据变化翻译成人话{% for item in highlights %} {{ item.name }} 今日讨论量 {{ item.post_count }} 条 情感指数 {{ %.2f|format(item.sentiment) }} 环比 {{ item.change_direction }} {{ item.change_percent }}% 主要讨论话题{{ item.top_keywords|join(、) }}。 {% endfor %}from jinja2 import Template report_template Template(report_tpl) report_text report_template.render( highlights[ { name: 600519, post_count: 342, sentiment: 0.38, change_direction: 下降, change_percent: 12.5, top_keywords: [减持, 技术面, 回调], } ] )Jinja2 在这里充当的是“数字到句子”的映射层。join过滤器把关键词列表拼成中文顿号连接的句子format控制小数位数避免出现 0.3833333 这种没有可读性的数值。模板里的判断逻辑尽量控制在“有数据输出段落无数据跳过”的层面不要在里面写复杂的算法逻辑。4.3 Matplotlib 图表自动嵌入 docx纯文本报告的说服力有限图表是必须的。用 Matplotlib 生成情感走势图后通过 python-docx 把图片插入文档import matplotlib.pyplot as plt from docx import Document def gen_sentiment_chart(dates, scores, output_pathsentiment_trend.png): plt.figure(figsize(10, 4)) plt.plot(dates, scores, markero, linewidth2) plt.axhline(y0.5, colorgray, linestyle--, linewidth1) plt.title(个股情感指数趋势) plt.xticks(rotation30) plt.tight_layout() plt.savefig(output_path, dpi150) plt.close() def insert_chart_to_docx(doc_path, chart_path): doc Document(doc_path) doc.add_picture(chart_path, widthdocuments.shared.Pt(350)) doc.save(doc_path)注意plt.close()必须执行否则在循环生成多张图时会消耗大量内存且出现图形内容串用。插入图片时width参数用Pt(350)控制宽度matplotlib 默认输出尺寸在 Word 里会偏大。生成的图表文件名要带上日期避免定时任务重复运行产生覆盖混乱。4.4 全流程串联cron 定时任务跑通日报自动化把采集、分析、报告生成三个模块串进一个入口脚本再用 cron 定时执行30 17 * * 1-5 cd /opt/stock_sentiment /usr/bin/python3 run_daily.py logs/daily.log 2130 17选择在收盘后一小时运行此时当天的股吧讨论已经积累得比较充分新浪财经的收盘报道也基本发布完毕。run_daily.py内部按顺序调用采集模块、情感分析模块和报告生成模块任意一步失败都要发告警——最简单的方式是在脚本里把错误信息用 requests 发到企业微信机器人。日志里除了打印异常栈还要打印本次采集到多少条数据、过滤了多少重复数据、情感分析输出多少条中性评论这些数字是判断流水线是否健康的最直接证据。5. 跑通之后值得改进的 3 个方向5.1 反爬与稳定性从单机定时到动态代理池requests 写好的采集器在连续运行 20 天左右会遇到第一个明显的瓶颈IP 被源站限流表现为返回 403 或验证码页面。最简单的应对是配置代理池requests 的Session对象在每次请求前随机挑选代理。但代理池的质量比数量更重要免费代理池里大量代理本身已失效重试机制反而会拖慢整体采集速度。如果只盯几十只股票对单只股票的请求频率控制在每 5 分钟一次以内通常不会触发限流——在爬虫里“少而稳”永远好过“多而乱”。5.2 情感指数要结合行情做校准情感指数本身没有绝对意义只有相对意义。一只股票情感指数 0.55 不代表市场看好它但如果昨天的指数是 0.42、今天是 0.55同时成交量放大这个变化才有分析价值。所以报告里应该同时输出情感指数的差分和涨跌幅做一张“情感分位”表将过去 60 个交易日的情感指数排序标注当天的值处于什么百分位。这个计算无需引入额外数据源用 pandas 的rank(pctTrue)就能完成却能让报告从“描述现状”升级为“提示异常”。5.3 报告保留可追溯性原始数据落库生成出去的 Word 报告只是最终产品所有中间数据——原始评论、清洗后文本、情感分值、置信度——要按照日期分表保存。便于追溯和回测渲染不同类型的报告模板。最后注意约束报告中引用的每条负面评论都要能定位到原文 URL只放摘要不放链接会显著降低报告的使用价值。本文还有配套的精品资源点击获取