使用 Python 中的 Selenium Wire 进行网页抓取

学习如何使用 Selenium Wire 进行网页抓取,实现请求拦截和动态代理切换,从而提升您的抓取效率。
5 min read
使用 Selenium Wire 进行网页数据采集

在本指南中,您将学习:

  • Selenium Wire 是什么
  • 为什么在网页抓取中要使用 Selenium Wire
  • Selenium Wire 的主要功能
  • 使用 Selenium Wire 进行网页抓取并切换代理的用例
  • 如何在 Selenium Wire 中集成 Bright Data 代理

让我们开始吧!

Selenium Wire 是什么?

Selenium Wire 是 Selenium Python 绑定的一个扩展,可对浏览器请求进行控制。具体来说,它允许您在运行 Selenium 的同时,直接通过 Python 代码实时拦截和修改请求与响应。

注意:虽然该库已不再维护,但仍有许多抓取技术和脚本依赖它。

为什么在网页抓取中使用 Selenium Wire?

Selenium 是一个用于浏览器自动化的流行框架,人们常用它在网页抓取时像普通用户一样与网站交互。查看更多内容,请访问我们的 Selenium 网页抓取指南

问题在于,浏览器本身存在一些限制,导致网页抓取面临挑战。例如,不支持动态设置带有授权的代理 URL 或 实时切换代理。Selenium Wire 可以帮助您克服这些限制。

以下是您应该在网页抓取场景中使用 Selenium Wire 的三个理由:

  • 访问网络层:解释、检查并修改 AJAX 网络流量,从而实现更高级的数据提取。
  • 绕过反爬机制ChromeDriver 会暴露大量信息,反爬系统可利用这些信息识别您是爬虫。Selenium Wire 被 undetected-chromedriver 等技术所使用,以避免被识别并帮助绕过大多数反爬解决方案。
  • 突破浏览器的限制:现代浏览器使用启动参数(flags)来在启动时配置行为,但这些设置是静态的,需要重新启动后方能改变。Selenium Wire 通过支持动态修改来克服这一限制。这样,您可以在同一次浏览器会话中更新请求头或代理,非常适合网页抓取。

Selenium Wire 的主要功能

现在您已经了解了 Selenium Wire 是什么以及为什么要在网页抓取中使用它。接下来让我们深入了解它的重要功能!

访问请求与响应

Selenium Wire 可以捕获浏览器发出的 HTTP/HTTPS 流量,让您访问下列属性:

属性 描述
driver.requests 它会按时间顺序列出捕获的请求
driver.last_request 它表示最近捕获的请求
(这比使用 driver.requests[-1] 更高效)
driver.wait_for_request(pat, timeout=10) 此方法会等待(等待时长由 timeout 参数定义),直到出现与 pat(可为子字符串或正则表达式)匹配的请求
driver.har 呈现 HTTP 事务的 JSON 格式HAR 文件
driver.iter_requests() 返回一个对捕获的请求进行迭代的迭代器

更具体地说,Selenium Wire 中的 Request 对象包含以下属性:

属性 描述
body 请求体以 bytes 的形式呈现。如果请求体为空,则 body 值为空(例如:b'')。
cert 以字典形式报告服务器 SSL 证书信息(对于非 HTTPS 请求,该值为空)。
date 显示发出该请求的时间。
headers 以类字典对象形式报告请求头(需要注意的是,在 Selenium Wire 中,头的大小写不敏感,并且允许重复)。
host 报告请求主机(例如,/)。
method 指定 HTTP 方法(GETPOST 等)。
params 以字典形式报告请求参数(注意如果在请求中同名参数出现多次,该字典中的值会是一个列表)。
path 报告请求路径。
querystring 报告查询字符串。
response 报告与该请求关联的响应对象(如果请求没有响应则值为 None)。
url 报告完整请求 URL,包括 hostpathquerystring
ws_messages 如果该请求是 WebSocket(此时 URL 通常以 wss:// 开头),ws_messages 将包含客户端与服务器间收发的 WebSocket 消息。

相应地,Response 对象具有以下属性:

属性 描述
body 响应体以 bytes 的形式呈现。如果响应没有主体,则值为空(例如:b'')。
date 显示接收该响应的时间。
headers 以类字典对象形式报告响应头(需要注意的是,在 Selenium Wire 中,头的大小写不敏感,并且允许重复)。
reason 报告响应的原因短语,例如 OKNot Found 等。
status_code 报告响应状态码,例如 200404 等。

您可以创建如下 Python 脚本来测试此功能:

from seleniumwire import webdriver

# Initialize the WebDriver with Selenium Wire
driver = webdriver.Chrome()

try:
    # Open the target website
    driver.get("https://brightdata.com/")

    # Access and print all captured requests
    for request in driver.requests:
        print(f"URL: {request.url}")
        print(f"Method: {request.method}")
        print(f"Headers: {request.headers}")
        print(f"Response Status Code: {request.response.status_code if request.response else 'No Response'}")
        print("-" * 50)

finally:
    # Close the browser
    driver.quit()

以上代码通过 driver.requests 获取目标网站的请求,然后通过 for 循环来拦截并打印各个请求的一些属性(比如 urlmethodheaders)。

预期结果如下所示:

部分已记录的请求

目标网页会发出多个请求,脚本会跟踪这些请求。

拦截请求与响应

Selenium Wire 可以通过拦截器来拦截并修改请求和响应。拦截器是在请求与响应通过浏览器时被调用的函数。

它有两种拦截器:

  • driver.request_interceptor:拦截请求,只接受一个参数。
  • driver.response_interceptor:拦截响应,接受两个参数:一个是源请求,一个是响应对象。

下面是一个使用请求拦截器的示例:

from seleniumwire import webdriver

# Define the request interceptor function
def interceptor(request):
    # Add a custom header to all requests
    request.headers["X-Test-Header"] = "MyCustomHeaderValue"

    # Block requests to a specific domain
    if "example.com" in request.url:
        print(f"Blocking request to: {request.url}")
        request.abort()  # Abort the request

# Initialize the WebDriver with Selenium Wire
driver = webdriver.Chrome()

# Assign the interceptor function to the driver
driver.request_interceptor = interceptor

try:
    # Open a website that makes multiple requests
    driver.get("https://brightdata.com/")

    # Print all captured requests
    for request in driver.requests:
        print(f"URL: {request.url}")
        print(f"Headers: {request.headers}")
        print("-" * 50)

finally:
    # Close the browser
    driver.quit()

这个示例所做的事情:

  • 拦截器函数:为每一个传出的请求调用该拦截器函数。此函数使用 request.headers[] 为所有请求添加一个自定义请求头。同时,它会阻止浏览器对 example.com 域名发出的加载请求。
  • 捕获请求:页面加载后,脚本打印所有捕获的请求,包括修改过的请求头。

注意:阻止请求对于那些会加载广告、分析脚本或第三方小部件(与您的目标无关)的网页资源很有用。阻止这些请求能够显著提升抓取速度、减少浏览器带宽消耗。

预期结果示例:

注意 X-Test-Header

可以看到,浏览器发出的请求被拦截,并添加了自定义的请求头。

WebSocket 监听

许多现代网页使用 WebSockets 来实现与服务器的实时通信。WebSockets 在浏览器与服务器间建立一个持久连接,从而可以持续地交换数据,而无需传统的 HTTP 请求开销。

很多关键数据都可能经由这种通道传输,直接访问这些数据对于数据获取来说非常有价值。通过拦截 WebSocket 通信,您可以提取服务器直接发送的原始数据,而不需要等待浏览器的处理或页面的渲染。

正如前面所述,Request 对象拥有 ws_messages 属性来管理 WebSocket。Selenium Wire 中的 WebSocket 对象中包含以下属性:

属性 描述
content 报告消息的内容,可以是 strbytes
date 显示消息的发送或接收时间。
headers 以类字典形式报告响应头(在 Selenium Wire 中,头的大小写不敏感并允许重复)。
from_client 一个布尔值,如果该消息由客户端发送,则为 True;若由服务器发送,则为 False

管理代理

代理服务器充当您设备与目标站点间的中间人,同时隐藏您的 IP 地址。它们在网页抓取场景尤为重要,原因在于:

  1. 帮助绕过基于 IP 的限制
  2. 在遇到速率限制时防止被封锁
  3. 允许从受地域限制的网站中抓取内容

以下是如何在 Selenium Wire 中配置代理的示例:

# Set up Selenium Wire options
options = {
    "proxy": {
        "http": "<YOUR_HTTP_PROXY_URL>",
        "https": "<YOUR_HTTPS_PROXY_URL>"
    }
}

# Initialize the WebDriver with Selenium Wire
driver = webdriver.Chrome(seleniumwire_options=options)

这个配置与在原生 Selenium 中配置代理的方式不同,后者需要依赖 Chrome 的 --proxy-server 启动标志。这意味着在原生 Selenium 中,代理配置是静态的。

一旦您设置了代理,它就会应用于整个浏览器会话里,若要修改代理就必须重启浏览器。在需要动态切换代理的场景下,这种限制往往难以接受。

相比之下,Selenium Wire 允许在同一个浏览器实例内动态修改代理,这得益于它对 proxy 属性的支持:

# Dynamically change the proxy
driver.proxy = {
    "http": "<NEW_HTTP_PROXY_URL>",
    "https": "<NEW_HTTPS_PROXY_URL>"
}

此外,Chrome 的 --proxy-server 标志并不支持在 URL 中携带认证信息:

protocol://username:password@host:port

而 Selenium Wire 完全支持带认证的代理,这对于网页抓取来说更佳。

由于代理配置是 Selenium Wire 最显著的优势之一,我们将在下一章深化探讨这个主题。

网页抓取用例:Selenium Wire 的代理切换

如前所述,使用 Selenium Wire 的主要原因之一是其高级别的代理管理能力。

在本指导部分,您将了解如何在 Selenium Wire 中配置代理切换,以便让您的出口 IP 每次请求都能够改变。

前提条件

要复现本教程,您的系统需要满足以下要求:

  • Python 3.7 或更高版本:高于 3.7 的 Python 版本皆可。我们将通过 pip 安装依赖,pip 在 Python 3.4 之后版本中默认为自带。
  • 支持的网络浏览器:Selenium Wire 是对 Selenium 的扩展,因此需要一个受支持的浏览器

在安装 Selenium Wire 之前,您可以先创建一个虚拟环境目录:

python -m venv venv

然后在 Windows 上激活:

venv\Scripts\activate

在 macOS/Linux 上激活:

source venv/bin/activate

接着,安装 Selenium Wire:

pip install selenium-wire

注意:您无需单独安装 Selenium。它会和 Selenium Wire 一起被安装,因为 Selenium Wire 的依赖中已经包含了它。

假设您将主文件夹命名为 selenium_wire/。完成该步骤后,文件夹结构形如:

selenium_wire/
├── selenium_wire.py
└── venv/

其中 selenium_wire.py 将包含您在下一步要实现的逻辑。

步骤 1:随机化代理

首先,您需要一个有效的代理列表。如果不清楚从哪里获取,可以参考我们的 免费代理列表。将它们添加到列表中,并使用 random.choice() 随机选择一个:

def get_random_proxy():
    proxies = [
        "http://PROXY_1:PORT_NUMBER_X",
        "http://PROXY_2:PORT_NUMBER_Y",
        "http://PROXY_3:PORT_NUMBER_Z",
        # ...
    ]
    
    # Randomize the list
    return random.choice(proxies)

每次调用该函数时都会返回列表中的一个随机代理 URL。

记得先导入 random

import random

步骤 2:设置代理

调用 get_random_proxy() 函数得到一个代理 URL:

proxy = get_random_proxy()

然后初始化浏览器实例并设置此代理:

# Selenium Wire configuration with the proxy
seleniumwire_options = {
    "proxy": {
        "http": proxy,
        "https": proxy
    }
}

# Browser configuration
chrome_options = Options()
chrome_options.add_argument("--headless")  # Run the browser in headless mode 

# Initialize a browser instance with the given configurations
driver = webdriver.Chrome(service=Service(), options=chrome_options, seleniumwire_options=seleniumwire_options)

上述代码需要以下导入:

from seleniumwire import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

如果您想在浏览器会话中动态切换代理,则可以改用:

driver.proxy = {
    "http": proxy,
    "https": proxy
}

这样一来,受控的 Chrome 实例就会通过指定的代理来进行请求。

步骤 3:访问目标页面

访问目标网站,提取输出,然后关闭浏览器:

try:
    # Visit the target page
    driver.get("https://httpbin.io/ip")

    # Extract the page output
    body = driver.find_element(By.TAG_NAME, "body").text
    print(body)
except Exception as e:
    # Handle any errors that occur with the browser or the proxy
    print(f"Error with proxy {proxy}: {e}")
finally:
    # Close the browser
    driver.quit()

务必导入 By

from selenium.webdriver.common.by import By

在本示例中,目标页面是 HTTPBin 项目的 /ip 端点。这样设计是为了让页面返回调用方的 IP 地址。如果一切正常,脚本应在每次运行时都打印出来自代理列表中不同的 IP。

可以开始测试了!

步骤 4:整合以上步骤

以下是完整的 Selenium Wire 代理切换逻辑,放在 selenium_wire.py 文件中:

import random
from seleniumwire import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

def get_random_proxy():
    proxies = [
        "http://PROXY_1:PORT_NUMBER_X",
        "http://PROXY_2:PORT_NUMBER_Y",
        "http://PROXY_3:PORT_NUMBER_Z",
        # Add more proxies here...
    ]
    
    # Randomly pick a proxy
    return random.choice(proxies)
 
# Pick a random proxy URL 
proxy = get_random_proxy()

# Selenium Wire configuration with the proxy
seleniumwire_options = {
    "proxy": {
        "http": proxy,
        "https": proxy
    }
}

# Browser configuration
chrome_options = Options()
chrome_options.add_argument("--headless")  # Run the browser in headless mode 

# Initialize a browser instance with the given configurations
driver = webdriver.Chrome(service=Service(), options=chrome_options, seleniumwire_options=seleniumwire_options)

try:
    # Visit the target page
    driver.get("https://httpbin.io/ip")

    # Extract the page output
    body = driver.find_element(By.TAG_NAME, "body").text
    print(body)
except Exception as e:
    # Handle any errors that occur with the browser or the proxy
    print(f"Error with proxy {proxy}: {e}")
finally:
    # Close the browser
    driver.quit()

执行:

python3 selenium_wire.py

每次执行时,输出类似:

{
  "origin": "PROXY_1:XXXX"
}

或:

{
  "origin": "PROXY_2:YYYY"
}

如此往复…

多次执行脚本,您就会看到不同的 IP 地址,说明代理切换已经生效!

更好的代理切换方案:使用 Bright Data 代理

正如我们所见,手动在 Selenium Wire 中进行代理切换不仅涉及大量模板化代码,而且需要维护一个可用代理的列表。

幸运的是,Bright Data 的旋转代理提供了更高效的方案!

我们的旋转代理会自动处理 IP 地址切换,不再需要手动管理代理。我们在 195 个国家/地区均有覆盖,并提供 99.9% 的网络正常运行时间和成功率。我们的全球代理网络包含:

按照下列步骤,使用 Bright Data 代理与 Selenium Wire 进行集成。

如果已有账户,请登录 Bright Data。否则可以免费创建一个账户。登录后您可看到如下用户面板:

Bright Data 控制面板

点击 “View proxy products” 按钮:

查看代理产品

随后来到 “Proxies & Scraping Infrastructure” 页面:

配置您的住宅代理

往下滚动找到 “Residential Proxies” 选项卡,点击 “Get started”:

住宅代理

您会进入住宅代理配置页面,根据向导一步步设置代理服务。如果在配置时有任何疑问,可随时 联系 24/7 技术支持

配置您的住宅代理
配置您的住宅代理

进入 “Access parameters” 标签页,获取代理的主机、端口、用户名和密码:

访问参数

注意,“Host” 字段里已经包含端口。

接下来只需将这些信息组合到代理 URL 中即可,其语法如下:

<username>:<password>@<host>

例如,在此示例中可写成:

brd-customer-hl_4hgu8dwd-zone-residential:[email protected]:XXXXX

最后切换 “Active proxy” 到开启状态,按照提示完成流程,您就可以直接使用了!

激活代理切换

以下是如何在 Selenium Wire 中使用 Bright Data 代理的示例:

# Bright Data proxy URL
proxy = "brd-customer-hl_4hgu8dwd-zone-residential:[email protected]:XXXXX"

# Set up Selenium Wire options
options = {
    "proxy": {
        "http": proxy,
        "https": proxy
    }
}

# Initialize the WebDriver with Selenium Wire
driver = webdriver.Chrome(seleniumwire_options=options)

使用此方法,代理切换会更加简单!

Selenium 对比 Selenium Wire 在网页抓取中的差异

总结如下,对比下表:

Selenium Selenium Wire
定位 一款用于自动化网页浏览器的工具,通常用于 UI 测试和网络交互 在 Selenium 的基础上扩展,提供检查和修改 HTTP/HTTPS 请求和响应的能力
HTTP/HTTPS 请求处理 不支持直接访问 HTTP/HTTPS 请求或响应 允许检查、修改并捕获 HTTP/HTTPS 请求与响应
代理支持 有限的代理支持(需手动配置) 高级代理管理,支持动态设置
性能 轻量且速度快 由于捕获和处理网络流量存在开销,速度略慢
应用场景 主要用于 Web 应用功能性测试,也可用于基本的网页抓取 适用于测试 API、调试网络流量以及网页抓取

结论

在本文中,您学习了 Selenium Wire 是什么,以及如何将其应用到网页抓取当中。我们特别关注了代理集成和代理切换的内容。需要注意的是,Selenium Wire 虽然有用,但并非万能,而且它已经不再积极维护。

更好的方法并不是对 Selenium Wire 进一步扩展,而是直接使用原生的 Selenium 或其他浏览器自动化工具,并结合一个专门的抓取浏览器。

Bright Data 的 Scraping Browser 是一个可伸缩的云端浏览器,可与 Playwright、Puppeteer、Selenium 等工具配合使用。它可在每个请求时自动切换出口 IP,并能应对浏览器指纹识别、重试、验证码识别 等多重挑战。无需再担心被阻拦,大大简化您的抓取流程。

立即注册并开始免费试用吧!