跳到主要内容

Selenium 浏览器自动化

Selenium 是一个强大的浏览器自动化工具,最初用于 Web 应用测试,现在广泛用于爬虫开发。它可以模拟真实用户操作浏览器,能够处理 JavaScript 渲染的动态页面。

官方文档

本教程内容基于 Selenium 官方文档

为什么需要 Selenium?

传统的 requests + BeautifulSoup 方案无法处理以下情况:

  • JavaScript 动态渲染:页面内容通过 JS 加载
  • 需要交互操作:点击按钮、滚动页面、填写表单
  • 登录验证:需要模拟登录才能访问的内容
  • 反爬检测:网站检测浏览器指纹

Selenium 通过控制真实浏览器来解决这些问题,但代价是速度较慢、资源消耗较大。

安装配置

安装 Selenium

pip install selenium

# 验证安装
python -c "import selenium; print(selenium.__version__)"

安装浏览器驱动

Selenium 需要浏览器驱动来控制浏览器。推荐使用 webdriver-manager 自动管理驱动:

pip install webdriver-manager

手动安装驱动(可选)

如果需要手动安装,请根据浏览器类型下载对应驱动:

浏览器驱动名称下载地址
ChromeChromeDriverhttps://chromedriver.chromium.org/
FirefoxGeckoDriverhttps://github.com/mozilla/geckodriver
EdgeEdgeDriverhttps://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
SafariSafariDrivermacOS 自带,无需下载

驱动版本必须与浏览器版本匹配。下载后将驱动放入系统 PATH 路径中。

快速开始

第一个示例

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

# 配置 Chrome 选项
options = Options()
options.add_argument('--headless') # 无头模式,不显示浏览器窗口

# 创建 WebDriver 实例
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)

try:
# 访问网页
driver.get('https://www.example.com')

# 获取页面标题
print(f'页面标题: {driver.title}')

# 获取页面源码
print(driver.page_source[:500])

finally:
# 关闭浏览器
driver.quit()

使用 Firefox

from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from webdriver_manager.firefox import GeckoDriverManager

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

driver = webdriver.Firefox(
service=Service(GeckoDriverManager().install()),
options=options
)

driver.get('https://www.example.com')
print(driver.title)
driver.quit()

浏览器配置

Chrome 常用配置

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

# 无头模式
options.add_argument('--headless')

# 禁用 GPU(无头模式下推荐)
options.add_argument('--disable-gpu')

# 禁用沙箱(Linux 下可能需要)
options.add_argument('--no-sandbox')

# 禁用共享内存(解决内存不足问题)
options.add_argument('--disable-dev-shm-usage')

# 设置窗口大小
options.add_argument('--window-size=1920,1080')

# 设置 User-Agent
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')

# 禁用图片加载(加速爬取)
prefs = {'profile.managed_default_content_settings.images': 2}
options.add_experimental_option('prefs', prefs)

# 禁用自动化检测
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)

# 忽略证书错误
options.add_argument('--ignore-certificate-errors')

driver = webdriver.Chrome(options=options)

启动速度优化

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

# 禁用不必要的功能
options.add_argument('--disable-extensions')
options.add_argument('--disable-infobars')
options.add_argument('--disable-notifications')
options.add_argument('--disable-popup-blocking')

# 禁用日志
options.add_experimental_option('excludeSwitches', ['enable-logging'])

driver = webdriver.Chrome(options=options)

元素定位

Selenium 提供了多种定位元素的方法,通过 By 类指定定位策略。

八种定位方式

from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://example.com')

# 1. 通过 ID 定位(最快最准确)
element = driver.find_element(By.ID, 'username')

# 2. 通过 name 属性定位
element = driver.find_element(By.NAME, 'password')

# 3. 通过 class 名定位
element = driver.find_element(By.CLASS_NAME, 'btn-primary')

# 4. 通过标签名定位
element = driver.find_element(By.TAG_NAME, 'input')

# 5. 通过链接文本定位(精确匹配)
element = driver.find_element(By.LINK_TEXT, '登录')

# 6. 通过部分链接文本定位(模糊匹配)
element = driver.find_element(By.PARTIAL_LINK_TEXT, '注册')

# 7. 通过 CSS 选择器定位(推荐)
element = driver.find_element(By.CSS_SELECTOR, 'div.content > a.link')

# 8. 通过 XPath 定位(最灵活)
element = driver.find_element(By.XPATH, '//div[@class="content"]//a[text()="登录"]')

driver.quit()

定位多个元素

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# find_elements 返回列表,找不到返回空列表
links = driver.find_elements(By.TAG_NAME, 'a')

for link in links:
print(link.text, link.get_attribute('href'))

driver.quit()

CSS 选择器详解

CSS 选择器语法简洁,性能好,是推荐的定位方式:

# 基本 CSS 选择器
driver.find_element(By.CSS_SELECTOR, '#username') # id 选择器
driver.find_element(By.CSS_SELECTOR, '.btn-primary') # class 选择器
driver.find_element(By.CSS_SELECTOR, 'input[name="user"]') # 属性选择器

# 层级关系
driver.find_element(By.CSS_SELECTOR, 'div p') # 后代选择器
driver.find_element(By.CSS_SELECTOR, 'div > p') # 子选择器
driver.find_element(By.CSS_SELECTOR, 'div + p') # 相邻兄弟
driver.find_element(By.CSS_SELECTOR, 'div ~ p') # 后续兄弟

# 属性匹配
driver.find_element(By.CSS_SELECTOR, '[type="text"]') # 精确匹配
driver.find_element(By.CSS_SELECTOR, '[class*="btn"]') # 包含
driver.find_element(By.CSS_SELECTOR, '[href^="http"]') # 开头匹配
driver.find_element(By.CSS_SELECTOR, '[href$=".pdf"]') # 结尾匹配

# 伪类选择器
driver.find_element(By.CSS_SELECTOR, 'li:first-child') # 第一个子元素
driver.find_element(By.CSS_SELECTOR, 'li:nth-child(2)') # 第二个子元素
driver.find_element(By.CSS_SELECTOR, 'li:last-child') # 最后一个子元素

XPath 详解

XPath 功能强大,适合复杂的定位场景:

# 基本 XPath
driver.find_element(By.XPATH, '//input') # 所有 input
driver.find_element(By.XPATH, '//div[@id="main"]') # 带属性的 div
driver.find_element(By.XPATH, '//input[@type="text"]') # 带属性的 input

# 文本定位
driver.find_element(By.XPATH, '//a[text()="登录"]') # 精确文本
driver.find_element(By.XPATH, '//a[contains(text(), "登")]') # 包含文本

# 属性包含
driver.find_element(By.XPATH, '//div[contains(@class, "content")]')

# 层级关系
driver.find_element(By.XPATH, '//div[@id="main"]/p') # 子元素
driver.find_element(By.XPATH, '//div[@id="main"]//p') # 后代元素

# 位置选择
driver.find_element(By.XPATH, '//li[1]') # 第一个 li
driver.find_element(By.XPATH, '//li[last()]') # 最后一个 li
driver.find_element(By.XPATH, '//li[position() < 3]') # 前两个 li

# 逻辑运算
driver.find_element(By.XPATH, '//input[@type="text" and @name="user"]')
driver.find_element(By.XPATH, '//input[@type="text" or @type="password"]')

# 轴定位
driver.find_element(By.XPATH, '//div[@id="main"]/parent::div') # 父元素
driver.find_element(By.XPATH, '//div[@id="main"]/following::p') # 之后的所有 p
driver.find_element(By.XPATH, '//div[@id="main"]/preceding::p') # 之前的所有 p

相对定位(Selenium 4 新增)

Selenium 4 引入了相对定位器,可以根据元素之间的空间关系定位:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

driver = webdriver.Chrome()
driver.get('https://example.com')

# 找到某个元素
password = driver.find_element(By.ID, 'password')

# 在 password 上方的元素
username = driver.find_element(locate_with(By.TAG_NAME, 'input').above(password))

# 在 password 下方的元素
submit = driver.find_element(locate_with(By.TAG_NAME, 'button').below(password))

# 在某个元素左侧
label = driver.find_element(locate_with(By.TAG_NAME, 'label').to_left_of(password))

# 在某个元素右侧
icon = driver.find_element(locate_with(By.TAG_NAME, 'span').to_right_of(password))

# 在某个元素附近
nearby = driver.find_element(locate_with(By.TAG_NAME, 'div').near(password))

driver.quit()

等待机制

网页加载和 JavaScript 执行需要时间,必须使用等待机制确保元素可用。

隐式等待

隐式等待是全局设置,对所有元素定位生效:

from selenium import webdriver

driver = webdriver.Chrome()

# 设置隐式等待时间为 10 秒
driver.implicitly_wait(10)

# 如果元素未立即找到,会等待最多 10 秒
element = driver.find_element(By.ID, 'content')

driver.quit()

隐式等待的特点:

  • 全局生效,只需设置一次
  • 找到元素后立即返回,不会等待完整时间
  • 找不到元素会抛出 NoSuchElementException

显式等待

显式等待针对特定条件,更加灵活和精确:

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# 创建 WebDriverWait 对象,最长等待 10 秒
wait = WebDriverWait(driver, 10)

# 等待元素出现
element = wait.until(EC.presence_of_element_located((By.ID, 'content')))

# 等待元素可见
element = wait.until(EC.visibility_of_element_located((By.ID, 'content')))

# 等待元素可点击
element = wait.until(EC.element_to_be_clickable((By.ID, 'submit')))

# 等待元素消失
wait.until(EC.invisibility_of_element_located((By.ID, 'loading')))

# 等待标题包含特定文本
wait.until(EC.title_contains('Example'))

# 等待 URL 包含特定内容
wait.until(EC.url_contains('dashboard'))

driver.quit()

常用等待条件

from selenium.webdriver.support import expected_conditions as EC

# 元素相关
EC.presence_of_element_located(locator) # 元素存在于 DOM
EC.visibility_of_element_located(locator) # 元素可见
EC.invisibility_of_element_located(locator) # 元素不可见
EC.element_to_be_clickable(locator) # 元素可点击
EC.element_to_be_selected(locator) # 元素被选中
EC.presence_of_all_elements_located(locator) # 所有匹配元素

# 文本相关
EC.text_to_be_present_in_element(locator, text) # 元素包含文本
EC.text_to_be_present_in_element_value(locator, text) # value 包含文本

# 页面相关
EC.title_is(title) # 标题等于
EC.title_contains(title) # 标题包含
EC.url_to_be(url) # URL 等于
EC.url_contains(url) # URL 包含

# 框架和窗口
EC.frame_to_be_available_and_switch_to_it(locator) # 框架可用
EC.new_window_is_opened(current_handles) # 新窗口打开
EC.number_of_windows_to_be(num) # 窗口数量

# Alert
EC.alert_is_present() # 弹窗出现

自定义等待条件

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Chrome()
driver.get('https://example.com')

# 使用 lambda 表达式
wait = WebDriverWait(driver, 10)
element = wait.until(lambda d: d.find_element(By.ID, 'content'))

# 自定义条件函数
def element_has_text(driver, locator, text):
element = driver.find_element(*locator)
return text in element.text

# 使用自定义条件
wait.until(lambda d: element_has_text(d, (By.ID, 'content'), '完成'))

driver.quit()

Fluent Wait(高级等待)

Fluent Wait 提供更精细的控制,可以设置轮询间隔和忽略异常:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, TimeoutException

driver = webdriver.Chrome()

# 创建 Fluent Wait
wait = WebDriverWait(
driver,
timeout=10, # 最长等待时间
poll_frequency=0.5, # 轮询间隔(默认 0.5 秒)
ignored_exceptions=[NoSuchElementException] # 忽略的异常
)

# 使用等待
element = wait.until(lambda d: d.find_element(By.ID, 'content'))

driver.quit()

等待机制选择建议

重要警告

不要混用隐式等待和显式等待,可能导致不可预测的等待时间。

场景推荐方式
简单页面,元素加载快隐式等待
动态加载,需要精确控制显式等待
需要特定条件判断显式等待
复杂场景,需要忽略异常Fluent Wait

元素操作

基本操作

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

driver = webdriver.Chrome()
driver.get('https://example.com/form')

# 点击元素
button = driver.find_element(By.ID, 'submit')
button.click()

# 输入文本
input_box = driver.find_element(By.ID, 'username')
input_box.clear() # 先清空
input_box.send_keys('admin') # 输入内容

# 获取元素文本
title = driver.find_element(By.TAG_NAME, 'h1')
print(title.text)

# 获取元素属性
link = driver.find_element(By.TAG_NAME, 'a')
print(link.get_attribute('href'))
print(link.get_attribute('target'))

# 获取元素的 HTML
div = driver.find_element(By.ID, 'content')
print(div.get_attribute('innerHTML'))

driver.quit()

表单操作

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

driver = webdriver.Chrome()
driver.get('https://example.com/form')

# 文本输入
username = driver.find_element(By.NAME, 'username')
username.send_keys('admin')

# 密码输入
password = driver.find_element(By.NAME, 'password')
password.send_keys('123456')

# 复选框
checkbox = driver.find_element(By.NAME, 'remember')
if not checkbox.is_selected():
checkbox.click()

# 单选框
radio = driver.find_element(By.NAME, 'gender')
radio.click()

# 下拉选择框
select_element = driver.find_element(By.NAME, 'country')
select = Select(select_element)

# 选择方式
select.select_by_visible_text('China') # 通过可见文本
select.select_by_value('cn') # 通过 value 属性
select.select_by_index(0) # 通过索引

# 获取选中项
selected_option = select.first_selected_option
print(selected_option.text)

# 取消选择
select.deselect_all()

# 文件上传
file_input = driver.find_element(By.NAME, 'file')
file_input.send_keys('C:/path/to/file.txt') # 发送文件路径

driver.quit()

键盘操作

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get('https://example.com')

input_box = driver.find_element(By.NAME, 'search')
input_box.send_keys('Python')

# 特殊按键
input_box.send_keys(Keys.ENTER) # 回车
input_box.send_keys(Keys.TAB) # Tab 键
input_box.send_keys(Keys.ESCAPE) # Esc 键
input_box.send_keys(Keys.BACK_SPACE) # 退格
input_box.send_keys(Keys.DELETE) # 删除
input_box.send_keys(Keys.CONTROL, 'a') # Ctrl+A 全选
input_box.send_keys(Keys.CONTROL, 'c') # Ctrl+C 复制
input_box.send_keys(Keys.CONTROL, 'v') # Ctrl+V 粘贴

# 组合键
from selenium.webdriver.common.action_chains import ActionChains

ActionChains(driver).key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()

driver.quit()

鼠标操作

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Chrome()
driver.get('https://example.com')

element = driver.find_element(By.ID, 'target')
actions = ActionChains(driver)

# 单击
actions.click(element).perform()

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

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

# 悬停(鼠标移到元素上)
actions.move_to_element(element).perform()

# 拖拽
source = driver.find_element(By.ID, 'source')
target = driver.find_element(By.ID, 'target')
actions.drag_and_drop(source, target).perform()

# 按偏移量拖拽
actions.click_and_hold(source).move_by_offset(100, 0).release().perform()

# 移动到元素偏移位置
actions.move_to_element_with_offset(element, 10, 10).perform()

driver.quit()

滚动操作

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

# 滚动到页面底部
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')

# 滚动到页面顶部
driver.execute_script('window.scrollTo(0, 0)')

# 滚动到指定位置
driver.execute_script('window.scrollTo(0, 500)')

# 滚动到元素可见
element = driver.find_element(By.ID, 'target')
driver.execute_script('arguments[0].scrollIntoView()', element)

# 水平滚动
driver.execute_script('window.scrollTo(500, 0)')

driver.quit()

多窗口与框架

多窗口处理

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# 获取当前窗口句柄
main_window = driver.current_window_handle
print(f'主窗口: {main_window}')

# 点击链接打开新窗口
driver.find_element(By.LINK_TEXT, '打开新窗口').click()

# 获取所有窗口句柄
all_windows = driver.window_handles
print(f'所有窗口: {all_windows}')

# 切换到新窗口
for window in all_windows:
if window != main_window:
driver.switch_to.window(window)
break

# 在新窗口中操作
print(driver.title)

# 关闭当前窗口
driver.close()

# 切换回主窗口
driver.switch_to.window(main_window)

driver.quit()

iframe 处理

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# 通过索引切换(从 0 开始)
driver.switch_to.frame(0)

# 通过 name 或 id 切换
driver.switch_to.frame('frame_name')
driver.switch_to.frame('frame_id')

# 通过 WebElement 切换
frame_element = driver.find_element(By.TAG_NAME, 'iframe')
driver.switch_to.frame(frame_element)

# 在 frame 内操作
print(driver.find_element(By.ID, 'content').text)

# 切换回主文档
driver.switch_to.default_content()

# 切换到父 frame(嵌套 frame 时使用)
driver.switch_to.parent_frame()

driver.quit()

Alert 弹窗处理

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# 触发 alert
driver.find_element(By.ID, 'alert_button').click()

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

# 获取 alert 文本
print(alert.text)

# 接受 alert(点击确定)
alert.accept()

# 取消 alert(点击取消)
alert.dismiss()

# 在 prompt 中输入文本
alert.send_keys('输入内容')
alert.accept()

driver.quit()

执行 JavaScript

Selenium 可以直接执行 JavaScript 代码,实现更灵活的操作:

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

# 执行简单脚本
driver.execute_script('alert("Hello")')

# 执行脚本并获取返回值
title = driver.execute_script('return document.title')
print(title)

# 传递参数
element = driver.find_element(By.ID, 'target')
driver.execute_script('arguments[0].style.border = "3px solid red"', element)

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

# 点击被遮挡的元素
driver.execute_script('arguments[0].click()', element)

# 修改元素属性
driver.execute_script('arguments[0].setAttribute("value", "new value")', element)

# 获取元素的计算样式
style = driver.execute_script('return window.getComputedStyle(arguments[0])', element)

# 异步执行脚本
driver.execute_async_script('''
var callback = arguments[arguments.length - 1];
setTimeout(function() {
callback('done');
}, 1000);
''')

driver.quit()
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

# 获取所有 Cookie
cookies = driver.get_cookies()
for cookie in cookies:
print(cookie)

# 获取单个 Cookie
cookie = driver.get_cookie('session_id')
print(cookie)

# 添加 Cookie
driver.add_cookie({
'name': 'user_token',
'value': 'abc123',
'domain': 'example.com',
'path': '/',
'secure': True
})

# 删除单个 Cookie
driver.delete_cookie('user_token')

# 删除所有 Cookie
driver.delete_all_cookies()

driver.quit()

截图与页面源码

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

# 截取整个页面
driver.save_screenshot('screenshot.png')

# 截取元素
element = driver.find_element(By.ID, 'content')
element.screenshot('element.png')

# 获取截图为 base64
screenshot_base64 = driver.get_screenshot_as_base64()

# 获取截图为二进制数据
screenshot_png = driver.get_screenshot_as_png()

# 获取页面源码
html = driver.page_source
with open('page.html', 'w', encoding='utf-8') as f:
f.write(html)

driver.quit()

无头模式爬虫实战

下面是一个完整的无头模式爬虫示例:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
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
import time
import json
import random

class SeleniumSpider:
def __init__(self, headless=True):
self.options = Options()

if headless:
self.options.add_argument('--headless')

self.options.add_argument('--disable-gpu')
self.options.add_argument('--no-sandbox')
self.options.add_argument('--disable-dev-shm-usage')
self.options.add_argument('--window-size=1920,1080')

# 设置 User-Agent
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
]
self.options.add_argument(f'user-agent={random.choice(user_agents)}')

# 禁用自动化检测
self.options.add_experimental_option('excludeSwitches', ['enable-automation'])
self.options.add_experimental_option('useAutomationExtension', False)

# 创建驱动
self.driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=self.options
)

# 设置隐式等待
self.driver.implicitly_wait(10)

# 显式等待
self.wait = WebDriverWait(self.driver, 15)

def get(self, url):
"""访问页面"""
self.driver.get(url)
# 随机延迟,模拟人类行为
time.sleep(random.uniform(0.5, 1.5))

def wait_for_element(self, locator, timeout=15):
"""等待元素出现"""
wait = WebDriverWait(self.driver, timeout)
return wait.until(EC.presence_of_element_located(locator))

def wait_for_clickable(self, locator, timeout=15):
"""等待元素可点击"""
wait = WebDriverWait(self.driver, timeout)
return wait.until(EC.element_to_be_clickable(locator))

def scroll_to_bottom(self):
"""滚动到页面底部"""
self.driver.execute_script(
'window.scrollTo(0, document.body.scrollHeight)'
)
time.sleep(1)

def scroll_page(self):
"""模拟滚动页面"""
total_height = self.driver.execute_script(
'return document.body.scrollHeight'
)
for i in range(0, total_height, random.randint(200, 400)):
self.driver.execute_script(f'window.scrollTo(0, {i})')
time.sleep(random.uniform(0.1, 0.3))

def extract_data(self, url):
"""提取数据示例"""
self.get(url)

# 等待内容加载
self.wait_for_element((By.CLASS_NAME, 'content'))

# 滚动页面加载更多内容
self.scroll_page()

# 提取数据
items = []
elements = self.driver.find_elements(By.CSS_SELECTOR, '.item')

for elem in elements:
try:
title = elem.find_element(By.CSS_SELECTOR, '.title').text
price = elem.find_element(By.CSS_SELECTOR, '.price').text
link = elem.find_element(By.TAG_NAME, 'a').get_attribute('href')

items.append({
'title': title,
'price': price,
'link': link
})
except Exception as e:
print(f'提取失败: {e}')
continue

return items

def save_to_json(self, data, filename):
"""保存数据到 JSON"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f'已保存 {len(data)} 条数据到 {filename}')

def close(self):
"""关闭浏览器"""
self.driver.quit()


# 使用示例
if __name__ == '__main__':
spider = SeleniumSpider(headless=True)

try:
# 访问页面
spider.get('https://example.com')

# 获取标题
print(f'页面标题: {spider.driver.title}')

# 提取数据
items = spider.extract_data('https://example.com/products')

# 保存数据
spider.save_to_json(items, 'products.json')

finally:
spider.close()

处理动态加载

很多网站使用无限滚动或点击加载更多,需要特殊处理:

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

driver = webdriver.Chrome()
driver.get('https://example.com/infinite-scroll')

wait = WebDriverWait(driver, 10)

# 记录已加载的项目数
last_count = 0
max_scrolls = 10 # 限制滚动次数

for i in range(max_scrolls):
# 获取当前项目数
items = driver.find_elements(By.CSS_SELECTOR, '.item')
current_count = len(items)

print(f'滚动 {i+1}: 当前 {current_count} 个项目')

# 如果没有新项目加载,停止
if current_count == last_count:
print('没有更多内容')
break

last_count = current_count

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

# 等待新内容加载
time.sleep(2)

# 提取所有数据
items = driver.find_elements(By.CSS_SELECTOR, '.item')
print(f'总共加载 {len(items)} 个项目')

driver.quit()

绕过反爬检测

隐藏自动化特征

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

# 禁用自动化标志
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)

driver = webdriver.Chrome(options=options)

# 执行脚本隐藏 webdriver 属性
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
})

driver.get('https://example.com')

模拟人类行为

import random
import time
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Chrome()

def human_like_input(element, text):
"""模拟人类输入"""
for char in text:
element.send_keys(char)
time.sleep(random.uniform(0.05, 0.15))

def human_like_click(element):
"""模拟人类点击"""
actions = ActionChains(driver)

# 移动到元素附近
actions.move_to_element(element)
time.sleep(random.uniform(0.1, 0.3))

# 点击
actions.click()
actions.perform()

# 随机延迟
time.sleep(random.uniform(0.5, 1.5))

# 使用
driver.get('https://example.com')
input_box = driver.find_element(By.NAME, 'search')
human_like_input(input_box, 'Python 爬虫')

button = driver.find_element(By.ID, 'submit')
human_like_click(button)

性能优化

禁用图片和 CSS

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

# 禁用图片
prefs = {
'profile.managed_default_content_settings.images': 2,
'profile.managed_default_content_settings.stylesheets': 2,
}
options.add_experimental_option('prefs', prefs)

driver = webdriver.Chrome(options=options)

复用浏览器会话

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()

# 使用远程调试端口
options.add_argument('--remote-debugging-port=9222')

driver = webdriver.Chrome(options=options)

# 后续可以连接到同一个端口复用浏览器
# options.add_argument('--remote-debugging-port=9222')
# driver = webdriver.Chrome(options=options)

小结

本章我们学习了:

  1. 安装配置 - Selenium 和浏览器驱动的安装
  2. 浏览器配置 - 无头模式、窗口大小、User-Agent 等
  3. 元素定位 - 八种定位方式和相对定位
  4. 等待机制 - 隐式等待、显式等待、Fluent Wait
  5. 元素操作 - 点击、输入、表单、键盘鼠标
  6. 高级操作 - 多窗口、iframe、Alert、JavaScript 执行
  7. 实战技巧 - 无头模式爬虫、动态加载、反爬绕过

练习

  1. 使用 Selenium 登录一个网站并获取登录后的页面内容
  2. 实现一个处理无限滚动页面的爬虫
  3. 编写一个自动填写表单的脚本