Files
datie-bom/browser_login/README.md

6.7 KiB
Raw Blame History

ERP 自动化数据采集 — 技术说明

目标系统:腾一工业互联网平台
框架DrissionPagePython
更新2026-04-16


一、ERP 接口如何鉴权

1.1 基础机制

该 ERP 基于 ASP.NET Boilerplate (ABP) 框架,接口鉴权通过以下两种凭据组合完成:

凭据 类型 说明
auth_token Cookie / JWT 登录后由服务端颁发携带用户ID、角色、租户ID等声明
ASP.NET_SessionId Cookie 服务端会话标识
Abp.TenantId / Abp.TenantCode Cookie 多租户标识
x-sig 请求Header 客户端动态生成的请求签名,每次请求不同

1.2 auth_token 解析JWT

登录成功后的 JWT 结构Base64 解码 Payload

{
  "nameid": "103",
  "unique_name": ["DTCS", "DTCS"],
  "role": "0099",
  "http://www.aspnetboilerplate.com/identity/claims/tenantId": "18",
  "iss": "gdtykj",
  "aud": "TfTechApi",
  "exp": 1776330877,
  "nbf": 1776323677
}

该 Token 有效期约 2 小时exp - nbf = 7200s)。

1.3 x-sig 签名

每次 POST 请求都携带一个动态值,例:

x-sig: QpE4yJ/TNqPJvIkYjww5QM.w/cn1wtipR2zQZSWlrF9L8

该签名由浏览器端 JavaScript 在运行时生成,算法未公开,无法离线复现。
⚠️ 这意味着无法绕过浏览器直接用 requests 调用 API

1.4 典型 API 请求示例BOM 查询)

POST https://yunmes.tftykj.cn/api/services/TfTechApi/Material/MaterialBom_SearchList_Proxy
Content-Type: application/x-www-form-urlencoded
x-sig: <动态签名>
Cookie: auth_token=<JWT>; Abp.TenantId=18; ...

page=1&rows=50&MaterialCode=&MaterialName=&...

二、我们的程序如何绕过 x-sig 获取数据

核心思路:让真实浏览器替我们发请求

由于 x-sig 无法离线生成,我们不直接调用 API,而是:

Python 控制真实 Chrome 浏览器
    → 浏览器完成登录cookies 自动注入)
    → 浏览器跳转到目标页面(页面自动发起 API 请求)
    → Python 拦截浏览器网络层的原始响应
    → 提取响应 JSON保存到本地
┌──────────────────────────────────────────┐
│              Python 程序                  │
│                                          │
│  DrissionPage.listen.start()  ←──────┐  │
│         ↓                             │  │
│  page.refresh()  →  Chrome 发出请求  │  │
│                         ↓             │  │
│                    服务器响应(含数据)│  │
│                         ↓             │  │
│  packet = listen.wait()  ────────────┘  │
│         ↓                                │
│  packet.response.body  →  JSON 数据     │
└──────────────────────────────────────────┘

Vue 表单填写的特殊处理

该 ERP 前端使用 Vue.js 双向绑定。普通的 clear() + input() 会留下残值导致登录失败。
我们使用 JS 原生 setter 强制清空后再模拟键盘输入:

# 用 JS 原生 setter 清空(触发 Vue 响应式)
page.run_js("""
    var el = arguments[0];
    Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype, 'value'
    ).set.call(el, '');
    el.dispatchEvent(new Event('input', {bubbles: true}));
""", ele)
time.sleep(0.05)   # 等 Vue 处理 clear 事件
ele.input(value)   # 模拟键盘输入,触发 keydown/change

三、使用方式

3.1 环境准备

# 安装依赖
pip install DrissionPage python-dotenv

# 配置账号(自动模式使用,手动模式不需要)
cp .env.example .env   # 或直接编辑 .env

.env 文件内容:

ERP_URL=https://yunmes.tftykj.cn/#
ERP_TENANT=gddtsk
ERP_USERNAME=DTCS
ERP_PASSWORD=123456

⚠️ .env 已加入 .gitignore,不会被提交到代码仓库。


3.2 自动模式(适合生产部署)

.env 读取账号自动完成登录,无需人工干预。

python bom_query.py

执行流程:

  1. 打开 Chrome访问 ERP 登录页
  2. 自动填写租户代码 / 账号 / 密码
  3. 点击登录按钮,等待跳转(约 2s
  4. 导航到 BOM 表查询页,触发数据加载
  5. 拦截 API 响应,保存 JSON 到 output/ 目录

典型日志:

[15:58:21]   BOM 查询启动 [自动登录]
[15:58:23]   填写登录信息...
[15:58:25] ✅ 登录成功2s
[15:58:43] ✅ 拦截成功 → HTTP 200
[15:58:43]   本页记录: 50 条 | 总计: 1494 条
[15:58:43] ✅ 已保存: output/bom_page1_20260416_155843.json

3.3 手动模式(适合涉密账号,不留存密码)

程序只负责打开浏览器,由人工在浏览器中完成登录,程序检测到登录成功后自动继续执行数据采集。

python bom_query.py --manual

执行流程:

  1. 打开 Chrome访问 ERP 登录页(字段为空,不填写任何内容)
  2. 终端提示:请在浏览器中手动输入账号并登录
  3. 用户在浏览器中自行输入账号、密码并点击登录
  4. 程序检测到登录成功后,自动导航到 BOM 页面并采集数据
  5. 采集完成,保存 JSON 到 output/ 目录

特点:

  • 密码不出现在任何配置文件或代码中
  • 程序等待最长 120 秒供人工操作
  • 每 10 秒打印一次进度提示

3.4 输出文件

数据保存在 output/ 目录,文件名格式:

output/bom_page1_YYYYMMDD_HHMMSS.json

JSON 结构ABP 标准响应):

{
  "result": {
    "totalCount": 1494,
    "items": [
      {
        "materialCode": "1ADAA00001",
        "materialName": "DA400大锥度中走丝",
        ...
      }
    ]
  },
  "success": true
}

四、项目文件结构

browser_login/
├── login.py        # 核心登录模块(自动 / 手动 / 共用等待逻辑)
├── bom_query.py    # BOM 数据采集(支持 --manual 参数)
├── .env            # 账号配置(自动模式,不提交 git
├── .gitignore      # 忽略 .env 等敏感文件
├── output/         # 采集结果 JSON 文件
└── README.md       # 本文档

五、已知限制

限制 说明
必须使用真实浏览器 x-sig 由 JS 动态生成,无法离线复现
每次只获取 50 条 当前固定取第 1 页,翻页逻辑待开发
JWT 有效期 ~2h 长时间运行需重新登录
表格等待逻辑待优化 当前用刷新触发请求,偶发 WARN 提示