90 lines
3.8 KiB
Python
90 lines
3.8 KiB
Python
#!/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())
|