第三章:数据采集与清洗——金融数据的“淘金”之路
做金融大模型,数据就是命根子。这话一点都不夸张。
我见过太多团队,模型架构搞得花里胡哨,结果喂进去的数据一塌糊涂,最后出来的结果自然也是惨不忍睹。说白了,你喂的是金子,模型才能吐出金子;你喂的是垃圾,那产出只能是垃圾。
这一章,我们就来聊聊怎么搞到这些“金子”,以及怎么把“矿石”提炼成“纯金”。
3.1 金融数据的三大来源:财报、新闻、公告
金融数据来源很多,但最核心的,我个人认为就三个:财报、新闻、公告。这三者构成了金融市场的“信息三角”。
| 数据来源 | 核心内容 | 更新频率 | 获取难度 |
|---|---|---|---|
| 财报 | 营收、利润、现金流、资产负债等 | 季度/年度 | 中等(结构化较好) |
| 新闻 | 行业动态、政策解读、公司事件 | 实时/每日 | 较高(非结构化) |
| 公告 | 重大合同、人事变动、股权变更 | 不定期 | 中等(格式较规范) |
为什么是这三个?财报告诉你公司“过去”怎么样,新闻告诉你“现在”发生了什么,公告则暗示“未来”可能有什么变化。三者结合,才能拼凑出一个相对完整的图景。
我的经验: 别只盯着财报。很多量化策略的失效,就是因为忽略了新闻情绪。我记得有一次,一家公司财报数据非常漂亮,但新闻里全是负面报道,结果股价第二天就崩了。所以,多源数据融合才是王道。
3.2 网络爬虫基础:Requests vs. Scrapy
数据不会自己送上门来。你得去“拿”。网络爬虫就是干这个的。
对于初学者,我建议从 Requests 入手。它简单、直接,就像一把瑞士军刀,适合处理小规模、结构简单的页面。
import requests
url = "https://api.example.com/financial/report?code=600519"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
print(data)
else:
print(f"请求失败,状态码:{response.status_code}")
嗯,这里要注意。很多金融网站都有反爬机制。你直接拿 requests.get() 去抓,大概率会被封IP。所以,headers 里的 User-Agent 一定要伪装成浏览器。
那什么时候用 Scrapy 呢?当你需要大规模、持续性地采集数据时。比如,你要抓取过去十年所有上市公司的财报数据。Scrapy 就像一个工业级的挖掘机,支持异步、分布式、自动重试等高级功能。
避坑指南: 我曾经为了图省事,直接用 Requests 写了个循环去抓几千个页面。结果跑了半小时,IP 被封了,数据全丢了。后来改用 Scrapy,配合代理池和自动重试机制,才顺利搞定。所以,小项目用 Requests,大项目上 Scrapy,别犹豫。
3.3 数据清洗:去重、去噪、标准化
数据采集回来,只是第一步。真正的“脏活累活”才刚刚开始。
你想想看,从网上扒下来的数据,什么情况都有:重复的、缺失的、格式混乱的、夹杂着HTML标签的……不洗干净,模型根本没法用。
3.3.1 去重
重复数据是最大的噪音。同一个公告,可能被多个网站转载,你抓下来就是好几份。最简单的去重方法就是基于 内容哈希。
import hashlib
def get_content_hash(text):
return hashlib.md5(text.encode('utf-8')).hexdigest()
seen_hashes = set()
unique_data = []
for item in raw_data:
content_hash = get_content_hash(item['content'])
if content_hash not in seen_hashes:
seen_hashes.add(content_hash)
unique_data.append(item)
3.3.2 去噪
去噪就是去掉那些“没用”的信息。比如,新闻页面里的广告、导航栏、版权声明等。对于金融数据,我常用的方法是 正则表达式 + 规则过滤。
import re
def clean_text(text):
# 去除HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
# 去除常见的广告关键词
ad_keywords = ['广告', '推广', '点击这里']
for keyword in ad_keywords:
text = text.replace(keyword, '')
return text
警告: 正则表达式虽然强大,但容易误伤。比如,你本想去除“广告”二字,结果把“广告法”也去掉了。所以,清洗规则一定要在真实数据上反复测试,别想当然。
3.3.3 标准化
标准化是让数据“说同一种语言”。比如,日期格式,有的用“2024-01-01”,有的用“2024年1月1日”。金额单位,有的用“元”,有的用“万元”。
from datetime import datetime
def standardize_date(date_str):
# 尝试多种格式解析
formats = ['%Y-%m-%d', '%Y年%m月%d日', '%Y/%m/%d']
for fmt in formats:
try:
return datetime.strptime(date_str, fmt).strftime('%Y-%m-%d')
except ValueError:
continue
return None # 解析失败
3.4 数据存储:CSV / JSON / 数据库
清洗干净的数据,得找个地方存起来。选哪种存储方式,取决于你的使用场景。
| 存储方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CSV | 简单、通用、Excel可直接打开 | 不支持复杂结构、查询慢 | 小规模数据、快速预览 |
| JSON | 支持嵌套结构、可读性好 | 文件体积大、解析慢 | API交互、配置文件 |
| 数据库 | 支持复杂查询、事务、并发 | 需要安装、学习成本高 | 大规模数据、生产环境 |
我个人习惯是:原始数据存 JSON,保留最完整的信息;清洗后的结构化数据存数据库,方便后续查询和分析;中间结果或小批量数据用 CSV,方便调试和分享。
import json
import csv
import sqlite3
# 存为 JSON
with open('financial_data.json', 'w', encoding='utf-8') as f:
json.dump(cleaned_data, f, ensure_ascii=False, indent=2)
# 存为 CSV
with open('financial_data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['date', 'title', 'content'])
writer.writeheader()
writer.writerows(cleaned_data)
# 存入 SQLite 数据库
conn = sqlite3.connect('financial.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
title TEXT,
content TEXT
)
''')
for item in cleaned_data:
cursor.execute('INSERT INTO news (date, title, content) VALUES (?, ?, ?)',
(item['date'], item['title'], item['content']))
conn.commit()
conn.close()
核心逻辑: 数据采集与清洗,本质上是一个“从无序到有序”的过程。你从互联网这个巨大的“信息海洋”里,把有用的“鱼”捞出来,去掉“泥沙”,再按类别放进不同的“鱼缸”里。这个过程决定了你模型的天花板。
好了,这一章的内容就到这里。数据采集与清洗,听起来枯燥,但它是整个金融大模型项目的基石。你花在清洗上的每一分钟,都会在模型训练和推理阶段得到回报。