national-stats-mcp 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api-client.js +2 -2
- package/dist/index.js +289 -136
- package/dist/types.d.ts +3 -1
- package/national-stats-mcp-1.2.0.tgz +0 -0
- package/nbs_api_v2_guide.md +481 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ enum CategoryCode {
|
|
|
187
187
|
- 搜索接口 `/external/query` 用于关键词搜索和快速定位cid
|
|
188
188
|
- 树遍历接口 `/new/queryIndexTreeAsync` 用于浏览分类树和获取cid
|
|
189
189
|
- 指标查询接口 `/new/queryIndicatorsByCid` 用于获取指标列表
|
|
190
|
-
- 数据查询接口 `/
|
|
190
|
+
- 数据查询接口 `/stream/esData` 用于批量获取时间序列数据
|
|
191
191
|
- 内置缓存机制:树结构24小时,指标列表12小时
|
|
192
192
|
- UUID标识符系统和时间分片机制
|
|
193
193
|
- 完整的错误处理和重试逻辑
|
package/dist/api-client.js
CHANGED
|
@@ -408,10 +408,10 @@ class NewStatsApiClient {
|
|
|
408
408
|
showType: params.showType || '1',
|
|
409
409
|
rootId: params.rootId || this.rootId,
|
|
410
410
|
};
|
|
411
|
-
console.error(`Data Request: ${this.baseUrl}/
|
|
411
|
+
console.error(`Data Request: ${this.baseUrl}/stream/esData`);
|
|
412
412
|
console.error(`Payload:`, JSON.stringify(payload, null, 2));
|
|
413
413
|
try {
|
|
414
|
-
const response = await axios_1.default.post(`${this.baseUrl}/
|
|
414
|
+
const response = await axios_1.default.post(`${this.baseUrl}/stream/esData`, payload, {
|
|
415
415
|
timeout: this.timeout,
|
|
416
416
|
headers: {
|
|
417
417
|
'Content-Type': 'application/json',
|
package/dist/index.js
CHANGED
|
@@ -8,60 +8,151 @@ const mcp_http_server_1 = require("mcp-http-server");
|
|
|
8
8
|
const api_client_1 = require("./api-client");
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const commander_1 = require("commander");
|
|
11
|
-
//
|
|
12
|
-
const VERSION = '
|
|
13
|
-
//
|
|
14
|
-
const apiClient = new api_client_1.
|
|
11
|
+
// 获取版本号
|
|
12
|
+
const VERSION = '2.0.0';
|
|
13
|
+
// 创建新版API客户端实例
|
|
14
|
+
const apiClient = new api_client_1.NewStatsApiClient();
|
|
15
|
+
// 31省 das 常量
|
|
16
|
+
const PROVINCE_LIST = [
|
|
17
|
+
{ text: '北京', value: '110000000000' },
|
|
18
|
+
{ text: '天津', value: '120000000000' },
|
|
19
|
+
{ text: '河北', value: '130000000000' },
|
|
20
|
+
{ text: '山西', value: '140000000000' },
|
|
21
|
+
{ text: '内蒙古', value: '150000000000' },
|
|
22
|
+
{ text: '辽宁', value: '210000000000' },
|
|
23
|
+
{ text: '吉林', value: '220000000000' },
|
|
24
|
+
{ text: '黑龙江', value: '230000000000' },
|
|
25
|
+
{ text: '上海', value: '310000000000' },
|
|
26
|
+
{ text: '江苏', value: '320000000000' },
|
|
27
|
+
{ text: '浙江', value: '330000000000' },
|
|
28
|
+
{ text: '安徽', value: '340000000000' },
|
|
29
|
+
{ text: '福建', value: '350000000000' },
|
|
30
|
+
{ text: '江西', value: '360000000000' },
|
|
31
|
+
{ text: '山东', value: '370000000000' },
|
|
32
|
+
{ text: '河南', value: '410000000000' },
|
|
33
|
+
{ text: '湖北', value: '420000000000' },
|
|
34
|
+
{ text: '湖南', value: '430000000000' },
|
|
35
|
+
{ text: '广东', value: '440000000000' },
|
|
36
|
+
{ text: '广西', value: '450000000000' },
|
|
37
|
+
{ text: '海南', value: '460000000000' },
|
|
38
|
+
{ text: '重庆', value: '500000000000' },
|
|
39
|
+
{ text: '四川', value: '510000000000' },
|
|
40
|
+
{ text: '贵州', value: '520000000000' },
|
|
41
|
+
{ text: '云南', value: '530000000000' },
|
|
42
|
+
{ text: '西藏', value: '540000000000' },
|
|
43
|
+
{ text: '陕西', value: '610000000000' },
|
|
44
|
+
{ text: '甘肃', value: '620000000000' },
|
|
45
|
+
{ text: '青海', value: '630000000000' },
|
|
46
|
+
{ text: '宁夏', value: '640000000000' },
|
|
47
|
+
{ text: '新疆', value: '650000000000' },
|
|
48
|
+
];
|
|
49
|
+
// 全国
|
|
50
|
+
const NATIONAL = { text: '全国', value: '000000000000' };
|
|
51
|
+
/**
|
|
52
|
+
* 根据 region 参数构造 das 数组
|
|
53
|
+
*/
|
|
54
|
+
function buildDas(region) {
|
|
55
|
+
if (!region || region === '全国' || region === 'all') {
|
|
56
|
+
return [NATIONAL];
|
|
57
|
+
}
|
|
58
|
+
if (region === '31省' || region === '分省' || region === 'provinces') {
|
|
59
|
+
return PROVINCE_LIST;
|
|
60
|
+
}
|
|
61
|
+
// 支持单个省名或逗号分隔的多个省名
|
|
62
|
+
const names = region.split(',').map(s => s.trim());
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const name of names) {
|
|
65
|
+
if (name === '全国') {
|
|
66
|
+
result.push(NATIONAL);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const found = PROVINCE_LIST.find(p => p.text === name || p.text === name.replace(/[市省区]$/, ''));
|
|
70
|
+
if (found) {
|
|
71
|
+
result.push(found);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// 尝试模糊匹配
|
|
75
|
+
const fuzzy = PROVINCE_LIST.find(p => p.text.includes(name) || name.includes(p.text));
|
|
76
|
+
if (fuzzy) {
|
|
77
|
+
result.push(fuzzy);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result.length > 0 ? result : [NATIONAL];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 确定 showType: 多地区用 "2"(按地区分组),单地区用 "1"(按时间分组)
|
|
85
|
+
*/
|
|
86
|
+
function getShowType(das) {
|
|
87
|
+
return das.length > 1 ? '2' : '1';
|
|
88
|
+
}
|
|
15
89
|
// 创建MCP服务器
|
|
16
90
|
exports.server = new mcp_js_1.McpServer({
|
|
17
91
|
name: 'national-stats-mcp',
|
|
18
92
|
version: VERSION,
|
|
19
|
-
description: '国家统计局数据查询API'
|
|
93
|
+
description: '国家统计局数据查询API (V2.0 新版)'
|
|
20
94
|
});
|
|
21
95
|
// 设置服务器instructions
|
|
22
96
|
exports.server.instructions = `
|
|
23
|
-
|
|
97
|
+
该服务主要用于帮助用户查询国家统计局的统计数据(使用V2.0新版API)。
|
|
24
98
|
|
|
25
99
|
主要工具包括:
|
|
26
|
-
- search_statistics:
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
100
|
+
- search_statistics: 通过关键词搜索数据集,获取cid
|
|
101
|
+
- browse_tree: 浏览指标分类树,支持月度/季度/年度/分省数据
|
|
102
|
+
- get_indicators: 根据cid获取可用指标列表
|
|
103
|
+
- get_data: 获取具体统计数据(支持全国/分省/指定省份)
|
|
104
|
+
- search_and_get: 一步到位:搜索 + 获取数据
|
|
105
|
+
|
|
106
|
+
使用流程:
|
|
107
|
+
1. 用 search_statistics 搜索关键词找到 cid
|
|
108
|
+
2. 用 get_indicators 获取该 cid 下的指标 ID
|
|
109
|
+
3. 用 get_data 传入 cid、indicatorIds、时间范围和地区获取数据
|
|
110
|
+
|
|
111
|
+
快捷方式:
|
|
112
|
+
- search_and_get 可以一步完成上述流程(适合简单查询)
|
|
113
|
+
- browse_tree 可以浏览完整的分类体系
|
|
114
|
+
|
|
115
|
+
地区参数说明(region):
|
|
116
|
+
- "全国" 或不传: 查全国数据
|
|
117
|
+
- "31省" 或 "分省": 查所有31省数据
|
|
118
|
+
- "北京" / "广东" 等: 查单个省份
|
|
119
|
+
- "北京,上海,广东": 逗号分隔查多个省份
|
|
32
120
|
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- 查看可用时间范围调用get_statistics_time_options
|
|
38
|
-
- 根据指标代码查询具体数据使用get_statistics_data
|
|
121
|
+
时间格式说明(dts):
|
|
122
|
+
- 年度: "2020YY-2024YY"
|
|
123
|
+
- 季度: "2024ASS-2025BSS" (A=1季度, B=2季度, C=3季度, D=4季度)
|
|
124
|
+
- 月度: "202401MM-202412MM"
|
|
39
125
|
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
126
|
+
分类代码(code):
|
|
127
|
+
- 1: 月度数据
|
|
128
|
+
- 2: 季度数据
|
|
129
|
+
- 3: 年度数据
|
|
130
|
+
- 5: 分省季度数据
|
|
131
|
+
- 6: 分省年度数据
|
|
132
|
+
- 7: 其他/普查数据
|
|
44
133
|
`;
|
|
45
|
-
//
|
|
46
|
-
exports.server.tool('search_statistics', '
|
|
47
|
-
keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"
|
|
48
|
-
|
|
49
|
-
|
|
134
|
+
// ====== 工具1: 搜索数据集 ======
|
|
135
|
+
exports.server.tool('search_statistics', '通过关键词搜索国家统计局数据集,返回匹配的数据集及其cid。用于定位要查询的数据。', {
|
|
136
|
+
keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"CPI"、"人口"、"居民消费价格" 等'),
|
|
137
|
+
pageSize: zod_1.z.number().optional().default(10).describe('每页结果数,默认10'),
|
|
138
|
+
pagenum: zod_1.z.number().optional().default(1).describe('页码,从1开始'),
|
|
50
139
|
}, async (args) => {
|
|
51
|
-
const { keyword,
|
|
140
|
+
const { keyword, pageSize = 10, pagenum = 1 } = args;
|
|
52
141
|
try {
|
|
53
|
-
|
|
54
|
-
const searchResult = await apiClient.search(keyword, db, page);
|
|
55
|
-
// 解析搜索结果
|
|
56
|
-
const parsedResults = apiClient.parseSearchResult(searchResult);
|
|
142
|
+
const results = await apiClient.search({ search: keyword, pageSize, pagenum });
|
|
57
143
|
return {
|
|
58
144
|
content: [
|
|
59
145
|
{
|
|
60
146
|
type: 'text',
|
|
61
147
|
text: JSON.stringify({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
148
|
+
count: results.length,
|
|
149
|
+
results: results.map(r => ({
|
|
150
|
+
name: r.show_name,
|
|
151
|
+
type: r.type_text,
|
|
152
|
+
cid: r.cid || null,
|
|
153
|
+
globalid: r.treeinfo_globalid,
|
|
154
|
+
timeRange: r.sdate && r.edate ? `${r.sdate} - ${r.edate}` : null,
|
|
155
|
+
}))
|
|
65
156
|
}, null, 2)
|
|
66
157
|
}
|
|
67
158
|
],
|
|
@@ -69,169 +160,233 @@ exports.server.tool('search_statistics', '通过关键词搜索国家统计局
|
|
|
69
160
|
}
|
|
70
161
|
catch (error) {
|
|
71
162
|
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: 'text',
|
|
75
|
-
text: `Error: ${error.message}`,
|
|
76
|
-
},
|
|
77
|
-
],
|
|
163
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
78
164
|
};
|
|
79
165
|
}
|
|
80
166
|
});
|
|
81
|
-
//
|
|
82
|
-
exports.server.tool('
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
wdcode: zod_1.z.string()
|
|
86
|
-
.default('zb')
|
|
87
|
-
.describe('维度代码,默认为zb(指标)'),
|
|
167
|
+
// ====== 工具2: 浏览分类树 ======
|
|
168
|
+
exports.server.tool('browse_tree', '浏览国家统计局指标分类树。可逐层钻取获取子节点,叶子节点的_id即为cid。', {
|
|
169
|
+
code: zod_1.z.string().default('3').describe('分类代码: 1=月度, 2=季度, 3=年度, 5=分省季度, 6=分省年度, 7=其他'),
|
|
170
|
+
pid: zod_1.z.string().optional().describe('父节点ID,不传则获取顶层节点'),
|
|
88
171
|
}, async (args) => {
|
|
89
|
-
const {
|
|
172
|
+
const { code, pid } = args;
|
|
90
173
|
try {
|
|
91
|
-
const
|
|
174
|
+
const nodes = await apiClient.getTree({ code, pid });
|
|
92
175
|
return {
|
|
93
176
|
content: [
|
|
94
177
|
{
|
|
95
178
|
type: 'text',
|
|
96
|
-
text: JSON.stringify(
|
|
179
|
+
text: JSON.stringify({
|
|
180
|
+
count: nodes.length,
|
|
181
|
+
nodes: nodes.map(n => ({
|
|
182
|
+
id: n._id,
|
|
183
|
+
name: n.name,
|
|
184
|
+
isLeaf: n.isLeaf,
|
|
185
|
+
timeRange: n.sdate && n.edate ? `${n.sdate} - ${n.edate}` : n.sdate || null,
|
|
186
|
+
}))
|
|
187
|
+
}, null, 2)
|
|
97
188
|
}
|
|
98
189
|
],
|
|
99
190
|
};
|
|
100
191
|
}
|
|
101
192
|
catch (error) {
|
|
102
193
|
return {
|
|
103
|
-
content: [
|
|
104
|
-
{
|
|
105
|
-
type: 'text',
|
|
106
|
-
text: `Error: ${error.message}`,
|
|
107
|
-
},
|
|
108
|
-
],
|
|
194
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
109
195
|
};
|
|
110
196
|
}
|
|
111
197
|
});
|
|
112
|
-
//
|
|
113
|
-
exports.server.tool('
|
|
114
|
-
|
|
115
|
-
.describe('数据库代码,指定数据类型:hgnd(年度数据), hgjd(季度数据)等'),
|
|
198
|
+
// ====== 工具3: 获取指标列表 ======
|
|
199
|
+
exports.server.tool('get_indicators', '根据cid获取数据集下的所有可用指标(含指标ID、名称、单位、统计口径)。', {
|
|
200
|
+
cid: zod_1.z.string().describe('数据集ID(从搜索结果或树节点的_id获取)'),
|
|
116
201
|
}, async (args) => {
|
|
117
|
-
const {
|
|
202
|
+
const { cid } = args;
|
|
118
203
|
try {
|
|
119
|
-
const
|
|
204
|
+
const indicators = await apiClient.getIndicators({ cid });
|
|
120
205
|
return {
|
|
121
206
|
content: [
|
|
122
207
|
{
|
|
123
208
|
type: 'text',
|
|
124
|
-
text: JSON.stringify(
|
|
209
|
+
text: JSON.stringify({
|
|
210
|
+
cid,
|
|
211
|
+
count: indicators.length,
|
|
212
|
+
indicators: indicators.map(ind => ({
|
|
213
|
+
id: ind._id,
|
|
214
|
+
name: ind.i_showname,
|
|
215
|
+
mark: ind.i_mark || null,
|
|
216
|
+
order: ind.order,
|
|
217
|
+
}))
|
|
218
|
+
}, null, 2)
|
|
125
219
|
}
|
|
126
220
|
],
|
|
127
221
|
};
|
|
128
222
|
}
|
|
129
223
|
catch (error) {
|
|
130
224
|
return {
|
|
131
|
-
content: [
|
|
132
|
-
{
|
|
133
|
-
type: 'text',
|
|
134
|
-
text: `Error: ${error.message}`,
|
|
135
|
-
},
|
|
136
|
-
],
|
|
225
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
137
226
|
};
|
|
138
227
|
}
|
|
139
228
|
});
|
|
140
|
-
//
|
|
141
|
-
exports.server.tool('
|
|
142
|
-
|
|
143
|
-
|
|
229
|
+
// ====== 工具4: 获取数据(核心工具)======
|
|
230
|
+
exports.server.tool('get_data', '获取统计数据。支持全国/31省/指定省份。需提供cid和indicatorIds(通过get_indicators获取)。', {
|
|
231
|
+
cid: zod_1.z.string().describe('数据集ID'),
|
|
232
|
+
indicatorIds: zod_1.z.array(zod_1.z.string()).describe('指标ID数组(从get_indicators获取)'),
|
|
233
|
+
dts: zod_1.z.string().describe('时间范围,如 "2020YY-2024YY"(年度)、"202401MM-202412MM"(月度)'),
|
|
234
|
+
region: zod_1.z.string().optional().default('全国').describe('地区: "全国"(默认), "31省"/"分省"(全部省份), "北京"(单省), "北京,上海,广东"(多省逗号分隔)'),
|
|
144
235
|
}, async (args) => {
|
|
145
|
-
const {
|
|
236
|
+
const { cid, indicatorIds, dts, region } = args;
|
|
146
237
|
try {
|
|
147
|
-
const
|
|
238
|
+
const das = buildDas(region);
|
|
239
|
+
const showType = getShowType(das);
|
|
240
|
+
const data = await apiClient.getData({
|
|
241
|
+
cid,
|
|
242
|
+
indicatorIds,
|
|
243
|
+
das,
|
|
244
|
+
dts: [dts],
|
|
245
|
+
showType,
|
|
246
|
+
});
|
|
148
247
|
return {
|
|
149
248
|
content: [
|
|
150
249
|
{
|
|
151
250
|
type: 'text',
|
|
152
|
-
text: JSON.stringify(
|
|
251
|
+
text: JSON.stringify({
|
|
252
|
+
query: { cid, indicatorIds, dts, region: region || '全国', showType },
|
|
253
|
+
count: data.length,
|
|
254
|
+
data: data.map(d => ({
|
|
255
|
+
code: d.code,
|
|
256
|
+
name: d.name,
|
|
257
|
+
values: d.values.map(v => ({
|
|
258
|
+
indicator: v.i_showname || v._id,
|
|
259
|
+
value: v.value,
|
|
260
|
+
region: v.da_name || d.name,
|
|
261
|
+
unit: v.du_name,
|
|
262
|
+
}))
|
|
263
|
+
}))
|
|
264
|
+
}, null, 2)
|
|
153
265
|
}
|
|
154
266
|
],
|
|
155
267
|
};
|
|
156
268
|
}
|
|
157
269
|
catch (error) {
|
|
158
270
|
return {
|
|
159
|
-
content: [
|
|
160
|
-
{
|
|
161
|
-
type: 'text',
|
|
162
|
-
text: `Error: ${error.message}`,
|
|
163
|
-
},
|
|
164
|
-
],
|
|
271
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
165
272
|
};
|
|
166
273
|
}
|
|
167
274
|
});
|
|
168
|
-
//
|
|
169
|
-
exports.server.tool('
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.describe('时间范围,例如 LAST6 (最近6季度), 2025 (指定年份), LAST30 (最近30年) 等'),
|
|
275
|
+
// ====== 工具5: 搜索并获取数据(快捷工具)======
|
|
276
|
+
exports.server.tool('search_and_get', '一步到位:根据关键词搜索 → 定位cid → 获取指标 → 查询数据。适合快速查询。支持指定地区。', {
|
|
277
|
+
keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"人口"、"CPI"'),
|
|
278
|
+
indicatorName: zod_1.z.string().optional().describe('指标名称过滤(模糊匹配),不传则取第一个指标'),
|
|
279
|
+
startTime: zod_1.z.string().optional().describe('起始时间,如 "2020YY"、"202401MM"'),
|
|
280
|
+
endTime: zod_1.z.string().optional().describe('结束时间,如 "2024YY"、"202412MM"'),
|
|
281
|
+
region: zod_1.z.string().optional().default('全国').describe('地区: "全国"(默认), "31省", "北京", "北京,上海,广东"'),
|
|
176
282
|
}, async (args) => {
|
|
177
|
-
const {
|
|
283
|
+
const { keyword, indicatorName, startTime, endTime, region } = args;
|
|
178
284
|
try {
|
|
179
|
-
|
|
285
|
+
// 1. 搜索定位cid
|
|
286
|
+
const searchResults = await apiClient.search({ search: keyword, pageSize: 10 });
|
|
287
|
+
if (!searchResults || searchResults.length === 0) {
|
|
288
|
+
throw new Error(`未找到与 "${keyword}" 相关的数据集`);
|
|
289
|
+
}
|
|
290
|
+
// 优先选择最新的数据集
|
|
291
|
+
const target = searchResults.reduce((latest, current) => {
|
|
292
|
+
if (!latest.edate)
|
|
293
|
+
return current;
|
|
294
|
+
if (!current.edate)
|
|
295
|
+
return latest;
|
|
296
|
+
return current.edate > latest.edate ? current : latest;
|
|
297
|
+
});
|
|
298
|
+
// 提取 cid
|
|
299
|
+
const cid = target.cid || extractCidFromGlobalId(target.treeinfo_globalid);
|
|
300
|
+
if (!cid) {
|
|
301
|
+
throw new Error('无法从搜索结果中提取 cid');
|
|
302
|
+
}
|
|
303
|
+
// 2. 获取指标列表
|
|
304
|
+
const indicators = await apiClient.getIndicators({ cid });
|
|
305
|
+
if (!indicators || indicators.length === 0) {
|
|
306
|
+
throw new Error(`cid ${cid} 下没有找到指标`);
|
|
307
|
+
}
|
|
308
|
+
// 筛选指标
|
|
309
|
+
let targetIndicator = indicatorName
|
|
310
|
+
? indicators.find(ind => ind.i_showname.includes(indicatorName))
|
|
311
|
+
: indicators[0];
|
|
312
|
+
if (!targetIndicator) {
|
|
313
|
+
throw new Error(`未找到包含 "${indicatorName}" 的指标。可用指标: ${indicators.map(i => i.i_showname).join(', ')}`);
|
|
314
|
+
}
|
|
315
|
+
// 3. 构造时间范围
|
|
316
|
+
const timeRange = startTime && endTime
|
|
317
|
+
? `${startTime}-${endTime}`
|
|
318
|
+
: `${target.sdate || '2020'}YY-${target.edate || '2024'}YY`;
|
|
319
|
+
// 4. 构造地区维度
|
|
320
|
+
const das = buildDas(region);
|
|
321
|
+
const showType = getShowType(das);
|
|
322
|
+
// 5. 查询数据
|
|
323
|
+
const data = await apiClient.getData({
|
|
324
|
+
cid,
|
|
325
|
+
indicatorIds: [targetIndicator._id],
|
|
326
|
+
das,
|
|
327
|
+
dts: [timeRange],
|
|
328
|
+
showType,
|
|
329
|
+
});
|
|
180
330
|
return {
|
|
181
331
|
content: [
|
|
182
332
|
{
|
|
183
333
|
type: 'text',
|
|
184
|
-
text: JSON.stringify(
|
|
334
|
+
text: JSON.stringify({
|
|
335
|
+
dataset: target.show_name,
|
|
336
|
+
cid,
|
|
337
|
+
indicator: {
|
|
338
|
+
id: targetIndicator._id,
|
|
339
|
+
name: targetIndicator.i_showname,
|
|
340
|
+
mark: targetIndicator.i_mark || null,
|
|
341
|
+
},
|
|
342
|
+
timeRange,
|
|
343
|
+
region: region || '全国',
|
|
344
|
+
count: data.length,
|
|
345
|
+
data: data.map(d => ({
|
|
346
|
+
code: d.code,
|
|
347
|
+
name: d.name,
|
|
348
|
+
values: d.values.map(v => ({
|
|
349
|
+
indicator: v.i_showname || targetIndicator.i_showname,
|
|
350
|
+
value: v.value,
|
|
351
|
+
region: v.da_name || d.name,
|
|
352
|
+
unit: v.du_name,
|
|
353
|
+
}))
|
|
354
|
+
}))
|
|
355
|
+
}, null, 2)
|
|
185
356
|
}
|
|
186
357
|
],
|
|
187
358
|
};
|
|
188
359
|
}
|
|
189
360
|
catch (error) {
|
|
190
361
|
return {
|
|
191
|
-
content: [
|
|
192
|
-
{
|
|
193
|
-
type: 'text',
|
|
194
|
-
text: `Error: ${error.message}`,
|
|
195
|
-
},
|
|
196
|
-
],
|
|
362
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
197
363
|
};
|
|
198
364
|
}
|
|
199
365
|
});
|
|
200
|
-
//
|
|
201
|
-
exports.server.tool('
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
})));
|
|
215
|
-
return {
|
|
216
|
-
content: [
|
|
217
|
-
{
|
|
218
|
-
type: 'text',
|
|
219
|
-
text: JSON.stringify(results, null, 2)
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
225
|
-
return {
|
|
226
|
-
content: [
|
|
227
|
-
{
|
|
228
|
-
type: 'text',
|
|
229
|
-
text: `Error: ${error.message}`,
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
};
|
|
233
|
-
}
|
|
366
|
+
// ====== 工具6: 获取省份列表 ======
|
|
367
|
+
exports.server.tool('list_provinces', '获取31省份代码列表,用于了解可用的地区参数。', {}, async () => {
|
|
368
|
+
return {
|
|
369
|
+
content: [
|
|
370
|
+
{
|
|
371
|
+
type: 'text',
|
|
372
|
+
text: JSON.stringify({
|
|
373
|
+
national: NATIONAL,
|
|
374
|
+
provinces: PROVINCE_LIST,
|
|
375
|
+
usage: '在 get_data 和 search_and_get 的 region 参数中使用省份名称,如 "北京" 或 "31省"'
|
|
376
|
+
}, null, 2)
|
|
377
|
+
}
|
|
378
|
+
],
|
|
379
|
+
};
|
|
234
380
|
});
|
|
381
|
+
/**
|
|
382
|
+
* 从 globalid 提取 cid (最后一段)
|
|
383
|
+
*/
|
|
384
|
+
function extractCidFromGlobalId(globalId) {
|
|
385
|
+
if (!globalId)
|
|
386
|
+
return null;
|
|
387
|
+
const parts = globalId.split('.');
|
|
388
|
+
return parts[parts.length - 1] || null;
|
|
389
|
+
}
|
|
235
390
|
// 启动服务器
|
|
236
391
|
async function startServer() {
|
|
237
392
|
const program = new commander_1.Command();
|
|
@@ -240,7 +395,6 @@ async function startServer() {
|
|
|
240
395
|
.option('-h, --host <host>', 'Host to listen on for HTTP/SSE mode', 'localhost')
|
|
241
396
|
.parse();
|
|
242
397
|
const options = program.opts();
|
|
243
|
-
// 通过端口参数判断是否使用HTTP模式还是STDIO模式
|
|
244
398
|
if (options.port) {
|
|
245
399
|
await (0, mcp_http_server_1.startSseAndStreamableHttpMcpServer)({
|
|
246
400
|
host: options.host,
|
|
@@ -251,7 +405,6 @@ async function startServer() {
|
|
|
251
405
|
});
|
|
252
406
|
}
|
|
253
407
|
else {
|
|
254
|
-
// 使用STDIO模式
|
|
255
408
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
256
409
|
await exports.server.connect(transport);
|
|
257
410
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -82,7 +82,7 @@ export interface DataValue {
|
|
|
82
82
|
du_name?: string;
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
* 数据点 - 来自
|
|
85
|
+
* 数据点 - 来自 stream/esData
|
|
86
86
|
*/
|
|
87
87
|
export interface DataPoint {
|
|
88
88
|
code: string;
|
|
@@ -95,6 +95,8 @@ export interface DataPoint {
|
|
|
95
95
|
export interface DataResponse {
|
|
96
96
|
data: DataPoint[];
|
|
97
97
|
success: boolean;
|
|
98
|
+
state?: number;
|
|
99
|
+
message?: string;
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
100
102
|
* 数据查询请求参数
|
|
Binary file
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# 国家统计局新版数据接口使用文档
|
|
2
|
+
|
|
3
|
+
> **基础地址**: `https://data.stats.gov.cn/dg/website/publicrelease/web/external`
|
|
4
|
+
> **鉴权**: 无需鉴权,公开访问
|
|
5
|
+
> **版本**: V2.0 (2026.06.05更新)
|
|
6
|
+
> **特点**: 采用 UUID 标识符,支持时间分片,元数据与数据分离,批量查询效率高。
|
|
7
|
+
|
|
8
|
+
### 变更记录
|
|
9
|
+
|
|
10
|
+
| 日期 | 变更内容 |
|
|
11
|
+
|------|---------|
|
|
12
|
+
| 2026.06.05 | 数据查询接口从 `/getEsDataByCidAndDt` 迁移至 `/stream/esData`;`showType` 变为必填参数;响应新增 `state`、`message` 字段 |
|
|
13
|
+
| 2026.03.27 | V2.0 发布,采用 UUID 标识符架构 |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 目录
|
|
18
|
+
|
|
19
|
+
- [核心概念](#核心概念)
|
|
20
|
+
- [接口概览](#接口概览)
|
|
21
|
+
- [接口详解](#接口详解)
|
|
22
|
+
- [编码与ID规律](#编码与id规律)
|
|
23
|
+
- [使用示例](#使用示例)
|
|
24
|
+
- [最佳实践](#最佳实践)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 核心概念
|
|
29
|
+
|
|
30
|
+
新版 API 摒弃了旧版的层级代码(如 `A0101`),转而使用 **UUID** 作为唯一标识。数据获取遵循 **"树形导航 -> 指标元数据 -> 批量取值"** 的三步走策略。
|
|
31
|
+
|
|
32
|
+
### 1. 关键标识符
|
|
33
|
+
|
|
34
|
+
| 标识符 | 全称 | 含义 | 来源接口 |
|
|
35
|
+
| :--- | :--- | :--- | :--- |
|
|
36
|
+
| **`pid`** | Parent ID | **父节点 ID**。用于在目录树中向下展开。初始请求时为空或根节点 ID。 | `queryIndexTreeAsync` 返回节点的 `_id` |
|
|
37
|
+
| **`cid`** | Catalog ID | **数据集 ID**。代表一个**叶子节点**(Leaf Node)。<br>通常对应:**特定指标 + 特定地区 + 特定时间分段**。<br>*注意:同一业务指标因时间分段不同会有多个 `cid`。* | `queryIndexTreeAsync` 返回中 `isLeaf: true` 节点的 `_id` |
|
|
38
|
+
| **`indicatorId`** | Indicator ID | **具体指标 ID**。代表数据集中的某一列(如"同比增长"、"累计值")。 | `queryIndicatorsByCid` 返回列表中的 `_id` |
|
|
39
|
+
| **`du`** | Unit ID | **单位 ID**。指向数据单位的唯一标识(如 `%`, `亿元`)。 | `queryIndicatorsByCid` 返回 |
|
|
40
|
+
|
|
41
|
+
### 2. 时间分片机制 (Time Slicing)
|
|
42
|
+
|
|
43
|
+
这是新版 API 最显著的特征。**同一个业务指标(如 CPI)会被拆分成多个 `cid`**,通常按 5 年或统计制度变革周期分割。
|
|
44
|
+
|
|
45
|
+
* **现象**: 搜索 "CPI" 可能会得到多个 `cid`:
|
|
46
|
+
* `cid_A`: 2016-2020
|
|
47
|
+
* `cid_B`: 2021-2025
|
|
48
|
+
* `cid_C`: 2026-至今
|
|
49
|
+
* **影响**: 获取长期历史数据时,必须**分别请求**这些 `cid`,然后在本地按时间拼接。
|
|
50
|
+
|
|
51
|
+
### 3. 时间编码格式
|
|
52
|
+
|
|
53
|
+
在 `stream/esData` 接口中使用:
|
|
54
|
+
|
|
55
|
+
* **月度**: `YYYYMM` (如 `202602`),后缀 `MM` (如 `202602MM`)
|
|
56
|
+
* **季度**: `YYYYQ` (如 `20254` 表示第四季度),后缀 `SS` (如 `202504SS`,注意这里 Q4 可能编码为 04)
|
|
57
|
+
* **年度**: `YYYY` (如 `2025`),后缀 `YY` (如 `2025YY`)
|
|
58
|
+
* **范围**: `Start-End` (如 `202501MM-202602MM`)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 接口概览
|
|
63
|
+
|
|
64
|
+
| 步骤 | 接口路径 | 方法 | 用途 | 关键入参 |
|
|
65
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
66
|
+
| **1. 遍历目录** | `/new/queryIndexTreeAsync` | GET | 获取分类树,找到目标数据集 (`cid`) | `pid`, `code` |
|
|
67
|
+
| **2. 获取指标** | `/new/queryIndicatorsByCid` | GET | 获取某数据集下的所有指标列表 (`indicatorId`) | `cid` |
|
|
68
|
+
| **3. 查询数据** | `/stream/esData` | POST | 批量获取具体数值 | `cid`, `indicatorIds`, `dts`, `das`, `showType` |
|
|
69
|
+
| *(可选)* | `/external/query` | GET | 关键词搜索,快速定位 `cid` | `search`, `pagenum` |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 接口详解
|
|
74
|
+
|
|
75
|
+
### 1. 遍历目录树 (Get Tree)
|
|
76
|
+
|
|
77
|
+
用于从根节点开始,层层下钻,直到找到 `isLeaf: true` 的节点。
|
|
78
|
+
|
|
79
|
+
```http
|
|
80
|
+
GET /new/queryIndexTreeAsync?pid={parent_id}&code={category_code}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**参数**
|
|
84
|
+
|
|
85
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
86
|
+
| :--- | :--- | :--- | :--- |
|
|
87
|
+
| `pid` | string | ❌ | 父节点 ID。首次请求可为空字符串 `""` 或根节点 ID。 |
|
|
88
|
+
| `code` | string | ✅ | 顶级分类代码。常见值:<br>`1`: 月度数据<br>`2`: 季度数据<br>`3`: 年度数据<br>`5`: 分省季度<br>`6`: 分省年度<br>`7`: 其他/普查 |
|
|
89
|
+
|
|
90
|
+
**响应关键字段**
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"data": [
|
|
95
|
+
{
|
|
96
|
+
"_id": "fc982599...",
|
|
97
|
+
"name": "价格指数",
|
|
98
|
+
"isLeaf": false,
|
|
99
|
+
"treeinfo_globalid": "...",
|
|
100
|
+
"sdate": "2021",
|
|
101
|
+
"edate": "2025"
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"success": true
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**字段说明**
|
|
109
|
+
|
|
110
|
+
| 字段 | 说明 |
|
|
111
|
+
|------|------|
|
|
112
|
+
| `_id` | 节点唯一标识。若 `isLeaf=false`,则作为下一次请求的 `pid`;若 `isLeaf=true`,则作为 `cid`。 |
|
|
113
|
+
| `isLeaf` | `true`=叶子节点(可查数据),`false`=目录节点(需继续下钻)。 |
|
|
114
|
+
| `sdate/edate` | 该数据集覆盖的时间范围,用于判断是否包含所需年份。 |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2. 获取指标列表 (Get Indicators)
|
|
119
|
+
|
|
120
|
+
拿到 `cid` 后,查询该数据集包含哪些具体指标。
|
|
121
|
+
|
|
122
|
+
```http
|
|
123
|
+
GET /new/queryIndicatorsByCid?cid={catalog_id}&dt=&name=
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**参数**
|
|
127
|
+
|
|
128
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
129
|
+
| :--- | :--- | :--- | :--- |
|
|
130
|
+
| `cid` | string | ✅ | 数据集 ID (来自步骤 1 的叶子节点 `_id`) |
|
|
131
|
+
| `dt` | string | ❌ | 时间过滤,通常留空 |
|
|
132
|
+
| `name` | string | ❌ | 指标名称过滤,通常留空 |
|
|
133
|
+
|
|
134
|
+
**响应关键字段**
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"data": {
|
|
139
|
+
"list": [
|
|
140
|
+
{
|
|
141
|
+
"_id": "6d249959...",
|
|
142
|
+
"i_showname": "规上工业增加值同比增长 (%) ",
|
|
143
|
+
"i_mark": "统计口径说明...",
|
|
144
|
+
"du": "414774...",
|
|
145
|
+
"dp": "11",
|
|
146
|
+
"order": 1
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**字段说明**
|
|
154
|
+
|
|
155
|
+
| 字段 | 说明 |
|
|
156
|
+
|------|------|
|
|
157
|
+
| `_id` | 指标唯一 ID,后续查询数据时必须传入。 |
|
|
158
|
+
| `i_showname` | 指标显示名称,含单位。 |
|
|
159
|
+
| `i_mark` | **统计口径说明**。非常重要,解释了数据的计算方法和适用范围。 |
|
|
160
|
+
| `du` | 单位 ID,需结合其他接口或常识解析(如 `%`)。 |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### 3. 查询具体数据 (Get Data)
|
|
165
|
+
|
|
166
|
+
**核心接口**。支持批量查询多个指标、多个时间点的数据。
|
|
167
|
+
|
|
168
|
+
```http
|
|
169
|
+
POST /stream/esData
|
|
170
|
+
Content-Type: application/json
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**请求体 (Body)**
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"cid": "e2d9463aceae483eb122794e53180bf9",
|
|
178
|
+
"indicatorIds": [
|
|
179
|
+
"6d249959166b4b07aad922e2aa51097d",
|
|
180
|
+
"f991aa39485440158f761a71e39b03a1"
|
|
181
|
+
],
|
|
182
|
+
"das": [
|
|
183
|
+
{
|
|
184
|
+
"text": "全国",
|
|
185
|
+
"value": "000000000000"
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"dts": ["202501MM-202602MM"],
|
|
189
|
+
"showType": "1",
|
|
190
|
+
"rootId": "fc982599aa684be7969d7b90b1bd0e84"
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**参数说明**
|
|
195
|
+
|
|
196
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
197
|
+
| :--- | :--- | :--- | :--- |
|
|
198
|
+
| `cid` | string | ✅ | 数据集 ID |
|
|
199
|
+
| `indicatorIds` | array | ✅ | 指标 ID 数组 (来自步骤 2) |
|
|
200
|
+
| `das` | array | ✅ | 地区维度。`value`: `000000000000` 代表全国。如果是分省数据,需传入具体省份代码。 |
|
|
201
|
+
| `dts` | array | ✅ | 时间范围数组。格式 `Start-End`。 |
|
|
202
|
+
| `showType` | string | ✅ | 显示类型,固定传 `"1"`(必填,不传会返回 500) |
|
|
203
|
+
| `rootId` | string | ✅ | 根节点 ID。通常固定为月度数据的根 ID,可通过第一次树请求获取。 |
|
|
204
|
+
|
|
205
|
+
**响应结构**
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"data": [
|
|
210
|
+
{
|
|
211
|
+
"code": "202602MM",
|
|
212
|
+
"name": "2026年2月",
|
|
213
|
+
"values": [
|
|
214
|
+
{
|
|
215
|
+
"_id": "6d249959...",
|
|
216
|
+
"i_showname": "规上工业增加值同比增长 (%) ",
|
|
217
|
+
"value": "5.8",
|
|
218
|
+
"da_name": "全国",
|
|
219
|
+
"du_name": "%"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"_id": "f991aa39...",
|
|
223
|
+
"value": "6.1"
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"code": "202601MM",
|
|
229
|
+
"name": "2026年1月",
|
|
230
|
+
"values": [ ... ]
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
"success": true,
|
|
234
|
+
"state": 20000,
|
|
235
|
+
"message": "成功"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**注意事项**
|
|
240
|
+
* 如果某个月份数据未发布,`values` 可能为空数组 `[]`。
|
|
241
|
+
* 返回数据按时间倒序或正序排列,需根据 `code` 自行排序。
|
|
242
|
+
* `showType` 为必填参数,不传将返回 HTTP 500 错误。
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 编码与ID规律
|
|
247
|
+
|
|
248
|
+
由于 ID 均为 UUID,无法通过算法推导,必须通过遍历获取。但存在以下业务规律:
|
|
249
|
+
|
|
250
|
+
### 1. `cid` 与时间的关系
|
|
251
|
+
* 同一类指标(如 CPI),通常会按 `5年` 或 `10年` 切割成不同的 `cid`。
|
|
252
|
+
* 检查 `queryIndexTreeAsync` 返回的 `sdate` 和 `edate` 字段,可以判断该 `cid` 覆盖的时间范围。
|
|
253
|
+
|
|
254
|
+
### 2. `indicatorId` 的稳定性
|
|
255
|
+
* 在同一个 `cid` 内,`indicatorId` 是稳定的。
|
|
256
|
+
* **跨 `cid` 不保证稳定**:2021-2025 的 "GDP" 指标 ID 可能与 2026- 的 "GDP" 指标 ID 不同。**建议每个 `cid` 都重新调用 `queryIndicatorsByCid` 获取映射。**
|
|
257
|
+
|
|
258
|
+
### 3. 地区代码 (`da`)
|
|
259
|
+
* `000000000000`: 全国
|
|
260
|
+
* 分省数据的 `cid` 通常位于"分省年度/季度数据"目录下。
|
|
261
|
+
|
|
262
|
+
### 4. 根节点 ID (`rootId`)
|
|
263
|
+
* 月度数据根节点通常为: `fc982599aa684be7969d7b90b1bd0e84`
|
|
264
|
+
* 可通过 `GET /new/queryIndexTreeAsync?pid=` (空) 获取第一级节点确认。
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 使用示例
|
|
269
|
+
|
|
270
|
+
### 示例一:搜索定位并获取最新 CPI 数据
|
|
271
|
+
|
|
272
|
+
> 场景:快速获取最近几个月的全国居民消费价格指数 (CPI)
|
|
273
|
+
|
|
274
|
+
**Step 1: 关键词搜索定位 `cid`**
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
curl "https://data.stats.gov.cn/dg/website/publicrelease/web/external/query?search=CPI&pagenum=1&pageSize=5"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
*解析*: 在返回结果中寻找 `type_text` 为 "月度数据" 且 `show_name` 包含 "居民消费价格" 的项。假设找到最新时间段 (2026-) 的 `cid` 为 `5c745282...`。
|
|
281
|
+
|
|
282
|
+
**Step 2: 获取指标 ID**
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
curl "https://data.stats.gov.cn/dg/website/publicrelease/web/external/new/queryIndicatorsByCid?cid=5c7452825c7c4dcba391db5ca7f335c5"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
*解析*: 找到 `i_showname` 为 "居民消费价格指数 (上年同月=100) " 的项,记录其 `_id`,假设为 `ind_cpi_total` (`53180dfb...`)。
|
|
289
|
+
|
|
290
|
+
**Step 3: 查询数据**
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
curl -X POST "https://data.stats.gov.cn/dg/website/publicrelease/web/external/stream/esData" \
|
|
294
|
+
-H "Content-Type: application/json" \
|
|
295
|
+
-d '{
|
|
296
|
+
"cid": "5c7452825c7c4dcba391db5ca7f335c5",
|
|
297
|
+
"indicatorIds": ["53180dfb9c14411ba4b762307c85920c"],
|
|
298
|
+
"das": [{"text": "全国", "value": "000000000000"}],
|
|
299
|
+
"dts": ["202601MM-202603MM"],
|
|
300
|
+
"showType": "1",
|
|
301
|
+
"rootId": "fc982599aa684be7969d7b90b1bd0e84"
|
|
302
|
+
}'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### 示例二:遍历树获取所有月度数据 CID (Python)
|
|
308
|
+
|
|
309
|
+
> 场景:构建本地索引,获取所有可用的月度数据集 ID
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import requests
|
|
313
|
+
import time
|
|
314
|
+
import json
|
|
315
|
+
|
|
316
|
+
BASE_URL = "https://data.stats.gov.cn/dg/website/publicrelease/web/external"
|
|
317
|
+
|
|
318
|
+
def get_tree(pid="", code="1"):
|
|
319
|
+
"""递归获取树结构,收集所有叶子节点 (cid)"""
|
|
320
|
+
url = f"{BASE_URL}/new/queryIndexTreeAsync"
|
|
321
|
+
params = {"pid": pid, "code": code}
|
|
322
|
+
try:
|
|
323
|
+
resp = requests.get(url, params=params, timeout=10)
|
|
324
|
+
nodes = resp.json().get('data', [])
|
|
325
|
+
except Exception as e:
|
|
326
|
+
print(f"Error fetching tree for pid={pid}: {e}")
|
|
327
|
+
return []
|
|
328
|
+
|
|
329
|
+
cids = []
|
|
330
|
+
for node in nodes:
|
|
331
|
+
if node.get('isLeaf'):
|
|
332
|
+
cids.append({
|
|
333
|
+
"name": node['name'],
|
|
334
|
+
"cid": node['_id'],
|
|
335
|
+
"sdate": node.get('sdate'),
|
|
336
|
+
"edate": node.get('edate')
|
|
337
|
+
})
|
|
338
|
+
else:
|
|
339
|
+
time.sleep(0.5)
|
|
340
|
+
sub_cids = get_tree(pid=node['_id'], code=code)
|
|
341
|
+
cids.extend(sub_cids)
|
|
342
|
+
return cids
|
|
343
|
+
|
|
344
|
+
# 执行遍历 (慎用,耗时较长)
|
|
345
|
+
# all_cids = get_tree(code="1")
|
|
346
|
+
# with open('nbs_cids_monthly.json', 'w', encoding='utf-8') as f:
|
|
347
|
+
# json.dump(all_cids, f, ensure_ascii=False, indent=2)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 最佳实践
|
|
353
|
+
|
|
354
|
+
### 1. 优先使用搜索接口定位 `cid`
|
|
355
|
+
|
|
356
|
+
全量遍历树非常耗时(可能有数千个节点)。如果已知关键词(如 "GDP", "CPI", "人口"),先用 `/external/query` 搜索,从结果中提取相关的 `cid` 或线索,再精准查询。
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
❌ 低效方式:
|
|
360
|
+
从根节点开始递归遍历整棵树,寻找 "GDP"
|
|
361
|
+
|
|
362
|
+
✅ 高效方式:
|
|
363
|
+
search.htm?s=GDP
|
|
364
|
+
→ 从结果中提取 cid 或相关线索
|
|
365
|
+
→ 直接调用 queryIndicatorsByCid
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### 2. 处理"时间分片"
|
|
369
|
+
|
|
370
|
+
**不要假设一个 `cid` 能查到所有历史数据。**
|
|
371
|
+
* **策略**: 当发现数据缺失(如 `values` 为空)或需要更长历史时,检查相邻的 `cid`(通过树结构查找同级节点,或通过搜索查看是否有其他年份范围的同类指标)。
|
|
372
|
+
* **合并**: 在客户端按时间戳合并不同 `cid` 返回的数据。
|
|
373
|
+
|
|
374
|
+
### 3. 缓存元数据
|
|
375
|
+
|
|
376
|
+
* **树结构 (`queryIndexTreeAsync`)**: 变化极慢,建议本地缓存 24 小时以上。
|
|
377
|
+
* **指标列表 (`queryIndicatorsByCid`)**: 变化较慢,建议按 `cid` 缓存。
|
|
378
|
+
* **数据 (`stream/esData`)**: 每月更新,建议设置较短缓存或按需请求。
|
|
379
|
+
|
|
380
|
+
### 4. 批量请求减少 IO
|
|
381
|
+
|
|
382
|
+
`stream/esData` 支持 `indicatorIds` 数组。
|
|
383
|
+
* ❌ **错误**: 循环调用接口,每次查 1 个指标。
|
|
384
|
+
* ✅ **正确**: 一次性传入该 `cid` 下所有需要的指标 ID(如 CPI 的总指数、食品、居住等 10 个子类),一次请求拿回所有数据。
|
|
385
|
+
|
|
386
|
+
### 5. 关注 `i_mark` (统计口径)
|
|
387
|
+
|
|
388
|
+
在 `queryIndicatorsByCid` 返回的 `i_mark` 字段中,包含了至关重要的统计说明(如"按不变价计算"、"基期为 2020 年"等)。展示数据时务必附带此说明,否则可能导致误读。
|
|
389
|
+
|
|
390
|
+
### 6. 完整封装示例 (JavaScript/Node.js)
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
class NbsNewClient {
|
|
394
|
+
constructor() {
|
|
395
|
+
this.base = 'https://data.stats.gov.cn/dg/website/publicrelease/web/external';
|
|
396
|
+
this.rootId = 'fc982599aa684be7969d7b90b1bd0e84';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async searchCid(keyword) {
|
|
400
|
+
const url = `${this.base}/query?search=${encodeURIComponent(keyword)}&pagenum=1&pageSize=10`;
|
|
401
|
+
const res = await fetch(url);
|
|
402
|
+
const json = await res.json();
|
|
403
|
+
return json.data?.data?.map(item => ({
|
|
404
|
+
name: item.show_name,
|
|
405
|
+
cid: item.cid || this.extractCidFromGlobalId(item.treeinfo_globalid),
|
|
406
|
+
type: item.type_text
|
|
407
|
+
})) || [];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
extractCidFromGlobalId(globalId) {
|
|
411
|
+
if (!globalId) return null;
|
|
412
|
+
const parts = globalId.split('.');
|
|
413
|
+
return parts[parts.length - 1];
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async getIndicators(cid) {
|
|
417
|
+
const url = `${this.base}/new/queryIndicatorsByCid?cid=${cid}`;
|
|
418
|
+
const res = await fetch(url);
|
|
419
|
+
const json = await res.json();
|
|
420
|
+
return json.data?.list || [];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async getData(cid, indicatorIds, startTime, endTime, regionCode = "000000000000") {
|
|
424
|
+
const payload = {
|
|
425
|
+
cid,
|
|
426
|
+
indicatorIds,
|
|
427
|
+
das: [{ text: "全国", value: regionCode }],
|
|
428
|
+
dts: [`${startTime}-${endTime}`],
|
|
429
|
+
showType: "1",
|
|
430
|
+
rootId: this.rootId
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const res = await fetch(`${this.base}/stream/esData`, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: { 'Content-Type': 'application/json' },
|
|
436
|
+
body: JSON.stringify(payload)
|
|
437
|
+
});
|
|
438
|
+
const json = await res.json();
|
|
439
|
+
|
|
440
|
+
if (!json.success) throw new Error(json.message);
|
|
441
|
+
return json.data;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 使用示例
|
|
446
|
+
const client = new NbsNewClient();
|
|
447
|
+
|
|
448
|
+
async function main() {
|
|
449
|
+
const results = await client.searchCid("居民消费价格");
|
|
450
|
+
console.log("Found CIDs:", results);
|
|
451
|
+
|
|
452
|
+
const targetCid = results[0].cid;
|
|
453
|
+
|
|
454
|
+
const indicators = await client.getIndicators(targetCid);
|
|
455
|
+
const cpiIndicator = indicators.find(i => i.i_showname.includes("居民消费价格指数 (上年同月=100)"));
|
|
456
|
+
|
|
457
|
+
if (cpiIndicator) {
|
|
458
|
+
const data = await client.getData(
|
|
459
|
+
targetCid,
|
|
460
|
+
[cpiIndicator._id],
|
|
461
|
+
"202601MM",
|
|
462
|
+
"202603MM"
|
|
463
|
+
);
|
|
464
|
+
console.log("CPI Data:", data);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
main();
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 接口速查表
|
|
474
|
+
|
|
475
|
+
| 需求 | 接口 | 关键参数 |
|
|
476
|
+
|------|------|---------|
|
|
477
|
+
| 关键词找数据集 | `GET /query` | `search`, `pagenum` |
|
|
478
|
+
| 浏览分类体系 | `GET /new/queryIndexTreeAsync` | `pid`, `code` |
|
|
479
|
+
| 拿指标 ID | `GET /new/queryIndicatorsByCid` | `cid` |
|
|
480
|
+
| 查历史时间序列 | `POST /stream/esData` | `cid`, `indicatorIds`, `dts`, `showType` |
|
|
481
|
+
| 获取根节点 ID | `GET /new/queryIndexTreeAsync?pid=` | `pid` (空) |
|