从Laravel调用Python Selenium脚本无法打开浏览器的问题排查
问题:Laravel调用Python Selenium脚本无法打开浏览器
终端直接执行login_script.py能正常打开浏览器完成登录,但通过Laravel的PythonController.php调用时,浏览器无法打开,页面空白且无任何报错。
用户提供的代码
Python代码(login_script.py)
import subprocess from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # Install Selenium if not already installed try: import selenium except ImportError: print("Installing Selenium...") subprocess.run(['pip', 'install', 'selenium']) # Define your credentials and login URL username = 'myusername' password = 'mypassword' login_url = 'https://example.com/login' # Create a WebDriver instance (assuming Chrome) driver = webdriver.Chrome() # Maximize the browser window driver.maximize_window() # Navigate to the login page driver.get(login_url) # Wait for the page to load (you might need to adjust the time depending on the page load time) #time.sleep(1) # Find the username and password input fields and fill them in username_field = driver.find_element(By.NAME, "username") password_field = driver.find_element(By.NAME, "password") username_field.send_keys(username) password_field.send_keys(password) # Submit the form password_field.send_keys(Keys.RETURN) # Optionally, you might want to add some delay here to allow time for the page to load # Wait for the login process to complete time.sleep(999999999) # Adjust as needed # Close the browser session #driver.quit()
Laravel代码(PythonController.php)
<?php namespace Modules\SideMenuBuilder\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; use Illuminate\Support\Facades\Artisan; class PythonController extends Controller { public function openBrowser(Request $request) { try { // Case 1: $basePath = base_path(); $process = new Process(['python3', base_path() . '/open_browser.py']); $process->run(); return $process->getOutput(); // Case 2: // Activate virtual environment $activate_command = 'source pythonenv/bin/activate'; // Run Python script within the activated virtual environment'; $python_command = 'python3 open_browser.py'; // Combine commands with && to execute sequentially $command = "$activate_command && $python_command 2>&1"; // Execute the command and capture output $output = shell_exec($command); // Output the result return response()->json(['output' => $output]); } catch (Exception $e) { return response()->json(['output' => $e->getMessage()]); } //exit(); } }
核心原因排查
- 无图形化环境:Laravel运行在Web服务器(Nginx/Apache)环境下,服务器通常没有桌面图形界面,而Selenium默认需要图形环境才能启动浏览器;终端执行时是在用户的桌面会话中,所以能正常运行。
- 权限与环境变量差异:Web服务器进程(如
www-data)的权限、环境变量和终端用户不同,可能找不到ChromeDriver、Python依赖,或者无法访问图形资源。 - 脚本路径与虚拟环境问题:Laravel代码中脚本文件名不匹配(用户脚本是
login_script.py,代码里写的是open_browser.py),且非交互式shell中source激活虚拟环境的方式无效。
解决方案
1. 配置Selenium为Headless模式(推荐)
修改Python脚本,使用Chrome的Headless模式,无需图形化环境即可运行:
# 修改WebDriver初始化部分 options = webdriver.ChromeOptions() options.add_argument('--headless=new') # 新版Headless模式,兼容性更好 options.add_argument('--no-sandbox') # 解决服务器权限限制 options.add_argument('--disable-dev-shm-usage') # 避免内存不足问题 driver = webdriver.Chrome(options=options)
2. 修复权限与路径问题
- 确保ChromeDriver和Chrome浏览器能被Web服务器用户访问,或者在脚本中指定ChromeDriver绝对路径:
driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=options)
- 给Python脚本添加服务器用户的执行权限:
chown www-data:www-data login_script.py chmod +x login_script.py
3. 修正Laravel中的脚本调用
- 修正脚本文件名:把代码中的
open_browser.py改为实际的login_script.py,并使用绝对路径:
// Case1修改后 $process = new Process(['python3', base_path() . '/login_script.py']);
- 虚拟环境直接调用:非交互式shell中
source无效,直接使用虚拟环境的Python绝对路径:
// Case2修改后 $python_path = base_path() . '/pythonenv/bin/python3'; $script_path = base_path() . '/login_script.py'; $command = "$python_path $script_path 2>&1"; $output = shell_exec($command); return response()->json(['output' => $output]);
4. 捕获详细错误信息
在Laravel中添加错误输出捕获,方便排查问题:
// Case1中补充错误捕获 $process->run(); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return response()->json([ 'output' => $process->getOutput(), 'error' => $process->getErrorOutput() ]);
修改后的完整Python脚本
import subprocess from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # Install Selenium if not already installed try: import selenium except ImportError: print("Installing Selenium...") subprocess.run(['pip', 'install', 'selenium']) # Define your credentials and login URL username = 'myusername' password = 'mypassword' login_url = 'https://example.com/login' # 配置Chrome Headless模式 options = webdriver.ChromeOptions() options.add_argument('--headless=new') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') options.add_argument('--window-size=1920,1080') # 设置窗口大小,保证元素能正常加载 # 初始化WebDriver driver = webdriver.Chrome(options=options) try: # 导航到登录页 driver.get(login_url) # 显式等待元素加载,比time.sleep更可靠 username_field = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.NAME, "username")) ) password_field = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.NAME, "password")) ) # 填写凭证并提交 username_field.send_keys(username) password_field.send_keys(password) password_field.send_keys(Keys.RETURN) # 等待登录跳转完成 WebDriverWait(driver, 10).until( EC.url_changes(login_url) ) # 可选:截图验证登录结果 # driver.save_screenshot(base_path() + '/login_success.png') finally: # 关闭浏览器 time.sleep(5) driver.quit()
内容的提问来源于stack exchange,提问作者Rajeev Singh
相关产品推荐
相关产品推荐

