链路解剖

老版 StockSight 是怎么搭的:两个脚本、三种文档、七个 Kibana 面板

README 只用一句话描述架构。本页把 24 个文件拆成六个组件,说明各自职责、数据怎么流、以及在什么地方会坏。

所有结论都对应到具体文件与行号,你可以直接打开源码核对。

核对对象:shirosaidev/stocksight master 分支关键文件:sentiment.py 983 行 / stockprice.py 246 行核验日期:2026-09-30

采集源Twitter / 新闻标题 / Yahoo 价格
处理清洗 + NLTK 黑白名单 + 打分
存储ES 索引 stocksight(3 种文档)
展示Kibana 7 个 saved object
示意图:依据 sentiment.py、stockprice.py、docker-compose.yml 与 export.json 整理的链路;本机未启动 Elasticsearch,因此这不是运行截图。
StockSight · 组件清单

StockSight 的 24 个文件里,真正干活的是哪六个?

其余文件是文档站资源与导出数据,可以忽略。

组件体积职责不做什么注意点
sentiment.py38,481 字节 / 983 行两条采集路径(Twitter 流、新闻标题)+ 情绪打分 + 写 ES不读价格、不做预测、不下单大部分逻辑塞在一个文件里,含 5 个类与 8 个顶层函数
stockprice.py10,266 字节 / 246 行按固定间隔取 Yahoo 行情,写入同一个索引不参与情绪计算URL 参数写死为 2 分钟线、5 天区间
config.py.sample1,111 字节模板:ES 连接、Twitter 四件套凭据、NLTK 黑白名单、Twitter 关注列表不是可直接运行的配置文件必须手动复制成 config.py,否则脚本连 --help 都跑不出来
Dockerfile343 字节基于 python:3.6 装依赖并设启动脚本不含 Elasticsearch 与 Kibanapython:3.6 已于 2021-12 停止维护
docker-compose.yml665 字节编排三个服务:应用、elasticsearch:5.6.16、kibana:5.6.16不做反向代理与鉴权ES 单节点、堆内存固定 512MB;两个镜像都是 2017 年版本
export.json10,212 字节Kibana 面板导出:1 个仪表盘 + 1 个保存搜索 + 5 个可视化不含索引映射需要在 Kibana 里手动导入 saved objects

同一个索引被两个脚本同时写入,这是它设计的核心:文本与价格放在一起,才能在 Kibana 里做「情绪 vs 价格」的叠加图。代价是索引结构必须同时容纳三种文档。

StockSight · 数据流

StockSight 的一条数据从采集到进面板,中间经过哪几步?

两条采集路径的清洗与过滤逻辑并不相同,这是后面会出问题的地方。

步骤Twitter 路径新闻标题路径两者差异造成的后果
采集tweepy 流式接口,按关键词或关注列表过滤requests 抓一个网页,取其中的 h3 文本一条依赖 API 凭据,一条依赖网页结构
清洗clean_text() 去链接、去 HTML、去 RT,再去掉 @ 与 # 开头的词只做正则去标点后分词同一条新闻与同一条推文的文本形态不同
过滤检查 NLTK 黑白名单先检查 token 数 ≥5,再检查黑白名单标题长度门槛是硬编码 5,与配置项 nltk_min_tokens 无关
打分调用同一个 sentiment_analysis()调用同一个函数这一步是一致的
入库es.index(doc_type="tweet")es.index(doc_type="newsheadline")同一索引不同 type,在 ES 6 之后不再被支持
展示Kibana 导入 export.json 后按 date 字段建索引模式没有导入这一步,面板就是空的

「Twitter 路径」在今天还有一个额外前提:需要有效的 API 凭据。README 里写的「创建一个 Twitter 应用并生成四件套」这一步,官方入口与配额政策在过去几年有过多次变化,需以你申请时的实际情况为准(本站未验证)。

StockSight · 索引结构

一个索引里塞了三种文档,字段各是什么?

字段名直接决定你在 Kibana 里能做什么图。下表取自源码里的写入语句与映射定义。

文档类型关键字段写入位置适用场景注意点
tweetdate / text / polarity / subjectivity / sentiment 等sentiment.py 第 227 行看某段时间内的推文情绪分布字段随版本演进过多次,旧教程里的字段名可能已失效
newsheadlinedate / location / message / polarity / subjectivity / sentimentsentiment.py 第 327 行看新闻标题的情绪与来源分布字段名是 message 而不是 text,写查询时容易搞混
stock行情字段(价格、成交量等)stockprice.py 第 91 行与情绪序列做叠加对照采集频率固定 120 秒,粒度远粗于 2 分钟线
sentiment.py — 索引创建与映射(节选)# 第 741-743 行:注释写的是「set up elasticsearch mappings and create index」 mappings = { "mappings": { ... # 第 862 行 es.indices.create(index=args.index, body=mappings, ignore=[400, 404])

第 862 行的 ignore=[400, 404] 意味着索引已存在时不会报错,也不会更新映射。第一次跑完之后再改映射,需要手动删索引——而 --delindex 参数会直接删掉整个索引,历史数据一并清空。

StockSight · 打分规则

情绪分数是怎么算出来的?

这一节是理解全部情绪结论的基础。规则不复杂,因此边界也很清楚。

环节实现例子注意点
极性polarity = (TextBlob.polarity + VADER.compound) / 2两个模型都返回 0 时,极性为 0两个模型的取值范围不同,直接取平均是简化处理
标签TextBlob<0 且 VADER≤-0.05 → negative;TextBlob>0 且 VADER≥0.05 → positive;其余 → neutral一个模型说正、另一个说负时会落到 neutral双重条件让多数文本落入 neutral,这是实测中最明显的现象
主观度直接取 TextBlob.sentiment.subjectivity0 表示纯客观陈述只在文本层计算,与极性来自不同模型
可选外挂加 --websentiment 时,额外要求第三方服务也给出同向判断三个来源都同意才判正 / 负本机对 http://text-processing.com/api/sentiment/ 发 POST 得到 HTTP 405,该分支在本网络下不可用
sentiment.py — 标签判定(第 467 行起,节选)if sentiment_url is None: if text_tb.sentiment.polarity < 0 and text_vs['compound'] <= -0.05: sentiment = "negative" elif text_tb.sentiment.polarity > 0 and text_vs['compound'] >= 0.05: sentiment = "positive" else: sentiment = "neutral"

这段代码里没有中文分词、没有财经领域词典、没有否定词处理,也没有把「涨停」「减持」「立案调查」这类 A 股语境词纳入规则。它对中文文本的判断结果需要实测验证——见「情绪模型」页。

StockSight · 脆弱点

StockSight 有哪些「跑久了就会坏」的设计

这些问题不影响第一次跑通,但会在长期使用中逐渐暴露。

位置写法后果可行的加固方式
新闻标题抽取latestheadlines.append((i.next.next.next.next, url))依赖 DOM 的固定层级,页面改版即失效;取不到内容时不会报错,只是没有数据改成按结构选择器解析,并对空结果打日志
标题长度门槛if len(tokens) < 5: 硬编码配置项 nltk_min_tokens = 1 形同虚设,短标题被静默丢弃改成读配置项;把丢弃计数暴露到日志
忽略词判定在 for t in nltk_tokens_ignored 循环体内使用 continuecontinue 只跳出内层循环,不会跳过这条文档,忽略词实际未生效用标志位或 any() 判断后再决定是否入库
去重用一个内存列表 self.headlines 记录已见标题进程重启后历史全部丢失,同一条新闻会被重复计数用持久化的集合或直接依赖 ES 的唯一约束
索引写入es.index(..., doc_type=…)ES 6 起单索引多 type 被移除、ES 7 弃用、ES 8 删除;elasticsearch-py 8.x 也移除了该参数要么锁死 ES 5.x 环境,要么把三种文档拆成三个索引
价格区间URL 写死 interval=2m&range=5d只能拿到约 5 个交易日的 2 分钟线,做不了长周期回看参数外置成配置,按需要改区间
StockSight · 价格采集

StockSight 的行情数据从哪来?为什么写死了 Yahoo 地址

stockprice.py 只有 246 行,其中最关键的是第 29 行那个 URL。本机实测了这个地址。

stockprice.py 第 29 行(原文)url = "https://query1.finance.yahoo.com/v8/finance/chart/SYMBOL?region=US&lang=en-US&includePrePost=false&interval=2m&range=5d&corsDomain=finance.yahoo.com&.tsrc=finance"
观察项本机实测结果说明
接口可达性HTTP 200,返回 87,872 字节把 SYMBOL 换成 TSLA 直接请求,未被拦截
数据粒度dataGranularity = 2m与 URL 参数一致
返回条数976 条时间戳约 5 个交易日的 2 分钟线,符合 range=5d
元数据字段含 chartPreviousClose / currency / exchangeName / fiftyTwoWeekHigh 等脚本只取其中一部分写入 ES
长期可用性本次可访问该地址属于非公开文档化的接口,参数与返回结构可能变化,需以实测为准

注意「本次可访问」不等于「长期稳定」。非公开接口没有兼容性承诺,脚本又把参数写死在源码里,一旦接口调整就需要改代码。这是自建数据链路常见的维护成本。

FAQ

关于 StockSight 这套链路的常见问题有哪些

为什么把推文、新闻和价格放进同一个索引?

为了在 Kibana 里做同一条时间轴的叠加分析。三类文档共享 date 字段,建索引模式时可以一起纳入。代价是索引结构和查询都更复杂,而且在 ES 6 之后「一个索引多个 type」不再被支持。以官方源码与 Elasticsearch 官方文档为准。

忽略词配置为什么不起作用?

因为判定写成 for t in nltk_tokens_ignored: if t in tokens: ... continue,这个 continue 只跳出内层循环,外层流程继续执行到打分与入库,所以配置的忽略词实际上没有过滤效果。这是一处实现与配置不一致,以官方源码为准。

不装 Elasticsearch 能只用情绪打分吗?

理论上可以,但需要自己改代码:sentiment.py 在顶层就 import nltk / tweepy / elasticsearch / newspaper,且在第 227 行直接调用 es.index()。本机实测即使只跑 --help 也会因为导入链失败而退出,以官方实现为准。

Kibana 面板一定要导入 export.json 吗?

不是必须,但不导入就只有一个空索引。仓库自带的 export.json 里有 1 个仪表盘、1 个保存的搜索与 5 个可视化(polarity、sentinel、stockprice、tweets、wordcloud),导入路径是 Kibana 的「管理 → 已保存对象 → 导入」。以官方 README 的说明为准。

这套结构放到今天还合理吗?

字段设计(同一时间轴放文本与价格)与「标签化数据来源」的思路仍然合理;不合理的是实现层:单索引多 type、写死的抓取选择器、内存去重、硬编码区间参数。如果今天重做,通常会拆成多个索引或改用文档型存储。这是本站的判断,不是官方结论。

为什么本页的图都是示意图?

因为要截 Elasticsearch 与 Kibana 的真实界面,需要 Docker 起两个 5.6 版容器,而本机没有安装 Docker(docker --version 直接报「不是内部或外部命令」)。按「不伪造证据」原则,本站只用示意图并明确标注,不用任何生成图片冒充运行截图。

看懂结构之后,最现实的问题是「我今天能不能装上」

本机实测把官方 README 的安装步骤跑了一遍,失败点有四层。安装页给出完整的报错原文与可行顺序。