You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python脚本获取session_id与浏览器实际值不一致问题排查

Odoo站点API自动获取有效session实现方案

问题根因

你之前写的两类自动登录代码都存在明确逻辑错误,和请求库本身没有关系:

  • requests版本核心错误:
    1. 登录地址硬编码为https://www.odoo.com/web/login,没有使用你自己配置的目标站点website变量,请求发到了Odoo官方站,自然拿不到目标站的有效会话
    2. CSRF token解析逻辑不可靠,通过字符串切分脚本内容的方式极易取到错误值,导致登录请求被CSRF校验拦截,实际没有进入登录态
    3. 调用API时手动传入cookies=r.cookies属于多余错误操作:requests.Session()本身会自动维护全请求链路的所有cookie,手动传入最后一次响应的片段cookie,会丢失登录重定向过程中设置的完整cookie字段,触发会话过期提示
  • Playwright版本核心错误:
    1. 打印cookie的时机是登录页加载完成后、提交登录凭证前,拿到的是未登录的游客会话,和登录后刷新的有效session_id必然不一致
    2. 点击登录按钮后用固定长等待替代登录完成事件监听,既不可靠也没有在登录完成后重新拉取最新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 11:06:29