第二章:数据采集与清洗——从原始数据到可用资产

做AI投研,最怕什么?不是模型不够强,而是数据本身就有问题。我见过太多团队,模型调得天花乱坠,结果数据源里藏着几个坑,直接让预测结果偏离十万八千里。这一章,我们就来聊聊数据采集与清洗——说白了,就是怎么把散落在各处的原始数据,变成能喂给模型的干净食材。

2.1 数据源分类:结构化 vs 非结构化

数据源其实就两大类:结构化和非结构化。你想想看,Excel表格里的股价、财报里的营收数字,这些就是结构化数据——行是行,列是列,规规矩矩。而新闻标题、社交媒体评论、甚至CEO的语音采访,这些就是非结构化数据——杂乱无章,但信息量巨大。

核心区别:

  • 结构化数据:有固定模式,可直接存入数据库。例如:日频股价、财务指标、宏观经济数据。
  • 非结构化数据:无固定格式,需要额外处理。例如:新闻文本、研报PDF、财报电话会录音。

我个人习惯,在项目初期先梳理清楚数据源的结构化程度。为什么?因为处理成本完全不同。结构化数据拿来就能用,非结构化数据可能需要花80%的时间在清洗和转化上。我在一个量化项目中遇到过,团队花了两周爬取新闻数据,结果因为没做实体识别,模型把“苹果公司”和“苹果水果”混为一谈——嗯,这坑我踩过。

2.2 API数据获取:高效且规范

获取数据,首选API。为什么?因为API是数据提供方专门为你准备的接口,数据格式统一、更新及时、而且有频率限制保护你。我自己常用的金融数据API包括:

数据源 类型 特点
Yahoo Finance API 股票行情 免费,覆盖面广,但延迟较高
Alpha Vantage 多资产数据 免费额度有限,适合小规模实验
东方财富Choice A股数据 专业级,但需要付费授权
彭博终端API 全球金融 机构标配,成本极高

我建议你从免费API开始练手。比如用Python的yfinance库拉取历史股价,代码非常简单:

import yfinance as yf

# 获取贵州茅台的历史数据
stock = yf.Ticker("600519.SS")
data = stock.history(period="1y")
print(data.head())

这里有个小技巧:API调用时一定要加异常处理。我曾经在凌晨跑批量任务,结果API突然限流,整个脚本崩溃,数据全丢了。后来我养成了习惯——每次请求都加try-except,并且设置重试机制。

避坑指南:我曾经因为没看API文档的速率限制,连续发了1000个请求,结果IP被封了三天。建议每次请求后加time.sleep(0.5),做个有礼貌的爬虫。

2.3 网页爬虫基础:当API不够用时

有些数据没有API,或者API覆盖不全。这时候就需要爬虫上场了。爬虫的核心逻辑就三步:请求网页、解析HTML、提取数据。

我用requestsBeautifulSoup组合最多。举个例子,爬取某财经网站的新闻标题:

import requests
from bs4 import BeautifulSoup

url = "https://example-finance-news.com"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

# 找到所有新闻标题
titles = soup.find_all('h2', class_='news-title')
for title in titles:
    print(title.get_text().strip())

但要注意,很多网站现在都用动态加载(JavaScript渲染)。这时候requests就拿不到数据了。我建议用Selenium模拟浏览器,或者直接分析XHR请求。我记得有一次爬取某券商研报,页面数据是通过Ajax异步加载的,我花了半天才找到真正的数据接口——嗯,这种经验多了,你自然就会先看网络请求面板。

法律与道德提醒:爬虫不是万能的。请务必遵守网站的robots.txt协议,不要高频爬取,不要爬取需要登录才能访问的数据。我曾经见过有人因为爬取付费数据被起诉——这可不是闹着玩的。

2.4 数据清洗与标准化:脏数据是模型的天敌

数据拿到手,别急着建模。先看看数据里有没有坑。我总结了一套清洗流程,你直接拿去用:

  1. 缺失值处理:是直接删除,还是用均值/中位数填充?我个人习惯,如果缺失率超过30%,直接删掉这个特征;如果低于5%,用前向填充(金融时序数据常用)。
  2. 异常值检测:股价突然涨了1000%?大概率是数据错误。用3-sigma或IQR方法识别并处理。
  3. 数据类型统一:日期格式要统一成YYYY-MM-DD,数字要去掉逗号和货币符号。
  4. 去重:同一个数据被重复采集了?用drop_duplicates()搞定。

举个例子,清洗后的数据应该是这样的:

import pandas as pd

# 原始数据可能长这样
df = pd.DataFrame({
    'date': ['2024-01-01', '2024/01/02', '01-03-2024'],
    'price': ['100.50', '101.20', '102.30'],
    'volume': ['1,000,000', '2,000,000', None]
})

# 标准化处理
df['date'] = pd.to_datetime(df['date'])  # 统一日期格式
df['price'] = df['price'].astype(float)  # 转数值
df['volume'] = df['volume'].str.replace(',', '').astype(float)  # 去逗号
df['volume'] = df['volume'].fillna(method='ffill')  # 填充缺失值

print(df.dtypes)

标准化这一步,说白了就是让数据“说同一种语言”。不同数据源可能用不同的单位、不同的时间格式、不同的编码方式。我建议你建立一个数据字典,记录每个字段的原始格式和目标格式——这样团队协作时不会乱。

我的经验:数据清洗通常占整个项目时间的60%以上。别嫌麻烦,这一步做扎实了,后面的建模和决策才能站得住脚。我曾经因为没清洗干净,模型在回测时表现完美,实盘却一塌糊涂——后来发现是数据里混入了未来信息。

知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

数据采集与清洗全流程 数据源分类 结构化 非结构化 数据获取方式 API调用 网页爬虫 清洗与标准化 缺失值 异常值 可用数据集 → 投研模型 核心原则:先分类,再获取,最后清洗标准化 数据质量决定模型上限,清洗工作不可跳过

数据采集与清洗,是整个AI投研流程的基石。你想想看,如果地基没打好,上面盖的楼再漂亮也没用。我个人习惯,每次拿到新数据源,都会先花一天时间做探索性分析——看看数据长什么样,有没有明显的异常,分布是否合理。这一步看似简单,但能帮你避免后面90%的坑。

最后一个小建议:建立数据版本管理。每次清洗后,把原始数据和清洗后的数据都保存下来,并记录清洗规则。这样当模型效果不好时,你可以回溯检查是不是数据出了问题。我用DVC(Data Version Control)来管理,你也可以试试。


公众号:蓝海资料掘金营,微信deep3321