> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Selenium 完全指南
- URL: https://blog.vercanti.com/selenium-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:46.000Z
- Updated: 2026-08-28T14:57:18.000Z
- Description: 最后更新：2026-03-20 1. Selenium完全指南 · 安装与环境配置(/selenium-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85%E4%B8%8E%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE) 2. Selenium完全指南 · 启动浏览器(/selenium-wan-quan-zhi-nan/#%E5%90%AF%E5%8A%A8%E6%B5%8F%E8%A7%88%E5%99%A8) 3. Selenium完全指南 · 导航(/selenium-wan-quan-zhi-na
- Author: yellowdog
- Tags: Python, 框架与库

最后更新：2026-03-20

> 官方文档：<https://www.selenium.dev/documentation/>  
> 适用版本：Selenium 4.x（2026-05-08 核实）

---

## 目录

1. [Selenium完全指南 · 安装与环境配置](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85%E4%B8%8E%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE)
2. [Selenium完全指南 · 启动浏览器](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%90%AF%E5%8A%A8%E6%B5%8F%E8%A7%88%E5%99%A8)
3. [Selenium完全指南 · 导航](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%AF%BC%E8%88%AA)
4. [Selenium完全指南 · 等待机制](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E7%AD%89%E5%BE%85%E6%9C%BA%E5%88%B6)
5. [Selenium完全指南 · 元素定位](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%85%83%E7%B4%A0%E5%AE%9A%E4%BD%8D)
6. [Selenium完全指南 · 读取元素信息](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E8%AF%BB%E5%8F%96%E5%85%83%E7%B4%A0%E4%BF%A1%E6%81%AF)
7. [Selenium完全指南 · 鼠标与键盘操作](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E9%BC%A0%E6%A0%87%E4%B8%8E%E9%94%AE%E7%9B%98%E6%93%8D%E4%BD%9C)
8. [Selenium完全指南 · 表单操作](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E8%A1%A8%E5%8D%95%E6%93%8D%E4%BD%9C)
9. [Selenium完全指南 · 窗口与标签页](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E7%AA%97%E5%8F%A3%E4%B8%8E%E6%A0%87%E7%AD%BE%E9%A1%B5)
10. [Selenium完全指南 · 框架与 iframe](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E6%A1%86%E6%9E%B6%E4%B8%8E-iframe)
11. [Selenium完全指南 · 弹窗处理](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%BC%B9%E7%AA%97%E5%A4%84%E7%90%86)
12. [Selenium完全指南 · JavaScript 执行](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#javascript-%E6%89%A7%E8%A1%8C)
13. [Selenium完全指南 · 截图](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E6%88%AA%E5%9B%BE)
14. [Selenium完全指南 · 最佳实践](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
15. [Selenium完全指南 · 常见陷阱与注意事项](https://blog.vercanti.com/selenium-wan-quan-zhi-nan/#%E5%B8%B8%E8%A7%81%E9%99%B7%E9%98%B1%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

---

## 安装与环境配置

### 安装

```bash
pip install selenium

```

Selenium 4.6+ 内置 **Selenium Manager**，会自动下载匹配当前浏览器版本的 WebDriver，通常不需要手动管理驱动。

如果需要手动管理驱动，也可以使用 `webdriver-manager`：

```bash
pip install webdriver-manager

```

### 支持的浏览器

| 浏览器               | 驱动               | 自动管理（Selenium Manager） |
| ----------------- | ---------------- | ---------------------- |
| Chrome / Chromium | chromedriver     | 支持                     |
| Firefox           | geckodriver      | 支持                     |
| Edge              | msedgedriver     | 支持                     |
| Safari            | safaridriver（内置） | macOS 自带，需开启开发者模式      |

### 版本要求

| 包        | 最低版本 | 说明                     |
| -------- | ---- | ---------------------- |
| selenium | 4.6+ | 内置 Selenium Manager，推荐 |
| Python   | 3.8+ |                        |

---

## 启动浏览器

### Chrome

```python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

# 最简启动（Selenium Manager 自动管理驱动）
driver = webdriver.Chrome()

# 带选项启动
options = Options()
options.add_argument("--headless=new")       # 无头模式
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
options.add_argument("--user-agent=...")     # 自定义 UA
options.add_experimental_option("excludeSwitches", ["enable-automation"])  # 去除自动化标记

driver = webdriver.Chrome(options=options)

# 手动指定驱动路径
service = Service(executable_path="/path/to/chromedriver")
driver = webdriver.Chrome(service=service, options=options)

```

**Chrome Options 常用参数：**

| 参数                                             | 说明                   |
| ---------------------------------------------- | -------------------- |
| \--headless=new                                | 无头模式（Chrome 112+，新版） |
| \--no-sandbox                                  | 禁用沙箱，Docker 环境必须加    |
| \--disable-dev-shm-usage                       | 避免 /dev/shm 空间不足崩溃   |
| \--window-size=W,H                             | 设置窗口大小               |
| \--disable-gpu                                 | 禁用 GPU，旧版无头模式需要      |
| \--disable-images                              | 禁止加载图片，提升速度          |
| \--proxy-server=host:port                      | 设置代理                 |
| \--ignore-certificate-errors                   | 忽略 SSL 证书错误          |
| \--disable-blink-features=AutomationControlled | 隐藏自动化特征              |

### Firefox

```python
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("--headless")

driver = webdriver.Firefox(options=options)

```

### Edge

```python
from selenium import webdriver

driver = webdriver.Edge()

```

### webdriver.Chrome() 参数表

| 参数          | 类型      | 默认值  | 说明             |
| ----------- | ------- | ---- | -------------- |
| options     | Options | None | 浏览器选项对象        |
| service     | Service | None | 驱动服务配置（路径、端口等） |
| keep\_alive | bool    | True | 保持 HTTP 连接     |

### 关闭浏览器

```python
driver.close()   # 关闭当前标签页
driver.quit()    # 关闭整个浏览器进程，释放驱动资源

```

推荐使用上下文管理器，自动调用 `quit()`：

```python
from selenium import webdriver

with webdriver.Chrome() as driver:
    driver.get("https://example.com")
    # 退出 with 块时自动 quit()

```

---

## 导航

```python
driver.get(url)               # 打开 URL，等待页面加载完成
driver.back()                 # 后退
driver.forward()              # 前进
driver.refresh()              # 刷新

```

**driver.get() 参数表：**

| 参数  | 类型  | 默认值 | 说明                      |
| --- | --- | --- | ----------------------- |
| url | str | 必填  | 目标 URL，必须包含协议（https://） |

**获取当前页面信息：**

```python
driver.current_url    # 当前 URL（str）
driver.title          # 页面标题（str）
driver.page_source    # 页面 HTML 源码（str）

```

---

## 等待机制

Selenium 操作的最大痛点是时序问题。页面元素可能尚未加载完毕，直接操作会抛出异常。

### 隐式等待（ImplicitWait）

全局设置，对所有 `find_element` 生效，找不到元素时轮询等待，直到超时。

```python
driver.implicitly_wait(10)  # 秒，全局生效

```

| 参数             | 类型    | 默认值 | 说明             |
| -------------- | ----- | --- | -------------- |
| time\_to\_wait | float | 0   | 最长等待秒数，0 表示不等待 |

缺点：与显式等待混用会产生不可预期的超时叠加，建议只用一种。

### 显式等待（WebDriverWait）

推荐方式。针对具体条件等待，超时抛出 `TimeoutException`。

```python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

wait = WebDriverWait(driver, timeout=10)

# 等待元素可见
el = wait.until(EC.visibility_of_element_located((By.ID, "submit")))

# 等待元素可点击
el = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.submit")))

```

**WebDriverWait() 参数表：**

| 参数                  | 类型        | 默认值  | 说明            |
| ------------------- | --------- | ---- | ------------- |
| driver              | WebDriver | 必填   | 浏览器驱动实例       |
| timeout             | float     | 必填   | 最长等待秒数        |
| poll\_frequency     | float     | 0.5  | 轮询间隔秒数        |
| ignored\_exceptions | list      | None | 等待期间忽略的异常类型列表 |

**wait.until() / wait.until\_not() 参数表：**

| 参数      | 类型       | 说明                                        |
| ------- | -------- | ----------------------------------------- |
| method  | callable | 接受 driver 为参数的可调用对象，返回值为 False/None 时继续等待 |
| message | str      | 超时时的错误信息                                  |

**常用 Expected Conditions：**

| 条件                                                     | 说明                     |
| ------------------------------------------------------ | ---------------------- |
| presence\_of\_element\_located(locator)                | 元素存在于 DOM（不要求可见）       |
| visibility\_of\_element\_located(locator)              | 元素可见（宽高>0，不被隐藏）        |
| element\_to\_be\_clickable(locator)                    | 元素可见且可交互               |
| invisibility\_of\_element\_located(locator)            | 元素不可见或不在 DOM 中         |
| text\_to\_be\_present\_in\_element(locator, text)      | 元素文本包含指定字符串            |
| title\_contains(title)                                 | 页面标题包含指定字符串            |
| title\_is(title)                                       | 页面标题完全匹配               |
| url\_contains(url)                                     | URL 包含指定字符串            |
| alert\_is\_present()                                   | 弹窗已出现                  |
| number\_of\_windows\_to\_be(num)                       | 窗口数量达到指定值              |
| frame\_to\_be\_available\_and\_switch\_to\_it(locator) | iframe 可用并自动切入         |
| staleness\_of(element)                                 | 元素已从 DOM 中移除（用于等待页面刷新） |

### 强制等待（不推荐）

```python
import time
time.sleep(2)  # 固定等待，不推荐用于生产代码

```

---

## 元素定位

### find\_element() / find\_elements()

```python
from selenium.webdriver.common.by import By

# 返回单个 WebElement，找不到抛 NoSuchElementException
element = driver.find_element(By.ID, "username")

# 返回 list[WebElement]，找不到返回空列表
elements = driver.find_elements(By.CLASS_NAME, "item")

```

**find\_element() 参数表：**

| 参数    | 类型  | 默认值 | 说明              |
| ----- | --- | --- | --------------- |
| by    | By  | 必填  | 定位策略，使用 By 类的常量 |
| value | str | 必填  | 选择器值            |

**By 定位策略：**

| 策略                | 常量                     | 示例值                     | 说明                |
| ----------------- | ---------------------- | ----------------------- | ----------------- |
| ID                | By.ID                  | "username"              | 匹配 id 属性，最快       |
| Name              | By.NAME                | "email"                 | 匹配 name 属性        |
| Class Name        | By.CLASS\_NAME         | "btn-primary"           | 单个 class 名，不支持复合类 |
| Tag Name          | By.TAG\_NAME           | "input"                 | 匹配标签名             |
| CSS Selector      | By.CSS\_SELECTOR       | "div.card > a"          | 标准 CSS 选择器，最灵活    |
| XPath             | By.XPATH               | "//div\[@class='nav'\]" | 支持复杂逻辑，但较慢        |
| Link Text         | By.LINK\_TEXT          | "点击登录"                  | 匹配 <a> 的完整文本      |
| Partial Link Text | By.PARTIAL\_LINK\_TEXT | "登录"                    | 匹配 <a> 的部分文本      |

**CSS 选择器常用语法：**

```css
#id              /* ID */
.class           /* class */
div              /* 标签 */
div.card         /* 标签 + class */
input[type="text"]   /* 属性 */
div > p          /* 直接子元素 */
div p            /* 后代元素 */
p:nth-child(2)   /* 第2个子元素 */
p:first-child    /* 第1个子元素 */

```

**XPath 常用语法：**

```xpath
//div[@id='main']              # 属性定位
//a[text()='登录']              # 文本定位
//a[contains(text(),'登录')]    # 文本包含
//input[@type='text']          # 属性过滤
//div[@class='nav']//a         # 后代
//ul/li[1]                     # 第1个 li
//li[last()]                   # 最后一个
//*[contains(@class,'btn')]    # class 包含（class 是空格分隔的，@class= 是全量匹配）
//input[@name='q']/..          # 父节点

```

### 在元素内部继续查找

`WebElement` 也有 `find_element` / `find_elements` 方法，作用域限定在该元素内：

```python
container = driver.find_element(By.ID, "results")
items = container.find_elements(By.CLASS_NAME, "item")

```

---

## 读取元素信息

```python
element = driver.find_element(By.ID, "title")

element.text                           # 可见文本内容（str），等价于 innerText
element.get_attribute("href")          # 获取 HTML 属性值
element.get_attribute("innerHTML")     # 内部 HTML
element.get_attribute("outerHTML")     # 包含自身的 HTML
element.get_attribute("value")         # 表单元素的当前值（用属性而非 text）
element.get_property("value")          # 获取 DOM property（JS 属性，实时值）
element.get_dom_attribute("class")     # 只读取 HTML 属性（不读 JS property）

element.is_displayed()                 # 是否可见（bool）
element.is_enabled()                   # 是否可交互（bool）
element.is_selected()                  # 是否被选中，适用于 checkbox/radio（bool）

element.tag_name                       # 标签名，如 "input"（str）
element.size                           # 元素大小，如 {"width": 100, "height": 50}
element.location                       # 相对页面左上角的坐标，如 {"x": 10, "y": 20}
element.rect                           # 合并 size 和 location，如 {"x":10,"y":20,"width":100,"height":50}

```

**get\_attribute() vs get\_property() 区别：**

| 方法                        | 读取来源                   | 动态更新 | 典型用途                        |
| ------------------------- | ---------------------- | ---- | --------------------------- |
| get\_attribute(name)      | HTML 属性 + DOM property | 部分   | 读 href、src、class、value（初始值） |
| get\_property(name)       | DOM property（JS）       | 是    | 读 value（当前用户输入值）、checked    |
| get\_dom\_attribute(name) | 仅 HTML 属性              | 否    | 读原始 HTML 中的属性值              |

---

## 鼠标与键盘操作

### 基础点击

```python
element.click()          # 左键点击

```

### ActionChains（链式操作）

适用于复杂交互：悬停、双击、右键、拖拽、组合键等。

```python
from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)

```

**ActionChains 参数表：**

| 参数       | 类型        | 默认值 | 说明                  |
| -------- | --------- | --- | ------------------- |
| driver   | WebDriver | 必填  | 浏览器驱动实例             |
| duration | int       | 250 | 动作持续时间（毫秒），用于模拟人类速度 |

**常用鼠标动作：**

| 方法                                                             | 参数                     | 说明                   |
| -------------------------------------------------------------- | ---------------------- | -------------------- |
| click(on\_element=None)                                        | WebElement \| None     | 左键点击，None 则点当前位置     |
| double\_click(on\_element=None)                                | WebElement \| None     | 双击                   |
| context\_click(on\_element=None)                               | WebElement \| None     | 右键点击                 |
| move\_to\_element(to\_element)                                 | WebElement             | 移动鼠标到元素中心（悬停）        |
| move\_to\_element\_with\_offset(to\_element, xoffset, yoffset) | WebElement, int, int   | 移动到元素内偏移位置           |
| move\_by\_offset(xoffset, yoffset)                             | int, int               | 从当前位置偏移移动            |
| click\_and\_hold(on\_element=None)                             | WebElement \| None     | 按住鼠标左键               |
| release(on\_element=None)                                      | WebElement \| None     | 释放鼠标左键               |
| drag\_and\_drop(source, target)                                | WebElement, WebElement | 从 source 拖拽到 target  |
| drag\_and\_drop\_by\_offset(source, xoffset, yoffset)          | WebElement, int, int   | 拖拽元素偏移指定距离           |
| scroll\_to\_element(element)                                   | WebElement             | 滚动到元素（Selenium 4.2+） |
| scroll\_by\_amount(delta\_x, delta\_y)                         | int, int               | 按像素滚动页面              |
| perform()                                                      | 无                      | 执行所有已排队的动作           |
| reset\_actions()                                               | 无                      | 清空已排队的动作             |

**使用示例：**

```python
from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)

# 悬停
actions.move_to_element(menu).perform()

# 双击
actions.double_click(element).perform()

# 右键
actions.context_click(element).perform()

# 拖拽到另一个元素
actions.drag_and_drop(source, target).perform()

# 拖拽偏移
actions.drag_and_drop_by_offset(element, xoffset=200, yoffset=0).perform()

# 链式组合
actions.move_to_element(menu).pause(0.5).click(submenu).perform()

# 滚动页面
actions.scroll_by_amount(0, 500).perform()  # 向下滚动 500px

# 滚动到元素
actions.scroll_to_element(element).perform()

```

### 键盘操作

```python
from selenium.webdriver.common.keys import Keys

element.send_keys("hello world")        # 输入文本
element.send_keys(Keys.ENTER)           # 按回车
element.send_keys(Keys.TAB)             # 按 Tab
element.send_keys(Keys.CONTROL, "a")    # Ctrl+A 全选
element.send_keys(Keys.CONTROL, "c")    # Ctrl+C 复制
element.send_keys(Keys.CONTROL, "v")    # Ctrl+V 粘贴
element.send_keys(Keys.BACKSPACE)       # 删除
element.send_keys(Keys.ESCAPE)          # Esc
element.send_keys(Keys.PAGE_DOWN)       # 向下翻页
element.send_keys(Keys.ARROW_DOWN)      # 向下箭头

```

**常用 Keys 常量：**

| 常量                             | 说明           |
| ------------------------------ | ------------ |
| Keys.ENTER                     | 回车           |
| Keys.RETURN                    | 回车（同上）       |
| Keys.TAB                       | Tab          |
| Keys.ESCAPE                    | Esc          |
| Keys.SPACE                     | 空格           |
| Keys.BACKSPACE                 | 退格           |
| Keys.DELETE                    | Delete       |
| Keys.CONTROL                   | Ctrl         |
| Keys.ALT                       | Alt          |
| Keys.SHIFT                     | Shift        |
| Keys.COMMAND                   | Command（Mac） |
| Keys.F5                        | F5（刷新）       |
| Keys.PAGE\_UP                  | Page Up      |
| Keys.PAGE\_DOWN                | Page Down    |
| Keys.HOME                      | Home         |
| Keys.END                       | End          |
| Keys.ARROW\_UP/DOWN/LEFT/RIGHT | 方向键          |

---

## 表单操作

```python
# 输入框
input_el = driver.find_element(By.NAME, "username")
input_el.clear()               # 清空内容
input_el.send_keys("admin")    # 输入文本

# 提交表单（在表单内任意元素上调用）
input_el.submit()

# checkbox / radio
checkbox = driver.find_element(By.ID, "agree")
if not checkbox.is_selected():
    checkbox.click()

# 下拉框 <select>
from selenium.webdriver.support.ui import Select

select_el = driver.find_element(By.ID, "country")
sel = Select(select_el)

sel.select_by_value("CN")               # 按 value 属性选
sel.select_by_visible_text("中国")      # 按显示文本选
sel.select_by_index(2)                  # 按索引选（从0开始）
sel.deselect_all()                      # 取消所有选中（多选框）
sel.first_selected_option              # 当前选中的第一个 WebElement
sel.all_selected_options               # 所有选中项的列表
sel.options                            # 所有选项的列表

```

---

## 窗口与标签页

```python
# 当前窗口句柄
current = driver.current_window_handle   # str

# 所有窗口句柄
all_handles = driver.window_handles      # list[str]

# 切换到指定窗口
driver.switch_to.window(all_handles[-1])

# 打开新标签页（Selenium 4+）
driver.switch_to.new_window("tab")    # 打开新标签页并切换
driver.switch_to.new_window("window") # 打开新窗口并切换

# 等待新窗口出现并切换
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
wait.until(EC.number_of_windows_to_be(2))
driver.switch_to.window(driver.window_handles[-1])

# 关闭当前标签页后切回原始窗口
driver.close()
driver.switch_to.window(original_handle)

# 窗口大小与位置
driver.maximize_window()
driver.minimize_window()
driver.set_window_size(1920, 1080)
driver.set_window_position(0, 0)
driver.get_window_size()     # {"width": 1920, "height": 1080}
driver.get_window_position() # {"x": 0, "y": 0}

```

---

## 框架与 iframe

页面内嵌 iframe 时，必须先切换到 iframe 才能操作其中的元素。

```python
# 按 WebElement 切换
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe)

# 按 id 或 name 切换
driver.switch_to.frame("frame_id")

# 按索引切换（页面中第n个iframe，从0开始）
driver.switch_to.frame(0)

# 退出 iframe，回到主文档
driver.switch_to.default_content()

# 退出一层嵌套 iframe（回到父框架）
driver.switch_to.parent_frame()

```

---

## 弹窗处理

原生 JavaScript 弹窗（`alert`、`confirm`、`prompt`）需要通过 `switch_to.alert` 处理。

```python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 等待弹窗出现
wait = WebDriverWait(driver, 10)
alert = wait.until(EC.alert_is_present())

alert.text          # 弹窗文本内容（str）
alert.accept()      # 点击确定（alert/confirm/prompt 均适用）
alert.dismiss()     # 点击取消（confirm/prompt）
alert.send_keys("input text")  # 向 prompt 输入内容，再 accept()

```

---

## JavaScript 执行

```python
# 同步执行，返回值即 JS return 的值
result = driver.execute_script("return document.title;")

# 传入 Python 对象作为参数（arguments[0] 对应第一个参数）
driver.execute_script("arguments[0].click();", element)
driver.execute_script("arguments[0].style.border='3px solid red';", element)

# 滚动到元素
driver.execute_script("arguments[0].scrollIntoView(true);", element)

# 滚动到底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# 修改 input 值（绕过只读限制）
driver.execute_script("arguments[0].value='new value';", input_el)

# 异步执行（适用于 AJAX 回调）
driver.execute_async_script("""
    var callback = arguments[arguments.length - 1];
    setTimeout(function() { callback('done'); }, 1000);
""")

```

**execute\_script() 参数表：**

| 参数     | 类型  | 默认值 | 说明                                   |
| ------ | --- | --- | ------------------------------------ |
| script | str | 必填  | JavaScript 代码字符串                     |
| \*args | any | 无   | 传给 JS 的参数，在 JS 中通过 arguments\[n\] 访问 |

---

## 截图

```python
# 截整个页面（视口范围）
driver.save_screenshot("page.png")           # 保存到文件
png_bytes = driver.get_screenshot_as_png()   # 返回 bytes
base64_str = driver.get_screenshot_as_base64()  # 返回 base64 字符串

# 截单个元素
element.screenshot("element.png")           # 保存到文件
png_bytes = element.screenshot_as_png       # 返回 bytes
base64_str = element.screenshot_as_base64   # 返回 base64 字符串

```

---

## 最佳实践

### 始终使用显式等待，避免 time.sleep

```python
# 不推荐
time.sleep(3)

# 推荐
wait = WebDriverWait(driver, 10)
el = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
el.click()

```

### 用上下文管理器保证浏览器关闭

```python
with webdriver.Chrome(options=options) as driver:
    driver.get("https://example.com")
    # 异常也会触发 quit()

```

### 优先使用 CSS Selector，XPath 作为备选

CSS Selector 执行比 XPath 快，语法更简洁。XPath 在需要向上查找父节点、基于文本定位时更适合。

### 提取复用 locator

```python
# 将选择器定义为常量，方便维护
USERNAME_INPUT = (By.ID, "username")
LOGIN_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")

driver.find_element(*USERNAME_INPUT).send_keys("admin")
wait.until(EC.element_to_be_clickable(LOGIN_BUTTON)).click()

```

### 使用 Page Object 模式组织代码

```python
class LoginPage:
    URL = "https://example.com/login"
    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    SUBMIT = (By.CSS_SELECTOR, "button[type='submit']")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def open(self):
        self.driver.get(self.URL)
        return self

    def login(self, username, password):
        self.wait.until(EC.visibility_of_element_located(self.USERNAME)).send_keys(username)
        self.driver.find_element(*self.PASSWORD).send_keys(password)
        self.driver.find_element(*self.SUBMIT).click()
        return self

```

### 截图辅助调试

```python
try:
    element = wait.until(EC.element_to_be_clickable((By.ID, "btn")))
    element.click()
except Exception as e:
    driver.save_screenshot("debug.png")
    raise

```

---

## 常见陷阱与注意事项

### StaleElementReferenceException

页面刷新或 DOM 重新渲染后，之前持有的 `WebElement` 引用失效。解决方案：重新查找元素，或等待 `staleness_of` 后再查找。

```python
# 错误做法：页面刷新后继续使用旧引用
el = driver.find_element(By.ID, "btn")
driver.refresh()
el.click()  # StaleElementReferenceException

# 正确做法：刷新后重新查找
driver.refresh()
wait.until(EC.staleness_of(el))  # 等待旧元素失效
el = wait.until(EC.element_to_be_clickable((By.ID, "btn")))
el.click()

```

### ElementNotInteractableException

元素在 DOM 中存在但不可交互（被遮挡、display:none、opacity:0 等）。解决方案：等待 `element_to_be_clickable`，或用 JS 点击。

```python
# JS 强制点击（绕过可见性限制，慎用）
driver.execute_script("arguments[0].click();", element)

```

### class 属性包含空格时不能用 By.CLASS\_NAME

`By.CLASS_NAME` 只接受单个 class 名。

```python
# 错误：含空格会抛 InvalidSelectorException
driver.find_element(By.CLASS_NAME, "btn btn-primary")

# 正确：用 CSS Selector
driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")

```

### XPath 中 @class= 是全量匹配

```xpath
# 错误：class="btn btn-primary active" 不会被匹配到
//button[@class='btn']

# 正确：用 contains
//button[contains(@class,'btn')]

```

### 隐式等待与显式等待不要混用

两者同时存在时，超时时间会以不可预期的方式叠加，导致实际等待时间超出预期。只选择一种策略全局使用。

### 无头模式下 window-size 需要显式设置

无头模式下浏览器默认窗口极小，可能导致元素不可见或布局异常：

```python
options.add_argument("--window-size=1920,1080")

```

### 反爬检测

部分网站检测 `navigator.webdriver` 属性：

```python
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
options.add_argument("--disable-blink-features=AutomationControlled")

# 注入脚本覆盖 webdriver 属性
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")

```

---

## 参见

[Playwright完全指南](https://blog.vercanti.com/playwright-wan-quan-zhi-nan/)  
[BeautifulSoup与lxml完全指南](https://blog.vercanti.com/beautifulsoup-yu-lxml-wan-quan-zhi-nan/)  
[httpx完全指南](https://blog.vercanti.com/httpx-wan-quan-zhi-nan/)