requests.Session():对验证码的POST请求返回403,下一次请求仍然返回验证码页面
我在调试一个与requests.Session()相关的特定HTTP/会话问题。
验证码答案由人工输入,我的代码提交该值。我并非在尝试自动化captcha解决。
Environment:
- Python 3.11.9
- requests 2.32.5
- beautifulsoup4 4.14.3
- 一个
requests.Session()在验证码页面GET与验证码提交POST之间复用
Instrumented failing flow from the same runtime profile/session:
GET #1:
200 https://hh.ru/account/captcha?state=
<redacted>&captchaState=<redacted>
redirects: []
captcha form:
method=POST
action=https://hh.ru/account/captcha?state=
<redacted>&captchaState=<redacted>
payload keys=['_xsrf', 'captchaText', 'isBot']
cookies before submit (keys):
['_ddg10', '_ddg1', '_ddg8', '_ddg9', '_xsrf', 'crypted_hhuid', 'hhrole', 'hhtoken', 'hhuid', 'regions', 'session_hhuid']
POST submit attempt:
status=403
url=https://hh.ru/account/captcha?state=
<redacted>&captchaState=<redacted>
redirects=[]
headers: Location='', Set-Cookie present, Content-Type='application/json'
body prefix: {"hhcaptcha":{"isBot":true,...
GET #2 in same Session:
status=200
url=https://hh.ru/account/captcha?state=
<redacted>&_xsrf=<redacted>&captchaText=<redacted>&isBot=false
redirects=[]
headers: Location='', Set-Cookie present, Content-Type='text/html; charset=utf-8'
body prefix: <!DOCTYPE html><html class="light-page-root">...
Additional request/response details captured from PreparedRequest.headers:
POST submit request:
method: POST
url: https://hh.ru/account/captcha?state=
<redacted>
User-Agent: ru.hh.android/7.121.11146, Device: 23053RN02Y, Android OS: 11 (...)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: https://hh.ru/account/captcha?state=
<redacted>
Origin: https://hh.ru
Content-Type: application/x-www-form-urlencoded
Cookie names: _ddg8, _ddg10, _ddg9, _ddg1, crypted_hhuid, session_hhuid, _xsrf, hhtoken, hhuid, hhrole, regions
Preceding GET request:
method: GET
url: https://hh.ru/account/captcha?state=
<redacted>
User-Agent: ru.hh.android/7.121.11146, Device: 23053RN02Y, Android OS: 11 (...)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Referer: https://hh.ru/
Cookie names: _ddg8, _ddg10, _ddg9, _ddg1
POST response:
status: 403
Content-Type: application/json
Server: ddos-guard
redirects: []
body prefix: {"hhcaptcha":{"isBot":true,...
Fallback GET response:
status: 200
Content-Type: text/html; charset=utf-8
Server: ddos-guard
redirects: []
body prefix: <!DOCTYPE html><html class="light-page-root">...
Possible causes I am considering:
- 解析后的表单提交方式为POST,但回退时会重试GET
- POST提交可能在验证码状态记入会话前被ddos-guard拒绝
- 浏览器与requests的请求头可能不匹配
验证码答案由人工输入。验证码文本本身是正确的(也是手动输入)。
Minimal reproduction shape:
session = requests.Session()
r1 = session.get(captcha_url, headers=get_headers, allow_redirects=True, timeout=20)
payload = {
"_xsrf": "...",
"captchaText": user_input,
"isBot": "false",
}
s = session.post(form_action, data=payload, headers=post_headers, allow_redirects=True, timeout=20)
current fallback path then retries with GET and sends the same payload as query params
r2 = session.get(form_action, params=payload, headers=get_headers, allow_redirects=True, timeout=20)
Expected: - 验证码提交成功后,同一个Session的下一次请求不应再被解析为验证码页面。
Actual: - POST尝试返回来自ddos-guard的 403 - 重试的GET仍然显示为验证码页面。
在这种情况下,你会首先检查什么:
- 浏览器与requests之间的请求头差异
- POST表单之后的错误GET回退
- 验证码GET与 POST之间的会话/ cookie状态不匹配
Or is there some other common mistake when submitting such forms with requests.Session()?
解决方案
requests.Session() 可能不是这里的问题。一个 Session 确实能在请求之间持久化cookies,而逐请求的参数覆盖会话中的值但不会回写到会话中,因此在同一会话中先GET再在同一会话中POST的基本流程是有效的。
首先要检查的是POST本身,而不是后续的GET。403、Server: ddos-guard、和 {"hhcaptcha":{"isBot":true... 的响应意味着提交在正常应用流程接受验证码之前就被拒绝。在那种情况下,验证码状态永远不会记入会话,因此下一次请求仍会落到验证码页面。
另外,你的回退策略是错的:如果表单显示 method=POST,用相同的载荷再用 GET 重试并不等同。应完全移除那条路径。
我先会检查以下几项:
- 不要自己设置
Cookie头。让requests.Session()来管理cookies。这就是Session jar的作用,手动重放cookies很容易让你们不同步。 - 确保
_xsrf以及其他隐藏输入字段来自当前验证码页面。如果页面重新生成它们而你提交了旧值,即使验证码文本正确,POST也可能失败。 -
尽量让你的POST与浏览器提交尽可能接近:
-
相同的隐藏字段
- 相同的表单编码
- 浏览器是直接提交表单还是通过JS/XHR提交
-
在加载页面到提交表单之间是否出现额外的Cookies
-
你的
Accept头看起来有些可疑:
text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
那最后一段可能该是:
*/*;q=0.8
这可能不是主要问题,但值得修正。
- 如果你在任何地方手动准备请求,请使用 Session.prepare_request(),而不是 Request.prepare()。否则会话状态(尤其是Cookies)可能不会按你预期的方式生效。