【原创】基于 Playwright + ONNX 实现若依自动登录并识别算式验证码
·
很多前端后台管理系统(例如 RuoYi-Vue、若依框架)在登录页面都带有简单的算式验证码,
例如:
3+5=?、7-2=?、9×1=?
虽然这些验证码对人类来说简单,但在自动化脚本中却需要额外处理。
本文介绍如何使用 ONNX 深度学习模型 来识别验证码图片、
自动计算结果,并借助 Playwright 异步浏览器自动化框架 完成登录与数据提取。
一、技术方案概述
本文方案分为三大模块:
| 模块 | 功能 | 技术栈 |
|---|---|---|
| 浏览器自动化 | 打开网站、填写账号密码、提交登录、抓取数据 | Playwright (Python) |
| 验证码识别 | 识别验证码图像中的算式 | ONNXRuntime + Torch |
| 结果处理 | 清洗符号、计算结果、自动填充 | 正则表达式 + Python eval |
整体流程如下:
-
使用 Playwright 打开登录页面;
-
截图验证码图片;
-
使用别人己经训练好的
mathcode.onnx模型进行推理; -
将识别出的字符串转为数学算式;
-
自动计算出结果;
-
填入验证码输入框;
-
提交登录;
-
登录后进入后台页面抓取数据。
二、环境准备
1. 安装依赖 【CPU版】
pip install playwright torch torchvision onnxruntime pillow numpy
2. 准备模型与字符集
-
mathcode.onnx:用己经训练好验证码识别模型; -
下载地址https://github.com/fupinglee/CalculateCaptcha_Recognition/blob/main/mathcode.onnx
三、核心代码实现
以下是完整 Python 源码,支持 Linux、Windows、macOS 全平台:
import asyncio
import json
import platform
import time
import re
import torch
import onnxruntime
import numpy as np
from PIL import Image
import torchvision.transforms as transforms
from playwright.async_api import async_playwright
import common # 字符映射模块
# ---------- 公共函数 ----------
def detect_os():
"""自动检测 Chrome 浏览器路径"""
if platform.system() == 'Windows':
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
elif platform.system() == 'Darwin': # macOS
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
else:
return "/usr/bin/google-chrome" # Linux 默认路径
def vec2Text(vec):
"""将预测结果向量转为字符串"""
vec = torch.argmax(vec, dim=1)
text = ''.join([common.captcha_array[i] for i in vec])
return text
def to_numpy(tensor):
"""torch.Tensor 转 numpy"""
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
def clean_expr(expr: str) -> str:
"""清洗表达式,统一符号"""
expr = expr.replace("?", "?").replace("=", "=")
expr = expr.replace("≈", "=").replace("~", "-")
expr = expr.replace("×", "*").replace("x", "*").replace("X", "*")
expr = re.sub(r"[^0-9+\-*/=?]", "", expr)
return expr
def calc_expr(expr: str) -> str:
"""计算验证码算式,如 5*3=? → 15"""
expr = clean_expr(expr)
expr = expr.replace("?", "").replace("=", "")
try:
return str(eval(expr))
except Exception:
return expr
# ---------- ONNX 验证码识别 ----------
def ocr_onnx_predict(img_path: str, model_path="mathcode.onnx") -> str:
"""使用 ONNX 模型识别算式验证码并返回结果"""
try:
img = Image.open(img_path)
trans = transforms.Compose([
transforms.Resize((60, 160)),
transforms.ToTensor()
])
img_tensor = trans(img).reshape(1, 3, 60, 160)
ort_session = onnxruntime.InferenceSession(model_path)
input_name = ort_session.get_inputs()[0].name
onnx_out = ort_session.run(None, {input_name: to_numpy(img_tensor)})
onnx_out = torch.tensor(np.array(onnx_out)).view(-1, len(common.captcha_array))
text = vec2Text(onnx_out)
print("🤖 OCR识别结果:", text)
solved = calc_expr(text)
print("🧮 计算结果:", solved)
return solved
except Exception as e:
print("❌ OCR识别失败:", e)
return ""
# ---------- 主逻辑 ----------
async def main():
chromium_path = detect_os()
print(f"✅ 检测到浏览器路径: {chromium_path}")
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=False,
executable_path=chromium_path,
args=["--start-maximized"]
)
context = await browser.new_context()
page = await context.new_page()
await page.goto("https://vue.ruoyi.vip/login")
# 选择器定义
username_selector = "input[placeholder='账号']"
password_selector = "input[placeholder='密码']"
captcha_selector = "input[placeholder='验证码']"
captcha_img_selector = ".login-code-img"
submit_button_selector = ".el-button.el-button--primary"
await page.wait_for_selector(captcha_img_selector)
captcha_path = f"captcha_{int(time.time())}.png"
captcha_element = await page.query_selector(captcha_img_selector)
await captcha_element.screenshot(path=captcha_path)
print(f"📸 验证码截图已保存: {captcha_path}")
captcha_text = ocr_onnx_predict(captcha_path)
await page.fill(username_selector, "admin")
await page.fill(password_selector, "admin123")
await page.fill(captcha_selector, captcha_text)
try:
async with page.expect_navigation(wait_until="networkidle", timeout=8000):
await page.click(submit_button_selector)
except Exception:
print("⚠️ 登录可能失败,请检查验证码或模型准确率。")
await page.goto("https://vue.ruoyi.vip/system/dept", wait_until="networkidle")
await page.wait_for_selector(".el-table__body")
table_data = await page.evaluate("""
() => {
const rows = Array.from(document.querySelectorAll('.el-table__body-wrapper tbody tr'));
return rows.map(row => {
const cells = Array.from(row.querySelectorAll('td .cell'));
return cells.map(cell => cell.innerText.trim());
});
}
""")
print("📊 表格数据:")
for row in table_data:
print(row)
with open("table_data.json", "w", encoding="utf-8") as f:
json.dump(table_data, f, ensure_ascii=False, indent=2)
print("✅ 已保存到 table_data.json")
if __name__ == "__main__":
asyncio.run(main())
四、运行效果
实际运行时,输出类似如下:
✅ 检测到浏览器路径: /usr/bin/google-chrome
📸 验证码截图已保存: captcha_1762121839.png
🤖 OCR识别结果: 4-0=?
🧮 计算结果: 4
📊 表格数据:
['若依科技', '0', '正常', '2025-05-26 10:07:40', '修改新增']
['深圳总公司', '1', '正常', '2025-05-26 10:07:40', '修改新增删除']
['研发部门', '1', '正常', '2025-05-26 10:07:41', '修改新增删除']
['市场部门', '2', '正常', '2025-05-26 10:07:42', '修改新增删除']
['测试部门', '3', '正常', '2025-05-26 10:07:42', '修改新增删除']
['财务部门', '4', '正常', '2025-05-26 10:07:42', '修改新增删除']
['运维部门', '5', '正常', '2025-05-26 10:07:43', '修改新增删除']
['长沙分公司', '2', '正常', '2025-05-26 10:07:41', '修改新增删除']
['市场部门', '1', '正常', '2025-05-26 10:07:43', '修改新增删除']
['财务部门', '2', '正常', '2025-05-26 10:07:43', '修改新增删除']
✅ 已保存到 table_data.json
五、总结与优化方向
通过本文的实践,我们成功实现了:
-
✅ 自动识别登录验证码;
-
✅ 自动填写与计算;
-
✅ 自动登录与抓取数据;
-
✅ 全流程无人值守。
📘 结语
本文展示了一个从深度学习模型推理到浏览器自动化结合的完整案例,
让验证码不再成为阻碍自动化测试的难题。
在企业测试、运维监控、自动化报表等场景中都能发挥作用。
六、合规与伦理说明
- 本文爬虫仅用于个人学习、求职调研,不得用于商业用途或批量爬取网站数据,避免侵犯网站版权和用户隐私;
- 爬取时严格控制请求频率,避免给网站服务器造成压力,遵守《网络安全法》和目标网站的用户协议;
- 若网站明确禁止爬虫(如
robots.txt文件标注),请停止爬取行为。
更多推荐




所有评论(0)