Files
tushare/fetch_sector_data_simple.py
2025-10-19 07:46:53 +08:00

310 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""
简化版板块数据抓取脚本
优化性能,避免超时问题
"""
import os
import sys
import time
import json
import pandas as pd
import pymysql
from datetime import datetime
from typing import Dict, List, Optional
import logging
import tudata as ts
# 添加项目根目录到路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from config import Config
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/sector_simple_fetch.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class SimpleSectorDataFetcher:
"""简化版板块数据抓取器"""
def __init__(self):
self.ts_pro = ts.pro_api(Config.TS_TOKEN)
self.db_config = {
'host': Config.DB_HOST,
'port': Config.DB_PORT,
'user': Config.DB_USER,
'password': Config.DB_PASSWORD,
'database': Config.DB_NAME,
'charset': 'utf8mb4'
}
self.progress_file = 'logs/sector_simple_progress.json'
self.stats = {
'start_time': datetime.now().isoformat(),
'processed_tables': 0,
'total_records': 0,
'failed_operations': []
}
def get_db_connection(self):
"""获取数据库连接"""
return pymysql.connect(**self.db_config)
def create_tables(self):
"""创建所需的数据表"""
tables_sql = {
'industry_classify_simple': """
CREATE TABLE IF NOT EXISTS industry_classify_simple (
id INT AUTO_INCREMENT PRIMARY KEY,
ts_code VARCHAR(20) NOT NULL COMMENT '股票代码',
name VARCHAR(100) COMMENT '股票名称',
industry VARCHAR(100) COMMENT '所属行业',
area VARCHAR(50) COMMENT '地域',
market VARCHAR(20) COMMENT '市场',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_ts_code (ts_code),
INDEX idx_industry (industry),
INDEX idx_area (area)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简化行业分类表'
""",
'concept_classify_simple': """
CREATE TABLE IF NOT EXISTS concept_classify_simple (
id INT AUTO_INCREMENT PRIMARY KEY,
concept_code VARCHAR(20) NOT NULL COMMENT '概念代码',
concept_name VARCHAR(100) NOT NULL COMMENT '概念名称',
stock_count INT DEFAULT 0 COMMENT '成分股数量',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_concept_code (concept_code),
INDEX idx_concept_name (concept_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简化概念分类表'
"""
}
conn = self.get_db_connection()
try:
with conn.cursor() as cursor:
for table_name, sql in tables_sql.items():
cursor.execute(sql)
logger.info(f"✅ 创建/检查表: {table_name}")
conn.commit()
logger.info("🎯 所有板块相关表创建完成")
except Exception as e:
logger.error(f"❌ 创建表失败: {e}")
raise
finally:
conn.close()
def fetch_industry_classify_simple(self):
"""简化版行业分类数据抓取"""
logger.info("🚀 开始抓取行业分类数据(简化版)...")
try:
conn = self.get_db_connection()
try:
with conn.cursor() as cursor:
# 直接从stock_basic表获取数据
cursor.execute("""
SELECT ts_code, name, industry, area, market
FROM stock_basic
WHERE ts_code IS NOT NULL AND name IS NOT NULL
""")
stock_data = cursor.fetchall()
if not stock_data:
logger.warning("⚠️ 未获取到股票基本信息")
return 0
# 清空现有数据
cursor.execute("DELETE FROM industry_classify_simple")
# 批量插入数据
insert_sql = """
INSERT INTO industry_classify_simple
(ts_code, name, industry, area, market)
VALUES (%s, %s, %s, %s, %s)
"""
records = []
for row in stock_data:
ts_code, name, industry, area, market = row
records.append((
ts_code,
name,
industry or '',
area or '',
market or ''
))
if records:
cursor.executemany(insert_sql, records)
conn.commit()
record_count = len(records)
self.stats['total_records'] += record_count
logger.info(f"✅ 行业分类数据抓取完成: {record_count}条记录")
return record_count
else:
logger.warning("⚠️ 没有数据需要插入")
return 0
except Exception as e:
logger.error(f"❌ 行业分类数据处理失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
except Exception as e:
logger.error(f"❌ 抓取行业分类数据失败: {e}")
self.stats['failed_operations'].append({
'operation': 'fetch_industry_classify_simple',
'error': str(e),
'time': datetime.now().isoformat()
})
return 0
def fetch_concept_classify_simple(self):
"""简化版概念分类数据抓取"""
logger.info("🚀 开始抓取概念分类数据(简化版)...")
try:
# 只获取概念列表,不获取成分股详情
df = self.ts_pro.concept()
if df.empty:
logger.warning("⚠️ 未获取到概念分类数据")
return 0
conn = self.get_db_connection()
try:
with conn.cursor() as cursor:
# 清空现有数据
cursor.execute("DELETE FROM concept_classify_simple")
# 批量插入数据
insert_sql = """
INSERT INTO concept_classify_simple
(concept_code, concept_name, stock_count)
VALUES (%s, %s, %s)
"""
records = []
for _, row in df.iterrows():
records.append((
row.get('code', ''),
row.get('name', ''),
0 # 暂时设为0,后续可以单独更新
))
cursor.executemany(insert_sql, records)
conn.commit()
record_count = len(records)
self.stats['total_records'] += record_count
logger.info(f"✅ 概念分类数据抓取完成: {record_count}条记录")
return record_count
except Exception as e:
logger.error(f"❌ 概念分类数据插入失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
except Exception as e:
logger.error(f"❌ 抓取概念分类数据失败: {e}")
self.stats['failed_operations'].append({
'operation': 'fetch_concept_classify_simple',
'error': str(e),
'time': datetime.now().isoformat()
})
return 0
def save_progress(self):
"""保存进度"""
try:
os.makedirs('logs', exist_ok=True)
with open(self.progress_file, 'w') as f:
json.dump(self.stats, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"❌ 保存进度失败: {e}")
def run(self):
"""执行简化版板块数据抓取"""
logger.info("🎯 开始简化版板块数据抓取任务...")
try:
# 创建数据表
self.create_tables()
# 按优先级抓取数据
tasks = [
('行业分类(简化)', self.fetch_industry_classify_simple),
('概念分类(简化)', self.fetch_concept_classify_simple)
]
for task_name, task_func in tasks:
logger.info(f"📊 正在处理: {task_name}")
try:
record_count = task_func()
self.stats['processed_tables'] += 1
logger.info(f"✅ {task_name}完成: {record_count}条记录")
# 保存进度
self.save_progress()
# API限频
time.sleep(1)
except Exception as e:
logger.error(f"❌ {task_name}失败: {e}")
self.stats['failed_operations'].append({
'task': task_name,
'error': str(e),
'time': datetime.now().isoformat()
})
# 最终统计
self.stats['end_time'] = datetime.now().isoformat()
self.stats['duration'] = (datetime.now() - datetime.fromisoformat(self.stats['start_time'])).total_seconds()
logger.info("🎉 简化版板块数据抓取任务完成!")
logger.info(f"📊 处理表数: {self.stats['processed_tables']}")
logger.info(f"📈 总记录数: {self.stats['total_records']:,}")
logger.info(f"⏱️ 耗时: {self.stats['duration']:.2f}秒")
if self.stats['failed_operations']:
logger.warning(f"⚠️ 失败操作数: {len(self.stats['failed_operations'])}")
self.save_progress()
except Exception as e:
logger.error(f"❌ 简化版板块数据抓取任务失败: {e}")
self.stats['error'] = str(e)
self.save_progress()
raise
def main():
"""主函数"""
try:
fetcher = SimpleSectorDataFetcher()
fetcher.run()
except KeyboardInterrupt:
logger.info("👋 用户中断程序")
except Exception as e:
logger.error(f"❌ 程序执行失败: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())