chore: initial commit — existing lottoData codebase
Files: - lottery.py (1189 lines) — DoubleColorBallGenerator core engine - fetch_data.py (131 lines) — history data fetcher from 55128.cn - web_executor.py (216 lines) — data fetch Web console (Flask :5000) - app.py (505 lines) — number generation Web service (Flask :8085) - index.html (1171 lines) — frontend SPA - web_console.html (323 lines) — fetch console frontend - deploy/ — systemd service + cron script + logs BIZ-74 architecture review baseline
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
双色球数据抓取 Web 服务
|
||||
提供 Web 界面执行抓取任务和查看实时结果
|
||||
监听 0.0.0.0,支持局域网访问
|
||||
"""
|
||||
|
||||
from flask import Flask, send_from_directory, jsonify
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 脚本路径和输出文件
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SCRIPT_PATH = os.path.join(SCRIPT_DIR, "fetch_data.py")
|
||||
OUTPUT_FILE = os.path.join(SCRIPT_DIR, "双色球历史数据.xlsx")
|
||||
STATUS_FILE = os.path.join(SCRIPT_DIR, ".fetch_status.json")
|
||||
|
||||
# 全局状态
|
||||
execution_status = {
|
||||
"is_running": False,
|
||||
"last_update": None,
|
||||
"last_record_count": 0,
|
||||
"last_error": None
|
||||
}
|
||||
|
||||
# 状态锁
|
||||
status_lock = threading.Lock()
|
||||
|
||||
|
||||
def load_status():
|
||||
"""从文件加载状态"""
|
||||
global execution_status
|
||||
if os.path.exists(STATUS_FILE):
|
||||
try:
|
||||
with open(STATUS_FILE, 'r', encoding='utf-8') as f:
|
||||
execution_status = json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def save_status():
|
||||
"""保存状态到文件"""
|
||||
with status_lock:
|
||||
with open(STATUS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(execution_status, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""首页 - Web 控制台"""
|
||||
return send_from_directory(SCRIPT_DIR, 'web_console.html')
|
||||
|
||||
|
||||
@app.route('/api/status')
|
||||
def api_status():
|
||||
"""获取当前执行状态"""
|
||||
with status_lock:
|
||||
return jsonify({
|
||||
"isRunning": execution_status.get("is_running", False),
|
||||
"lastUpdate": execution_status.get("last_update"),
|
||||
"recordCount": execution_status.get("last_record_count", 0),
|
||||
"lastError": execution_status.get("last_error")
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/execute', methods=['POST'])
|
||||
def api_execute():
|
||||
"""执行抓取脚本"""
|
||||
global execution_status
|
||||
|
||||
with status_lock:
|
||||
if execution_status.get("is_running", False):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "任务正在执行中,请稍后再试"
|
||||
}), 409
|
||||
|
||||
# 启动执行线程
|
||||
def run_script():
|
||||
global execution_status
|
||||
|
||||
with status_lock:
|
||||
execution_status["is_running"] = True
|
||||
execution_status["last_error"] = None
|
||||
save_status()
|
||||
|
||||
try:
|
||||
print(f"[{datetime.now()}] 开始执行抓取脚本...")
|
||||
|
||||
# 执行 Python 脚本
|
||||
result = subprocess.run(
|
||||
["python3", SCRIPT_PATH],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# 解析输出,获取记录数
|
||||
record_count = 0
|
||||
for line in result.stdout.split('\n'):
|
||||
if '共保存' in line and '条记录' in line:
|
||||
try:
|
||||
record_count = int(line.split('共保存')[1].split('条记录')[0].strip())
|
||||
except:
|
||||
pass
|
||||
elif '成功解析' in line and '条数据' in line:
|
||||
try:
|
||||
record_count = int(line.split('成功解析')[1].split('条数据')[0].strip())
|
||||
except:
|
||||
pass
|
||||
|
||||
with status_lock:
|
||||
execution_status["last_update"] = datetime.now().isoformat()
|
||||
execution_status["last_record_count"] = record_count
|
||||
execution_status["is_running"] = False
|
||||
save_status()
|
||||
|
||||
print(f"✅ 执行成功,共抓取 {record_count} 条数据")
|
||||
|
||||
else:
|
||||
error_msg = result.stderr or f"脚本执行失败,返回码:{result.returncode}"
|
||||
with status_lock:
|
||||
execution_status["last_error"] = error_msg
|
||||
execution_status["is_running"] = False
|
||||
save_status()
|
||||
print(f"❌ {error_msg}")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
error_msg = "脚本执行超时(超过 5 分钟)"
|
||||
with status_lock:
|
||||
execution_status["last_error"] = error_msg
|
||||
execution_status["is_running"] = False
|
||||
save_status()
|
||||
print(f"❌ {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"执行异常:{str(e)}"
|
||||
with status_lock:
|
||||
execution_status["last_error"] = error_msg
|
||||
execution_status["is_running"] = False
|
||||
save_status()
|
||||
print(f"❌ {error_msg}")
|
||||
|
||||
# 在后台线程执行
|
||||
thread = threading.Thread(target=run_script, daemon=True)
|
||||
thread.start()
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "任务已启动,正在执行中..."
|
||||
})
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""检查依赖"""
|
||||
missing = []
|
||||
|
||||
try:
|
||||
import flask
|
||||
except ImportError:
|
||||
missing.append("flask")
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
missing.append("requests")
|
||||
|
||||
try:
|
||||
import bs4
|
||||
except ImportError:
|
||||
missing.append("beautifulsoup4")
|
||||
|
||||
try:
|
||||
import pandas
|
||||
except ImportError:
|
||||
missing.append("pandas")
|
||||
|
||||
try:
|
||||
import openpyxl
|
||||
except ImportError:
|
||||
missing.append("openpyxl")
|
||||
|
||||
if missing:
|
||||
print(f"❌ 缺少依赖包:{', '.join(missing)}")
|
||||
print(f" 请运行:pip3 install {' '.join(missing)}")
|
||||
return False
|
||||
|
||||
print("✅ 所有依赖已安装")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("双色球数据抓取 Web 服务")
|
||||
print("=" * 60)
|
||||
|
||||
if not check_dependencies():
|
||||
exit(1)
|
||||
|
||||
load_status()
|
||||
|
||||
print(f"\n📂 脚本路径:{SCRIPT_PATH}")
|
||||
print(f"📁 输出文件:{OUTPUT_FILE}")
|
||||
print(f"\n🌐 服务启动中...")
|
||||
print(f" 监听地址:http://0.0.0.0:5000")
|
||||
print(f" 访问方式:局域网内任意设备访问 http://<本机 IP>:5000")
|
||||
print(f"\n✅ 服务就绪!")
|
||||
print("=" * 60)
|
||||
|
||||
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
|
||||
Reference in New Issue
Block a user