0%

Python 自动化框架 Selenium 开发者能力调用小记

最近搞一个自动化小工具, 主要是控制浏览器干一些事情,最开始使用 Selenium 实现。

其中要用到了一些网络监听相关的功能。期间翻了很多博客,发现这些博客记录的信息都太累赘(使用不再维护的 Selenium-Wire 或者跑一个内置的代理服务器监听之类的)或太古老了(很早的实现方式,新版本 API 作了修改),完全用不了。

最终在仔细研究了几遍官网文档和外网的零星资料之后,终于是吧这个功能实现了。鉴于国内还没有很多这方面的信息,特此记录一下。

PS:后来还要实现通过浏览器下载文件的效果,这个时候发现了行业新秀 Playwright,这玩意对于这个场景太合适了,可以监控文件下载结果,这一点完全碾压 Selenium

环境信息:

  • Windows:10
  • Python:3.14.0
  • Selenium:4.40.0
  • Chrome:144.x

演示代码

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.devtools.v144 import network
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.remote import websocket_connection
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 开发者功能 - 开启网络监控
def enabled_network_monitor(connection: websocket_connection.WebSocketConnection) -> int:
    connection.execute(network.enable())
    connection.execute(network.set_request_interception(
        (network.RequestPattern(url_pattern='*', interception_stage=network.InterceptionStage.HEADERS_RECEIVED),),
    ))
    return connection.add_callback(network.RequestIntercepted, _handle_request_intercepted)

# 开发者功能 - 关闭网络监控
def disabled_network_monitor(connection: websocket_connection.WebSocketConnection, intercepted_request_id: int):
    connection.remove_callback(network.RequestIntercepted, intercepted_request_id)
    connection.execute(network.set_request_interception([]))
    connection.execute(network.disable())

# 开发者功能 - 处理请求拦截
def _handle_request_intercepted(event: network.RequestIntercepted):
    print(f'请求拦截: {event.request.method} - {event.request.url}, 响应结果: {event.response_status_code}, 是否下载: {event.is_download}')
    connection.execute(network.continue_intercepted_request(
        interception_id=event.interception_id
    ))

# 等待元素可见
def wait_until_element_visible(driver, locator, timeout=10):
    try:
        element = WebDriverWait(driver, timeout).until(
            EC.visibility_of_element_located(locator)
        )
        return element
    except:
        return None

# 创建浏览器对象
# 组件下载地址: https://developer.chrome.google.cn/docs/chromedriver?hl=zh-cn
options = webdriver.ChromeOptions()
options.add_argument('--start-maximized')
options.binary_location = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
service = ChromeService(executable_path='chromedriver.exe')
driver = webdriver.Chrome(options=options, service=service)

# 开启 DevTools
devtools, connection = driver.start_devtools()
intercepted_request_id = enabled_network_monitor(connection)

# 访问博客
driver.get('https://6xyun.cn')

# 等待文章加载完成
wait_until_element_visible(driver, By.CSS_SELECTOR, 'body > div.container.use-motion > main > div > div.content-wrap > div > article')
# 找到归档按钮
archives_button = driver.find_element(By.CSS_SELECTOR, '#menu > li.menu-item.menu-item-archives > a')
archives_button.click()

# 等待文章加载完成
wait_until_element_visible(driver, By.CSS_SELECTOR, 'body > div.container.use-motion > main > div > div.content-wrap > div > nav')
# 查找当前页面所有文章
articles = driver.find_elements(By.XPATH, '/html/body/div[2]/main/div/div[1]/div/div/div/article')
for article in articles:
    date = article.find_element(By.CSS_SELECTOR, 'header > div.post-meta > time').get_attribute('content')
    title = article.find_element(By.CSS_SELECTOR, 'header > div.post-title > a > span').get_attribute('textContent').strip()
    url = article.find_element(By.CSS_SELECTOR, 'header > div.post-title > a').get_attribute('href')
    print(f'时间:{date}, 标题:{title}, 链接:{url}')

time.sleep(10)

# 关闭网络监控
disabled_network_monitor(connection, intercepted_request_id)

# 关闭浏览器
driver.quit()

注意事项

  • 驱动版本需要和浏览器匹配,否则启动不了