使用 Playwright + GitHub Actions 完成一些页面自动化任务的初步探索(下)

书接上回,我们继续自动化反抄袭和反剽窃的行动(详情参见我的上一篇文章《51CTO,一个抄袭剽窃的流氓网站!》)。在上一篇文章中,我们搭建了 MVP 项目,也以测试通过。现在,要着手解决登录问题了。
1. 通过认证的方案汇总
我们所有操作的前提是要获取被访问网站的身份。梳理各种解决方案,可以分为这样几种:
-
第一种:完全自动化模拟登录,但因为大多数网站在登录过程中会设置图片验证环节,使得这种方式基本不太可行。有一些方案会调用第三方的 OCR API 试图攻克这一难题,理论上,对于部分类型的验证应该是有效的,但本文不会深究。
-
第二种:手动登录,将数据保留下来,供下次复用。Playwright 提供的
context.storageState()方法就是为此设计的:await context.storageState({path: 'state.json' }); -
第三种:直接使用本地真实浏览器数据。Playwright 提供的
const context =await chromium.launchPersistentContext('C:\\Users\\你的用户名\\AppData\\Local\\Google\\Chrome\\User Data',{channel: 'chrome',headless: false} ); -
第四种:从浏览器中导出 cookie,然后在 Playwright 中复用
以上几种方法,第一种方法因验证码问题被排除,第二种方法在测试时发现会卡在验证码环节(验证码总是失败),第三种和第四种方法均可行,最后选择的是第四种方法。
2. 导出 Cookie
既然我们选择了复用 cookie 的方案,那么第一步就是要把 cookie 准备好。这部分工作有些繁琐,并不容易实现,可以借助 AI 来辅助处理。首先,使用浏览器的 Cookie 插件将目标站点的 cookie 导出为 json 文件。我使用的工具是 Cookie Manager。但是,要注意的是,使用工具导出的 Cookie 一般都不会被 Playwright 直接接受,因为里面有一些字段不是 Playwright 认可的,并且还有很多项是与身份信息无关的,建议把导出的 json 交给 AI,说明诉求,请 AI 处理一下,就可以得到精简且有效的 cookie 信息了。这里就不方便展示详细的 cookie 信息了。
3. 复用 Cookie 验证是否登录成功
我们把处理好的 cookie 保存为 cookie.json,放到工程根目录下,然后,修改我们在上一篇文章中的 main.ts 文件,使用 await context.addCookies(cookies) 把我们预先存储好的 cookie 文件加载上,然后访问一个只有在登录后才能访问的页面:
import { chromium } from 'playwright';
import { readFile } from 'fs/promises';async function main() {const browser = await chromium.launch({headless: true});const context = await browser.newContext();const cookies = JSON.parse(await readFile('cookie.json', 'utf8'));await context.addCookies(cookies);const page = await context.newPage();// 打开登录页await page.goto('https://a/page/that/only/show/up/after/login');// await page.waitForLoadState("networkidle");// 等待目标内容加载完毕await page.waitForSelector(".span_read");console.log(await page.content());await browser.close();
}main().catch(err => {console.error(err);process.exit(1);
});
注意,这里可能会遇到一个问题,那就是很多系统的页面并不是在请求 url 时就会加载的,而是在页面 load 完毕后,调用 js 发送 AJAX 请求后再渲染出来的,所以,如果只是单纯的使用 page.goto('...') 打开页面后就直接操纵页面的话,大概率是会失败的,如果把页面内容打印出来就会发现,HTML 内容与真实浏览器显示的相差很多。这时候,我们通常需要给页面加载留出一些等待时间,例如使用 await page.waitForLoadState("networkidle"),但这个方法并不保险,很多时候在等待它延时结束后,目标内容可能还未渲染出来,所以,最保险的做法就是等待你需要操作的内容就绪,就如同上述代码所做的那样:await page.waitForSelector(".span_read")。
4. 选定操作目标,模拟点击操作
当页面全部加载完毕后,就可以使用 Playwright 的 API 模拟用户操作了,这是 Playwright 功能最强大的地方,它对于页面的模拟操作简单而有效。首先,是要选定页面上的目标,在这一点,和几乎所有的页面自动化测试框架或爬虫工具一样,Playwright 也支持 CSS selector,这使得我们在选择操作目标时也变得极其容易,以下代码就展示了如何选择页面上所有 class="span_read" 的元素:
const btns = page.locator('.span_read');
而以下代码则是选择第一个元素,然后模拟在它上面执行单击操作:
await btns.nth(0).click();
5. 等待并确认点击是否已成功执行
大多数情况下,在模拟点击之后,我们都需要等待并确认点击是否已成功执行,这么做有很现实的原因:
-
脚本必须确保操作执行成功后才能进行下一步操作,否则页面会报错
-
下一步操作要在上一步执行成功后渲染出的 HTML 元素基础上执行
这里,我们列举两种判断上一个操作执行成功的方法。
5.1 根据 DOM 出现标志性元素判定执行成功
假设我们点击了一个按钮,当它执行成功后,某个 DOM 元素里会填入 “完成” 字样,就像这样: 完成,这是后台执行完毕后在前台的反馈信息,它可以作为这个点击操作执行完毕的完美“标志”,如果我们要等待并确认点击是执行完毕,可以这样写:
await btn.click();
await expect(page.locator('.done')
).toHaveText('完成');
5.2 根据 DOM 标志性元素消失判定执行成功
在另外一些场景里,当我们点击了一个按钮,在它执行成功后,某个 DOM 元素可能会被移除,这也是后台执行完毕后在前台的反馈信息,可以作为这个点击操作执行完毕的完美“标志”。如果我们要等待并确认点击操作执行完毕,可以这样写:
await btn.click();
// 完成后会消失的元素
const flag = page.locator('.done');
await flag.waitFor({state: 'detached',timeout: 30000
});
6. GitHub Actions 工作流的注意事项
GitHub Actions 的工作流使用 yaml 文件描述,必须放置于工程根目录下的 .github\workflows\ 文件夹下。代码提交后,GitHub 能自动识别出目录里的工作流描述文件,并能在 GitHub 项目页面的 Actions 标签中看到。不过,对于免费账号来说,GitHub Actions 工作流的延迟是很大的,很少能精准按照预定执行。晚几分钟甚至几十分钟都是常有的,在测试时需要等待足够长的时间。
7. 小结
这次的尝试比较简单,但 MVP 证明是可行的。不过,个人在开发脚本中有一个比较强烈的感受,那就是:Playwright 脚本的复用性一般都极差,因为它都是针对某一个特定页面编写的。所以,我们也不打算给大家分享具体的脚本实现了,如果没有目标页面的结构做参照,解读脚本的操作无异于讲天书。
本文介绍了一种利用Playwright和GitHub Actions构建反抄袭自动化脚本的MVP方案。作者因长期受剽窃困扰(如51CTO官方抄袭事件),决定开发定期检测脚本。技术方案选用Node.js环境下的Playwright实现浏览器自动化,通过GitHub Actions实现免费托管调度。文章详细记录了项目搭建过程:包括初始化Playwright项目、配置TypeScript运行时、编写基础脚本验证可行性