feat: 港股数据补采2——hk_fundamentals_collector基本面采集(腾讯行情hk前缀,PE/PB/市值,复用A股解析模式,港股pb在field43)

This commit is contained in:
hmo
2026-08-14 21:58:52 +08:00
parent ad5208c6cd
commit 1710d9821a
@@ -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 在 43A股在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())