☰
Selenium 封装 POST 请求:从 fetch 到 requests 的实战指南
2026/10/16 7:30:15 网站建设 项目流程

简介:一份关于Selenium封装POST参数提交的示例资源,面向Web自动化测试与接口调试开发者,解决通过浏览器驱动模拟POST请求并获取异步返回JSON值的实际需求。资源包共3个文件,包含一个Java实现类、一份txt说明文档及其备份文件,压缩包仅5KB,结构简单便于快速查看。已有3698人学习下载。示例采用JavaScript的fetch API构造POST请求,在WebDriver会话中执行,并将页面返回文本解析为JSON;同时提示可改用execute_async_script处理异步回调、按需增加请求头或认证令牌等注意事项。对希望理解Selenium在非典型HTTP请求场景中如何发挥作用、或想快速验证接口响应的读者,是一份轻量而实用的参考资料。

1. 为什么要在 Selenium 里封装 POST 提交

自动化脚本跑得多了,总会遇到这种场面:页面上一个表单要填十几项,每一步都要等加载,而这一切只是为了给后台接口发一个 POST 请求。你心里清楚,如果直接拿 requests 把参数抛给接口,半秒钟就出结果,何必让 Selenium 在前端慢慢磨。可问题在于,浏览器里的登录态、Cookie、CSRF Token 往往拿不出来,硬用 requests 又会在鉴权上栽跟头。这篇文章要讲的就是怎么在 Selenium 里把 POST 参数提交封装成自己的方法,既能借浏览器的身份发请求,又能省掉重复的界面操作。无论你是写爬虫,还是做接口回归测试,这套思路都可以直接抄过去改用。

2. 先分清两条路:fetch 还是 requests 会话

刚开始接触这个需求的人,最容易的想法是“Selenium 里能不能直接调用 post”。很遗憾,答案是不能。Selenium 是个浏览器自动化框架,驱动的是真实浏览器,它提供的是 click、send_keys、navigate 这一类模拟人操作的能力,并不像 requests 那样直连 HTTP 层。所以你要么绕道浏览器内执行 JavaScript,要么把登录态搬到 Python 侧的 HTTP 会话上。这两条路各有取舍,下面拆开说。

2.1 为什么 Selenium 不能直接发 POST

Selenium 通过 WebDriver 协议和浏览器通信,它的核心语义是“操作页面元素”,不是“发起网络请求”。你可以在 Selenium 里打开一个 URL,那其实是浏览器在导航,WebDriver 只告诉浏览器“去这个网址”,至于请求是 GET 还是 POST,WebDriver 并不保证。真要发 POST,纯 Selenium 没有对应 API。所以绝大多数人实际采用的是:

  • 用driver.execute_async_script()在页面环境里执行 JavaScript,通过fetch或XMLHttpRequest发 POST。
  • 把浏览器的 Cookie 同步给requests.Session,再用 Python 发 POST。

这两种方式我都实测过。第一种的好处是请求天然带着当前浏览器页面的 Cookie、Origin、Referer,遇到同源接口基本不用处理鉴权头;第二种的好处是响应数据直接以 Python 对象返回,调试和断言都方便,对非 JSON 格式的支持也更好。日常做自动化测试,我一般先看接口的交互格式:如果前后端走 JSON,优先 fetch;如果是表单、文件上传或需要自定义证书,建议杀到 requests 那条路。

2.2 两条路的特点与选型

为了让你少折腾,我把两个方案的核心区别列成一张表:

方案实现方式Cookie 来源适用场景主要坑
A. execute_async_script + fetch在浏览器上下文发请求自动带当前页面 Cookie同源 JSON 接口,登录态已有CORS、异步回传时序
B. requests.Session + Cookie 同步Python 侧会话请求手动从 driver 里取 Cookie复杂参数、文件上传、需要断点调试Cookie 同步不完整

选型时记住一个原则:能用 fetch 解决的同源接口,尽量不碰 requests。因为 fetch 是在页面环境里跑,脏活累活浏览器替你干完了,少了很多会话维护的麻烦。而当你需要操作二进制数据、multipart 上传,或者想在 Python 层做断言时,再换 requests 也不迟。

3. 封装一个可复用的 post_request:从同步到异步

这一章是实际操作部分。我会写一个标准的封装,先拿 JSON 数据举例子,再改成表单单格式。你拿到手以后,把 URL 和参数换成自己的接口就能跑。

3.1 利用 execute_async_script 提交 JSON

Selenium 里有execute_script和execute_async_script两个方法。前者执行完同步代码立刻返回,后者会等 JavaScript 里调用了回调函数再返回。发 POST 属于异步请求,所以我选择execute_async_script。下面这个类是核心部分:

from selenium import webdriver from selenium.webdriver.common.by import By import json class SeleniumPost: def __init__(self, driver): self.driver = driver def post_json(self, url, data, timeout=10): script = """ const url = arguments[0]; const data = arguments[1]; const callback = arguments[arguments.length - 1]; fetch(url, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }) .then(response => response.json()) .then(result => callback(result)) .catch(error => callback({error: error.message})); """ self.driver.set_script_timeout(timeout) return self.driver.execute_async_script(script, url, data)

这里要注意三个地方:

  • arguments[arguments.length - 1]就是回调函数,Selenium 会把它注入到脚本底部,请求完成后必须调用它,否则浏览器会一直挂起等待。
  • set_script_timeout(timeout)必须先设置,不然后续某个请求挂了,脚本会等很久才报错。
  • execute_async_script的返回值来自回调函数传入的参数。回调里传了普通对象,Selenium 会把它按 JSON 序列化再解析,所以 Python 侧拿到的直接是字典,不用手动处理字符串。

用的时候很简单:

driver = webdriver.Chrome() driver.get("https://某管理后台.dev/login") # 正常登录后,直接调 POST poster = SeleniumPost(driver) resp = poster.post_json( "https://某管理后台.dev/api/user/create", {"name": "test_user", "role": "admin"} ) print(resp)

整体跑下来,你只需要关心返回的字典里有没有 error 字段。如果接口返回的 content-type 不是 application/json,.then(response => response.json())这一步会直接抛异常,所以我还习惯加一个判断,把非 JSON 响应转换成文本返回。

3.2 兼容 form 格式的表单参数

很多老系统后端只认application/x-www-form-urlencoded,前端发请求还得用 URLSearchParams 拼字符串。封装也简单,把请求体改成表单序列化即可:

class SeleniumPost: def post_form(self, url, form_data, timeout=10): script = """ const url = arguments[0]; const data = arguments[1]; const callback = arguments[arguments.length - 1]; const formBody = new URLSearchParams(); for (const key in data) { formBody.append(key, data[key]); } fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: formBody.toString() }) .then(response => response.text()) .then(text => callback({success: true, text: text})) .catch(error => callback({error: error.message})); """ self.driver.set_script_timeout(timeout) return self.driver.execute_async_script(script, url, form_data)

参数说明里值得留意的是URLSearchParams的循环赋值。如果 Python 侧传入的 form_data 是一个嵌套字典,那这里只会把它[object Object]拼进去,所以传参前必须把 value 都转成字符串或数字。另一个容易忽略的点是:CSRF Token 往往藏在页面的 meta 标签或者某个 input 里,这类系统单独用 fetch 发 POST 大概率会被拦。我一般会先借助 Selenium 把 token 读出来,再合并到参数里再提交:

token = driver.find_element(By.NAME, "csrf_token").get_attribute("value") form_data = {"name": "test", "csrf_token": token}

这样就能把鉴权链路补齐。

4. 避坑:Selenium 封装 POST 的常见问题排查

封装本身不难,难的是各种诡异报错。我把这几年用下来的高频问题整理出来,每一条都是实际遇到过并且解决了的现象级记录。

4.1 脚本一直不返回,页面像卡住了一样

现象:调用execute_async_script之后,Python 侧迟迟不返回,最终超时抛异常。

原因:JavaScript 回调没有被触发。最常见的情况是接口返回的响应格式和.json()内联处理冲突,response.json()本身抛异常,错误只进入 catch,但 catch 里没有调用 callback,所以 Selenium 一直在等。

解决:在 catch 分支里也必须调用 callback,而且最好把错误对象传回去,不要只调一个空的 catch。修改如下:

.catch(error => callback({error: error.message, code: 'FETCH_FAILED'}))

这样即使请求失败,Python 侧也能拿到一个明确字典。从那以后,我的脚本基本都要求“所有异常路径必须走 callback”,这已经成了习惯。

4.2 请求发出去了,但接口报 403

现象:用浏览器直接操作表单是成功的,换成fetch封装后接口返回 403。

原因:跨域或 CSRF 校验不过。同源页面里 fetch 默认会带上 Cookie,但如果后端读的是自定义请求头,比如 X-CSRF-Token,而你只在 header 里写了 Content-Type,那么鉴权直接失败。

解决:把请求头补齐,从页面里读取 token 并放进去。同时检查后端是否要求X-Requested-With之类的标记头,如果要求,也在 header 里加上。注意 fetch 默认不会带自定义 header,除非你先构造new Headers()。

4.3 使用 requests.Session 同步 Cookie 后仍提示未登录

现象:从driver.get_cookies()取出来一堆 Cookie,然后塞给 requests 的 CookieJar,结果 POST 接口还是说未登录。

原因:Cookie 的问题很清楚,但很多站点不只看 Cookie,还看 User-Agent、Referer 和 Origin。requests 默认 UA 和浏览器完全不一样,后端风控一识别就拒了。

解决:同步 Cookie 的同时,把User-Agent、Referer、Origin也手动设置成和浏览器一样。这里有一个不易发现的细节:get_cookies()返回的 Cookie 里,某些属性比如sameSite、domain不全是标准 RFC 格式,直接循环 add_cookie 可能报错,我一般会只取 name/value 两个字段,不够用时用 lenient 的字典构造。写出来的同步函数类似:

def copy_cookies_to_session(driver, session): for cookie in driver.get_cookies(): session.cookies.set(cookie['name'], cookie['value'])

这个函数简单到过份,但能解决九成的问题。

4.4 返回的 JSON 变成字符串,P 的时候到处 strip

现象:execute_async_script 回调里传了一个字符串,Python 侧得到的是str,而不是 dict。

原因:执行的脚本里调用了 callback 时传了 JSON.stringify 后的结果。Selenium 只做一次 JSON 反序列化,如果你在 JS 侧提前 stringify,那到 Python 这边就是一层字符串包着字典,还得再json.loads一次。

解决:回调时直接传对象,不手动 stringify。远端响应如果不是 JSON,就统一返回{success: false, text: ...}。封装方法内部再根据返回类型做判断,不要让调用方关心底层细节。

4.5 同页面连续发多个 POST,第二个请求拿不到结果

现象:第一次调用正常,第二次返回 None 或莫名超时。

原因:多半是set_script_timeout被设置后,又在页面里执行了其它长时间脚本,把全局脚本超时拉低了;或者前一次请求的 Promise 没有及时清理,浏览器同一线程的 fetch 队列被阻塞。Selenium 的 execute_async_script 每次执行都是独立上下文,但如果回调未触发,全局超时计时器会覆盖后续调用。

解决:在 catch 和 finally 里都保证回调被调用,另外建议每次调用前重置set_script_timeout(timeout),不要依赖上一次的设置。

5. 进阶:把 POST 结果接回浏览器流程

封装好 POST 之后,真正有价值的地方在于让结果是驱动后续的浏览器操作,而不是孤立地发一次请求。我常用一个技巧叫“POST 作为前置条件,页面刷新做验证”。比如你要测用户列表页的新建用户功能,正常路径是:

  1. 用 Selenium 打开列表页。
  2. 点新建按钮,填一堆表单。
  3. 提交后回到列表页,看新增数据。

这套流程如果只在界面上跑,每轮用例都要耗几十秒。换成封装好 POST,流程会变成这样:

driver.get("https://某管理后台.dev/user/list") poster = SeleniumPost(driver) # 直接调后端接口创建用户 result = poster.post_json("/api/user/create", { "name": "测试账号", "status": 1 }) # 接口成功后,让浏览器主动刷新列表页 if result.get("user_id"): driver.refresh() # 在表格里找到新增用户 assert driver.find_element(By.XPATH, "//td[text()='测试账号']").is_displayed()

这里有个关键点:刷新的时机不要放在 POST 之前,否则页面还在旧状态,看不到新增数据。如果页面一直没有更新,可以加一个WebDriverWait等待表格里的文本出现,而不是盲目 sleep。接口失败也别急着抛异常,先回到接口报错信息里去排查,这样日志里既能看出是哪一步出的问题,又能保留浏览器当前截图当证据。

我还会再加一层保险:把 POST 的响应时间也记下来,超过预期阈值就输出告警,这样接口变慢不会拖到页面层才被发现。事实上在多数模拟项目中,这种前置 POST 加页面验证的组合,直接把用例执行时间砍半,而且比纯 UI 操作稳定得多。

近半年我自己的习惯也固定下来了:只要遇到“填表单只是为了发一个 POST”的场景,我就直接在封装好的 post_form 或 post_json 里补参数,跑通了再决定要不要保留这条链路。每次动手之前,我都会强制自己确认一下三件事——接口是 JSON 还是 form、页面有没有 CSRF Token、Cookie 会不会过期。确认完之后再写封装,基本不出错。希望这篇文章帮你把同样的思路落地到自己的项目里,直接改 URL 和参数就能用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询