Python脚本获取session_id与浏览器实际值不一致问题排查
Odoo站点API自动获取有效session实现方案
问题根因
你之前写的两类自动登录代码都存在明确逻辑错误,和请求库本身没有关系:
- requests版本核心错误:
- 登录地址硬编码为
https://www.odoo.com/web/login,没有使用你自己配置的目标站点website变量,请求发到了Odoo官方站,自然拿不到目标站的有效会话 - CSRF token解析逻辑不可靠,通过字符串切分脚本内容的方式极易取到错误值,导致登录请求被CSRF校验拦截,实际没有进入登录态
- 调用API时手动传入
cookies=r.cookies属于多余错误操作:requests.Session()本身会自动维护全请求链路的所有cookie,手动传入最后一次响应的片段cookie,会丢失登录重定向过程中设置的完整cookie字段,触发会话过期提示
- 登录地址硬编码为
- Playwright版本核心错误:
- 打印cookie的时机是登录页加载完成后、提交登录凭证前,拿到的是未登录的游客会话,和登录后刷新的有效session_id必然不一致
- 点击登录按钮后用固定长等待替代登录完成事件监听,既不可靠也没有在登录完成后重新拉取最新cookie;同时使用WebKit内核和你手动操作的Chrome/Edge环境指纹差异大,容易被反爬规则拦截
- 共性问题:Odoo会校验请求上下文一致性,请求头的
Referer、User-Agent,cookie中的tz、cids、frontend_lang字段缺失或不匹配时,也会判定为无效会话。
稳定实现方案
方案1:requests实现(无浏览器依赖,速度快)
适合没有强JS反爬的Odoo站点,修正所有逻辑错误后的可运行代码如下:
import requests from bs4 import BeautifulSoup from cred import origin, referer, website, login, password, api_url # 全局复用请求头 HEADERS = { 'authority': website, 'accept': '*/*', 'accept-language': 'en-GB,en;q=0.9,en-US;q=0.8', 'origin': origin, 'referer': referer, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33', } # API请求体固定 API_PAYLOAD = { 'id': 22, 'jsonrpc': '2.0', 'method': 'call', 'params': { 'args': [], 'model': 'report.pos.order', 'method': 'read_group', 'kwargs': { 'context': { 'pivot_column_groupby': ['date:month'], 'pivot_row_groupby': ['product_id'], }, 'domain': [], 'fields': ['product_qty:sum', 'average_price:avg', 'price_total:sum'], 'groupby': ['product_id'], 'lazy': False, }, }, } def get_login_session(): session = requests.Session() session.headers.update(HEADERS) # 访问目标站登录页,禁止硬编码其他地址 login_resp = session.get(f'https://{website}/web/login', timeout=10) login_resp.raise_for_status() # 稳定解析CSRF token:直接匹配表单中的hidden input,不用字符串切分 soup = BeautifulSoup(login_resp.text, 'html.parser') csrf_node = soup.find('input', {'name': 'csrf_token'}) if not csrf_node: raise RuntimeError("登录页结构异常,未找到CSRF token") csrf_token = csrf_node['value'] # 提交登录表单,自动跟随重定向 submit_resp = session.post( f'https://{website}/web/login', data={ 'csrf_token': csrf_token, 'login': login, 'password': password, 'redirect': f'https://{website}/web' }, allow_redirects=True, timeout=10 ) submit_resp.raise_for_status() # 校验登录结果 cookie_dict = session.cookies.get_dict() if 'session_id' not in cookie_dict: raise RuntimeError("登录失败,未获取到有效session_id") # 补全Odoo强制校验的cookie字段 for k, v in { 'tz': 'Africa/Lagos', 'frontend_lang': 'en_US', 'cids': '1' }.items(): session.cookies.set(k, v, domain=website) return session if __name__ == '__main__': session = get_login_session() # 直接用session发请求,不需要手动传cookies/headers api_resp = session.post(api_url, json=API_PAYLOAD, timeout=10) print(api_resp.json())
方案2:Playwright实现(绕过前端反爬,稳定性高)
适合开启了Cloudflare、JS指纹校验的站点,和手动浏览器操作行为完全一致,代码如下:
from playwright.sync_api import sync_playwright import requests from cred import origin, referer, website, login, password, api_url HEADERS = { 'authority': website, 'accept': '*/*', 'accept-language': 'en-GB,en;q=0.9,en-US;q=0.8', 'origin': origin, 'referer': referer, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33', } API_PAYLOAD = { 'id': 22, 'jsonrpc': '2.0', 'method': 'call', 'params': { 'args': [], 'model': 'report.pos.order', 'method': 'read_group', 'kwargs': { 'context': { 'pivot_column_groupby': ['date:month'], 'pivot_row_groupby': ['product_id'], }, 'domain': [], 'fields': ['product_qty:sum', 'average_price:avg', 'price_total:sum'], 'groupby': ['product_id'], 'lazy': False, }, }, } def get_valid_cookies(): with sync_playwright() as p: # 用Chromium内核,和手动操作的浏览器环境指纹一致,降低拦截概率 browser = p.chromium.launch(headless=False) context = browser.new_context( user_agent=HEADERS['user-agent'], locale='en-US', timezone_id='Africa/Lagos' ) page = context.new_page() # 访问登录页,等待网络空闲 page.goto(f'https://{website}/web/login', wait_until='networkidle', timeout=30000) # 填写登录凭证 page.fill('#login', login) page.fill('#password', password) # 点击登录后等待跳转完成,替代固定sleep with page.expect_navigation(wait_until='networkidle', timeout=30000): page.click("button[type='submit']") # 登录完成后再拉取全量cookie all_cookies = context.cookies() session_cookie = next((c for c in all_cookies if c['name'] == 'session_id'), None) if not session_cookie: browser.close() raise RuntimeError("登录失败,未获取到有效session_id") # 转成字典格式供requests使用,不需要长期开着浏览器 cookie_dict = {c['name']: c['value'] for c in all_cookies} cookie_dict.update({ 'tz': 'Africa/Lagos', 'frontend_lang': 'en_US', 'cids': '1' }) browser.close() return cookie_dict if __name__ == '__main__': cookies = get_valid_cookies() api_resp = requests.post( api_url, cookies=cookies, headers=HEADERS, json=API_PAYLOAD, timeout=10 ) print(api_resp.json())
注意事项
- 拿到的session_id默认有效期为几小时到几天,不需要每次调用API都重新登录,可以缓存到本地复用,过期后再重新获取即可
- 如果站点开启了双因素认证、验证码校验,需要在Playwright流程中补充对应输入步骤,否则无法拿到登录后的有效会话
- 不要在使用session时频繁切换IP、修改请求头,否则Odoo会主动失效当前会话,触发过期提示
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

