144 lines
4.8 KiB
Python
144 lines
4.8 KiB
Python
"""
|
|
deploy/profile-scripts/ths_news.py — 同花顺新闻采集
|
|
API: news.10jqka.com.cn/timeline_web/web/v1/news/list
|
|
offset=publishTime/1000(秒级游标),无限回溯
|
|
用法: python ths_news.py --backfill # 全量回填
|
|
python ths_news.py --daily # 每日增量
|
|
"""
|
|
import sys, os, json, time, sqlite3, requests, random
|
|
from datetime import datetime
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
DB = os.environ.get('MOFIN_DB', os.path.join(os.path.dirname(__file__), '..', 'data', 'mofin.db'))
|
|
|
|
URL = "https://news.10jqka.com.cn/timeline_web/web/v1/news/list"
|
|
HEADERS = {"User-Agent": "Mozilla/5.0", "Referer": "http://stockpage.10jqka.com.cn/"}
|
|
SLEEP = (0.3, 0.8)
|
|
MAX_PAGES = 500
|
|
|
|
|
|
def get_market_id(code):
|
|
"""17=上交所(6开头), 33=深交所(0/3开头)"""
|
|
return 17 if code.startswith('6') else 33
|
|
|
|
|
|
def get_codes():
|
|
"""从 v8.1 5y 策略交易取 58 只股票"""
|
|
conn = sqlite3.connect(DB)
|
|
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'])
|
|
conn.close()
|
|
return sorted(codes)
|
|
|
|
|
|
def fetch_stock(code, max_pages=MAX_PAGES):
|
|
"""翻页抓取一只股票的全部历史新闻"""
|
|
market_id = get_market_id(code)
|
|
items = []
|
|
offset = None
|
|
for page in range(max_pages):
|
|
params = {"marketId": market_id, "code": code, "size": 20}
|
|
if offset:
|
|
params["offset"] = offset
|
|
try:
|
|
r = requests.get(URL, params=params, headers=HEADERS, timeout=15)
|
|
data = r.json()
|
|
news = data.get('data', {}).get('newsList', [])
|
|
except:
|
|
break
|
|
if not news:
|
|
break
|
|
for it in news:
|
|
ts = it.get('publishTime')
|
|
if ts:
|
|
items.append({
|
|
'code': code,
|
|
'title': it.get('title', ''),
|
|
'content': it.get('summary', ''),
|
|
'source': it.get('source', 'ths'),
|
|
'date': datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d %H:%M:%S'),
|
|
'url': it.get('jumpUrl', ''),
|
|
})
|
|
offset = ts / 1000.0
|
|
time.sleep(random.uniform(*SLEEP))
|
|
return items
|
|
|
|
|
|
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.commit()
|
|
|
|
|
|
def save_batch(conn, rows):
|
|
cur = conn.cursor()
|
|
n = 0
|
|
for r in rows:
|
|
try:
|
|
cur.execute(
|
|
"INSERT OR IGNORE INTO stock_news (code, title, content, source, date, url) VALUES (?,?,?,?,?,?)",
|
|
(r['code'], r['title'], r['content'], r['source'], r['date'], r['url']))
|
|
if cur.rowcount: n += 1
|
|
except: pass
|
|
conn.commit()
|
|
return n
|
|
|
|
|
|
def run_backfill():
|
|
"""一次性全量回填"""
|
|
codes = get_codes()
|
|
print(f"=== 同花顺新闻回填: {len(codes)} 只股票 ===", flush=True)
|
|
conn = sqlite3.connect(DB)
|
|
init_table(conn)
|
|
total_new = 0
|
|
for idx, code in enumerate(codes, 1):
|
|
exist = conn.execute("SELECT COUNT(*) FROM stock_news WHERE code=? AND source LIKE '同花顺%'", (code,)).fetchone()[0]
|
|
if exist > 100:
|
|
print(f" [{idx}/{len(codes)}] {code} 已有{exist}条, 跳过", flush=True)
|
|
continue
|
|
arts = fetch_stock(code)
|
|
if arts:
|
|
n = save_batch(conn, arts)
|
|
total_new += n
|
|
dates = sorted(set(a['date'][:10] for a in arts))
|
|
print(f" [{idx}/{len(codes)}] {code}: +{n}条 ({dates[0]}~{dates[-1]})", flush=True)
|
|
else:
|
|
print(f" [{idx}/{len(codes)}] {code}: 0条", flush=True)
|
|
total = conn.execute("SELECT COUNT(*) FROM stock_news").fetchone()[0]
|
|
print(f"\n完成: 新增{total_new}条, 总计{total}条", flush=True)
|
|
conn.close()
|
|
|
|
|
|
def run_daily():
|
|
"""每日增量(只取最新一页)"""
|
|
codes = get_codes()
|
|
print(f"=== 同花顺每日更新: {len(codes)} 只股票 ===", flush=True)
|
|
conn = sqlite3.connect(DB)
|
|
init_table(conn)
|
|
total_new = 0
|
|
for idx, code in enumerate(codes, 1):
|
|
arts = fetch_stock(code, max_pages=3)
|
|
if arts:
|
|
n = save_batch(conn, arts)
|
|
total_new += n
|
|
print(f"完成: +{total_new}条", flush=True)
|
|
conn.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if '--backfill' in sys.argv:
|
|
run_backfill()
|
|
elif '--daily' in sys.argv:
|
|
run_daily()
|
|
else:
|
|
print("用法: --backfill | --daily")
|