在量化交易或板块资金轮动策略的开发中,获取概念板块的主力资金流向数据是常见需求。(本文中代码仅技术交流,禁止商用,后果自负。)
1. 接口分析与直连
通过分析东方财富网页的 Network 请求,定位到承载表格数据的核心数据流接口:
https://push2.eastmoney.com/api/qt/clist/get
直接向该 API 发送 HTTP GET 请求,可以绕过所有前端 DOM 结构的解析与渲染过程。该接口支持单次最大返回 100 条记录(通过参数 pz 控制),因此只需通过 pn 参数进行 5 次分页请求即可完整抓取所有概念板块。
2. 主力最大股的智能解析
在返回的数据字典中,接口使用 f204 和 f205 字段表示今日主力流入最大股。需要注意的是,这两个字段在不同条件下其内部承载的“股票代码”与“股票名称”可能会发生对调。 本程序通过判断字符特征(是否仅包含数字,或是否包含 SZ/SH/BJ 前缀)来实现自动分离,免去了二次网络查询股票全称的开销。
3. Python 实现源码
代码仅依赖 Python 标准库与 requests 库:
import csvimport timeimport datetimeimport requestsdefformat_amount(val):if val isNoneor val == "-":return"-"try: val = float(val)except ValueError:return str(val) abs_val = abs(val)if abs_val >= 10**8:returnf"{val / 10**8:.2f}亿"elif abs_val >= 10**4:returnf"{val / 10**4:.2f}万"else:returnf"{val:.2f}"defformat_pct(val):if val isNoneor val == "-":return"-"try: val = float(val)returnf"{val:.2f}%"except ValueError:return str(val)defextract_stock_name(f204, f205):if f204 == "-"ornot f204:return f205if f205 == "-"ornot f205:return f204 is_f204_code = f204.isdigit() or f204.startswith(('SZ', 'SH', 'BJ'))if is_f204_code:return f205else:return f204defscrape_data(): url = "https://push2.eastmoney.com/api/qt/clist/get" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Referer": "https://data.eastmoney.com/" } all_items = [] print("正在通过东方财富数据中心 API 拉取资金流向数据...")for page_num in range(1, 7): params = {"fid": "f62","po": "1","pz": "100","pn": str(page_num),"np": "1","fltt": "2","invt": "2","fs": "m:90+t:3","fields": "f12,f14,f2,f3,f62,f184,f66,f69,f72,f75,f78,f81,f84,f87,f204,f205" } data = Nonefor attempt in range(5):try: response = requests.get(url, params=params, headers=headers, timeout=15) response.encoding = 'utf-8' data = response.json()breakexcept Exception as e: print(f"请求第 {page_num} 页数据出错 (重试 {attempt + 1}/5): {e}") time.sleep(1.5)if data isNone: print(f"请求第 {page_num} 页数据失败,结束拉取。")breakif"data"in data and data["data"] isnotNoneand"diff"in data["data"]: diff = data["data"]["diff"]ifnot diff:break all_items.extend(diff)else:break total_count = len(all_items) print(f"数据拉取完毕,共获得 {total_count} 条板块数据。")ifnot all_items: print("未获取到任何板块数据。")return scraped_rows = []for idx, item in enumerate(all_items): f204 = item.get("f204", "-") f205 = item.get("f205", "-") stock_name = extract_stock_name(f204, f205) row_data = {"序号": str(idx + 1),"概念板块": item.get("f14", "-"),"今日涨跌幅": format_pct(item.get("f3")),"今日主力净流入-净额": format_amount(item.get("f62")),"今日主力净流入-净占比": format_pct(item.get("f184")),"今日超大单净流入-净额": format_amount(item.get("f66")),"今日超大单净流入-净占比": format_pct(item.get("f69")),"今日大单净流入-净额": format_amount(item.get("f72")),"今日大单净流入-净占比": format_pct(item.get("f75")),"今日中单净流入-净额": format_amount(item.get("f78")),"今日中单净流入-净占比": format_pct(item.get("f81")),"今日小单净流入-净额": format_amount(item.get("f84")),"今日小单净流入-净占比": format_pct(item.get("f87")),"今日主力净流入最大股": stock_name } scraped_rows.append(row_data) today_str = datetime.date.today().strftime('%Y%m%d') output_file = f"概念板块资金流向_{today_str}.csv" headers_csv = ["序号", "概念板块", "今日涨跌幅", "今日主力净流入-净额", "今日主力净流入-净占比","今日超大单净流入-净额", "今日超大单净流入-净占比","今日大单净流入-净额", "今日大单净流入-净占比","今日中单净流入-净额", "今日中单净流入-净占比","今日小单净流入-净额", "今日小单净流入-净占比","今日主力净流入最大股" ] print(f"正在将数据保存至 {output_file}...")try:with open(output_file, "w", encoding="utf-8-sig", newline="") as f: writer = csv.DictWriter(f, fieldnames=headers_csv) writer.writeheader() writer.writerows(scraped_rows) print("保存成功!")except PermissionError: print(f"保存失败:由于文件 '{output_file}' 已被 Excel/WPS/VS Code 等其他程序打开占用,写入请求被拒绝。请先关闭文件,然后重试!")except Exception as e: print(f"保存文件出错: {e}")if __name__ == "__main__": scrape_data()