PHP exec调用含Selenium的Python脚本运行异常排查
问题描述
- 预期实现流程:网页展示关键词输入表单 → 用户提交关键词 → PHP通过
exec将关键词传入Python脚本 → Python调用selenium、wget下载对应关键词图片 → 脚本执行完成后返回true给PHP,PHP输出执行结果 - 实际故障现象:Python脚本可正常启动并执行完所有逻辑,但表单提交后PHP无对应结果输出,经定位只要Python代码中调用selenium启动webdriver就会触发该问题。
核心故障原因
- Windows会话隔离问题:XAMPP的Apache默认作为系统服务运行在隔离的会话0层,selenium默认启动的带GUI界面的Chrome无法在该会话中正常创建窗口,会导致进程僵死,阻塞PHP的
exec调用,PHP一直等不到进程退出就会一直卡住无输出 - 管道阻塞问题:selenium启动Chrome时会输出大量stderr日志,wget下载时默认打印进度条到stdout,这些输出如果不做重定向,会占满系统管道缓冲区导致进程卡住
- 代码逻辑缺陷:PHP端
exec调用未做参数转义、返回值判断逻辑错误;Python端未配置Chrome无头运行参数、未关闭冗余日志输出、执行完成后未主动退出webdriver进程、未处理目录重复创建的报错。
分步修复方案
1. PHP端代码修改
重点做参数转义、输出流重定向、修正返回值判断逻辑:
<?php echo '<form action="" method="post">'; echo '<input type="text" name="keywords" placeholder="keywords">'; echo '<input type="submit" value="search">'; echo '</form>'; if (isset($_POST['keywords'])) { // 转义关键词,防止特殊字符导致命令执行异常 $keywords = escapeshellarg($_POST['keywords']); // 重定向stderr到stdout,避免管道阻塞,写全python和脚本的绝对路径 $cmd = "D:/ProgramData/Anaconda3/python D:/xampp/htdocs/website_test/test.py {$keywords} 2>&1"; $output = []; $ret_code = 0; exec($cmd, $output, $ret_code); // 取输出的最后一行作为脚本返回值 $get_value = trim(end($output)); // 进程正常退出且返回值为true时判定执行成功 if ($ret_code === 0 && $get_value === "true") { echo 'Y'; }else { echo 'N'; // 调试阶段可打开下方注释,查看具体错误输出 // echo "<pre>".implode("\n",$output)."</pre>"; } } ?>
2. Python端代码修改
重点配置Chrome无头运行模式、关闭冗余日志、主动释放进程、处理异常:
from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import os import wget import sys import codecs sys.stdout = codecs.getwriter("utf-8")(sys.stdout.buffer, "strict") # 配置Chrome启动参数 chrome_options = Options() # 无头模式,无需启动GUI窗口,适配服务端无桌面运行环境 chrome_options.add_argument('--headless=new') # 关闭沙箱,适配服务端权限环境 chrome_options.add_argument('--no-sandbox') # 禁用扩展,减少冗余日志 chrome_options.add_argument('--disable-extensions') # 禁用GPU渲染,无头模式下不需要 chrome_options.add_argument('--disable-gpu') # 关闭Chrome的info级别日志 chrome_options.add_argument('--log-level=3') # 禁用共享内存,防止服务端内存不足报错 chrome_options.add_argument('--disable-dev-shm-usage') driver_path = "D:/chromedriver.exe" driver = webdriver.Chrome(executable_path=driver_path, options=chrome_options) driver.set_window_size(1920,1080) try: driver.get("https://www.instagram.com/") username = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CLASS_NAME, "KPnG0")) ) # FB登录逻辑 login = driver.find_element(by=By.XPATH, value='//*[@id="loginForm"]/div/div[5]/button/span[2]') login.click() username = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "email")) ) password = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "pass")) ) login_FB = driver.find_element(by=By.XPATH, value='//*[@id="loginbutton"]') username.clear() password.clear() username.send_keys('*********') password.send_keys('******') login_FB.click() search = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CLASS_NAME, "XTCLo")) ) keyword = sys.argv[1] search.send_keys(keyword) time.sleep(1) search.send_keys(Keys.RETURN) time.sleep(1) search.send_keys(Keys.RETURN) time.sleep(5) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, "FFVAD")) ) # 滚动加载图片 for i in range(5): driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(5) imgs = driver.find_elements(by=By.CLASS_NAME, value="FFVAD") save_path = os.path.join(os.getcwd(), keyword) # 目录不存在时再创建,避免重复运行报错 if not os.path.exists(save_path): os.mkdir(save_path) # 下载图片 count = 0 for img in imgs: save_as = os.path.join(save_path, keyword + str(count) + '.jpg') # 关闭wget进度条输出,避免多余内容占用输出缓冲区 wget.download(img.get_attribute("src"), save_as, bar=None) count += 1 print("true") except Exception as e: print(f"error: {str(e)}") finally: # 无论执行成功失败,都主动退出driver释放进程 driver.quit()
3. 额外检查项
- 确认Apache服务运行用户对
chromedriver.exe、Python安装目录、图片保存目录有读写权限,若权限不足可将Apache服务的运行用户改为当前登录的本地管理员账户 - Instagram存在反爬机制,频繁请求可能触发验证码导致脚本超时,可根据实际网络情况调整代码中的等待时长。
内容的提问来源于stack exchange,提问作者user19279759
相关产品推荐
相关产品推荐

