价格发现越来越发生在文本里。一条财报电话会的措辞变化、一则监管问询函、一个论坛里的持仓讨论,往往比正式披露更早反映预期。问题不是”文本重不重要”,而是你有没有能力把它变成能进回测循环的数值信号。
这篇文章给你一条从网页到信号的完整流水线,并且每一步都附可运行的 Python。我们不只讲”怎么爬”,更讲”怎么让它可信”。

一、先讲红线:爬虫不是”能爬就爬”#
动手之前,有三件事比代码更重要:
- 读 robots.txt。 目标站根目录下的
robots.txt写明了哪些路径允许抓取、抓取频率上限。这是行业共识级的礼貌与合规底线。 - 限速与标识。 给请求加
User-Agent,并在两次请求间sleep(随机 1~3 秒)。高频轰炸既可能把对方服务器打挂,也会让你的 IP 很快进黑名单。 - 看清楚 ToS 与版权。 新闻、研报、论坛内容大多有版权与”禁止抓取”条款。研究用途vs商业用途、个人vs分发,法律边界完全不同。本文所有代码仅作方法演示,实盘务必以目标平台授权为准。
二、Python 实战 1:采集财经新闻标题#
最朴素的静态页面,用 requests + BeautifulSoup 就够。动态渲染(JS 加载)的页面要上 Selenium / Playwright,思路一致,只是多一层”等页面渲染完”。
import requests, time, random
from bs4 import BeautifulSoup
HEADERS = {"User-Agent": "Mozilla/5.0 (quant-research; contact@example.com)"}
def fetch_headlines(url, css_selector=".news-title", pause=(1, 3)):
"""抓取某新闻列表页的标题文本。"""
resp = requests.get(url, headers=HEADERS, timeout=10)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, "html.parser")
items = [h.get_text(strip=True) for h in soup.select(css_selector)]
time.sleep(random.uniform(*pause)) # 礼貌性限速,千万别删
return items
# 用法示例(请替换为你有授权抓取的源):
# headlines = fetch_headlines("https://example.com/finance/news")
# print(headlines[:5])python要点:选择器(css_selector)和目标站 DOM 结构强绑定,对方改版你的爬虫就失效—这是文本采集最大的运维成本。生产上建议把”抓取”和”解析”解耦,解析规则可配置化。
三、Python 实战 2:中文分词与清洗#
英文按空格分词,中文要借助 jieba。清洗的核心是:去停用词、去标点、过滤单字噪声,否则 TF-IDF 会被”的/了/在”这类高频无意义词占据。
import jieba
from collections import Counter
STOP = set("的 了 在 是 和 与 及 对 等 也 都 我们 你 我 该 这 那".split())
def tokenize(text):
return [w for w in jieba.lcut(text)
if w.strip() and w not in STOP and len(w) > 1]
# 示例语料(真实场景来自第二步抓取的标题/正文)
docs = [
"公司营收同比增长超预期,盈利上调",
"监管下调行业风险评级,债务压力缓解",
"核心产品创新突破,利好长期增长",
"季度亏损扩大,诉讼风险上升",
"分红方案超预期,股东回报增强",
"需求下滑叠加成本上涨,盈利承压",
]
tokens = [tokenize(d) for d in docs]
print(tokens[0]) # ['公司','营收','同比','增长','超预期','盈利','上调']python四、Python 实战 3:情感打分 → 日度情绪指数#
词典法是最稳的起点:维护一份”利好词/利空词”表,统计一篇文本里正负词的出现比例,归一化到 [-1, 1]。它可解释、不易过拟合,适合作为信号的”baseline”。
POS = set("增长 超预期 利好 盈利 上调 分红 创新 突破 增强 缓解".split())
NEG = set("亏损 下调 风险 监管 债务 下滑 诉讼 承压 扩大 裁员".split())
def sentiment(text):
toks = tokenize(text)
p = sum(w in POS for w in toks)
n = sum(w in NEG for w in toks)
return (p - n) / max(1, p + n) # 落在 [-1, 1]
# 构造 30 天的合成新闻流,每天聚合出一条日度情绪
import numpy as np
rng = np.random.default_rng(0)
daily = []
for _ in range(30):
day_news = rng.choice(docs, size=int(rng.integers(3, 9)))
daily.append(np.mean([sentiment(n) for n in day_news]))
daily = np.array(daily)
print("前 5 日日度情绪:", np.round(daily[:5], 2))python把这条指数画出来,你会看到它有明显的主题波动—某些天因为利空词集中而跌到负值区,某些天因利好扎堆而冲高。

五、Python 实战 4:TF-IDF 把文本压成特征,并做领先-滞后检验#
单一情感值太粗。TfidfVectorizer 能把每篇文档变成”词×文档”的数值矩阵,行归一后,每篇文档就是高维空间里的一个点,可以做聚类、相似度、甚至喂给下游模型。
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer(tokenizer=tokenize, max_features=200)
X = vec.fit_transform(docs)
print("TF-IDF 矩阵形状 (文档数 × 词数):", X.shape)
# 领先-滞后检验:情绪是否真的领先于收益?
# 假设次日收益受「当日情绪」驱动:ret[t] = a * sent[t-1] + 噪声
ret = 0.5 * np.roll(daily, 1) + rng.normal(0, 0.4, len(daily))
lead_corr = np.corrcoef(daily[:-1], ret[1:])[0, 1]
print(f"情绪(t) 与 收益(t+1) 的相关系数: {lead_corr:.3f}")python
如果 lead_corr 显著为正,说明“今天文本情绪 → 明天价格”这条链路在样本里成立。但注意:这只说明样本里成立。 真正要下结论,必须跨时间段、跨标的重复检验,并用样本外数据验证。绝大多数“文本 alpha”死在这第二步。
五、把单篇打分落成日频面板#
上面的 daily 只是 30 个随机数。真实工程里,你要维护一张「日期 × 来源」的面板,而时间戳是防止前视偏差的生命线:
import pandas as pd
# 每条记录都带发布时间戳——它必须早于你用它下注的时刻
records = pd.DataFrame({
"datetime": pd.date_range("2026-01-01", periods=200, freq="1h"),
"source": np.random.choice(["新闻", "研报", "论坛"], 200),
"text": np.random.choice(docs, 200),
})
records["sent"] = records["text"].map(sentiment)
daily_panel = (records.set_index("datetime")["sent"]
.resample("1D").mean() # 日频聚合
.rolling(5).mean()) # 5 日平滑,抑制噪声
print(daily_panel.dropna().head())python三个关键工程点:(1) 时间戳必须早于你下注的时刻,否则就是前视;(2) 不同来源要分开存,方便做“新闻 vs 论坛”的截面对比与加权;(3) 对原始情绪做平滑(移动平均)通常能提升信噪比,但会引入滞后,必须在回测里显式建模这段延迟。
进阶:词典法之外#
词典法便宜、可解释,但抓不住反讽和上下文。进阶路线有两条:
- FinBERT / 金融预训练模型:在金融语料上微调的 BERT,能给出句子级情感概率,对“业绩‘好’得离谱”这类反讽更鲁棒;代价是推理成本与训练数据偏差。
- 词向量 / 主题模型:用 Word2Vec、LDA 把“营收超预期”和“利润大涨”在向量空间里拉近距离,做语义聚类,发现词典法漏掉的隐性主题。
无论哪条路线,最终都要回到第四步的领先-滞后检验——模型越复杂,越要证明它不是在拟合样本噪声。
六、真实陷阱:别让你的信号是幻觉#
1. 前视偏差(最致命)。 用”当天收盘后发布的财报新闻”去解释”当天收益”,是典型的前视。文本时间戳必须严格早于你用它下注的时刻;新闻抓取时间、数据入库时间都要打点留痕。
2. 机器人放大与回音室。 社交媒体上大量内容由机器人生成,情绪指数会被水军和算法推荐放大。要做的不是直接吃原始情绪,而是做异常度检测(今天情绪相对过去 N 天 Z-score 偏离多少),过滤”日常噪声”。
3. 文本噪声 > 信号。 错别字、同义词、反讽(“这业绩真是’好’啊”)都会污染词典法。进阶用 FinBERT 这类金融预训练模型做上下文情感,但模型本身也可能过拟合到训练样本的特定表述。
4. 幸存者偏差。 你能抓到的论坛帖子,往往来自还活着的账号;退市、封号的那些声音消失了,你的语料天然偏乐观。
5. 合规与可得性。 抓取频率受限、源站改版、接口收费变动,都会让”信号”变成”时有时无的噪声”。生产系统必须监控数据源健康度,信号缺失时要能优雅降级。
小结#
网页爬虫与文本挖掘,本质是把非结构化的语言翻译成结构化的数值。它的门槛不在爬虫本身,而在:合规地、稳定地、时序正确地把文本变成可信信号,再用领先-滞后和样本外检验证明它不是幻觉。词典法打底、TF-IDF 做特征工程、FinBERT 做语义升级—这条流水线,是今天另类数据量化最值得投入的基本功之一。
至此,我们聊完了”行为组合理论”(怎么组织你的心理账户)和”文本挖掘”(怎么从语言里挖信号)。两者看似无关,其实都指向同一件事:量化不只是数学,更是理解信息与人的方式。