126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
import os
|
|
import importlib
|
|
import inspect
|
|
import asyncio
|
|
from typing import List, Dict, Optional
|
|
from core.crawler import BasePlugin
|
|
from core.log import logger
|
|
|
|
class PluginManager:
|
|
def __init__(self, plugin_dir='plugins'):
|
|
self.plugin_dir = plugin_dir
|
|
self.plugins = []
|
|
self.plugin_stats = {}
|
|
self._load_plugins()
|
|
self._init_stats()
|
|
|
|
def _init_stats(self):
|
|
for plugin in self.plugins:
|
|
self.plugin_stats[plugin.name] = {
|
|
'success_count': 0,
|
|
'failure_count': 0,
|
|
'last_run': None
|
|
}
|
|
|
|
def _load_plugins(self):
|
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
full_plugin_path = os.path.join(base_dir, self.plugin_dir)
|
|
|
|
if not os.path.exists(full_plugin_path):
|
|
logger.error(f"插件目录不存在: {full_plugin_path}")
|
|
return
|
|
|
|
for filename in os.listdir(full_plugin_path):
|
|
if filename.endswith('.py') and not filename.startswith('__'):
|
|
module_name = f"{self.plugin_dir}.{filename[:-3]}"
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
for name, obj in inspect.getmembers(module):
|
|
if inspect.isclass(obj) and issubclass(obj, BasePlugin) and obj is not BasePlugin:
|
|
plugin_instance = obj()
|
|
if plugin_instance.enabled:
|
|
logger.info(f"成功加载插件: {name} 来自 {module_name}")
|
|
self.plugins.append(plugin_instance)
|
|
else:
|
|
logger.info(f"插件已禁用,跳过加载: {name} 来自 {module_name}")
|
|
except Exception as e:
|
|
logger.error(f"加载插件失败 {module_name}: {e}")
|
|
|
|
def get_plugin_by_name(self, plugin_name: str) -> Optional[BasePlugin]:
|
|
for plugin in self.plugins:
|
|
if plugin.name == plugin_name:
|
|
return plugin
|
|
return None
|
|
|
|
def get_all_plugin_info(self) -> List[Dict]:
|
|
plugins_info = []
|
|
for plugin in self.plugins:
|
|
stats = self.plugin_stats.get(plugin.name, {
|
|
'success_count': 0,
|
|
'failure_count': 0,
|
|
'last_run': None
|
|
})
|
|
plugins_info.append({
|
|
'id': plugin.name,
|
|
'name': plugin.name,
|
|
'enabled': plugin.enabled,
|
|
'description': getattr(plugin, 'description', f'从{plugin.name}网站爬取代理'),
|
|
'last_run': stats['last_run'],
|
|
'success_count': stats['success_count'],
|
|
'failure_count': stats['failure_count']
|
|
})
|
|
return plugins_info
|
|
|
|
def toggle_plugin(self, plugin_name: str, enabled: bool) -> bool:
|
|
plugin = self.get_plugin_by_name(plugin_name)
|
|
if plugin:
|
|
plugin.enabled = enabled
|
|
logger.info(f"插件 {plugin_name} 已{'启用' if enabled else '禁用'}")
|
|
return True
|
|
return False
|
|
|
|
async def run_plugin(self, plugin_name: str):
|
|
plugin = self.get_plugin_by_name(plugin_name)
|
|
if not plugin:
|
|
logger.error(f"插件不存在: {plugin_name}")
|
|
return []
|
|
|
|
if not plugin.enabled:
|
|
logger.warning(f"插件已禁用: {plugin_name}")
|
|
return []
|
|
|
|
try:
|
|
results = await plugin.run()
|
|
success_count = len(results)
|
|
failure_count = 0
|
|
|
|
from datetime import datetime
|
|
self.plugin_stats[plugin.name] = {
|
|
'success_count': self.plugin_stats[plugin.name]['success_count'] + success_count,
|
|
'failure_count': self.plugin_stats[plugin.name]['failure_count'] + failure_count,
|
|
'last_run': datetime.now().isoformat()
|
|
}
|
|
|
|
logger.info(f"插件 {plugin_name} 执行完成,成功: {success_count}")
|
|
return results
|
|
except Exception as e:
|
|
logger.error(f"插件 {plugin_name} 执行失败: {e}")
|
|
from datetime import datetime
|
|
self.plugin_stats[plugin.name] = {
|
|
'success_count': self.plugin_stats[plugin.name]['success_count'],
|
|
'failure_count': self.plugin_stats[plugin.name]['failure_count'] + 1,
|
|
'last_run': datetime.now().isoformat()
|
|
}
|
|
return []
|
|
|
|
async def run_all(self):
|
|
"""并发运行所有插件"""
|
|
tasks = [plugin.run() for plugin in self.plugins]
|
|
# 并发执行并收集结果
|
|
results_list = await asyncio.gather(*tasks)
|
|
|
|
# 将嵌套列表扁平化并产出结果
|
|
for results in results_list:
|
|
for proxy in results:
|
|
yield proxy
|