You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 12:57:45