feat: 港股数据补采2——hk_fundamentals_collector基本面采集(腾讯行情hk前缀,PE/PB/市值,复用A股解析模式,港股pb在field43)
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user