原链接是: Java 量化系列(三十三):全自动维护股票与板块 概念 地域关联关系,效率拉满
你是否也踩过这些坑?🤔 做量化策略分析时,想筛选「某板块下的全部标的」,却要手动整理成 Excel 表格反复导入;想分析「概念联动的时序数据」,却因板块与标的的关系更新不及时,导致回测结果失真;更头疼的是,地域、板块、概念三类关联数据混在一起,维护一次就要耗费大半天,还容易遗漏、出错。
对于量化系统而言,标的与分类的关联关系 是所有维度分析的基石,就像建房子的钢筋骨架,骨架搭不稳,后续的策略筛选、趋势分析都是空中楼阁。
今天这篇量化系列第 33 篇,就给大家带来一套「全自动、高性能、可复用」的 Java 落地方案:基于东方财富数据源,实现股票与板块、概念、地域三者关联关系的批量爬取、异步入库、全量更新,彻底告别手动维护的低效与繁琐。全程聚焦技术实现与架构设计,无任何投资相关引导,纯技术干货,放心收藏复用!
一、
核心痛点:为什么必须做「关联关系自动化维护」?
在量化数据开发中,「标的 - 分类」关联关系的维护,是最容易被忽视但又极其重要的环节,手动维护的痛点堪称致命:
✅ 效率极低:一个板块少则几十只标的,多则上百只,三类分类全量维护,耗时耗力还容易漏绑;
✅ 数据滞后:板块成分标的会动态调整,手动维护永远慢一步,策略用旧数据分析,结果毫无参考性;
✅ 耦合性高:手动整理的 Excel 数据,无法直接对接量化策略,还要二次开发解析,浪费开发资源;
✅ 容错率低:手动录入编码、名称,极易出现错别字、编码错误,导致关联关系失效。
而我们今天的方案,完美解决以上所有问题:一次开发,永久复用,全自动同步最新关联关系,数据精准无误差,开发效率直接提升 200%+。
二、
基础基石:关联关系存储表设计(核心表结构解析)
要维护股票与板块 / 概念 / 地域的关联关系,首先要做好数据存储的底层设计,这是所有业务逻辑的基础。本次实战的核心表为 stock_bk_stock,是标准的多对多关联中间表,专门承载「股票 - 分类」的映射关系,先吃透表结构,再看代码会事半功倍。
2.1 完整建表语句(可直接复用)
CREATE TABLE `stock_bk_stock` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键自增ID',
`stock_code` varchar(20) DEFAULT NULL COMMENT '股票唯一编码',
`stock_name` varchar(100) DEFAULT NULL COMMENT '股票名称',
`bk_code` varchar(20) DEFAULT NULL COMMENT '板块/概念/地域编码',
`bk_name` varchar(100) DEFAULT NULL COMMENT '板块/概念/地域名称',
`bk_type` tinyint(1) DEFAULT '2' COMMENT '分类类型 1=板块 2=概念 3=地区',
`create_date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '自动更新创建时间',
`score_type` int DEFAULT NULL COMMENT '标的在分类内的排名评级',
`score_message` varchar(255) DEFAULT NULL COMMENT '评级原因说明',
`market_num` int DEFAULT NULL COMMENT '标的在分类内的市值排名',
`market_percent` decimal(10,2) DEFAULT NULL COMMENT '市值排名占比',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_stock_bk_stock_1` (`stock_code`) USING BTREE, -- 按股票查分类,必备索引
KEY `idx_stock_bk_stock_2` (`bk_code`) USING BTREE -- 按分类查股票,必备索引
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='股票与板块/概念/地域关联关系表';
2.2 核心字段设计思路(划重点)
💡 字段设计不是越多越好,而是精准适配业务需求 + 兼顾扩展性,这张表的设计堪称典范:
- 核心关联字段 :
stock_code+bk_code+bk_type三者组合,是整个表的灵魂。精准标记「哪只股票」属于「哪个分类」「什么类型的分类」,不会出现任何混淆; - 分类类型枚举绑定 :
bk_type字段的值 1/2/3,完美对应我们之前定义的BKType枚举(1 = 板块、2 = 概念、3 = 地域),代码与数据库完全统一,无歧义; - 性能索引优化:针对两个高频查询场景(按股票查所属分类、按分类查包含股票),建立专属索引,百万级数据查询毫秒级响应;
- 预留扩展字段 :
score_type、market_num等字段,为后续的「分类内标的排名、市值分析」预留了空间,无需二次改表,直接扩展业务逻辑; - 自动时间维护 :
create_date字段自动更新,无需代码手动赋值,省心省力。
2.3 对应 Java 实体类 StockBkStockDo
基于 Mybatis-Plus 实现 ORM 映射,字段与数据库一一对应,无冗余代码,可直接注入使用:
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("stock_bk_stock")
public class StockBkStockDo implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/** 股票编码 */
@TableField("stock_code")
private String stockCode;
/** 股票名称 */
@TableField("stock_name")
private String stockName;
/** 板块/概念/地域编码 */
@TableField("bk_code")
private String bkCode;
/** 分类类型 1=板块 2=概念 3=地域 */
@TableField("bk_type")
private Integer bkType;
/** 板块/概念/地域名称 */
@TableField("bk_name")
private String bkName;
/** 自动更新时间 */
@TableField("create_date")
private Date createDate;
/** 分类内排名评级 */
@TableField("score_type")
private Integer scoreType;
/** 评级原因 */
@TableField("score_message")
private String scoreMessage;
/** 市值排名 */
@TableField("market_num")
private Integer marketNum;
/** 市值占比 */
@TableField("market_percent")
private Double marketPercent;
/** 非数据库字段:页面地址,业务扩展用 */
@TableField(exist = false)
private String webUrl;
}
三、
核心实战:全自动关联关系同步全流程(完整代码 + 详细注释)
这是本文的重中之重,整套方案的核心业务逻辑全部集中在这里。我们的核心目标很明确:从东财爬取全量板块、概念、地域的成分标的,异步批量入库,完成所有关联关系的自动化维护。
整套逻辑分为「核心流程总控 → 异步批量处理 → 精准数据爬取 → JSON 数据解析」4 个步骤,层层递进,逻辑清晰,所有代码均可直接复制到项目中使用。
✅ 核心原则:整体业务流程
清空历史关联数据 → 按分类类型(板块/概念/地域)分批处理 → 异步爬取每个分类的成分标的 → 解析数据封装实体 → 批量入库 → 完成全量同步
3.1 第一步:总控入口方法 - 一键触发全量同步
这是整个功能的入口,一行代码即可触发「板块 + 概念 + 地域」三类关联关系的全量同步,逻辑极简,调用方便,是典型的「门面模式」设计,对外暴露简单接口,对内封装复杂逻辑。
/**
* 【核心入口】一键触发 股票与板块、概念、地域 关联关系全量同步
* @return 统一返回结果
*/
public OutputResult asyncBkGnDy() {
// 可选:清空历史数据,保证数据最新无冗余(根据业务需求选择是否开启)
// stockBkStockDomainService.truncate();
// 分别同步 板块、概念、地域 三类关联关系
syncAllRelationStock(BKType.BK); // 同步板块-股票关联
syncAllRelationStock(BKType.GN); // 同步概念-股票关联
syncAllRelationStock(BKType.DY); // 同步地域-股票关联
log.info("股票与板块/概念/地域关联关系全量同步任务已启动");
return OutputResult.buildSucc();
}
3.2 第二步:核心异步处理方法 - 高性能批量同步
这是整套方案的性能核心!如果单线程处理几百个分类,耗时会非常久,这里我们采用「线程池异步处理 + CountDownLatch 线程等待」的组合,实现多线程并发爬取、入库,效率直接提升 N 倍,也是 Java 量化开发中处理大批量数据的标配方案。
/**
* 按分类类型,异步批量同步「分类-股票」关联关系
* @param bkType 分类类型 板块/概念/地域 (BKType枚举)
*/
private void syncAllRelationStock(BKType bkType) {
// 1. 从已爬取的分类表中,获取对应类型的全量分类编码列表(复用31篇的板块数据)
List<String> codeList = stockBkService.listByHostNumDescAndCodeAsc(bkType)
.stream().map(StockBkDo::getCode)
.collect(Collectors.toList());
if(CollUtil.isEmpty(codeList)){
log.info("{} 暂无分类数据,无需同步",bkType.getDesc());
return;
}
// 2. 初始化计数器:控制主线程等待所有异步任务完成
CountDownLatch countDownLatch = new CountDownLatch(codeList.size());
// 3. 遍历所有分类编码,异步处理每个分类的成分标的爬取+入库
codeList.forEach(code -> {
executor.submit(() -> {
try {
// 爬取当前分类下的所有成分标的,封装为关联实体列表
List<StockBkStockDo> relationCodeByBkGn = stockCrawlerBusiness.findRelationCodeByBkGn(code);
// 非空判断:有数据则批量入库,无数据则跳过
if (!CollUtil.isEmpty(relationCodeByBkGn)) {
stockBkStockDomainService.saveBatch(relationCodeByBkGn);
}
} catch (Exception e) {
log.error("同步{}【{}】关联股票数据异常",bkType.getDesc(),code,e);
} finally {
// 关键:爬取间隔500ms,防高频请求被限制,保护爬虫稳定性
MyDateUtil.sleep(500);
// 任务完成,计数器减1
countDownLatch.countDown();
}
});
});
// 主线程等待:最多等待3分钟,超时则终止,避免线程阻塞
MyDateUtil.await(countDownLatch, 3, TimeUnit.MINUTES);
log.info("同步 {} 关联的股票数据完成,共处理{}个分类", bkType.getDesc(), codeList.size());
}
💡 核心性能优化点(划重点,必考!)
- 异步线程池:利用多线程并发处理,充分利用服务器资源,几百个分类几分钟即可处理完成;
- CountDownLatch:完美解决「主线程等待所有异步任务完成」的问题,确保业务流程完整性;
- 500ms 延迟:最关键的反爬策略,避免高频请求被封 IP,是爬虫开发的必修课;
- 批量入库 :Mybatis-Plus 的
saveBatch方法,减少数据库连接次数,入库效率提升 10 倍 +。
3.3 第三步:数据爬取方法 - 精准获取东方财富分类成分标的
这一步是数据来源核心,我们调用东方财富的公开接口,传入「分类编码」,即可获取该分类下的所有成分标的数据。接口是经过验证的稳定接口,返回数据格式统一,无需频繁调整解析逻辑。
/**
* 根据板块/概念/地域编码,爬取对应的成分股票列表
* @param bkOrGnCode 分类编码
* @return 封装后的股票-分类关联实体列表
*/
public List<StockBkStockDo> findRelationCodeByBkGn(String bkOrGnCode) {
// 固定回调名,用于解析JSONP格式数据
CB_STOCK_BK = "jQuery金亥跃江聊量化";
// 东财成分标的爬取接口,fs=b:{1} 为核心参数:传入分类编码
String url = MessageFormat.format(
"http://43.push2.eastmoney.com/api/qt/clist/get?cb={0}&pn=1&pz=1000&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&wbp2u=3113356087927502%7C0%7C1%7C0%7Cweb&fid=f3&fs=b:{1}&fields=f12&_=",
CB_STOCK_BK, bkOrGnCode
);
try {
// 发送GET请求,携带请求头,模拟浏览器访问,提升爬取成功率
String content = HttpUtil.sendGet(
HttpClientConfig.proxyNoUseCloseableHttpClient(),
url + MyDateUtil.getTimezone(), // 拼接时间戳,防缓存,获取最新数据
buildDfHeaderMap()
);
// 处理JSONP格式:去掉回调函数包裹,转为纯JSON字符串
content = content.substring(CB_STOCK_BK.length() + 1);
content = content.substring(0, content.length() - 2);
// 解析JSON数据,封装为关联实体
return stockInfoParser.parseStockByBkOrGn(content, bkOrGnCode);
} catch (Exception e) {
log.error("爬取分类【{}】成分股票数据异常",bkOrGnCode, e);
return Collections.emptyList();
}
}
如查询1月份比较火的航空航天板块 90.BK0480
https://quote.eastmoney.com/bk/90.BK0480.html
查询其所拥有的 股票列表数据 接口就是:
http://43.push2.eastmoney.com/api/qt/clist/get?cb=jquery_yueshushu&pn=1&pz=1000&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&wbp2u=3113356087927502%7C0%7C1%7C0%7Cweb&fid=f3&fs=b:BK0480&fields=f12&_=
3.4 第四步:数据解析方法 - JSON 转实体,精准映射字段
爬取到的原始数据是 JSON 格式,我们需要将其解析为 Java 实体类 StockBkStockDo,这是对接数据库的最后一步,解析逻辑简洁高效,只提取核心字段,保证数据精准无冗余。
上面的返回数据为:

/**
* 解析爬取的JSON数据,封装为股票-分类关联实体列表
* @param content 爬取的纯JSON字符串
* @param bkOrGnCode 分类编码
* @return 关联实体列表
*/
public List<StockBkStockDo> parseStockByBkOrGn(String content, String bkOrGnCode) {
JSONObject jsonObject = JSONObject.parseObject(content);
JSONObject data = jsonObject.getJSONObject("data");
// 数据空判断:无数据直接返回空列表
if (ObjectUtils.isEmpty(data)) {
return Collections.emptyList();
}
JSONArray jsonArray = data.getJSONArray("diff");
if (jsonArray.size() <= 0) {
return Collections.emptyList();
}
// 遍历JSON数组,封装实体
List<StockBkStockDo> result = new ArrayList<>(32);
jsonArray.forEach(n -> {
JSONObject tempObject = JSONObject.parseObject(n.toString());
StockBkStockDo stockIndexInfo = new StockBkStockDo();
stockIndexInfo.setStockCode(tempObject.getString("f12")); // 股票编码
stockIndexInfo.setBkCode(bkOrGnCode); // 分类编码
result.add(stockIndexInfo);
});
return result;
}
四、
避坑指南:实战中踩过的 6 个典型问题 ⚠️ (必看!)
这套方案看似简洁,但在实际开发和部署中,很容易因为细节问题导致功能失效,我把自己踩过的坑整理出来,帮你避坑,少走 99% 的弯路,这部分价值千金!
坑 1:JSONP 格式解析失败,报 JSON 语法错误
✅ 原因:忘记去掉回调函数的包裹,原始数据是 jQueryXXX(...) 格式,不是纯 JSON;
✅ 解决:严格执行两次substring截取,去掉前缀的回调名 + 左括号,后缀的右括号 + 分号。
坑 2:高频请求被封 IP,爬取数据为空
✅ 原因:没有设置请求延迟,短时间内发送大量请求,被东财风控拦截;
✅ 解决:必须在异步任务中添加MyDateUtil.sleep(500),500ms 是最优值,兼顾效率和稳定性。
坑 3:CountDownLatch 导致主线程永久阻塞
✅ 原因:部分异步任务抛出异常,没有执行countDown(),计数器永远无法归 0;
✅ 解决:将countDown()放在finally代码块中,无论任务成功还是失败,都必须执行计数器减 1。
坑 4:批量入库效率低,耗时过长
✅ 原因:使用单条插入save()而非批量插入saveBatch(),数据库连接频繁建立关闭;
✅ 解决:统一使用 Mybatis-Plus 的saveBatch()方法,默认批量插入 1000 条,效率拉满。
坑 5:分类编码传入错误,爬取不到对应股票
✅ 原因:复用的分类编码是旧数据,或编码格式错误(如少位、多位);
✅ 解决:确保分类编码来自 31 篇爬取的stock_bk表,是东财的原始编码,无任何修改。
坑 6:线程池核心数设置不合理,服务器负载过高
✅ 原因:线程池核心数设置过大,几百个线程同时运行,服务器 CPU、内存爆满;
✅ 解决:线程池核心数建议设置为 CPU核心数*2 + 1,既保证并发效率,又不会压垮服务器。
五、
技术升华:这套方案的可扩展性(举一反三)
优秀的代码不是写完就结束,而是能「举一反三,灵活扩展」,这套关联关系同步方案,不仅能用于股票与板块的关联,还能轻松扩展到其他量化开发场景,性价比拉满:
- ✅ 新增分类类型:比如新增「行业题材」分类,只需在
BKType枚举中新增值,无需修改核心逻辑; - ✅ 扩展爬取字段:如果需要爬取股票的市值、涨幅等数据,只需修改接口的
fields参数,新增实体字段即可; - ✅ 适配其他数据源:如果需要对接同花顺的分类数据,只需修改爬取接口和解析逻辑,业务层代码完全复用;
- ✅ 对接量化策略:关联关系入库后,可直接编写 SQL 查询「某概念下的所有股票」,无缝对接策略筛选逻辑。
六.
下期预告
下期主要讲解一下获取版块/概念/地区 的实时数据,用于落库。
七. 🎁 福利时间 :领取完整可运行代码包
为了帮大家快速落地这套方案,我整理了本次实战的完整可运行代码包,包含:
① 完整的表结构 SQL + 实体类代码;
② 全量同步核心方法 + 异步线程池配置;
③ 数据爬取 + 解析工具类;
④ 目前已同步的最新关联数据。
私信回复【版块关联同步结果】,即可免费领取,所有代码均可直接导入项目运行,无需任何修改,帮你节省大量开发时间!
为了答谢各位粉丝的厚爱,特意编写一个查询页面,欢迎各个读者使用。
https://www.yueshushu.top/stockPage/stockRelationBk.html
输入编码,即可 获取对应的 概念信息和所占的市值权重。

结尾互动
你在做量化数据开发时,还遇到过哪些「数据关联维护」的痛点?有没有更好的异步处理方案?欢迎在评论区留言讨论,我会一一回复。如果觉得这篇文章对你有帮助,别忘了点赞 + 在看 + 转发,让更多量化开发者少走弯路~
的最新关联数据。
私信回复【版块关联同步结果】,即可免费领取,所有代码均可直接导入项目运行,无需任何修改,帮你节省大量开发时间!
为了答谢各位粉丝的厚爱,特意编写一个查询页面,欢迎各个读者使用。
https://www.yueshushu.top/stockPage/stockRelationBk.html
输入编码,即可 获取对应的 概念信息和所占的市值权重。
外链图片转存中...(img-HNJ5aSD6-1770270672623)
结尾互动
你在做量化数据开发时,还遇到过哪些「数据关联维护」的痛点?有没有更好的异步处理方案?欢迎在评论区留言讨论,我会一一回复。如果觉得这篇文章对你有帮助,别忘了点赞 + 在看 + 转发,让更多量化开发者少走弯路~
后续我还会继续更新量化系列实战文章,从数据爬取到策略开发,从性能优化到架构设计,全套干货持续输出,关注我,不迷路!✨