
本文详细介绍了如何利用Selenium的`execute_async_script`方法在python中执行javaScript的`fetch`请求,并高效地获取其异步响应。内容涵盖了设置自定义http头、处理GET/POST请求以及从浏览器环境无缝检索数据到Python的实用技巧。
在自动化测试和网页抓取场景中,我们经常需要模拟浏览器行为来发送HTTP请求,并获取响应数据。Selenium作为一款强大的浏览器自动化工具,通常通过模拟用户操作来与页面交互。然而,当需要直接在浏览器环境中执行javascript的fetch API来发送具有自定义HTTP头(如CUSTOM_HEADER_1、CUSTOM_HEADER_2等)的GET或POST请求,并获取其异步返回的jsON数据时,会遇到一些挑战。
Selenium中Fetch请求的挑战
直接使用driver.execute_script()执行fetch请求时,由于fetch是一个异步操作,它会立即返回一个JavaScript promise对象,而不是请求的实际数据。这意味着Python端无法直接获取到Promise解析后的结果。为了解决这一问题,我们需要利用Selenium提供的异步脚本执行机制。
核心解决方案:execute_async_script
Selenium的execute_async_script()方法专门用于执行异步JavaScript代码。它允许JavaScript代码在完成其异步操作后,通过调用一个特殊的callback函数将结果返回给Python。这是处理fetch请求异步特性的关键。
立即学习“Python免费学习笔记(深入)”;
当execute_async_script()被调用时,它会向JavaScript环境注入一个回调函数作为其第一个参数(通常命名为arguments[0])。JavaScript代码在执行fetch请求并处理完响应后,只需将最终结果作为参数传递给这个callback函数,Selenium就会等待该回调被执行,并将传递给回调函数的值作为execute_async_script()的返回值返回给Python。
实现步骤与代码示例
下面我们将通过一个完整的Python示例,演示如何使用execute_async_script来执行带有自定义头的GET和POST fetch请求,并获取json响应。
1. Python环境准备与WebDriver初始化
首先,我们需要导入必要的Selenium模块,并配置WebDriver。这里以chrome为例,并展示如何尝试配置代理(尽管需要注意其对fetch请求的潜在局限性)。
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException import json # 示例代理设置 (注意:对于execute_async_script发起的fetch,此设置可能不总是生效) # 如果fetch请求需要通过代理,通常需要更复杂的网络拦截或直接在fetch选项中指定代理 proxy_server_url = "127.0.0.1:8888" chrome_options = webdriver.ChromeOptions() # chrome_options.add_argument('--proxy-server=%s' % proxy_server_url) chrome_options.add_argument("ignore-certificate-Errors") chrome_options.add_argument("--headless") # 可选:无头模式运行 driver = webdriver.Chrome(options=chrome_options) # 设置异步脚本的超时时间,默认为0,需要手动设置 driver.set_script_timeout(10) # 设置为10秒 # 导航到一个空白页或任意页面,以确保浏览器环境已准备好执行JS driver.get("about:blank") print("WebDriver initialized and navigated to about:blank.")
2. 执行异步Fetch请求(GET示例)
我们将构建一个JavaScript脚本,用于执行GET请求,并期望返回JSON数据。
# 示例:GET请求,获取JSON数据 get_url = "https://jsonplaceholder.typicode.com/posts/1" # 示例URL get_headers = { "Accept": "application/json", "Custom-Header-GET": "get_value_123" } # 构建JavaScript fetch请求脚本 js_script_get = """ let callback = arguments[0]; // Selenium注入的回调函数 let url = arguments[1]; let headers = arguments[2]; fetch(url, { method: 'GET', headers: headers }) .then(response => { if (!response.ok) { // 如果响应状态码不是2xx,抛出错误 return response.text().then(text => { throw new Error(`Network response was not ok: ${response.status} - ${text}`); }); } return response.json(); // 尝试解析为JSON }) .then(data => callback(data)) // 将解析后的数据通过回调返回给Python .catch(error => callback({ error: error.message })); // 捕获错误并返回错误信息 """ print(f"nExecuting GET request to: {get_url}") try: response_data_get = driver.execute_async_script(js_script_get, get_url, get_headers) print("GET Request Response (Python):") print(json.dumps(response_data_get, indent=2, ensure_ascii=False)) except TimeoutException: print("GET request timed out.") except Exception as e: print(f"An error occurred during GET request: {e}")
3. 执行异步Fetch请求(POST示例)
接着,我们演示如何发送一个带有请求体的POST请求。
# 示例:POST请求,发送JSON数据 post_url = "https://jsonplaceholder.typicode.com/posts" # 示例URL post_data = {"title": "foo", "body": "bar", "userId": 1} post_headers = { "Content-Type": "application/json", "Accept": "application/json", "Custom-Header-POST": "post_value_456" } # 构建JavaScript fetch请求脚本 js_script_post = """ let callback = arguments[0]; let url = arguments[1]; let headers = arguments[2]; let body = arguments[3]; fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body) // 将POST数据转换为JSON字符串 }) .then(response => { if (!response.ok) { return response.text().then(text => { throw new Error(`Network response was not ok: ${response.status} - ${text}`); }); } return response.json(); }) .then(data => callback(data)) .catch(error => callback({ error: error.message })); """ print(f"nExecuting POST request to: {post_url}") try: response_data_post = driver.execute_async_script(js_script_post, post_url, post_headers, post_data) print("POST Request Response (Python):") print(json.dumps(response_data_post, indent=2, ensure_ascii=False)) except TimeoutException: print("POST request timed out.") except Exception as e: print(f"An error occurred during POST request: {e}") # 完成后关闭WebDriver driver.quit() print("nWebDriver closed.")
代码解析
- *`driver.execute_async_script(js_script, args)**: 这是核心方法。js_script是将在浏览器中执行的JavaScript代码。*args是传递给JavaScript脚本的参数。在JavaScript中,这些参数通过arguments[0],arguments[1]等访问。arguments[0]`总是Selenium注入的回调函数。
- JavaScript fetch API:
- fetch(url, options): 发送HTTP请求。
- options.method: 指定HTTP方法,如’GET’或’POST’。
- options.headers: 一个包含请求头的JavaScript对象。键值对对应HTTP头名称和值。
- options.body: 对于POST等请求,用于发送请求体。如果发送JSON数据,需要使用JSON.stringify()将其转换为字符串。
- Promise链式调用:
- .then(response => …): 处理fetch返回的响应对象。
- response.ok: 检查响应状态码是否在200-299之间。
- response.json() / response.text(): 将响应体解析为JSON对象或纯文本。这些也是异步操作,返回Promise。
- .then(data => callback(data)): 在数据成功解析后,通过callback函数将数据返回给Python。
- .catch(error => callback({ error: error.message })): 捕获fetch或响应处理过程中的任何错误,并将错误信息返回给Python。
注意事项
- 代理设置的局限性: 虽然可以在ChromeOptions中设置浏览器级代理,但通过execute_async_script直接发起的fetch请求,有时可能不会完全遵循这些代理设置。这取决于浏览器内部的实现和fetch API的行为。如果代理是关键,可能需要考虑在fetch请求中直接配置代理(如果API支持,但标准fetch不支持直接配置代理),或者使用其他网络拦截工具(如Selenium Wire)来捕获和修改请求。
- 数据类型转换: JavaScript通过callback返回的数据会由Selenium自动转换为Python类型。例如,JavaScript对象会被转换为Python字典,数组转换为列表,字符串转换为字符串。确保JavaScript返回的数据结构与Python期望的匹配。
- 超时处理: execute_async_script默认的超时时间可能很短或为0。务必使用driver.set_script_timeout()来设置一个合理的超时时间,以避免因网络延迟或脚本执行时间过长而导致的TimeoutException。
- 错误处理: 在JavaScript脚本中加入.catch()块是非常重要的,这样即使fetch请求失败或响应处理出错,Python也能接收到错误信息,而不是脚本挂起或抛出未捕获的异常。
- 跨域请求 (CORS): 如果fetch请求的目标URL与当前页面的源不同,可能会遇到CORS问题。确保服务器端已正确配置CORS策略以允许这些请求。
总结
通过execute_async_script方法,Selenium为我们提供了一个强大的机制,可以在浏览器环境中执行异步JavaScript代码,特别是像fetch这样的网络请求。这使得我们能够灵活地发送带有自定义HTTP头的GET或POST请求,并无缝地将异步响应数据检索到Python程序中进行进一步处理。理解其工作原理和注意事项,将有助于更高效地利用Selenium解决复杂的Web自动化任务。


