229 lines
7.4 KiB
Python
229 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
爬蟲管理 API 路由
|
|
提供網頁介面來管理爬蟲的啟用/停用狀態
|
|
"""
|
|
from flask import Blueprint, jsonify, request, redirect, url_for
|
|
from services.crawler_config_loader import (
|
|
load_crawler_config,
|
|
update_crawler_status,
|
|
get_crawler_info,
|
|
get_enabled_crawlers,
|
|
get_paused_crawlers,
|
|
CONFIG_PATH as CRAWLER_CONFIG_PATH,
|
|
)
|
|
import json
|
|
from datetime import datetime
|
|
|
|
# 創建 Blueprint
|
|
crawler_bp = Blueprint('crawler_management', __name__)
|
|
|
|
SOURCE_LABELS = {
|
|
'momo_main': ('MOMO 主站商品監控', '追蹤 MOMO 主站商品與售價,支援 PChome 價差判斷。'),
|
|
'edm_promo': ('MOMO 限時活動監控', '追蹤 MOMO 限時活動商品,補齊促銷壓力與主推機會。'),
|
|
'festival_11': ('1.1 檔期活動監控', '檔期活動商品來源;活動結束時可暫停等待下次啟用。'),
|
|
'mothers_day_2026': ('母親節檔期活動監控', '追蹤母親節檔期活動商品與促銷壓力。'),
|
|
'valentine_520_2026': ('520 情人節活動監控', '追蹤 520 檔期活動商品與促銷壓力。'),
|
|
'labor_day_2026': ('勞動節活動監控', '追蹤勞動節檔期活動商品與促銷壓力。'),
|
|
}
|
|
|
|
|
|
def _operator_text(value):
|
|
text = str(value or '').strip()
|
|
if not text:
|
|
return ''
|
|
replacements = {
|
|
'爬蟲任務': '資料擷取任務',
|
|
'商品爬蟲': '商品監控',
|
|
'促銷爬蟲': '促銷活動監控',
|
|
'EDM 爬蟲': 'EDM 活動監控',
|
|
'爬蟲': '監控來源',
|
|
'保留程式碼和邏輯': '保留設定',
|
|
'同版型活動': '同類型活動',
|
|
}
|
|
for old, new in replacements.items():
|
|
text = text.replace(old, new)
|
|
return text
|
|
|
|
|
|
def _sanitize_source_info(crawler_key, info):
|
|
if not info:
|
|
return None
|
|
sanitized = dict(info)
|
|
label, description = SOURCE_LABELS.get(
|
|
crawler_key,
|
|
(_operator_text(sanitized.get('name')) or '商品監控來源', _operator_text(sanitized.get('description'))),
|
|
)
|
|
sanitized['name'] = label
|
|
sanitized['description'] = description or _operator_text(sanitized.get('description')) or '支援商品、價格與促銷監控。'
|
|
for field in ('pause_reason', 'notes', 'activity_name'):
|
|
if field in sanitized:
|
|
sanitized[field] = _operator_text(sanitized.get(field))
|
|
for internal_field in ('function', 'page_type', 'status', 'paused_date'):
|
|
sanitized.pop(internal_field, None)
|
|
return sanitized
|
|
|
|
|
|
def _sanitize_sources(crawlers):
|
|
return {
|
|
key: _sanitize_source_info(key, info)
|
|
for key, info in (crawlers or {}).items()
|
|
}
|
|
|
|
@crawler_bp.route('/crawler_management')
|
|
def crawler_management_page():
|
|
"""爬蟲管理頁面 - 重定向到整合後的 settings 頁面"""
|
|
return redirect(url_for('system_public.settings'))
|
|
|
|
@crawler_bp.route('/api/crawlers', methods=['GET'])
|
|
def get_crawlers():
|
|
"""API: 取得所有爬蟲配置"""
|
|
try:
|
|
config = load_crawler_config()
|
|
return jsonify({
|
|
"status": "success",
|
|
"data": _sanitize_sources(config.get('crawlers', {}))
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 500
|
|
|
|
@crawler_bp.route('/api/crawlers/<crawler_key>', methods=['GET'])
|
|
def get_crawler(crawler_key):
|
|
"""API: 取得單一爬蟲資訊"""
|
|
try:
|
|
info = get_crawler_info(crawler_key)
|
|
if info is None:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": "監控來源不存在"
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"status": "success",
|
|
"data": _sanitize_source_info(crawler_key, info)
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 500
|
|
|
|
@crawler_bp.route('/api/crawlers/<crawler_key>/toggle', methods=['POST'])
|
|
def toggle_crawler(crawler_key):
|
|
"""API: 切換爬蟲啟用/停用狀態"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
enabled = data.get('enabled')
|
|
reason = data.get('reason', '')
|
|
|
|
if enabled is None:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": "缺少 enabled 參數"
|
|
}), 400
|
|
|
|
# 更新配置
|
|
success = update_crawler_status(crawler_key, enabled, reason)
|
|
|
|
if not success:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": "更新失敗"
|
|
}), 500
|
|
|
|
# 取得更新後的資訊
|
|
info = get_crawler_info(crawler_key)
|
|
display_info = _sanitize_source_info(crawler_key, info) or {'name': '監控來源'}
|
|
|
|
return jsonify({
|
|
"status": "success",
|
|
"message": f"監控來源「{display_info.get('name', '商品監控來源')}」已{'啟用' if enabled else '停用'}",
|
|
"data": display_info
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 500
|
|
|
|
@crawler_bp.route('/api/crawlers/<crawler_key>/schedule', methods=['PUT'])
|
|
def update_crawler_schedule(crawler_key):
|
|
"""API: 更新爬蟲執行頻率"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
schedule_hours = data.get('schedule_hours')
|
|
|
|
if schedule_hours is None:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": "缺少 schedule_hours 參數"
|
|
}), 400
|
|
|
|
try:
|
|
schedule_hours = int(schedule_hours)
|
|
if schedule_hours < 1 or schedule_hours > 24:
|
|
raise ValueError("執行頻率必須在 1-24 小時之間")
|
|
except ValueError as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 400
|
|
|
|
# 載入配置
|
|
config = load_crawler_config()
|
|
|
|
if crawler_key not in config.get('crawlers', {}):
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": "監控來源不存在"
|
|
}), 404
|
|
|
|
# 更新執行頻率
|
|
config['crawlers'][crawler_key]['schedule_hours'] = schedule_hours
|
|
config.setdefault('metadata', {})['last_updated'] = datetime.now().isoformat()
|
|
|
|
# 寫回配置文件
|
|
with open(CRAWLER_CONFIG_PATH, 'w', encoding='utf-8') as f:
|
|
json.dump(config, f, ensure_ascii=False, indent=2)
|
|
|
|
updated_info = _sanitize_source_info(crawler_key, config['crawlers'][crawler_key])
|
|
return jsonify({
|
|
"status": "success",
|
|
"message": f"執行頻率已更新為每 {schedule_hours} 小時",
|
|
"data": updated_info
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 500
|
|
|
|
@crawler_bp.route('/api/crawlers/status', methods=['GET'])
|
|
def get_crawlers_status():
|
|
"""API: 取得爬蟲狀態總覽"""
|
|
try:
|
|
enabled = get_enabled_crawlers()
|
|
paused = get_paused_crawlers()
|
|
|
|
return jsonify({
|
|
"status": "success",
|
|
"data": {
|
|
"enabled_count": len(enabled),
|
|
"paused_count": len(paused),
|
|
"enabled_crawlers": list(enabled.keys()),
|
|
"paused_crawlers": list(paused.keys())
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
"status": "error",
|
|
"message": str(e)
|
|
}), 500
|