老版 StockSight 是怎么搭的:两个脚本、三种文档、七个 Kibana 面板
README 只用一句话描述架构。本页把 24 个文件拆成六个组件,说明各自职责、数据怎么流、以及在什么地方会坏。
所有结论都对应到具体文件与行号,你可以直接打开源码核对。
StockSight 的 24 个文件里,真正干活的是哪六个?
其余文件是文档站资源与导出数据,可以忽略。
| 组件 | 体积 | 职责 | 不做什么 | 注意点 |
|---|---|---|---|---|
sentiment.py | 38,481 字节 / 983 行 | 两条采集路径(Twitter 流、新闻标题)+ 情绪打分 + 写 ES | 不读价格、不做预测、不下单 | 大部分逻辑塞在一个文件里,含 5 个类与 8 个顶层函数 |
stockprice.py | 10,266 字节 / 246 行 | 按固定间隔取 Yahoo 行情,写入同一个索引 | 不参与情绪计算 | URL 参数写死为 2 分钟线、5 天区间 |
config.py.sample | 1,111 字节 | 模板:ES 连接、Twitter 四件套凭据、NLTK 黑白名单、Twitter 关注列表 | 不是可直接运行的配置文件 | 必须手动复制成 config.py,否则脚本连 --help 都跑不出来 |
Dockerfile | 343 字节 | 基于 python:3.6 装依赖并设启动脚本 | 不含 Elasticsearch 与 Kibana | python:3.6 已于 2021-12 停止维护 |
docker-compose.yml | 665 字节 | 编排三个服务:应用、elasticsearch:5.6.16、kibana:5.6.16 | 不做反向代理与鉴权 | ES 单节点、堆内存固定 512MB;两个镜像都是 2017 年版本 |
export.json | 10,212 字节 | Kibana 面板导出:1 个仪表盘 + 1 个保存搜索 + 5 个可视化 | 不含索引映射 | 需要在 Kibana 里手动导入 saved objects |
同一个索引被两个脚本同时写入,这是它设计的核心:文本与价格放在一起,才能在 Kibana 里做「情绪 vs 价格」的叠加图。代价是索引结构必须同时容纳三种文档。
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 应用并生成四件套」这一步,官方入口与配额政策在过去几年有过多次变化,需以你申请时的实际情况为准(本站未验证)。
一个索引里塞了三种文档,字段各是什么?
字段名直接决定你在 Kibana 里能做什么图。下表取自源码里的写入语句与映射定义。
| 文档类型 | 关键字段 | 写入位置 | 适用场景 | 注意点 |
|---|---|---|---|---|
tweet | date / text / polarity / subjectivity / sentiment 等 | sentiment.py 第 227 行 | 看某段时间内的推文情绪分布 | 字段随版本演进过多次,旧教程里的字段名可能已失效 |
newsheadline | date / location / message / polarity / subjectivity / sentiment | sentiment.py 第 327 行 | 看新闻标题的情绪与来源分布 | 字段名是 message 而不是 text,写查询时容易搞混 |
stock | 行情字段(价格、成交量等) | stockprice.py 第 91 行 | 与情绪序列做叠加对照 | 采集频率固定 120 秒,粒度远粗于 2 分钟线 |
第 862 行的 ignore=[400, 404] 意味着索引已存在时不会报错,也不会更新映射。第一次跑完之后再改映射,需要手动删索引——而 --delindex 参数会直接删掉整个索引,历史数据一并清空。
情绪分数是怎么算出来的?
这一节是理解全部情绪结论的基础。规则不复杂,因此边界也很清楚。
| 环节 | 实现 | 例子 | 注意点 |
|---|---|---|---|
| 极性 | 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.subjectivity | 0 表示纯客观陈述 | 只在文本层计算,与极性来自不同模型 |
| 可选外挂 | 加 --websentiment 时,额外要求第三方服务也给出同向判断 | 三个来源都同意才判正 / 负 | 本机对 http://text-processing.com/api/sentiment/ 发 POST 得到 HTTP 405,该分支在本网络下不可用 |
这段代码里没有中文分词、没有财经领域词典、没有否定词处理,也没有把「涨停」「减持」「立案调查」这类 A 股语境词纳入规则。它对中文文本的判断结果需要实测验证——见「情绪模型」页。
StockSight 有哪些「跑久了就会坏」的设计
这些问题不影响第一次跑通,但会在长期使用中逐渐暴露。
| 位置 | 写法 | 后果 | 可行的加固方式 |
|---|---|---|---|
| 新闻标题抽取 | latestheadlines.append((i.next.next.next.next, url)) | 依赖 DOM 的固定层级,页面改版即失效;取不到内容时不会报错,只是没有数据 | 改成按结构选择器解析,并对空结果打日志 |
| 标题长度门槛 | if len(tokens) < 5: 硬编码 | 配置项 nltk_min_tokens = 1 形同虚设,短标题被静默丢弃 | 改成读配置项;把丢弃计数暴露到日志 |
| 忽略词判定 | 在 for t in nltk_tokens_ignored 循环体内使用 continue | continue 只跳出内层循环,不会跳过这条文档,忽略词实际未生效 | 用标志位或 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 的行情数据从哪来?为什么写死了 Yahoo 地址
stockprice.py 只有 246 行,其中最关键的是第 29 行那个 URL。本机实测了这个地址。
| 观察项 | 本机实测结果 | 说明 |
|---|---|---|
| 接口可达性 | HTTP 200,返回 87,872 字节 | 把 SYMBOL 换成 TSLA 直接请求,未被拦截 |
| 数据粒度 | dataGranularity = 2m | 与 URL 参数一致 |
| 返回条数 | 976 条时间戳 | 约 5 个交易日的 2 分钟线,符合 range=5d |
| 元数据字段 | 含 chartPreviousClose / currency / exchangeName / fiftyTwoWeekHigh 等 | 脚本只取其中一部分写入 ES |
| 长期可用性 | 本次可访问 | 该地址属于非公开文档化的接口,参数与返回结构可能变化,需以实测为准 |
注意「本次可访问」不等于「长期稳定」。非公开接口没有兼容性承诺,脚本又把参数写死在源码里,一旦接口调整就需要改代码。这是自建数据链路常见的维护成本。
关于 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 直接报「不是内部或外部命令」)。按「不伪造证据」原则,本站只用示意图并明确标注,不用任何生成图片冒充运行截图。