Playwright 登录后将 Cookie 传给 requests,为何仍返回 403?
可以将 Playwright 的 Cookie 转移到 requests.Session(),但复制 Cookie 并不能保证 API 接受请求。现有代码无法证明服务器拒绝的是 session;CSRF 校验、接口权限或其他请求条件也可能导致 403 Forbidden。
建议先停止清理 Cookie 值,保留 Cookie 的作用域,再对比浏览器中同一个 API 的成功请求。登录后跳转到首页,并不能说明该 session 有权访问目标 API。
原代码中需要检查的地方
原示例将 Cookie 转成 {name: value} 字典,会丢失 domain、path、secure 和过期时间。作用域不同的同名 Cookie 还会互相覆盖。正确导入 Cookie 后,requests.Session() 可以解决这部分问题,但不会自动补齐其他认证条件。
原代码还假设 csrf-token 请求头可以直接使用 CSRF_COOKIE 的值。这个对应关系由网站决定:有的网站需要解码后的 Cookie 值,有的网站从页面或其他响应中提供 token,也有的网站同时检查 Origin、Referer 等信息。设置了同名请求头,不代表它符合服务器的校验规则。
先确认浏览器中的 API 请求是否成功
在 Playwright 登录后的页面中,通过浏览器开发者工具的 Network 面板查看目标 API:
- 确认请求的 URL、HTTP 方法、查询参数和请求体与 Python 请求一致。
- 确认这个 API 在浏览器中确实返回成功,而不只是首页能打开。
- 查看实际发送的 Cookie、
csrf-token、Authorization、Origin和Referer,确认哪些字段参与认证。 - 检查 API 是否位于另一个子域名,以及登录后是否还需要访问页面来初始化相关 token。
如果浏览器请求同样返回 403,应先检查账号权限或接口要求。此时仅调整 Cookie 导出方式通常没有帮助。
保留 Cookie 元数据,再导入 requests
下面使用同步 Playwright API,假设 context 和 page 已存在且登录完成。示例仍沿用原问题中“CSRF header 直接取自 Cookie”的假设,实际使用前需要通过成功请求确认这一对应关系。
import requests
api_url = "https://example.com/api/data"
# 只取得适用于目标 URL 的 Cookie,保留原始值和作用域。
cookies = context.cookies([api_url])
session = requests.Session()
for cookie in cookies:
options = {
"domain": cookie["domain"],
"path": cookie["path"],
"secure": cookie["secure"],
}
expires = cookie.get("expires", -1)
if expires > 0:
options["expires"] = int(expires)
session.cookies.set(
cookie["name"],
cookie["value"],
**options,
)
headers = {
"accept": "application/json",
"user-agent": page.evaluate("navigator.userAgent"),
}
# 仅在确认网站使用这一对应关系后采用此逻辑。
csrf_cookies = [c for c in cookies if c["name"] == "CSRF_COOKIE"]
if len(csrf_cookies) > 1:
raise ValueError("存在多个同名 CSRF Cookie,需要确认服务器使用哪一个")
if csrf_cookies:
headers["csrf-token"] = csrf_cookies[0]["value"]
response = session.get(
api_url,
headers=headers,
timeout=30,
allow_redirects=False,
)
print("status:", response.status_code)
print("content-type:", response.headers.get("content-type"))
print("location:", response.headers.get("location"))
使用页面实际的 User-Agent 可以减少一项请求差异,但相同的 User-Agent 不能保证服务器将两个客户端视为相同环境。
示例关闭了重定向,以便观察原始响应。如果接口先跳转到登录页,自动跟随重定向可能会掩盖这一线索。查看响应正文也有帮助,例如可以区分 CSRF 错误、权限错误和访问验证页面。不要在公开日志中输出完整 Cookie 或 token。
不要根据显示效果删除引号
"\"value\"" 可能是字符串经过 JSON 编码后的显示形式,也可能表示 Cookie 本身确实包含引号,不能仅凭展示形式判断。
可以在本地用 repr(cookie["value"]) 检查字符串内容,再与浏览器实际发送的请求比较。不要公开检查结果中的凭据。
原问题中的 clean() 会执行 strip()、json.loads() 和引号删除。这些操作可能改变服务器需要的原始值,导致签名或 token 比较失败。应先原样转移 Cookie 值,只有网站明确要求转换时,才处理对应字段。
还要区分两个位置:即使 CSRF header 需要去掉外层引号,也不意味着发送到 Cookie 请求头中的值需要同步修改。它们可能有不同的格式要求。另外,所贴 clean() 片段缺少 Python 缩进,直接运行会先出现语法问题。
可以用 context.request 缩小排查范围
如果只需要在登录后调用接口,可以先尝试 context.request。它与对应的 BrowserContext 共享 Cookie 存储,可以省去手动导出和导入的步骤。
api_url = "https://example.com/api/data"
# headers 按浏览器成功请求设置,包含接口实际要求的 CSRF 等字段。
api_response = context.request.get(
api_url,
headers=headers,
timeout=30000,
)
print(api_response.status)
这里 Playwright 的 timeout 单位是毫秒,前面 requests 的 timeout 单位是秒。
context.request 不会自动补齐页面请求中的所有 header 或动态 token,也不等同于页面内发出的请求。如果它成功而 requests 失败,可以继续检查手动迁移 Cookie 的过程或客户端差异;如果两者都失败,应重新核对接口要求和浏览器中的成功请求。
仍然返回 403 时如何判断
完成上述对比后,可以根据响应信息定位原因:
- 响应明确提示 CSRF 错误:核对 token 的来源、编码、有效期,以及它是否与当前 session 配套。
- 响应要求重新登录:核对目标域名对应的 session Cookie、过期时间和登录初始化流程。
- 响应提示权限不足:确认账号是否有权调用该接口,复制 Cookie 无法增加权限。
- 响应是访问验证页面:可能存在客户端环境校验,普通 requests 未必适用,应考虑网站提供的正式 API 或受支持的访问方式。
现有材料不足以确定具体原因。判断时应重点参考同一 API 在浏览器中的成功请求,以及服务器返回的具体错误信息。
备注:内容仅供参考。
