setName('crawler') ->addArgument('action', Argument::REQUIRED, '执行动作: standings/schedule/all/menu') ->setDescription('懂球帝数据爬虫'); } protected function execute(Input $input, Output $output) { $action = $input->getArgument('action'); $allowed = [ 'standings', 'schedule', 'all', 'menu', 'news', 'league_news', 'article_content', 'live', 'lottery', 'lottery_draw', 'lottery_draw_force', 'match_data', 'init-db', 'test', 'csl_match', 'nba_match', 'cba_match', 'epl_match', 'bundesliga_match', 'laliga_match', 'seriea_match', 'ligue1_match', 'ucl_match', 'uel_match', 'tennis_match', 'esports_match', 'sports_match', 'truth_social', 'crypto_news', 'lottery_news', 'taiwan_lottery_news', 'hkjc_lottery_news', 'lottery_community', 'nba_news', 'cba_news', 'article_content_fetch', 'fifa_worldcup_news', 'dqd_worldcup', 'match_finish', 'live_detail', ]; if (!in_array($action, $allowed)) { $output->writeln("不支持的动作: {$action}, 可选: " . implode('/', $allowed) . ""); return; } // 文件锁防止同一动作并发执行 $lockFile = runtime_path() . "crawler_{$action}.lock"; $fp = fopen($lockFile, 'w'); if (!flock($fp, LOCK_EX | LOCK_NB)) { $output->writeln("[Crawler] {$action} 正在执行中,跳过本次"); fclose($fp); return; } try { $crawlerDir = app()->getRootPath() . 'public/dongqiudi-crawler'; $venvPython = $crawlerDir . '/venv/bin/python3'; $python = file_exists($venvPython) ? $venvPython : 'python3'; $cmd = "cd {$crawlerDir} && {$python} main.py {$action} 2>&1"; $output->writeln("[Crawler] 执行: {$cmd}"); $result = shell_exec($cmd); $output->writeln($result ?: '(无输出)'); } finally { flock($fp, LOCK_UN); fclose($fp); @unlink($lockFile); } } }