Java 量化系列(三十三):全自动维护股票与板块 概念 地域关联关系,效率拉满

原链接是: 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 核心字段设计思路(划重点)

💡 字段设计不是越多越好,而是精准适配业务需求 + 兼顾扩展性,这张表的设计堪称典范:

  1. 核心关联字段stock_code+bk_code+bk_type 三者组合,是整个表的灵魂。精准标记「哪只股票」属于「哪个分类」「什么类型的分类」,不会出现任何混淆;
  2. 分类类型枚举绑定bk_type 字段的值 1/2/3,完美对应我们之前定义的 BKType 枚举(1 = 板块、2 = 概念、3 = 地域),代码与数据库完全统一,无歧义;
  3. 性能索引优化:针对两个高频查询场景(按股票查所属分类、按分类查包含股票),建立专属索引,百万级数据查询毫秒级响应;
  4. 预留扩展字段score_typemarket_num 等字段,为后续的「分类内标的排名、市值分析」预留了空间,无需二次改表,直接扩展业务逻辑;
  5. 自动时间维护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());
}

💡 核心性能优化点(划重点,必考!)

  1. 异步线程池:利用多线程并发处理,充分利用服务器资源,几百个分类几分钟即可处理完成;
  2. CountDownLatch:完美解决「主线程等待所有异步任务完成」的问题,确保业务流程完整性;
  3. 500ms 延迟:最关键的反爬策略,避免高频请求被封 IP,是爬虫开发的必修课;
  4. 批量入库 :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,既保证并发效率,又不会压垮服务器。

五、 技术升华:这套方案的可扩展性(举一反三)

优秀的代码不是写完就结束,而是能「举一反三,灵活扩展」,这套关联关系同步方案,不仅能用于股票与板块的关联,还能轻松扩展到其他量化开发场景,性价比拉满:

  1. ✅ 新增分类类型:比如新增「行业题材」分类,只需在BKType枚举中新增值,无需修改核心逻辑;
  2. ✅ 扩展爬取字段:如果需要爬取股票的市值、涨幅等数据,只需修改接口的fields参数,新增实体字段即可;
  3. ✅ 适配其他数据源:如果需要对接同花顺的分类数据,只需修改爬取接口和解析逻辑,业务层代码完全复用;
  4. ✅ 对接量化策略:关联关系入库后,可直接编写 SQL 查询「某概念下的所有股票」,无缝对接策略筛选逻辑。

六. 下期预告

下期主要讲解一下获取版块/概念/地区 的实时数据,用于落库。

七. 🎁 福利时间 :领取完整可运行代码包

为了帮大家快速落地这套方案,我整理了本次实战的完整可运行代码包,包含:

① 完整的表结构 SQL + 实体类代码;

② 全量同步核心方法 + 异步线程池配置;

③ 数据爬取 + 解析工具类;

④ 目前已同步的最新关联数据。

私信回复【版块关联同步结果】,即可免费领取,所有代码均可直接导入项目运行,无需任何修改,帮你节省大量开发时间!

为了答谢各位粉丝的厚爱,特意编写一个查询页面,欢迎各个读者使用。

复制代码
https://www.yueshushu.top/stockPage/stockRelationBk.html

输入编码,即可 获取对应的 概念信息和所占的市值权重。

结尾互动

你在做量化数据开发时,还遇到过哪些「数据关联维护」的痛点?有没有更好的异步处理方案?欢迎在评论区留言讨论,我会一一回复。如果觉得这篇文章对你有帮助,别忘了点赞 + 在看 + 转发,让更多量化开发者少走弯路~

的最新关联数据。

私信回复【版块关联同步结果】,即可免费领取,所有代码均可直接导入项目运行,无需任何修改,帮你节省大量开发时间!

为了答谢各位粉丝的厚爱,特意编写一个查询页面,欢迎各个读者使用。

复制代码
https://www.yueshushu.top/stockPage/stockRelationBk.html

输入编码,即可 获取对应的 概念信息和所占的市值权重。

外链图片转存中...(img-HNJ5aSD6-1770270672623)

结尾互动

你在做量化数据开发时,还遇到过哪些「数据关联维护」的痛点?有没有更好的异步处理方案?欢迎在评论区留言讨论,我会一一回复。如果觉得这篇文章对你有帮助,别忘了点赞 + 在看 + 转发,让更多量化开发者少走弯路~

后续我还会继续更新量化系列实战文章,从数据爬取到策略开发,从性能优化到架构设计,全套干货持续输出,关注我,不迷路!✨

原链接是: Java 量化系列(三十三):全自动维护股票与板块 概念 地域关联关系,效率拉满

相关推荐
gzxx2007sddx5 天前
windows vnpy运行过程及问题记录
python·量化·vnpy
程序员羽毛7 天前
🚀 股票量化多策略盯盘哨兵 V3.0.0 涨停板监控+回测+回放+摸鱼全搞定
股票量化·监控·提醒·股票策略
skywalk816316 天前
介绍一下 Backtrader量化框架(C# 回测快)
开发语言·c#·量化
skywalk816316 天前
介绍一下QuantConnect Lean(python 15k star)
开发语言·python·量化
二狗哈20 天前
czsc入门10: Position类
量化·czsc
二狗哈22 天前
czsc入门8:Signal信号
python·量化·czsc
a flying bird1 个月前
大模型量化方案
大模型·量化
月满星沉1 个月前
ONNX量化
深度学习·onnx·量化
dzj20211 个月前
Unity的旁门左道用法(科学计算):用shader调用GPU做k线MA5的计算——DuckDB + Compute Shader
unity·金融·gpu·shader·量化·compute shader