From 1710d9821a72e43b031d00dcb3cc06e115548e38 Mon Sep 17 00:00:00 2001 From: hmo Date: Fri, 14 Aug 2026 21:58:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B8=AF=E8=82=A1=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=A1=A5=E9=87=872=E2=80=94=E2=80=94hk=5Ffundamentals=5Fcollec?= =?UTF-8?q?tor=E5=9F=BA=E6=9C=AC=E9=9D=A2=E9=87=87=E9=9B=86(=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E8=A1=8C=E6=83=85hk=E5=89=8D=E7=BC=80,PE/PB/=E5=B8=82?= =?UTF-8?q?=E5=80=BC,=E5=A4=8D=E7=94=A8A=E8=82=A1=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F,=E6=B8=AF=E8=82=A1pb=E5=9C=A8field43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hk_fundamentals_collector.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 deploy/profile-scripts/hk_fundamentals_collector.py diff --git a/deploy/profile-scripts/hk_fundamentals_collector.py b/deploy/profile-scripts/hk_fundamentals_collector.py new file mode 100644 index 00000000..24fd87ef --- /dev/null +++ b/deploy/profile-scripts/hk_fundamentals_collector.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""hk_fundamentals_collector.py — 港股通基本面采集(PE/PB/市值,阶段2数据补采) + +数据源:腾讯批量行情 hk 前缀(qt.gtimg.cn/q=hk00700,...)。 +复用 fundamentals_full_refresh 的解析模式,港股字段位置: + pe=parts[39] pb=parts[43](A股是46,港股是43) 总市值=parts[44] 流通市值=parts[45](亿) +写入:stock_fundamentals 表(与A股同表复用,code=5位港股代码,eps=price/pe)。 +调度:每日盘后(随A股基本面刷新节奏)。 +限速:批量100只/批,批间0.5s。 +""" +import sqlite3 +import sys +import time +import urllib.request + +DB_PATH = "/home/hmo/MoFin/data/mofin.db" +UA = "Mozilla/5.0" +BATCH = 100 + + +def fetch_hk_batch(codes): + """腾讯批量行情(hk前缀,每批100只),返回 {code: {pe,pb,price,mcap_total,mcap_flow}}""" + if not codes: + return {} + results = {} + symbols = [f"hk{c}" for c in codes] + url = f"http://qt.gtimg.cn/q={','.join(symbols)}" + try: + req = urllib.request.Request(url, headers={"User-Agent": UA}) + opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) + with opener.open(req, timeout=15) as r: + text = r.read().decode("gbk", errors="ignore") + for line in text.strip().split("\n"): + if "~" not in line: + continue + parts = line.split("~") + if len(parts) < 46: + continue + code = parts[2] # 港股代码(00700) + price = float(parts[3]) if parts[3] else 0 + pe = float(parts[39]) if parts[39] else 0 + pb = float(parts[43]) if parts[43] else 0 # 港股 PB 在 43(A股在46) + mcap_t = float(parts[44]) if parts[44] else 0 # 总市值(亿HKD) + mcap_f = float(parts[45]) if parts[45] else 0 # 流通市值(亿HKD) + if price > 0 and code: + results[code] = {"price": price, "pe": pe, "pb": pb, + "mcap_total": mcap_t, "mcap_flow": mcap_f} + except Exception as e: + print(f" batch fetch error: {str(e)[:60]}", flush=True) + return results + + +def main(): + conn = sqlite3.connect(DB_PATH, timeout=30) + conn.execute("PRAGMA busy_timeout=30000") + codes = [r[0] for r in conn.execute( + "SELECT code FROM hk_connect_stocks WHERE is_active=1 ORDER BY code").fetchall()] + print(f"港股通基本面采集:{len(codes)} 只(腾讯行情 PE/PB/市值)", flush=True) + + ok = 0 + t0 = time.time() + for i in range(0, len(codes), BATCH): + batch = codes[i:i + BATCH] + data = fetch_hk_batch(batch) + for code, d in data.items(): + eps = round(d["price"] / d["pe"], 2) if d["pe"] > 0 else 0 + conn.execute( + "INSERT OR REPLACE INTO stock_fundamentals (code, pe, pb, eps, mcap_total, mcap_flow, updated_at) " + "VALUES (?,?,?,?,?,?,datetime('now','localtime'))", + (code, d["pe"], d["pb"], eps, d["mcap_total"], d["mcap_flow"])) + ok += 1 + conn.commit() + print(f" [{i+len(batch)}/{len(codes)}] 本批{len(data)}只 | {time.time()-t0:.0f}s", flush=True) + time.sleep(0.5) + + n = conn.execute("SELECT COUNT(*) FROM stock_fundamentals WHERE length(code)=5").fetchone()[0] + sample = conn.execute( + "SELECT code, pe, pb, mcap_total FROM stock_fundamentals WHERE code IN ('00700','09988','01810')").fetchall() + conn.close() + print(f"\n完成:{ok} 只入 stock_fundamentals(港股 {n} 条),耗时{time.time()-t0:.0f}s", flush=True) + print("样例(code/pe/pb/总市值亿HKD):", flush=True) + for r in sample: + print(f" {r[0]}: pe={r[1]} pb={r[2]} mcap={r[3]:.0f}亿", flush=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main())