feat: 缺口2修复——news_collector_full全市场新闻采集(4013只东财search-api按code搜,只存近3天,4批cron盘前7:30-8:00)+4个包装脚本
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""news_collector_full.py — 全市场新闻采集(stock_news 日常刷新)
|
||||||
|
|
||||||
|
背景(2026-08-12 架构补缺):
|
||||||
|
stock_news 403万行 8/4 停更。现有 news_collector/ths_news 只覆盖 v8.1 的 58 只,
|
||||||
|
未注册 cron。p_oversold 的 news3 因子需要全市场个股近3日新闻数。
|
||||||
|
|
||||||
|
数据源:东财 search-api(news_collector.fetch_stock_news,keyword=code 翻页)
|
||||||
|
老莫改直连后实测可用(600519 返回 50 条含当日最新)。
|
||||||
|
策略:全市场 4013 只按 code 逐个搜,每只取第一页(50条)只存近3天。
|
||||||
|
4 个 cron 分批(--batch 0/1/2/3 各~1000只),0.4s 间隔防封,INSERT OR IGNORE 幂等。
|
||||||
|
|
||||||
|
调度:盘前 4 批(8:20/8:30/8:40/8:50)+ 盘后 4 批(16:40/16:45/16:50/16:55)
|
||||||
|
规范:单例守卫(5.3) + 限速防封 + 分批 commit + 近3天过滤
|
||||||
|
"""
|
||||||
|
import sys, os, json, time, sqlite3, fcntl
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
from news_collector import fetch_stock_news, init_table
|
||||||
|
|
||||||
|
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
|
||||||
|
SLEEP = 0.4 # 请求间隔(防东财封)
|
||||||
|
BATCHES = 4 # 4 批
|
||||||
|
RECENT_DAYS = 3 # 只存近 3 天新闻(news3 因子口径)
|
||||||
|
|
||||||
|
|
||||||
|
def _singleton_guard(tag):
|
||||||
|
lock_dir = Path("/tmp/mofin_locks")
|
||||||
|
lock_dir.mkdir(exist_ok=True)
|
||||||
|
try:
|
||||||
|
fd = os.open(str(lock_dir / f"{tag}.lock"), os.O_CREAT | os.O_RDWR)
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
return fd
|
||||||
|
except OSError:
|
||||||
|
print(f"[{tag}] 已有实例在运行,退出", flush=True)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def get_codes(conn, batch):
|
||||||
|
"""全市场 A 股(stock_daily distinct),按 batch 分批"""
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT DISTINCT code FROM stock_daily WHERE length(code)=6 ORDER BY code"
|
||||||
|
).fetchall()
|
||||||
|
codes = [str(r[0]) for r in rows]
|
||||||
|
return [c for i, c in enumerate(codes) if i % BATCHES == batch]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
batch = 0
|
||||||
|
limit = 0
|
||||||
|
i = 1
|
||||||
|
while i < len(sys.argv):
|
||||||
|
a = sys.argv[i]
|
||||||
|
if a.startswith("--batch"):
|
||||||
|
batch = int(a.split("=")[-1] if "=" in a else sys.argv[i + 1])
|
||||||
|
if a.startswith("--limit"):
|
||||||
|
limit = int(a.split("=")[-1] if "=" in a else sys.argv[i + 1])
|
||||||
|
i += 1
|
||||||
|
tag = f"news_collector_full_b{batch}"
|
||||||
|
_fd = _singleton_guard(tag)
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
print(f"[{tag}] {datetime.now().strftime('%H:%M:%S')} 全市场新闻采集(batch={batch}) 开始", flush=True)
|
||||||
|
|
||||||
|
conn = sqlite3.connect(str(DB_PATH), timeout=30)
|
||||||
|
init_table(conn)
|
||||||
|
codes = get_codes(conn, batch)
|
||||||
|
if limit > 0:
|
||||||
|
codes = codes[:limit]
|
||||||
|
print(f" [测试模式] 只跑前 {limit} 只", flush=True)
|
||||||
|
print(f" 股票池: {len(codes)} 只 (batch {batch}/{BATCHES})", flush=True)
|
||||||
|
|
||||||
|
since = (datetime.now() - timedelta(days=RECENT_DAYS)).strftime("%Y-%m-%d")
|
||||||
|
cur = conn.cursor()
|
||||||
|
ok = fail = empty = new = 0
|
||||||
|
for idx, code in enumerate(codes, 1):
|
||||||
|
try:
|
||||||
|
arts = fetch_stock_news(code, max_pages=1) # 第一页 50 条
|
||||||
|
if not arts:
|
||||||
|
empty += 1
|
||||||
|
continue
|
||||||
|
n = 0
|
||||||
|
for a in arts:
|
||||||
|
# 只存近 RECENT_DAYS 天(news3 口径),date 格式 'YYYY-MM-DD HH:MM' 或 'YYYY-MM-DD HH:MM:SS'
|
||||||
|
dstr = (a.get("date") or "")[:10]
|
||||||
|
if dstr and dstr >= since:
|
||||||
|
try:
|
||||||
|
cur.execute(
|
||||||
|
"INSERT OR IGNORE INTO stock_news (code, title, content, source, date, url) "
|
||||||
|
"VALUES (?,?,?,?,?,?)",
|
||||||
|
(code, a.get("title", ""), a.get("content", ""),
|
||||||
|
a.get("source", "eastmoney"), a.get("date", ""), a.get("url", "")))
|
||||||
|
if cur.rowcount:
|
||||||
|
n += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
new += n
|
||||||
|
ok += 1
|
||||||
|
except Exception as e:
|
||||||
|
fail += 1
|
||||||
|
if fail <= 5:
|
||||||
|
print(f" FAIL {code}: {str(e)[:60]}", flush=True)
|
||||||
|
if idx % 50 == 0:
|
||||||
|
conn.commit()
|
||||||
|
print(f" [{idx}/{len(codes)}] ok={ok} empty={empty} fail={fail} 新增={new} | {time.time()-t0:.0f}s", flush=True)
|
||||||
|
time.sleep(SLEEP)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
dt = time.time() - t0
|
||||||
|
print(f"[{tag}] 完成: {ok}/{len(codes)} 成功, {empty} 无新闻, {fail} 失败, 新增 {new} 条, 耗时 {dt:.0f}s", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user