126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
"""
|
|
news_collector.py — 东方财富新闻翻页采集(替换 mofin_news.py 的 10 条模式)
|
|
跑一次回填近 4 个月 (~500 条/股),之后每日增量更新
|
|
"""
|
|
import sys, os, json, re, time, sqlite3
|
|
from datetime import datetime, timedelta
|
|
import requests
|
|
|
|
DB = '/home/hmo/MoFin/data/mofin.db'
|
|
URL = "https://search-api-web.eastmoney.com/search/jsonp"
|
|
UA = {"User-Agent": "Mozilla/5.0", "Referer": "https://so.eastmoney.com/news/s"}
|
|
PAGE_SIZE = 50
|
|
SLEEP = 0.5 # 请求间隔
|
|
|
|
def fetch_stock_news(code, max_pages=10):
|
|
"""翻页采集一只股票的东方财富新闻"""
|
|
all_arts = []
|
|
for page in range(1, max_pages + 1):
|
|
inner = {
|
|
"uid": "", "keyword": code, "type": ["cmsArticleWebOld"],
|
|
"client": "web", "clientType": "web", "clientVersion": "curr",
|
|
"param": {"cmsArticleWebOld": {
|
|
"searchScope": "default", "sort": "default",
|
|
"pageIndex": page, "pageSize": PAGE_SIZE,
|
|
"preTag": "<em>", "postTag": "</em>",
|
|
}},
|
|
}
|
|
params = {"cb": "jQuery_nc", "param": json.dumps(inner, ensure_ascii=False), "_": "1"}
|
|
try:
|
|
r = requests.get(URL, params=params, headers=UA, timeout=20)
|
|
m = re.search(r'jQuery_nc\((.*)\)', r.text)
|
|
if not m: break
|
|
data = json.loads(m.group(1))
|
|
arts = data.get('result', {}).get('cmsArticleWebOld', [])
|
|
if not arts: break
|
|
for a in arts:
|
|
all_arts.append({
|
|
'code': code,
|
|
'title': re.sub(r'<[^>]+>', '', a.get('title', '')),
|
|
'content': re.sub(r'<[^>]+>', '', a.get('content', '')),
|
|
'source': a.get('mediaName', ''),
|
|
'date': a.get('date', '')[:19],
|
|
'url': f"http://finance.eastmoney.com/a/{a.get('code','')}.html",
|
|
})
|
|
time.sleep(SLEEP)
|
|
except Exception:
|
|
break
|
|
return all_arts
|
|
|
|
|
|
def init_table(conn):
|
|
conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS stock_news (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT NOT NULL,
|
|
title TEXT,
|
|
content TEXT,
|
|
source TEXT,
|
|
date TEXT,
|
|
url TEXT,
|
|
sentiment TEXT,
|
|
created_at TEXT DEFAULT (datetime('now','localtime')),
|
|
UNIQUE(code, date, title)
|
|
)
|
|
""")
|
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_sn_code_date ON stock_news(code, date)")
|
|
conn.commit()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
conn = sqlite3.connect(DB)
|
|
init_table(conn)
|
|
|
|
# 取策略用到的 58 只股票
|
|
codes = set()
|
|
for r in conn.execute("SELECT results_json FROM strategy_research WHERE version='v8.1' AND period_tag='5y'").fetchall():
|
|
for t in json.loads(r[0])['trades']:
|
|
codes.add(t['code'])
|
|
codes = sorted(codes)
|
|
|
|
# 回填模式
|
|
is_backfill = '--backfill' in sys.argv
|
|
is_daily = '--daily' in sys.argv
|
|
max_pages = 15 if is_backfill else 3 # 回填翻15页,日常只翻3页
|
|
|
|
if is_backfill:
|
|
print(f"=== 回填 {len(codes)} 只股票 (最多{max_pages}页/PAGE_SIZE={PAGE_SIZE}) ===", flush=True)
|
|
else:
|
|
print(f"=== 增量更新 {len(codes)} 只股票 ({PAGE_SIZE}条/页, 最多{max_pages}页) ===", flush=True)
|
|
|
|
total_new = 0
|
|
for idx, code in enumerate(codes, 1):
|
|
# 回填模式不过滤,日常只取最新
|
|
if not is_backfill:
|
|
latest = conn.execute("SELECT date FROM stock_news WHERE code=? ORDER BY date DESC LIMIT 1", (code,)).fetchone()
|
|
if latest:
|
|
# 只需取最新日期之后的数据
|
|
print(f" [{idx}/{len(codes)}] {code} 已有,跳到下一只", flush=True)
|
|
continue
|
|
|
|
arts = fetch_stock_news(code, max_pages)
|
|
if not arts:
|
|
print(f" [{idx}/{len(codes)}] {code}: 0条", flush=True)
|
|
continue
|
|
|
|
cursor = conn.cursor()
|
|
n = 0
|
|
for a in arts:
|
|
try:
|
|
cursor.execute("INSERT OR IGNORE INTO stock_news (code, title, content, source, date, url) VALUES (?,?,?,?,?,?)",
|
|
(a['code'], a['title'], a['content'], a['source'], a['date'], a['url']))
|
|
if cursor.rowcount:
|
|
n += 1
|
|
except:
|
|
pass
|
|
conn.commit()
|
|
total_new += n
|
|
if arts:
|
|
dates = sorted(set(a['date'][:10] for a in arts))
|
|
print(f" [{idx}/{len(codes)}] {code}: +{n}条 {dates[0]}~{dates[-1] if len(dates)>1 else dates[0]}", flush=True)
|
|
time.sleep(SLEEP)
|
|
|
|
total = conn.execute("SELECT COUNT(*) FROM stock_news").fetchone()[0]
|
|
print(f"\n完成: 新增{total_new}条, 总共{total}条", flush=True)
|
|
conn.close()
|