1. 项目概述:基金知识图谱的价值与挑战
在金融投资领域,基金产品数量庞大、关联复杂,普通投资者往往难以全面把握基金间的关联关系和深层特征。传统的关键词搜索和表格展示方式,无法直观呈现基金经理、持仓股票、行业分布等多维度信息的内在联系。这正是我们构建基金知识图谱的核心价值所在——将碎片化的基金数据转化为可视化的关系网络。
"天天基金网"作为国内领先的基金数据平台,包含了丰富的基金基础信息、历史净值、持仓明细等数据。但原始数据存在三个主要问题:一是信息分散在不同页面,需要聚合;二是部分字段格式不规范(如持仓比例有"10.12%"和"10.12"混用);三是关联实体未标准化(如"A股"和"沪深股市"指向同一概念)。这些正是数据清洗需要解决的重点问题。
知识图谱采用Neo4j图数据库存储,相比传统关系型数据库,其优势主要体现在:
- 天然适合表示"基金-经理-股票"这类网状关系
- 支持高效的关联查询(如"找出所有重仓新能源股票的消费类基金")
- 提供直观的可视化展示,便于发现隐藏模式
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集方案设计与实现
2.1 目标数据识别与拆分
通过分析天天基金网的页面结构,我们确定需要采集的六类核心数据:
- 基金基础信息(代码、名称、类型、成立日期等)
- 基金经理信息(从业年限、管理产品、历史业绩)
- 持仓明细(股票/债券名称、占比、市值)
- 行业配置(申万一级行业分类及比例)
- 净值变动(每日净值、累计收益)
- 关联关系(基金经理变更、基金公司旗下产品)
注意:部分数据需要跨页面关联采集,如基金详情页只显示当前经理,历史经理变更需从"基金经理"子页面获取。
2.2 Python采集脚本编写
使用Requests+BeautifulSoup组合实现基础采集,关键代码如下:
python复制import requests
from bs4 import BeautifulSoup
def get_fund_basic(fund_code):
url = f"https://fund.eastmoney.com/{fund_code}.html"
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
# 提取基础信息
info_table = soup.find('div', class_='infoOfFund')
fund_type = info_table.find('a').text
# 更多字段提取逻辑...
return {
'code': fund_code,
'type': fund_type,
# 其他字段...
}
对于动态加载的数据(如历史持仓),需要使用Selenium模拟浏览器操作:
python复制from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get(f"https://fundf10.eastmoney.com/ccmx_{fund_code}.html")
# 点击"显示全部持仓"按钮
show_all = driver.find_element(By.XPATH, '//a[contains(text(),"显示全部")]')
show_all.click()
# 解析持仓表格
holdings = []
rows = driver.find_elements(By.XPATH, '//table[@class="w782 comm tzxq"]/tbody/tr')
for row in rows:
cols = row.find_elements(By.TAG_NAME, 'td')
holdings.append({
'stock': cols[1].text,
'ratio': float(cols[2].text.strip('%'))
})
2.3 反爬策略应对方案
天天基金网设有基础反爬机制,我们采用以下应对策略:
- 请求间隔随机化(1-3秒)
- 轮换User-Agent池
- 关键页面使用代理IP
- 异常状态码自动重试
- 重要数据本地缓存
实测中需要注意:净值数据接口有频率限制(约10次/分钟),建议在非交易时段批量采集。
3. 数据清洗与标准化流程
3.1 原始数据问题诊断
采集到的原始数据主要存在四类问题:
- 格式不一致:数字("12.34" vs "12.34%")、日期("2023-01-01" vs "2023/1/1")
- 信息缺失:部分基金缺少行业配置数据
- 命名歧义:"腾讯控股"与"腾讯控股(00700.HK)"
- 异常值:持仓比例合计超过100%(因四舍五入)
3.2 清洗流水线设计
使用Pandas构建五步清洗流程:
python复制import pandas as pd
import numpy as np
def clean_holding_data(raw_df):
# 第一步:格式标准化
raw_df['ratio'] = raw_df['ratio'].str.replace('%','').astype(float)
# 第二步:股票名称规范化
raw_df['stock'] = raw_df['stock'].apply(
lambda x: x.split('(')[0] if '(' in x else x)
# 第三步:处理缺失值
if raw_df['ratio'].isnull().any():
avg_ratio = raw_df['ratio'].mean()
raw_df['ratio'] = raw_df['ratio'].fillna(avg_ratio)
# 第四步:异常值修正
total_ratio = raw_df['ratio'].sum()
if total_ratio > 100:
raw_df['ratio'] = raw_df['ratio'] * 100 / total_ratio
# 第五步:类型转换
raw_df['ratio'] = raw_df['ratio'].round(2)
return raw_df
3.3 实体统一与消歧
建立三个标准化映射表解决命名不一致问题:
- 股票别名表("腾讯控股"→"腾讯控股(00700.HK)")
- 行业分类表("互联网"→"传媒")
- 基金经理ID映射(同名不同人问题)
python复制# 示例:行业名称标准化
industry_mapping = {
'互联网': '传媒',
'IT': '计算机',
'白酒': '食品饮料'
}
df['industry'] = df['industry'].map(
lambda x: industry_mapping.get(x, x))
4. Neo4j图数据库建模与导入
4.1 数据模型设计
基金知识图谱包含五类节点和六类关系:
节点类型:
- 基金(代码、名称、类型、规模)
- 股票(代码、名称、行业)
- 基金经理(ID、姓名、从业年限)
- 基金公司(名称、成立时间)
- 行业(分类标准、名称)
关系类型:
- 基金持仓股票(比例、市值)
- 基金属于行业(比例)
- 基金经理管理基金(起始日期)
- 基金公司发行基金
- 股票属于行业
- 基金经理任职公司
4.2 Neo4j数据导入
使用Cypher语句创建节点和关系:
cypher复制// 创建基金节点
CREATE (f:Fund {
code: '110022',
name: '易方达消费行业股票',
type: '股票型',
established: date('2010-08-20')
})
// 创建股票节点
CREATE (s:Stock {
code: '600519',
name: '贵州茅台',
industry: '食品饮料'
})
// 创建持仓关系
MATCH (f:Fund {code: '110022'}), (s:Stock {code: '600519'})
CREATE (f)-[h:HOLDS {
ratio: 9.87,
market_value: 2875000000
}]->(s)
对于批量导入,建议使用apoc.load.csv方法:
cypher复制CALL apoc.load.csv('file:///fund_nodes.csv') YIELD map
CREATE (f:Fund)
SET f = map
4.3 性能优化技巧
-
索引优化:
cypher复制CREATE INDEX fund_code_index FOR (f:Fund) ON (f.code) CREATE INDEX stock_code_index FOR (s:Stock) ON (s.code) -
批量写入时关闭自动索引:
cypher复制CALL apoc.periodic.iterate( 'UNWIND $batch AS row RETURN row', 'CREATE (f:Fund) SET f = row', {batchSize:1000, parallel:true, params:{batch:$batch}}) -
内存配置调整(neo4j.conf):
code复制dbms.memory.heap.initial_size=4G dbms.memory.heap.max_size=8G dbms.memory.pagecache.size=2G
5. 典型应用场景与查询示例
5.1 基金经理能力圈分析
查询某基金经理最擅长的行业:
cypher复制MATCH (m:Manager)-[:MANAGES]->(f:Fund)-[:IN_INDUSTRY]->(i:Industry)
WHERE m.name = '张坤'
RETURN i.name AS industry, AVG(f.return_1y) AS avg_return
ORDER BY avg_return DESC LIMIT 3
5.2 行业关联度分析
找出与新能源行业最常共同持仓的行业:
cypher复制MATCH (i1:Industry {name:'新能源'})<-[:IN_INDUSTRY]-(s1:Stock)
<-[:HOLDS]-(f:Fund)-[:HOLDS]->(s2:Stock)-[:IN_INDUSTRY]->(i2:Industry)
WHERE i1 <> i2
RETURN i2.name AS related_industry, COUNT(*) AS co_occurrence
ORDER BY co_occurrence DESC LIMIT 5
5.3 基金相似度推荐
基于持仓相似度的基金推荐:
cypher复制MATCH (f1:Fund {code:'110022'})-[:HOLDS]->(s:Stock)<-[:HOLDS]-(f2:Fund)
WITH f1, f2, COUNT(s) AS common_stocks
MATCH (f1)-[:HOLDS]->(s1:Stock)
MATCH (f2)-[:HOLDS]->(s2:Stock)
WITH f1, f2, common_stocks,
COUNT(DISTINCT s1) AS s1_count,
COUNT(DISTINCT s2) AS s2_count
RETURN f2.name AS recommended_fund,
common_stocks*1.0/(s1_count + s2_count - common_stocks) AS jaccard_similarity
ORDER BY jaccard_similarity DESC LIMIT 5
6. 常见问题与解决方案
6.1 数据采集环节
Q1:部分基金页面返回403错误
- 检查请求头是否包含完整User-Agent
- 添加Referer字段(如
'Referer': 'https://fund.eastmoney.com/') - 启用代理IP轮换
Q2:动态加载数据无法抓取
- 使用Selenium时确保元素加载完成(WebDriverWait)
- 尝试直接调用页面中的JSON接口(通过Chrome开发者工具分析)
6.2 数据清洗环节
Q3:行业分类标准不一致
- 建立行业映射表统一处理
- 对未明确分类的股票,使用其所属申万一级行业
Q4:基金规模单位不统一(亿元vs万元)
python复制def clean_scale(scale_str):
if '亿' in scale_str:
return float(scale_str.replace('亿','')) * 100000000
elif '万' in scale_str:
return float(scale_str.replace('万','')) * 10000
else:
return float(scale_str)
6.3 Neo4j环节
Q5:批量导入速度慢
- 使用
apoc.periodic.iterate分批次提交 - 关闭自动索引(
dbms.index_sampling.background_enabled=false) - 调整JVM内存参数
Q6:复杂查询超时
- 添加适当的索引
- 使用PROFILE分析查询计划
- 对多跳查询设置路径长度上限
7. 进阶优化方向
-
增量更新机制:
- 记录最后采集时间戳
- 使用APOC的图算法识别新增/变更节点
- 部分更新而非全量重建
-
数据质量监控:
- 设置校验规则(如持仓合计≈100%)
- 定期运行质量检查脚本
- 异常数据自动报警
-
性能提升:
- 对大规模数据分图存储
- 使用Neo4j的Fabric功能实现分片查询
- 热点数据预计算(如行业关联度矩阵)
-
应用扩展:
- 结合NLP处理基金公告文本
- 添加时序分析(持仓变动趋势)
- 集成风险评估模型
在实际部署中,我们使用Docker容器化整个系统,通过Airflow调度每日数据更新任务。对于千万级节点的大规模图谱,建议采用Neo4j企业版集群部署,配合Bloom进行可视化展示。
