a_investor_sentiment · 股民情绪分析
抓 A 股散户公开讨论 → 本地中文金融情感词典打分 → 聚合成情绪指标 → 输出一份离线可看的 HTML 报告。
抓取、打分、聚合、出报告全部在本地完成:不调用大模型,不调用任何云 NLP API,不依赖 CDN —— 生成的 HTML 报告断网也能打开。
本项目同时是一个 Agent Skill(根目录的 SKILL.md),可以直接被 Agent 调用。
数据源
| 源 | 标识 | 需登录 | 拿到什么 | 定位 |
|---|---|---|---|---|
| 东方财富股吧 | eastmoney | 免 | 帖子标题 + 正文 + 阅读/评论数 | 默认主力,样本量最大(80 条/页) |
| 淘股吧 | tgb | 免 | 主帖 / 跟帖 + 作者 + 时间 | 游资、实盘视角 |
| 新浪股吧 | sina_guba | 免 | 散户主帖 + 正文 + 回复 | 默认三源之一,更「人话」(60 条/页) |
| 雪球 | xueqiu | 要 Cookie | 个股讨论(可翻页)+ 热帖评论 | 必须走 App 侧域名 api.xueqiu.com,主域被 WAF 拦死 |
| 同花顺股吧 | ths_guba | 要 Cookie | 散户帖标题 + 摘要 | ⚠️ 数据停更在 2020-01~04,仅作历史语料 |
| 新浪个股资讯 | sina_stock | 免 | 个股新闻标题 | 媒体口径,不是散户发言 |
| 新浪财经 7x24 | sina | 免 | 全市场快讯 | 需配 --keyword |
| 同花顺财经 | ths | 免 | 全市场新闻 | 需配 --keyword |
给定 --code 且未指定 --sources 时,默认只跑真散户文本的三源:eastmoney,tgb,sina_guba。
各平台接口的实测档案(具体 URL、解析选择器、每页条数、失效接口与踩坑点)见
references/sources.md。
快速开始
pip install requests beautifulsoup4 lxml
# Windows 控制台先执行,否则中文输出乱码
set PYTHONUTF8=1
# 一只票,默认三源
python scripts/senti.py --code 600519 --pages 2 --outdir out
# 多只票,逐只出报告
python scripts/senti.py --code 600519,000858,300750 --pages 2
# 只跑一个源
python scripts/senti.py --code 600519 --sources sina_guba --pages 3
# 不带个股代码,按关键词走全市场源
python scripts/senti.py --keyword 人工智能 --sources sina,ths --pages 2
# 四源(雪球需要登录 Cookie;放文件里比写在命令行安全)
python scripts/senti.py --code 600519 \
--sources eastmoney,tgb,sina_guba,xueqiu \
--cookie-file secrets/xq_cookie.txt --pages 3
Cookie 文件一行一条、支持 # 注释行。不要把它提交进仓库(见 .gitignore)。
输出
结果落到 --outdir(默认 sentiment_out/):
| 文件 | 内容 |
|---|---|
| {code}_{名称}_{时间}.json | 全量数据:每条文本的 score / hits / url + 指标 + 各源状态 |
| 同名 .html | 单文件报告:指标卡 / 情绪构成(整体 + 分平台)/ 小时走势 / 高频情绪词 / 代表性原帖 |
终端同时打印一份 Markdown 摘要。
hits 是复核打分的地方 —— 每条被判成正面还是负面,都能追溯到是哪些词顶起来的:
"hits": [["跌停", -3.0], ["主力出货", -2.5]]
参数
| 参数 | 默认 | 说明 |
|---|---|---|
| --code | — | 股票代码,逗号分隔;与 --keyword 至少给一个 |
| --keyword | — | 关键词,用于新浪 7x24 / 同花顺新闻过滤 |
| --sources | 依场景 | eastmoney,tgb,sina_guba,sina_stock,sina,xueqiu,ths,ths_guba |
| --all-sources | off | 八个源全跑 |
| --pages | 2 | 每源翻几页 |
| --delay | 1.0 | 请求间隔(秒)。别改成 0,会被封 IP |
| --workers | 3 | 并发源数,总耗时 ≈ 最慢的那个源 |
| --thr | 0.2 | 正 / 负判定阈值 |
| --timeout | 12 | 单请求超时(秒) |
| --body-limit / --no-body | 10 / off | 正文补齐条数;--no-body 只跑标题,快很多 |
| --cookie | $SENTI_COOKIE | 通用 Cookie(k=v; k2=v2),喂给需要登录的源 |
| --cookie-file | — | 从文件读 Cookie(一行一条,支持 # 注释) |
| --xq-cookie | $XQ_COOKIE | 雪球 Cookie(等价 --cookie,保留兼容) |
| --xq-sort | alpha | 雪球讨论排序:alpha=热门 / time=最新 / recommend |
| --xq-comments | 3 | 雪球补抓前 N 个热帖的评论(0 = 不抓) |
| --ths-cookie | $THS_COOKIE | 同花顺 Cookie(ths_guba 必填,不填则回退到 --cookie) |
| --outdir | sentiment_out | 输出目录 |
打分与指标口径
完整口径(含每一个常量与已知局限)见 references/indicators.md,这里是速览。
一条文本 → score ∈ [−1, +1]
清洗(去 URL / @ / $标签)
→ 正向最大匹配(长词优先,不依赖分词库)
→ 每条命中:词权重 × 程度乘子 ×(否定 ? −0.8 : 1)× 转折侧权重
→ + 表情分 + 涨跌幅数值分 + 连板分
raw = Σ score_i
score = tanh(raw / 2.5)
词权重 3.0 极端 / 2.0 强 / 1.0 普通 / 0.5 轻微;否定 = 命中词往前 4 字内有否定词 → 反转并打 8 折; 转折只认最后一个转折词(之前的部分 ×0.4,之后 ×1.0)。
一批文本 → 指标
| 指标 | 公式 | 读法 |
|---|---|---|
| 情绪净值 net | (正面条数 − 负面条数) / 有效条数 | 最直观的多空比,≥0.3 偏乐观、≤−0.3 偏悲观 |
| 均值 mean | mean(score) | 含强度信息,容易被少数极端帖拉偏 |
| 加权均值 wmean | Σ(score×w)/Σw,w = 1 + ln(1 + read/50 + reply×2 + like×2) | 热帖说话更响 |
| 争议度 disagree | 1 − |net| | 越接近 1 分歧越大。正负都为 0 时给 None,不造假的 1.0 |
| 极端占比 extreme | |score| ≥ 0.6 的占比 | 高 → 情绪化严重,常见于涨停 / 爆雷当天 |
| 热度 heat | 有效条数 / 覆盖小时数(条/小时) | 需要时间戳齐全才有值 |
去重与剔除:命中开户广告 / 荐股分成等 NOISE_RE 的整条丢弃;前 120 字相同视为同一条;
清洗后空文本丢弃。三者都记在 dropped 里。
样本量:n < 15 时结论自带「样本仅 N 条,方向仅供参考」前缀,不装作没这回事。
报告里 「情绪构成(整体)」保持全局口径,另设 「分平台情绪构成」 逐源展示 (分母是该源自己的有效条数,所以各源占比之和 = 100%,可横向比较)。
颜色口径:跟 A 股习惯 —— 正面 = 红 #f0453a,负面 = 绿 #22a06b(与欧美相反)。
目录结构
.
├── SKILL.md # Agent Skill 定义(触发条件 / 用法 / 坑)
├── scripts/
│ ├── senti.py # CLI 入口
│ ├── fetchers.py # 八个数据源抓取器(按源并发,单源独立失败)
│ ├── sentiment.py # 打分引擎(正向最大匹配)
│ ├── lexicon.py # 词表唯一来源(正向 155 / 负向 174 / 程度 27)
│ ├── analysis.py # 聚合指标
│ ├── report.py # Markdown + HTML 报告(内联 SVG,无 CDN)
│ └── selftest.py # 回归自测(打分 18 条 + 解析 10 条)
├── references/
│ ├── sources.md # 各平台接口实测档案
│ └── indicators.md # 指标口径唯一来源
└── dist/icon/ # 项目图标(SVG 源 + 512×512 PNG)
自测
python scripts/selftest.py
覆盖否定、转折、程度副词、表情、涨跌幅数值、连板、广告过滤等断言。 改词表或改解析必须跑一遍全绿再上;发现新误判就把那一条补进自测,而不是口头说「改好了」。
已知限制
- 词典法不等于理解。反话("这票真好,我谢谢你")、反问、用「利好」讽刺,一律按字面算 ——
所以每一条都留了
hits供人工复核。 - 新闻 ≠ 情绪。
sina_stock/ths是媒体标题,eastmoney/tgb/sina_guba才是散户发言。 默认混一起算,需要分开看就用--sources单独跑。 - 东财拿不到评论正文。其官方数据接口对匿名请求一律返回同一个兜底包,只能用「评论数」当权重; 想要评论正文可以走新浪股吧(能拿到主帖 + 回复)。
- 时间口径。东财列表页只有
MM-DD HH:MM,年份按「超过今天 → 减一年」推断,跨年边界可能误判; 详情页有完整时间戳的以详情页为准。 - 不做涨跌预测。本项目只输出情绪序列,不建立「情绪 → 涨跌」的领先 / 滞后关系, 也不应作为唯一的买卖依据。
免责声明
数据来自各平台公开页面,仅供个人研究使用,不构成任何投资建议。 请自行遵守目标站点的 robots.txt 与服务条款;默认请求间隔 1 秒,请勿调至 0。
Scan to join WeChat group