东方财富上证50成分股接口记录

标签: 量化 python

东方财富上证50成分股接口记录

这次为了给本地 DuckDB 行情库补 2026 年上证50成分股,抓了一下东方财富的数据页面:

https://data.eastmoney.com/other/index/

页面本身不是直接把表格写在 HTML 里,而是前端 JS 调用东方财富 datacenter 接口。对上证50来说,实际用到的接口是:

https://datacenter-web.eastmoney.com/api/data/v1/get

接口参数

核心参数如下:

params = {
    "reportName": "RPT_INDEX_TS_COMPONENT",
    "columns": (
        "SECUCODE,SECURITY_CODE,TYPE,SECURITY_NAME_ABBR,CLOSE_PRICE,"
        "INDUSTRY,REGION,WEIGHT,EPS,BPS,ROE,TOTAL_SHARES,FREE_SHARES,FREE_CAP"
    ),
    "quoteColumns": "f2,f3",
    "quoteType": "0",
    "source": "WEB",
    "client": "WEB",
    "filter": '(TYPE="2")',
    "pageNumber": "1",
    "pageSize": "100",
    "sortColumns": "SECURITY_CODE",
    "sortTypes": "1",
}

headers = {
    "Referer": "https://data.eastmoney.com/other/index/sz50.html",
    "User-Agent": "Mozilla/5.0",
}

几点要注意:

  1. reportName=RPT_INDEX_TS_COMPONENT 是指数成分股报表。
  2. 上证50对应 TYPE="2"。这个是在页面 JS 里看到的分支逻辑:indexcode == '000016' ? '2'
  3. sortTypes 不能传 "asc""desc",要传数字字符串,比如 "1"。否则接口会返回“排序顺序字段不能为非数字类型”。
  4. 返回结果里 SECUCODE 已经带交易所后缀,例如 600519.SH

最小抓取代码

import requests

url = "https://datacenter-web.eastmoney.com/api/data/v1/get"

params = {
    "reportName": "RPT_INDEX_TS_COMPONENT",
    "columns": (
        "SECUCODE,SECURITY_CODE,TYPE,SECURITY_NAME_ABBR,CLOSE_PRICE,"
        "INDUSTRY,REGION,WEIGHT,EPS,BPS,ROE,TOTAL_SHARES,FREE_SHARES,FREE_CAP"
    ),
    "quoteColumns": "f2,f3",
    "quoteType": "0",
    "source": "WEB",
    "client": "WEB",
    "filter": '(TYPE="2")',
    "pageNumber": "1",
    "pageSize": "100",
    "sortColumns": "SECURITY_CODE",
    "sortTypes": "1",
}

headers = {
    "Referer": "https://data.eastmoney.com/other/index/sz50.html",
    "User-Agent": "Mozilla/5.0",
}

r = requests.get(url, params=params, headers=headers, timeout=30)
r.raise_for_status()
payload = r.json()

rows = payload["result"]["data"]
print(len(rows))
print(rows[0])

2026-07-07 抓取时返回 50 条记录。

字段含义

这次我主要保留以下字段作为来源记录:

字段 含义
SECUCODE 带交易所后缀的证券代码,例如 600519.SH
SECURITY_CODE 六位证券代码
TYPE 指数类型编码,上证50为 2
SECURITY_NAME_ABBR 证券简称
CLOSE_PRICE 当前展示收盘价/最新价字段
INDUSTRY 行业
REGION 地区
WEIGHT 指数权重
EPS 每股收益
BPS 每股净资产
ROE 净资产收益率
TOTAL_SHARES 总股本
FREE_SHARES 自由流通股本
FREE_CAP 自由流通市值

写入本地 DuckDB 的原则

我的本地库里已经有一个历史成分表:

choice_sse50_constituents_2022_2025

虽然表名里带 2022_2025,但它实际承担的是上证50年度快照表的角色。策略只应该读这一张 canonical 表,不应该因为换了来源就多读一张临时表。

所以这次导入原则是:

  1. 东方财富接口返回保存成 CSV/JSON 文件,作为来源凭证。
  2. DuckDB 里只把标准化后的 snapshot_year=2026 成分写入历史成分表。
  3. 不在 DuckDB 里保留 eastmoney_* 这类临时输入表,避免以后策略层误用。

本次标准化后的字段:

index_key
index_name
choice_sector_code
snapshot_year
snapshot_date
code
local_code
stock_code
stock_name
continuous_start_snapshot_date
continuous_start_precision

其中:

code       = 600519.SH
local_code = sh600519

DuckDB 写入 SQL

导入时先删除同一年旧快照,再写入新快照:

delete from choice_sse50_constituents_2022_2025
where index_key = 'sse50'
  and snapshot_year = 2026;

然后把标准化后的 DataFrame 注册成 DuckDB 临时视图,插入 canonical 表:

con.register("choice_import", df[choice_cols])
con.execute("""
insert into choice_sse50_constituents_2022_2025
select * from choice_import
""")
con.unregister("choice_import")

校验

导入后用下面的 SQL 检查每年快照数量:

select
    snapshot_year,
    count(*) rows_n,
    count(distinct code) codes,
    min(snapshot_date) min_snapshot,
    max(snapshot_date) max_snapshot
from choice_sse50_constituents_2022_2025
where index_key = 'sse50'
group by 1
order by 1;

2026-07-07 这次导入结果:

snapshot_year  rows_n  codes  min_snapshot  max_snapshot
2026              50     50    2026-07-07    2026-07-07

再检查代码格式:

select code, stock_name
from choice_sse50_constituents_2022_2025
where snapshot_year = 2026
  and not regexp_matches(code, '^[0-9]{6}\.SH$');

结果为空,说明本次成分都是上交所代码,格式符合 QMT 代码口径。

完整导入脚本位置

本机脚本:

C:\workspace\qmt-484base\experment\import_eastmoney_sse50_2026.py

本机保存的来源文件:

C:\workspace\qmt-484base\experment\eastmoney_sse50_2026\eastmoney_sse50_2026.csv
C:\workspace\qmt-484base\experment\eastmoney_sse50_2026\eastmoney_sse50_2026_raw.json

本地 DuckDB:

C:\data-tick\duckdb\qmt_mock.duckdb

这套处理的重点不是多建数据表,而是把不同来源统一收敛到同一张策略使用的历史成分表里。来源可以换,但策略查询入口不要变。

目录
build:   20260707