简介针对Selenium自动化测试中模拟POST请求的需求这份资源面向需要参考接口请求封装实现的Web自动化开发者解决了如何借助WebDriver执行脚本构造并提交POST参数、再解析异步返回JSON的问题。资源包共3个文件包含Java源文件、txt说明文档及bak备份文件三类整体仅5KB结构轻量便于直接阅读或修改复用。目前已有3698人学习下载适合作为接口联调与自动化测试的参考片段。内容不仅提供可运行的封装示例还通过文本资料对异步请求触发、响应获取及JSON解析等关键点进行说明bak文件则可用于对比版本调整。对于想理解“用UI自动化工具发送POST请求”这一非典型用法的开发者能有效节省自行摸索的时间。1. Selenium 传 POST 参数别把它当 requests先想清楚要的是哪个层直接给结论Selenium 本身并没有提供类似driver.post(url, data)的接口想用它直接提交 POST 参数本质上是在跟浏览器的自动化协议较劲。很多人一上来就写driver.execute_script($.post(...))结果页面跳转了、cookie 没同步、前端框架拦截了请求折腾半天还是在原地打转。我拆过不少模拟项目 X 的登录模块最后发现真正靠谱的路径只有两条要么把 POST 请求从 Selenium 里剥离出来交给 requests 这类 HTTP 库去发再做 cookie 或 token 的桥接要么停留在浏览器层面用 CDPChrome DevTools Protocol拦截或构造请求。这篇笔记就是把这两条路拆开讲清楚包括参数怎么封装、cookie 怎么同步、以及我踩过的几个坑适合正在做自动化登录、接口联调或爬虫落地的人。2. 为什么 Selenium 不能直接发 POST协议层的边界与三条变通思路2.1 WebDriver 协议设计里就没有发请求的语义Selenium 的核心是 WebDriver 协议它定义的操作对象是浏览器包括元素查找、点击、输入、执行脚本、切换窗口等但从来没有send_request这类接口。你可以把它理解成遥控器上的按键集合按键再多也变不出一根网线。POST 提交属于网络层的动作浏览器没有暴露给 WebDriver 一个「发 HTTP 请求」的入口这是设计使然不是遗漏。那为什么有人觉得能发其实是绕了路。比如execute_script里跑fetch或者XMLHttpRequest这确实能发出 POST 请求但它是在页面上下文里发的——要受同源策略约束、要处理跨域、要处理前端框架的拦截器而且这个请求的响应你是拿不回来的除非再做一次回传。如果你只是想触发某个接口那没问题但如果你需要拿到响应体、需要设 headers、需要处理重定向这条路就走不通了。2.2 三条变通路径的选型判断我在实际拆资源时一般会根据场景把方案分成三档供你按需取舍。需求场景推荐方案理由登录接口需要拿到 token/cookie再让浏览器复用requests 独立发 POST cookie 注入 Selenium响应处理灵活cookie 可控页面内部有 AJAX POST希望它被「自然」触发正常操作页面或用 execute_script 调用页面的方法浏览器行为最真实反爬最少需要拦截/改写浏览器发出的某个 POST 请求CDP 的 Fetch domain 拦截并改写不影响页面逻辑能精确控制请求头绝大多数我见到的情况是第一种。比如某个跨平台系统的登录接口是/api/user/login前端拿到 token 后存 localStorageSelenium 直接访问业务页面会因为没有 token 而跳回登录页。这时候你在浏览器里填表单走登录流程也能过但速度慢、且经常被图形验证码卡住。用 requests 先把登录接口调通拿到 token 再注入浏览器整个自动化流程能快十倍。2.3 一个常见的翻车写法execute_script 里 setTimeout有人会在execute_script里写setTimeout配合fetch发 POST然后立刻用sleep等请求完成。这里有两个问题第一execute_script默认只会执行到脚本的最后一行异步回调不会等你sleep是玄学网络慢一点就取不到结果第二fetch 的响应体在页面上下文里Selenium 侧拿不到除非再写一套回调把数据塞回 DOM。我建议直接放弃这个思路回到 requests 方案。3. 把 POST 参数封装成独立函数requests 版登录模块实战3.1 封装思路一个函数只管一个接口我先说结论用 requests 封装 POST 提交重点不是把参数写进一个函数而是把「登录接口的调用」做成独立模块让返回值同时包含可用 cookie 和登录后的状态数据。这样 Selenium 侧只用做一步——把 cookie 注入浏览器剩下的验证码识别、失败重试、token 刷新都在 requests 层处理。下面是一个我常用的最小封装import requests def post_login(base_url, username, password, extraNone): session requests.Session() login_url f{base_url}/api/user/login payload { username: username, password: password, remember: False, } if extra: payload.update(extra) headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Content-Type: application/json, Origin: base_url, } resp session.post(login_url, jsonpayload, headersheaders, timeout10) resp.raise_for_status() data resp.json() if data.get(code) ! 0: raise RuntimeError(flogin failed: {data}) # 返回 session 和解析后的用户信息 return session, data这段代码的要点有几个用requests.Session()而不是每次新建requests.post是因为 session 会自动保存服务端下发的 cookie后续请求能直接复用。jsonpayload会把参数序列化成application/json格式如果你的接口是表单提交application/x-www-form-urlencoded就改成datapayload这两个不能混用后端解析方式不一样。Origin头不是所有接口都校验但登录接口常见校验。如果漏了服务端可能返回 403。timeout10必须写不写的话未知网络环境下请求可能挂死这是血泪经验。resp.raise_for_status()会把非 2xx 状态码转成异常便于定位问题。3.2 验证码与动态参数的处理很多登录接口不只是用户名和密码还可能带验证码、时间戳、签名等动态参数。我的处理方式是把它做成参数化字段而不是写死在函数里def post_login_with_captcha(base_url, username, password, captcha_id, captcha_text): session requests.Session() # 第一步获取验证码 ID code_url f{base_url}/api/captcha code_resp session.get(code_url, timeout10) captcha_info code_resp.json() # 第二步提交登录 login_url f{base_url}/api/user/login payload { username: username, password: password, captchaId: captcha_id, captcha: captcha_text, timestamp: captcha_info.get(timestamp), } resp session.post(login_url, datapayload, timeout10) return session, resp这个例子想说明的是POST 参数的封装不是简单地把字段塞进 dict而是要考虑请求之间的依赖关系。你先请求验证码接口拿到 id 和时间戳再组装登录参数这中间的顺序不能乱。否则登录接口会一直报「验证码过期」而你根本看不出来是验证码错还是时间戳错。参数说明我列一下captcha_id不是用户输入的验证码内容是服务端返回的验证码编号常用于校验来源。timestamp如果接口校验时间窗口比如 5 分钟内有效必须在请求发出前实时获取不能缓存。data和json的选择要看后端接口文档。通常 Java 系的接口更常见data表单格式Go 和 Node 系更喜欢json。不确定的时候先抓包看请求体格式。4. 让 Selenium 用上 POST 的成果cookie 注入与 localStorage 同步4.1 从 session 到 WebDriver 的 cookie 迁移这是整个封装里最容易翻车的一步。requests 的 session 保存的 cookie和 Selenium 的 driver 默认的 cookie 机制不是一回事。requests 的 cookie 是字典形态Session.cookies可以直接遍历Selenium 的add_cookie要求 dict 里必须有name和value而且域名必须是当前访问的域名不然会被 WebDriver 拒绝。我一般这样迁移from selenium import webdriver def inject_cookies(driver, session, domain): driver.get(fhttp://{domain}) # 先打开一次页面让 driver 处于该域下 driver.delete_all_cookies() for cookie in session.cookies: driver.add_cookie({ name: cookie.name, value: cookie.value, domain: domain, path: /, })这里有个关键细节必须先driver.get(url)一次哪怕这个页面加载失败也没关系只要浏览器地址栏在那个域名下add_cookie才会生效。如果你在空白页about:blank状态下执行添加 cookie会直接报InvalidCookieDomainException这是很多新手反复踩的坑。4.2 localStorage 的同步比 cookie 更隐蔽的登录态我遇到过一个某图像处理 Demo 的控制台登录成功后不写 cookie而是把 token 放到localStorage里前端 axios 拦截器每次请求从localStorage.getItem(token)取。这种场景下你光加 cookie 没用页面依然认为未登录。需要在 Selenium 里执行 JS 来写入token login_response_data[data][token] script flocalStorage.setItem(token, {token}); driver.execute_script(script)注意 token 字符串里的引号如果 token 本身包含特殊字符建议用json.dumps来生成安全的 JS 字符串import json token login_response_data[data][token] safe_token json.dumps(token) driver.execute_script(flocalStorage.setItem(token, {safe_token});)这一步往往是整个流程里最容易被忽略的。cookie 注入后页面还是跳回登录页不是 cookie 坏了是因为前端根本不看 cookie。排查的时候先打开 DevTools 看一下 Application 面板里到底存了什么。4.3 完整流程串起来把上面的模块串成一个可运行的流程大致长这样def login_with_selenium(base_url, username, password): # Step 1: requests 发 POST 拿登录态 session, login_data post_login(base_url, username, password) # Step 2: 启动 Selenium options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) driver webdriver.Chrome(optionsoptions) # Step 3: 打开目标域 driver.get(base_url) # Step 4: 注入 cookie 或 localStorage inject_cookies(driver, session, example.com) token login_data.get(data, {}).get(token) if token: safe_token json.dumps(token) driver.execute_script(flocalStorage.setItem(token, {safe_token});) # Step 5: 刷新页面验证登录态 driver.refresh() return driver这段代码的隐含假设是登录接口的响应里有data.token字段。如果你的接口结构不同对应的解析要改。但整体流程是通用的requests 拿状态 → 启动浏览器 → 注入状态 → 刷新验证。任何登录态的自动化都逃不出这个框架。5. 常见问题排查cookie 注入失败、跨域拦截、请求被前端改写5.1 现象add_cookie 报 InvalidCookieDomainException这是最常见的错误。原因是 WebDriver 的 cookie 校验机制要求domain必须是当前页面 URL 的主机名或父域且不能带端口。比如你页面 URL 是http://localhost:8080那 cookie 的 domain 应该写localhost而不是http://localhost:8080也不能写127.0.0.1。解决方法是先driver.get(page_url)然后从driver.current_url解析出主机名再设置from urllib.parse import urlparse parsed urlparse(driver.current_url) domain parsed.hostname driver.add_cookie({name: sessionid, value: xxx, domain: domain, path: /})5.2 现象POST 请求发出去了但接口返回 403 或跨域错误如果你是在execute_script里用fetch发的 POST跨域是必然要面对的问题。浏览器会先发一个OPTIONS预检请求如果后端没配 CORS 允许头请求直接在预检阶段被拦截页面控制台里能看到报错但 Selenium 本身不会抛异常。解决优先改用 requests 方案requests 不受 CORS 限制。如果非要留在浏览器里发那需要启动 Chrome 时加--disable-web-security参数但这个参数对跨域处理并不彻底而且会引入新的安全风险我不推荐。5.3 现象接口返回 400参数没问题却一直报错这种往往不是参数本身的问题是 Content-Type 不对。前端FormData提交和application/json提交后端接收方式完全不同。你用 requests 的data发表单格式但后端期待的是 JSON就会报参数缺失反过来也是。建议先把接口的请求体用抓包工具看一遍确认 Content-Type 再决定用json还是data。5.4 现象登录成功了但 Selenium 打开页面依然跳转登录页这是典型的 localStorage 没同步原因见 4.2。还有一种可能登录态有效期依赖 cookie 的过期时间而你在 requests 层登录后session 的 cookie 可能设置了HttpOnly这本来是安全行为但 Selenium 无法通过get_cookies()拿到 HttpOnly 的 cookie只能等页面自身携带。解决办法是在 requests 层配置trust_envTrue确保 session 确实拿到了完整的 Set-Cookie 响应头再手动解析。5.5 现象Chrome 被识别为自动化环境接口风控直接拒绝这是 Selenium 老生常谈的问题。POST 提交成功后浏览器侧的后续请求会带上webdriver标记敏感接口会直接拒绝。我一般会在启动参数里加两行options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation])第一行是禁用自动化受控特征第二行是去掉 Chrome 右上角的「Chrome 正受到自动测试软件控制」提示。加了这两行以后大多数基于navigator.webdriver的检测能绕过。但高级风控还会检查navigator.plugins的长度、window.chrome是否存在、鼠标轨迹等这类需要配合 stealth 插件不在本次封装范围内。判断是否需要走这条路看接口报错是 403 还是业务错误码即可。6. 验证封装结果的技巧用响应断言替代肉眼判断封装完成以后很多人验证的方式是看一眼页面有没有跳回登录页这太粗糙了。我一般会让 requests 层和 Selenium 层各做一道断言确保「POST 参数封装正确」这件事有据可依。requests 层断言很简单session, data post_login(base_url, username, password) assert data[code] 0, flogin failed: {data} assert session.cookies.get(sessionid) is not None第一行验证业务状态码第二行验证服务端下发 cookie 了。这两个断言只要有一行过不去后面 Selenium 那边怎么折腾都没用。Selenium 层呢光刷新还不够我习惯加一个「登录态探针」接口driver.get(base_url /api/user/me) body driver.find_element(tag name, pre).text assert username in body如果/api/user/me返回的是 JSON浏览器会以纯文本形式展示直接断言文本里的关键字段。这样比看它跳没跳转更可靠因为有些页面登录失效后不会立刻跳转而是先渲染一个空壳页面然后再异步跳走。如果页面是 SPA 并且接口响应被框架吞掉了那就退回到driver.execute_script(fetch(/api/user/me).then(rr.json()).then(dwindow.__med))然后轮询window.__me是否被赋值。我把这个技巧记成了一条习惯从那以后我做任何 Selenium 相关封装都会强制走一遍「requests 层断言 → 浏览器层探针断言 → 页面跳转确认」三步检查缺一步都不算验证通过。这一步看起来多花几秒实际上能省掉后面大量的定位时间希望帮到你。本文还有配套的精品资源点击获取