feat: 历史数据获取脚本 + 离线模式支持

- 新增 fetch_history.py 获取板块历史K线数据
- 支持从数据库读取数据(离线模式)
- 历史数据分析功能已可用
This commit is contained in:
2026-04-10 18:05:59 +08:00
parent 1fb58d23da
commit 8d77c4a852
4 changed files with 306 additions and 121 deletions

175
fetch_history.py Normal file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
批量获取板块历史K线数据
"""
import urllib.request
import json
import sqlite3
import os
import time
from datetime import datetime
from pathlib import Path
# 清除代理
for v in ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY']:
os.environ.pop(v, None)
SCRIPT_DIR = Path(__file__).parent
DATA_DIR = SCRIPT_DIR / "data"
DB_FILE = DATA_DIR / "board_history.db"
def get_board_codes():
"""从数据库获取已保存的板块代码"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute('SELECT DISTINCT board_code, board_name FROM board_data WHERE board_type="industry"')
industry = cursor.fetchall()
cursor.execute('SELECT DISTINCT board_code, board_name FROM board_data WHERE board_type="concept"')
concept = cursor.fetchall()
conn.close()
return industry, concept
def get_kline(code: str, days: int = 25):
"""获取板块历史K线"""
secid = f"90.{code}"
url = f"http://push2his.eastmoney.com/api/qt/stock/kline/get?secid={secid}&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61,f62,f63&klt=101&fqt=1&end=20500101&lmt={days}"
try:
req = urllib.request.Request(url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'http://quote.eastmoney.com/'
})
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read().decode())
if data.get('data') and data['data'].get('klines'):
klines = data['data']['klines']
result = []
for kline in klines:
parts = kline.split(',')
# 格式: 日期,开盘,收盘,最高,最低,成交量,成交额,振幅,涨跌幅,涨跌额,换手率
result.append({
'date': parts[0],
'pct_change': float(parts[8]) if parts[8] != '-' else 0,
'amount': float(parts[6]) if parts[6] != '-' else 0,
})
return result
except Exception as e:
pass
return None
def save_to_db(board_type: str, code: str, name: str, klines: list):
"""保存到数据库"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
count = 0
for kline in klines:
try:
cursor.execute('''
INSERT OR REPLACE INTO board_data
(date, board_type, board_code, board_name, pct_change, main_flow, leader_name)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
kline['date'],
board_type,
code,
name,
kline['pct_change'],
kline['amount'] / 1e8,
''
))
count += 1
except:
continue
conn.commit()
conn.close()
return count
def main():
print("\n📊 批量获取板块历史数据")
print("=" * 50)
industry, concept = get_board_codes()
print(f"\n行业板块: {len(industry)}")
print(f"概念板块: {len(concept)}")
# 获取行业板块历史
print("\n📡 获取行业板块历史数据...")
success = 0
for i, (code, name) in enumerate(industry, 1):
print(f" [{i}/{len(industry)}] {name}...", end=' ', flush=True)
klines = get_kline(code, 25)
if klines:
count = save_to_db('industry', code, name, klines)
print(f"{count}")
success += 1
else:
print("")
time.sleep(0.15)
print(f"\n行业板块完成: {success}/{len(industry)}")
# 获取概念板块历史
print("\n📡 获取概念板块历史数据...")
success = 0
for i, (code, name) in enumerate(concept, 1):
print(f" [{i}/{len(concept)}] {name}...", end=' ', flush=True)
klines = get_kline(code, 25)
if klines:
count = save_to_db('concept', code, name, klines)
print(f"{count}")
success += 1
else:
print("")
time.sleep(0.15)
print(f"\n概念板块完成: {success}/{len(concept)}")
# 统计
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute('SELECT COUNT(DISTINCT date) FROM board_data')
date_count = cursor.fetchone()[0]
cursor.execute('SELECT MIN(date), MAX(date) FROM board_data')
min_date, max_date = cursor.fetchone()
cursor.execute('SELECT COUNT(*) FROM board_data')
total = cursor.fetchone()[0]
cursor.execute('SELECT date, COUNT(*) FROM board_data GROUP BY date ORDER BY date')
dates = cursor.fetchall()
conn.close()
print(f"\n✅ 完成!")
print(f" 数据日期: {date_count}")
print(f" 日期范围: {min_date} ~ {max_date}")
print(f" 总记录: {total}")
print(f"\n 日期分布:")
for date, cnt in dates[-10:]:
print(f" {date}: {cnt}")
if __name__ == "__main__":
main()