在 Linux 服务器上用 Python + Selenium 模拟登录 JumpServer 的完整实践

本文记录了在 CentOS 8 服务器(无图形界面)上,从零搭建 Selenium 环境并模拟登录 JumpServer 堡垒机的全过程,包含踩坑记录与解决方案。

环境信息

项目 版本/说明
操作系统 CentOS 8(x86_64,已 EOL)
Python 3.11.9
浏览器 Google Chrome 155
自动化库 Selenium
目标系统 JumpServer 堡垒机

一、安装前:先摸清系统环境

安装任何东西之前,先跑一轮检测,避免装错版本:

cat /etc/os-release          #系统版本(CentOS 8 已 EOL,注意 yum 源可能失效)
uname -m                     #架构:x86_64
python3.11 --version         #确认 Python 版本
python3.11 -m pip --version  #确认 pip 属于 3.11
google-chrome --version      #检查浏览器是否已安装
echo $DISPLAY                #为空 = 无图形环境 = 必须无头模式
whoami                       #root = 必须加 --no-sandbox

二、安装 Selenium 和 Chrome

2.1 安装 selenium

python3.11 -m pip install selenium

注意:多版本 Python 共存时,一定要用 python3.11 -m pip,而不是裸 pip,否则可能装到其他版本上。

2.2 安装 Chrome

CentOS 8 没有自带可用的 Chromium,直接安装 Google Chrome 官方 RPM:

wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
dnf install -y ./google-chrome-stable_current_x86_64.rpm
google-chrome --version

注意:CentOS 8 已停止维护,如果 yum 报 Failed to download metadata,需先把源切到 vault:

sed -i 's/mirrorlist/#mirrorlist/; s|#baseurl=https://mirror.centos.org|baseurl=https://vault.centos.org|' /etc/yum.repos.d/CentOS-*.repo
dnf clean all && dnf makecache

2.3 补齐运行依赖库

dnf install -y atk at-spi2-atk cups-libs libdrm libxkbcommon libXcomposite \
    libXdamage libXrandr mesa-libgbm alsa-lib pango nss libXScrnSaver

2.4 安装 chromedriver(本案例最大的坑)

Selenium 4.6+ 虽然自带 Selenium Manager 可以自动下载驱动,但它只认官方源 storage.googleapis.com,国内服务器直连必超时:

selenium.common.exceptions.WebDriverException: error sending request for url
(https://storage.googleapis.com/chrome-for-testing-public/...)

解决方案:从淘宝 npmmirror 镜像手动下载(版本号大版本必须与 Chrome 一致):

wget https://cdn.npmmirror.com/binaries/chrome-for-testing/155.0.8059.79/linux64/chromedriver-linux64.zip
unzip chromedriver-linux64.zip
mv chromedriver-linux64/chromedriver /usr/local/bin/
chmod +x /usr/local/bin/chromedriver

注意:后续 Chrome 自动升级导致大版本变化时,需重新下载对应版本驱动覆盖 /usr/local/bin/chromedriver。

三、启动参数"三件套"

Linux 服务器(尤其是 root + 无图形环境)跑 Chrome 必须加这三个参数:

options.add_argument("--headless=new")          #无头模式:没有显示器也能跑
options.add_argument("--no-sandbox")            #root 下 Chrome 默认拒绝启动
options.add_argument("--disable-dev-shm-usage") #防止 /dev/shm 爆满导致崩溃

四、登录脚本与排错过程

4.1 第一版代码:元素找不到

初版代码用 img.captcha 定位验证码图片,直接报错:

selenium.common.exceptions.NoSuchElementException:
Unable to locate element: {"method":"css selector","selector":"img.captcha"}

加了 20 秒显式等待后依然 TimeoutException。这说明服务器上加载的页面和浏览器里看到的不是同一个。

4.2 诊断:让页面自己说话

与其猜,不如把页面真实结构打印出来:

driver.get(f"{JUMPSERVER_URL}/core/auth/login/")
time.sleep(5)

print("当前URL:", driver.current_url)
print("页面标题:", driver.title)
print("=== 所有 img ===")
for img in driver.find_elements(By.TAG_NAME, "img"):
    print(f"  class={img.get_attribute('class')!r}  alt={img.get_attribute('alt')!r}")
print("=== 所有 input ===")
for inp in driver.find_elements(By.TAG_NAME, "input"):
    print(f"  id={inp.get_attribute('id')!r}  name={inp.get_attribute('name')!r}")

driver.save_screenshot("/tmp/page.png")
open("/tmp/page.html", "w").write(driver.page_source)

输出结果:

=== 所有 img ===
  class='right-image'  alt='screen-image'
  class=''  alt='Logo'
=== 所有 input ===
  id='id_username'  name='username'
  id='password'  name=''          #注意密码框是 id="password"
  id='password-hidden'  name='password'
  id='id_auto_login'  name='auto_login'

真相大白:页面里根本没有验证码! JumpServer 默认策略是"登录失败 N 次后才显示验证码",平时浏览器里看到验证码是因为之前输错过密码。第一版代码所有报错都源于这个误判。

4.3 最终可用代码

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

JUMPSERVER_URL = "http://你的服务器地址:端口"
USERNAME = "你的账号"
PASSWORD = "你的密码"

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(service=Service("/usr/local/bin/chromedriver"), options=options)
wait = WebDriverWait(driver, 15)

try:
    driver.get(f"{JUMPSERVER_URL}/core/auth/login/")

    #填账号密码(诊断确认:id_username 和 password)
    wait.until(EC.visibility_of_element_located((By.ID, "id_username"))).send_keys(USERNAME)
    driver.find_element(By.ID, "password").send_keys(PASSWORD)
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

    #登录后跳转到 /ui/ 即成功
    wait.until(EC.url_contains("/ui/"))
    print("登录成功:", driver.current_url)

    #========== 已登录,干你的事 ==========
    #例如截图确认
    driver.save_screenshot("/tmp/logged_in.png")

finally:
    driver.quit()

运行结果:

登录成功: http://xxx.xxx.xxx.xxx:519/ui/#/

五、经验总结

5.1 等待策略:只在"状态变化点"等

方法 作用 特点
EC.presence_of_element_located 等待条件 超时抛异常,用作"页面就绪信号"
find_elements 立即查找 返回列表(没找到返回空),用于就绪后批量取数

每个状态变化点(打开页面、翻页、提交)等一次,之后连续操作不用等。

5.2 排错心法

  1. 报错先读最后一行,认准异常类型
  2. 元素找不到时,dump 页面的 URL/title/img/input/截图/源码,让事实说话
  3. “浏览器里能看到”≠“selenium 看到的”——浏览器有登录历史和缓存,selenium 是全新会话
  4. 国内服务器:chromedriver 走 npmmirror 镜像,告别 googleapis 超时

5.3 后续优化方向

  • Cookie 复用:登录一次存 Cookie,后续直接加载免登录
  • selenium + requests 组合:登录态交给 selenium,批量爬数据用 requests 带 cookie 请求,毫秒级响应
  • 优先官方 API:JumpServer 有 REST API(个人中心申请 AK/SK),程序化操作远比 UI 自动化稳定