【PYTHON】使用Selenium + ChromeDriver以及XPATH语法

【PYTHON】使用Selenium + ChromeDriver以及XPATH语法

      • 查看chrome版本
      • 手动下载驱动
      • 代码
      • 如何配置监听地址0.0.0.0无效
      • XPATH语法
        • [一、 基础路径选择](#一、 基础路径选择)
        • [二、 属性与文本过滤(谓语)](#二、 属性与文本过滤(谓语))
          • [1. 属性过滤(使用 `@` 符号)](#1. 属性过滤(使用 @ 符号))
          • [2. 文本内容过滤](#2. 文本内容过滤)
        • [三、 模糊匹配与高级函数](#三、 模糊匹配与高级函数)
        • [四、 节点关系与轴(Axes)](#四、 节点关系与轴(Axes))
        • [五、 按位置/次序选择](#五、 按位置/次序选择)
        • [六、 Python/Selenium 实战代码示例](#六、 Python/Selenium 实战代码示例)
        • [🛠️ XPath 调试技巧](#🛠️ XPath 调试技巧)
        • 特例
        • [1. `//divcontains(@class,'form')//labelnormalize-space(text())='密码'`](#1. //div[contains(@class,'form')]//label[normalize-space(text())='密码'])
        • [2. `//div@class='form'.//label\[text()='密码']`](#2. //div[@class='form'][.//label[text()='密码']])
        • [3. `//div@class='form'./label\[text()='密码']`](#3. //div[@class='form'][./label[text()='密码']])
        • [📊 核心区别总结](#📊 核心区别总结)

查看chrome版本

复制代码
reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version

手动下载驱动

120.0.6099.109需要换成自己的

复制代码
https://storage.googleapis.com/chrome-for-testing-public/120.0.6099.109/win64/chromedriver-win64.zip

代码

python 复制代码
import subprocess
import time
from selenium import webdriver

# Step 1: 以远程调试模式启动 Chrome(命令行执行)
# Windows: & "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 --user-data-dir="C:\chrome_debug_profile"
# macOS:   /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir="/tmp/chrome_debug"

# Step 2: Python 连接到已有实例
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")

driver = webdriver.Chrome(options=options)
print(f"已连接到现有浏览器,当前页面: {driver.title}")
# 此时所有 Cookie、登录状态均保留
python 复制代码
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager

# 1. 配置 Chrome 选项
options = webdriver.ChromeOptions()
# ✅ 关键配置:脚本结束后保持浏览器打开(不自动关闭)
options.add_experimental_option("detach", True)
options.add_argument("--start-maximized")          # 最大化窗口
options.add_argument("--disable-blink-features=AutomationControlled")  # 降低被检测概率

# 2. 初始化驱动(自动下载/匹配 ChromeDriver)
service = Service(executable_path=r"D:\pythonauto\chromeDriver\driver\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 常用配置


try:
    # 3. 打开百度
    driver.get("https://www.baidu.com")
    print("✅ 已打开百度")

    # 4. 等待搜索框加载并输入"手机"
    search_box = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "chat-textarea"))
    )
    search_box.clear()
    search_box.send_keys("手机")
    print("✅ 已输入关键词:手机")

    # 5. 点击"百度一下"按钮
    submit_btn = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.ID, "chat-submit-button"))
    )
    submit_btn.click()
    print("✅ 已点击搜索,请在浏览器中查看结果")

    # 6. ✅ 阻塞等待用户手动输入 q 退出
    print("\n" + "=" * 40)
    while True:
        user_input = input("💡 按 q 并回车退出浏览器: ").strip().lower()
        if user_input == "q":
            break
        print("❌ 输入无效,请输入 q 退出")

finally:
    # 7. 用户确认后关闭浏览器并释放资源
    print("🔒 正在关闭浏览器...")
    driver.quit()
    print("✅ 浏览器已安全关闭")

如何配置监听地址0.0.0.0无效

windows shell执行

复制代码
# 让 Chrome 正常监听 127.0.0.1:9223(不需要改任何参数)
# 然后添加系统级端口转发:外部访问 0.0.0.0:9223 → 127.0.0.1:9223

netsh interface portproxy add v4tov4 listenport=9223 listenaddress=0.0.0.0 connectport=9223 connectaddress=127.0.0.1

# 验证转发规则已生效
netsh interface portproxy show all

# 验证外部可访问
netstat -ano | findstr :9223
# 应同时看到 0.0.0.0:9223 LISTENING 和 127.0.0.1:9223 LISTENING

#删除规则
#netsh interface portproxy delete v4tov4 listenport=9223 listenaddress=0.0.0.0

XPATH语法

一、 基础路径选择

路径表达式用于描述目标节点与文档根节点的关系,分为绝对路径和相对路径。

语法 说明 示例
/ 绝对路径:从根节点开始,一层层往下找。 /html/body/div/form/input
// 相对路径:从任意节点开始,忽略前面的层级,查找所有匹配的后代元素。 //input//div//input
. 选取当前节点。 .
.. 选取当前节点的父节点。 //input/.. 选取 input 的父节点

💡 最佳实践 :在自动化测试中,强烈建议使用相对路径 (//)。绝对路径一旦前端页面结构发生微调(如多嵌套一层 div),定位就会立刻失效。


二、 属性与文本过滤(谓语)

使用方括号 [] 作为谓语(Predicate),可以根据属性值或文本内容精确筛选节点。

1. 属性过滤(使用 @ 符号)
语法 说明 示例
[@属性名='值'] 精确匹配属性值 //input[@id='username']
[@属性名] 仅判断是否拥有该属性 //*[@multiple]
[@attr1 and @attr2] 同时满足多个属性 //input[@type='text' and @name='email']
2. 文本内容过滤
语法 说明 示例
text()='文本' 精确匹配文本内容 //span[text()='下一步']
normalize-space(.)='文本' 去除首尾空白、合并中间空格后精确匹配(最稳健)。 //button[normalize-space(.)='确认']

三、 模糊匹配与高级函数

当属性值或文本是动态生成、包含空格或只有部分固定时,使用以下函数。

函数 说明 示例
contains() 包含匹配(最常用) //label[contains(@class,'btn')]``//span[contains(text(),'下一步')]
starts-with() 前缀匹配 //label[starts-with(@class,'btn')]
ends-with() 后缀匹配(XPath 2.0,部分浏览器支持) //input[ends-with(@id,'_input')]
string-length() 字符串长度判断 //input[string-length(@value)>5]

四、 节点关系与轴(Axes)

用于处理复杂的 DOM 结构,如父子、兄弟节点定位。

轴名称 说明 示例
parent:: 选取父节点 //input[@id='child']/parent::div
child:: 选取直接子元素 //ul/child::li (等同于 //ul/li)
descendant:: 选取所有后代元素 //ul/descendant::input (等同于 //ul//input)
ancestor:: 选取所有先辈节点(父、祖父等) //input/ancestor::form
following-sibling:: 选取当前节点之后的所有同级兄弟节点 //label[text()='标题']/following-sibling::input
preceding-sibling:: 选取当前节点之前的所有同级兄弟节点 //input[@type='submit']/preceding-sibling::button

五、 按位置/次序选择

当页面有多个相同结构的元素时,可以通过索引定位。

⚠️ 注意 :XPath 的索引是从 1 开始的,不是从 0 开始!

语法 说明 示例
[n] 选取某类型的第 n 个子元素 //ul/li 选取第二个 li
[last()] 选取最后一个元素 //p[last()]
[last()-n] 选取倒数第 n+1 个元素 //div/p[last()-2]
[position()<=n] 选取前 n 个元素 //option[position()<=3]

六、 Python/Selenium 实战代码示例

结合 Selenium 的 find_element 方法,以下是常见场景的代码实现:

python 复制代码
from selenium.webdriver.common.by import By

# 1. 基础属性定位
driver.find_element(By.XPATH, "//input[@id='username']")

# 2. 多属性组合定位 (AND 逻辑)
driver.find_element(By.XPATH, "//input[@type='text' and @name='email']")

# 3. 模糊匹配 class (防止 class 动态拼接导致失效)
driver.find_element(By.XPATH, "//button[contains(@class, 'aui-button--primary')]")

# 4. 通过文本定位并处理空白字符
driver.find_element(By.XPATH, "//button[normalize-space(text())='确认']")

# 5. 相对位置定位:找到 label 后的第一个 input 兄弟节点
driver.find_element(By.XPATH, "//label[text()='标题']/following-sibling::input")[[source_group_web_16]]

# 6. 向上查找祖先节点
driver.find_element(By.XPATH, "//input[@id='child']/ancestor::div[@class='form-item']")

# 7. 获取多个元素 (返回列表)
elements = driver.find_elements(By.XPATH, "//ul/li[@class='item']")
🛠️ XPath 调试技巧

在编写代码前,强烈建议先在浏览器中验证表达式:

  1. F12 打开浏览器开发者工具。
  2. 切换到 Elements 面板,按 Ctrl + F (或 Cmd + F) 调出搜索框。
  3. 在搜索框中直接输入你的 XPath 表达式,浏览器会高亮显示匹配到的元素及数量。
  4. 也可以在 Console 面板输入 $x("//你的xpath表达式") 进行验证。
特例

//divcontains(@class,'form')//labelnormalize-space(text())='密码'

//div@class='form'.//label\[text()='密码']

//div@class='form'./label\[text()='密码']

三者的区别

我们假设页面上有如下 HTML 结构:

html 复制代码
<!-- 结构 A:嵌套了一层 div -->
<div class="form is-active">
    <div class="form-item">
        <label> 密码 </label>
    </div>
</div>

<!-- 结构 B:label 直接在 form 下 -->
<div class="form">
    <label>密码</label>
</div>

下面为你逐一拆解它们的区别:

1. //div[contains(@class,'form')]//label[normalize-space(text())='密码']
  • 最终返回的是<label> 元素
  • class 匹配 :使用 contains() 匹配到结构 A 中 class="form is-active" 的 div。
  • 层级匹配 :使用了 //label(双斜杠), 穿透中间的 <div class="form-item">,找到任意层级的 label。
  • 文本匹配 :使用了 normalize-space() 自动去除 <label> 密码 </label> 中的前后空格。
  • 总结 :这是最健壮、最推荐的写法。它容错率最高,能应对动态 class、深层嵌套和格式化空白。

2. //div[@class='form'][.//label[text()='密码']]
  • 最终返回的是<div class="form"> 容器元素
  • class 匹配 :使用 = 精确匹配,不能 匹配结构 A(因为多了 is-active),只能匹配结构 B。
  • 层级匹配 :谓词内使用了 .//label(带点的 //),穿透中间层级,只要在这个 div 内部任意位置有"密码" label 即可。
  • 文本匹配 :使用了 text()='密码',如果 label 带有空格(如 <label> 密码 </label>),会匹配失败
  • 总结:这是一个**"通过内部特征来锁定外部容器"**的写法。它的致命弱点是 class 精确匹配和文本精确匹配,在实际自动化中极易因为前端微调而失效。

3. //div[@class='form'][./label[text()='密码']]
  • 最终返回的是<div class="form"> 容器元素
  • class 匹配 :同样是精确匹配,只能匹配结构 B。
  • 层级匹配 :谓词内使用了 ./label(单斜杠 /),只能 查找 div 的直接子元素 。如果 label 被包裹在 <div class="form-item"> 里(如结构 A),会直接匹配失败
  • 文本匹配:同样是精确匹配,遇到空格会失败。
  • 总结 :这是最脆弱的写法。它要求 div 和 label 必须是严格的父子关系,且 class 和文本不能有任何多余字符。

📊 核心区别总结
特性 表达式 1 (contains + // + normalize) 表达式 2 (= + .// + text) 表达式 3 (= + ./ + text)
返回目标 <label> 元素 <div> 容器 <div> 容器
兼容动态 class ✅ 兼容 ❌ 不兼容 ❌ 不兼容
兼容深层嵌套 ✅ 兼容 ✅ 兼容 仅限直接子元素
兼容文本含空格 ✅ 兼容 ❌ 不兼容 ❌ 不兼容
实战推荐度 ⭐⭐⭐⭐⭐ (极高) ⭐⭐ (极低) ⭐ (极低)
相关推荐
copyer_xyf1 小时前
Agentic RAG 实战:PostgreSQL + LangGraph 一条链路
python·postgresql·agent
JavaPub-rodert1 小时前
我又把自己的 Go 后台管理系统升级了一遍:文件管理、2GB 上传、私有文件预览、Docker 镜像全安排上了
开发语言·docker·golang·shiyuadmin
动力 continue1 小时前
Python 元类与异常类:类的“制造工厂”与“报错定制师”
开发语言·python·制造
quantdash_cc1 小时前
基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
android·开发语言·pandas·量化·quantdash
147API1 小时前
Python批量生成蒸馏数据,并发、重试、幂等和断点续跑
开发语言·jvm·python
青 春 记 忆1 小时前
零基础入门python04:用注册校验理解字典、集合和条件判断
开发语言·vscode·python·python3.11
kels88991 小时前
实战排坑:黄金实时API开发,XAUUSD Tick报文异常处理实践
开发语言·python·websocket·网络协议·信息可视化
阿pin1 小时前
Java随笔-JDK7 HashMap头插法为何能导致死循环?
java·开发语言·hashmap
FBI HackerHarry浩1 小时前
Pandas 库中用于分类变量独热编码(One-Hot Encoding)的函数 pd.get_dummies() 函数
开发语言·人工智能·python·pandas