在 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 排错心法
- 报错先读最后一行,认准异常类型
- 元素找不到时,dump 页面的 URL/title/img/input/截图/源码,让事实说话
- “浏览器里能看到”≠“selenium 看到的”——浏览器有登录历史和缓存,selenium 是全新会话
- 国内服务器:chromedriver 走 npmmirror 镜像,告别 googleapis 超时
5.3 后续优化方向
- Cookie 复用:登录一次存 Cookie,后续直接加载免登录
- selenium + requests 组合:登录态交给 selenium,批量爬数据用 requests 带 cookie 请求,毫秒级响应
- 优先官方 API:JumpServer 有 REST API(个人中心申请 AK/SK),程序化操作远比 UI 自动化稳定