PYTHON DEMO

Python 桌面扫码登录 Demo

用 PySide6 + requests 直接完成整条微信扫码登录链路,不内嵌 QtWebEngine,也不依赖系统浏览器。 下载源码、填入项目凭证即可运行;扫码确认后自动切换到用户资料页。

无需浏览器内核 PySide6 完整可运行源码 Python 服务端示例 Markdown 版(供 AI 读取)

1. 下载与运行

Demo 是一个单文件桌面程序(约 700 行),把「申请二维码参数 → 取 uuid → 长轮询 → 换 ticket → 拉取用户资料」 全部放在客户端完成,适合桌面软件、内网工具或需要独立登录窗口的场景。

下载 wx_login_demo.py 下载 requirements-demo.txt

运行环境:Python 3.10+,Windows / macOS / Linux 均可。 首次运行前先执行 pip install -r requirements-demo.txt,需要安装 PySide6 与 requests。
PowerShell
# 1. 安装依赖
pip install -r requirements-demo.txt

# 2. 填入「我的项目」里的凭证与白名单回调地址
$env:WX_CLIENT_ID     = "ak_你的client_id"
$env:WX_CLIENT_SECRET = "sk_你的client_secret"
$env:WX_REDIRECT_URL  = "https://你的站点/callback"

# 3. 启动
python wx_login_demo.py
macOS / Linux:把 set 换成 export 即可,例如 export WX_CLIENT_ID=ak_你的client_id。

2. 配置项

环境变量必填说明
WX_CLIENT_ID 是 项目 client_id,在「我的项目」中查看。
WX_CLIENT_SECRET 是 项目 client_secret,仅用于最后一步换取用户资料。
WX_REDIRECT_URL 是 必须属于项目域名白名单。它只作为 /oauth/qr 的参数,Demo 不会真正打开它。
WX_GATEWAY 否 登录网关地址,默认 https://login.fps5.cn。
关于 redirect_url: 网关会按项目的域名白名单校验这个地址。Demo 拿到微信 code 后直接调用网关的 /oauth/callback,并关闭自动跳转,只从 302 的 Location 里取一次性 ticket, 因此不需要在 redirect_url 对应的站点上写任何 PHP / Python 代码。

3. 登录链路

桌面端没有网页上下文,所以每一步都由 Python 直接发起:

01
申请二维码参数

GET https://login.fps5.cn/oauth/qr,传 client_id、redirect_url 和本地生成的 state,拿到 appid / scope / 网关 state。

02
解析微信 uuid

请求 open.weixin.qq.com/connect/qrconnect,从返回的 HTML 中提取 uuid,不加载浏览器。

03
显示二维码

下载 /connect/qrcode/{uuid} 图片,用 QPixmap 渲染到窗口。

04
长轮询扫码状态

lp.open.weixin.qq.com/connect/l/qrconnect,408 等待、404 已扫码、405 确认并返回 wx_code。

05
向网关换取 ticket

把 wx_code 与网关 state 交给 /oauth/callback,不跟随 302,从 Location 中取 ticket。

06
换取用户资料

POST /api/user/info,用 client_id + client_secret + ticket 拿到 openid、昵称、头像并展示。

4. 扫码状态码

长轮询返回的 wx_errcode 是整条链路最容易踩坑的地方:

状态码含义正确处理
408等待扫码继续轮询
404已扫码,等待手机确认继续轮询,提示用户点「允许」
405已确认授权从响应中取 wx_code,进入换票
403用户在手机取消提示后继续轮询
402二维码过期停止轮询,提示刷新二维码
500微信服务异常停止轮询,提示稍后重试
404 不是二维码过期。 如果把 404 和 402 一起当作过期处理,用户刚扫完码轮询就会停止;等他在手机上点「允许」时, 返回的 wx_code 已经没有接收方,界面就会一直停在原地、也没有任何提示。

5. 关键代码

完整实现见下载文件,核心逻辑只有三段。

5.1 申请参数并解析 uuid

Python
payload = requests.get(
    f"{GATEWAY}/oauth/qr",
    params={
        "client_id": CLIENT_ID,
        "redirect_url": REDIRECT_URL,
        "state": biz_state,          # 本地随机串,回跳时校验
    },
    timeout=15,
).json()["data"]

qr_url = "https://open.weixin.qq.com/connect/qrconnect?" + urlencode({
    "appid": payload["appid"],
    "redirect_uri": payload["redirect_uri"],
    "response_type": "code",
    "scope": payload.get("scope") or "snsapi_login",
    "state": payload["state"],       # 网关内部 state,原样回传
}) + "#wechat_redirect"

html = requests.get(qr_url, timeout=20).text
uuid = re.search(r"l/qrconnect\?uuid=([A-Za-z0-9_-]+)", html).group(1)

5.2 长轮询扫码状态

Python
last = ""
while True:
    params = {"uuid": uuid}
    if last:
        params["last"] = last

    body = requests.get(
        "https://lp.open.weixin.qq.com/connect/l/qrconnect",
        params=params,
        timeout=(10, 40),                # 微信会挂起约 15 秒
    ).text

    errcode = int(re.search(r"wx_errcode\s*=\s*(\d+)", body).group(1))
    code = re.search(r"wx_code\s*=\s*'([^']*)'", body).group(1)

    if code:                             # 405 确认,拿到授权码
        break
    if errcode == 404:                   # 已扫码,继续等手机确认
        show_tip("扫描成功,请在手机上点击「允许」")
    elif errcode == 402:                 # 真正过期
        raise RuntimeError("二维码已过期,请刷新")
    last = str(errcode)

5.3 换 ticket 并拉取用户资料

Python
# 关键:不跟随 302,只读 Location
resp = requests.get(
    f"{GATEWAY}/oauth/callback",
    params={"code": wx_code, "state": gateway_state},
    allow_redirects=False,
    timeout=20,
)

location = resp.headers["Location"]      # https://你的站点/callback?code=TICKET&state=...
query = parse_qs(urlsplit(location).query)
ticket = query["code"][0]
if query.get("state", [""])[0] != biz_state:
    raise RuntimeError("state 校验失败")

user = requests.post(
    f"{GATEWAY}/api/user/info",
    data={
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "code": ticket,
    },
    timeout=20,
).json()["data"]

print(user["openid"], user["nickname"], user["avatar"])

6. 常见问题

启动后提示「请先设置环境变量」

说明 client_id、client_secret 或 redirect_url 没读到。确认环境变量是在同一个终端里设置的,再执行 python wx_login_demo.py。

扫码后一直不跳转

先看窗口提示:显示「扫描成功,请在手机上点击「允许」」说明轮询正常,等手机确认即可。若手机已点允许仍无反应,查看日志 %TEMP%\wx_login_demo.log(macOS / Linux 为系统临时目录),确认轮询是否收到 405、网关回调是否返回 302。

提示 state 校验失败

通常是重复使用了旧二维码,或回调地址被其他页面接管。点击「刷新二维码」重新发起即可;Demo 每次刷新都会生成新的 state。

提示域名不在白名单

WX_REDIRECT_URL 的域名必须与项目里配置的域名一致。修改项目域名或回调地址后需要重新审核。

能打包成 exe 吗

可以,用 PyInstaller 打包即可。但 client_secret 会随程序分发,建议正式产品改为「客户端 → 自己的后端 → 网关」,由后端持有 secret 并完成换票。

相关文档: 服务端接入与更多语言示例见 API 文档; 项目创建、域名白名单与审核流程见 使用说明。