2024 桐庐半程马拉松
00:00:00
时间
0.00
距离(公里)
--:--
配速
--
步频
--
心率 (bpm)
--
配速
步频
|
share-image
ESC

使用 Playwright + 阿里云 OCR 自动化抓取运动数据并在 Hexo 展示

最近我需要将某个运动网站(RQ Run)上的个人训练记录同步到我的 Hexo 博客中。由于该网站需要登录才能查看详细数据,并且登录过程包含验证码,因此我使用 Node.js 版的 Playwright 编写了一个自动化脚本,结合阿里云 OCR 服务识别验证码,实现了数据的自动抓取,并通过 EJS 模板在博客上进行展示。

本文将详细讲解整个实现过程。

1. 技术选型

  • Playwright: 微软开源的自动化测试工具,支持 Chromium、Firefox 和 WebKit。相比 Puppeteer,它更现代、更稳定,且支持多种浏览器。
  • 阿里云 OCR: 用于识别登录时的图片验证码。
  • Hexo: 静态博客框架,利用其 _data 数据文件功能存储抓取到的 JSON 数据。

2. 环境准备

首先初始化项目并安装必要的依赖:

npm install playwright @alicloud/ocr-api20210707 @alicloud/openapi-client @alicloud/tea-util @alicloud/darabonba-stream
npx playwright install chromium

3. 编写抓取脚本 (rq_scraper.js)

脚本的核心逻辑分为三步:模拟登录识别验证码抓取数据

3.1 初始化浏览器

为了避免被网站检测为机器人,我们需要配置 UserAgent 和 HTTP Headers。此外,Playwright 的 headless: true 模式可以让脚本在后台静默运行。

const browser = await chromium.launch({
headless: true // 启用无头模式
});

const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
extraHTTPHeaders: {
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-origin',
// 注意:不要设置 'accept': 'application/json',否则普通页面访问会报错
}
});

3.2 验证码识别与自动重试

登录页面包含一个点击可刷新的验证码图片。我们先截图保存验证码,然后调用阿里云 OCR 接口进行识别。如果识别出的验证码不是4位数,或者登录提交后页面没有跳转(说明验证码错误),脚本会自动刷新验证码并重试。

// 阿里云 OCR 识别函数
async function recognizeCaptchaAliyun(filePath) {
// ... 配置阿里云 Client ...
const bodyStream = Stream.default.readFromFilePath(filePath);
const recognizeAllTextRequest = new ocr_api20210707.RecognizeAllTextRequest({
body: bodyStream,
type: 'Advanced'
});
// ... 调用接口并返回结果 ...
}

// 登录重试逻辑
let loginSuccess = false;
let attempts = 0;
while (!loginSuccess && attempts < 5) {
// 1. 截图验证码
await captchaElement.screenshot({ path: 'captcha.png' });

// 2. OCR 识别
let code = await recognizeCaptchaAliyun('captcha.png');

// 3. 填入验证码并提交
if (code.length === 4) {
await page.fill('#login_code', code);
await loginBtn.click();

// 4. 等待跳转,验证是否登录成功
try {
await page.waitForNavigation({ timeout: 10000 });
loginSuccess = true;
} catch (e) {
console.log('登录超时或失败,重试...');
}
} else {
// 识别长度不对,刷新验证码
await captchaElement.click();
}
}

3.3 数据抓取 (API 拦截 vs 页面解析)

最初我尝试直接解析 HTML 表格,但发现效率较低且容易受页面结构变化影响。经过分析网络请求,发现网站通过 API 获取 JSON 数据。于是我改为直接在浏览器上下文中调用该 API。

这里使用了 page.evaluate 在浏览器内部执行 fetch 请求,这样可以直接利用浏览器已登录的 Cookie 状态,无需手动提取 Cookie。

const apiData = await page.evaluate(async () => {
const allData = [];
let page = 1;
const limit = 50;
let totalPages = 1;

while (page <= totalPages) {
const response = await fetch(`https://www.rq.run/Dc/Api?_=User/Record/get_list&page=${page}&limit=${limit}...`);
const json = await response.json();

if (page === 1) {
// 根据总数计算需要翻多少页
totalPages = Math.ceil(json.data.total / limit);
}

allData.push(...json.data.list);
page++;
await new Promise(r => setTimeout(r, 200)); // 避免请求过快
}
return allData;
});

3.4 数据保存

抓取到的数据被保存到 Hexo 的数据目录 source/_data/rq_data.json 中。Hexo 会自动将该文件加载为 site.data.rq_data,供模板使用。

4. 前端展示 (Hexo + EJS)

在 Hexo 的布局文件 (layout/running.ejs) 中,我增加了一个“运动记录”的选项卡。由于数据量较大(可能几百条),一次性渲染会很长,因此我在前端实现了分页功能。

4.1 数据预处理

在 EJS 中,我先对数据进行了清洗和排序(按时间倒序)。

<%
var rqData = site.data.rq_data || [];
// 按时间倒序排列
rqData.sort(function(a, b) {
return new Date(b.training_at).getTime() - new Date(a.training_at).getTime();
});
%>

4.2 客户端分页渲染

为了更好的用户体验,我将数据通过 data- 属性注入到 HTML 中,并使用 JavaScript 进行分页渲染。

// 前端 JS 逻辑示例
const records = JSON.parse(document.getElementById('raw-data').textContent);
const pageSize = 10;

function renderPage(page) {
const start = (page - 1) * pageSize;
const end = start + pageSize;
const pageData = records.slice(start, end);

// 生成 HTML 并插入容器
const html = pageData.map(item => `
<div class="record-card">
<div class="date">${item.training_at}</div>
<div class="distance">${item.distance} km</div>
<div class="pace">${item.avg_speed}</div>
</div>
`).join('');

container.innerHTML = html;
}

5. 总结

通过 Playwright 和阿里云 OCR,我们成功攻克了带验证码的模拟登录难题;通过直接调用后端 API,我们提高了数据抓取的效率和稳定性。最终结合 Hexo 的数据驱动能力,实现了一个自动化的个人运动数据展示页面。

如果你也有类似的需求,不妨尝试一下这个方案!

文章作者:阿文
文章链接: https://www.awen.me/post/9224b480.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客