Nodejs使用puppeteer抓取iOS商店后台APP评论
2021/4/10 18:17:18
本文主要是介绍Nodejs使用puppeteer抓取iOS商店后台APP评论,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
需求
就是忽然有一天被拉进群,然后说要抓评论,还说后续要搞自动回复。因为苹果没有提供对应的api,后端搞不定登录态,所以决定搞前端。
主要问题
登录态 、 appId的双重验证
主要技术栈
nodejs、puppeteer
实现思路
用puppeteer模拟用户操作登录,获取登录态之后访问对应的获取评论接口,读取返回json后传给后端存储。
准备
设备:Linux + CentOS 7(必要,6不行) + 海外出口(非必要)
账号:ios后台账号及客服支持权限
开始操作
1、基础代码
用来测试安装成不成功
import puppeteer from "puppeteer"; let browser = await puppeteer.launch({ // headless: false, // 本地调试可打开这行看到浏览器 args: ["--no-sandbox", "--disable-setuid-sandbox"], defaultViewport: { width: 1200, height: 720, }, });
2、安装puppeteer
如果是海外出口的话,基本就是一个 npm install puppeteer --save 就完事了。如果是国内出口的话,就多搜搜帖子吧,幸运的女神会照料你的。
3、安装系统相关的包
当你运行puppeteer,出现类似下面的信息时,就说明需要安装/升级一些包。这里推荐一个网站,可以查找报错的资源属于哪个资源包,减少你的安装次数。
(1)复制绿框的部分,打开https://rpmfind.net/linux/rpm2html/search.php?query=&submit=Search+…&system=centos&arch=,复制进去搜索,找到对应的资源包名
(2)yum安装
yum search libXtst yum install 找到的对应的版本
上面提到,为什么CentOS 7是必须的呢?因为用到的浏览器的系统依赖里面有一个包是没有适用CentOS6的。
4、学习puppeteer语法
这里列举这次用到的主要方法,对puppeteer很熟悉的可以忽略。
打开浏览器
let browser = await puppeteer.launch({ // headless: false, // 本地调试可打开这行看到浏览器 args: ["--no-sandbox", "--disable-setuid-sandbox"], defaultViewport: { width: 1200, height: 720, }, });
新建页签
this.page = await browser.newPage()
设置agent
await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.0 Safari/537.36")
页面跳转
await this.page.goto("https://appstoreconnect.apple.com/")
输入字符
// 主iframe内输入 await this.page.type("#account_name", "123@qq.com") // 子iframe内输入 await this.page.frames()[1].type("#account_name", "123@qq.com")
点击
// 主iframe内点击 await this.page.click("#sign-in") // 子iframe内点击 await this.page.frames()[1].click("#sign-in")
获取页面链接
const pageUrl = await this.page.url();
监听页面请求/返回
this.page.on("response", callbackFn); this.page.on("request", callbackFn);
例如:获取账号信息
export const getAccountList = function () { return new Promise((resolve, reject) => { const callbackFn = async function (response: any) { if (response.url() == "https://appstoreconnect.apple.com/olympus/v1/session" && response.request().method() == "GET") { page.removeListener("response", callbackFn); log("获取账号列表成功"); let realJson = await response.json().catch((e: any) => { reject(e); }); resolve(realJson); } }; page.on("response", callbackFn); }); };
5、写抓取代码
写之前需要先要确认操作流程。正常用户的操作流程如下:
但是作为脚本,其实我们可以通过拼接数据的方式,直接访问接口拿到评论。因此最终确定的流程如下:
获取账号信息接口:https://appstoreconnect.apple.com/olympus/v1/session
获取APP列表信息接口:https://appstoreconnect.apple.com/iris/v1/apps
获取评论信息接口(最新100条):https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/[appID]/platforms/ios/reviews?sort=REVIEW_SORT_ORDER_MOST_RECENT
// 假设已经登录完成 var res = []; await Promise.all(["跳转到https://appstoreconnect.apple.com/","获取账号列表"]) for(账号列表){ 执行对应的点击切换账号操作 await Promise.all(["等待切换","获取账号信息"]) if(判断是否有app){ await Promise.all(["等待跳转到/apps","获取app列表信息"]) await 新开页签page1 page1.on("response",function(){ res.push(结果) }) for(app列表){ await 请求获取评论信息接口 } } } console.log(res);
关于操作流程解析 & 总结:
(1)为什么依然要保留点击选择账号这个操作呢?
苹果那边应该是对账号有做一定的访问权限管理:在当前账号下,是无法读取其他账号下的app相关信息的,因此需要保留操作以获得权限。
(2)如何获取app列表信息?
某一账号下的app列表信息接口是在https://appstoreconnect.apple.com/apps中请求的,如果不是自行发起请求的话需要进入到这个界面(点击【我的APP】就可以进入)。但是如果你的账号里没有app,是没有【我的APP】的入口的,进入这个页面也会被重定向到https://appstoreconnect.apple.com/
解决这个问题可以通过【获取账号信息接口】中的【modules[0].key】是否apps来判断能否跳转。这个值其实就是这个按钮的跳转地址(这里要感谢苹果大佬把整个页面都弄成接口配置,也就是所有信息内容都是读的接口)
虽然说这个跳转的问题不是很大,但是它会影响你切换的操作,因为/和/apps下切换账号的按钮的结构是不一样的……
/的
/apps的
6、登录态的解决
登录这块流程就和正常操作一样,点击、输入、点击、输入。需要解决的特殊问题如下:
(1)如何判断可以进行下一步操作?
这个问题其实在整个过程中都会遇到,因为很多操作它会有请求的发生,但却没有页面跳转的发生。解决的方案就是写个方法来判断一下页面的请求是否在一定时间内(如3s)没有更新状态。
// 使用 requestFn.listen(); await this.page.frames()[1].click("#sign-in").catch(fetchErr); await requestFn.testEnd();
// requestFn.js 节选 // 每次更新请求/状态,就会重新计时 Object.defineProperty(options, "requestCount", { set(newValue) { clearTimeout(timer); _startCountZeroTime(); requestCount = newValue; }, get() { return requestCount; }, }); // 计时器,如果3秒之后没有新的更新了,就执行resolve回调 const _startCountZeroTime = function () { if (timer) { timer = null; } timer = setTimeout(async () => { listenState = true; if (listenResolve) { _resetListenFn(); listenResolve(); } }, 3 * 1000); }; // 新请求+1 const _requestCallFn = function (request: any) { if (_filterRequestType(request.resourceType())) { options.requestCount++; } }; // 完成请求-1 const _requestfinishedCallFn = function (request: any) { if (_filterRequestType(request.resourceType())) { options.requestCount--; } }; // 新地址=1 const _framenavigatedCallFn = function (frame: any) { if (!frame.parentFrame()) { options.requestCount = 1; } }; const _resetListenFn = function () { listenState = false; page.removeListener("request", _requestCallFn); page.removeListener("requestfinished", _requestfinishedCallFn); page.removeListener("framenavigated", _framenavigatedCallFn); log("请求结束"); }; export const listen = async function () { if (!page) return; options.requestCount = 0; listenResolve = null; listenState = false; clearTimeout(timer); // 添加监听方法 page.on("request", _requestCallFn); page.on("requestfinished", _requestfinishedCallFn); // 这个是重定向监听,如果重定向,则需要重置请求计数状态 page.on("framenavigated", _framenavigatedCallFn); }; export const testEnd = async function () { await new Promise((resolve) => { listenResolve = resolve; // 如果在调用的时候已经完成,就resolve,如果还没有,就等完成后由其他地方执行 if (listenState) { _resetListenFn(); resolve(); } }); };
(2)需要双重验证的时候怎么办?
思路:promise等待1分钟验证码,然后通过手动调用接口的方式获取验证码
const codePromise = function () { return new Promise((coderesolve, codereject) => { this.codePromiseFn = coderesolve; this.codePromiseTimer = setTimeout(() => { codereject("没有验证码"); }, 1000 * 60); }).catch((e) => {throw e;}); }; let code: any = await codePromise().catch(fetchErr);
curl http://localhost/code?code=123456
code(nums){ clearTimeout(this.codePromiseTimer); this.codePromiseFn(nums) }
(3)如何保持登录态
思路:将cookies信息保存成文件,下次跳转页面前设置cookies。
// 判断是否有cookies文件,有的话就设置 if (await cookiesFn.checkCookieFile()) { log("has cookies"); await cookiesFn.loadCookies(this.page); } this.page.goto("https://appstoreconnect.apple.com/")
// 登录成功后获取cookies并保存 log("登录成功"); let cookies = await this.page.cookies().catch(fetchErr); cookiesFn.saveCookies(cookies);
// cookies.js const cookiesUrl = "../cookies/app.json"; export const loadCookies = async function (page: any) { const cookies = JSON.parse(fs.readFileSync(cookiesUrl).toString()); for (let index = 0; index < cookies.length; index++) { const cookie = cookies[index]; await page.setCookie(cookie); } }; export const saveCookies = function (cookies: any) { fs.writeFileSync(cookiesUrl, JSON.stringify(cookies, null, 2)); log(`更新cookies时间:${new Date()}`); }; export const checkCookieFile = function () { return new Promise((resolve) => { fs.stat(cookiesUrl, function (err, stat) { log("检测完毕"); if (stat && stat.isFile()) { resolve(true); } else { resolve(false); } }); }); };
(4)如何绕过appId的双重验证
对不起臣妾办不到,但是臣妾能尽量少被验证。苹果后台的登录态经实践有效期大约在1天左右。绕不过双重验证的原因是因为每次打开浏览器,都会被认为是新的设备,原因暂时不明,因此无法从技术上解决这个问题。
不过换个思路,既然你认为都是新的,那我以空间换次数,我不关浏览器了,不就一直是同一个了么。事实证明这个方法是可行的,不过一直开着浏览器不知道会不会有什么问题,这个还有待验证,等我验证完了再回来更新。
写在最后
其实如果要抓取评论,可以不需要登录态,苹果商店的接口就可以获取,只要你知道上线地区和对应的id就可以(id好像还是得从后台找)。
https://amp-api.apps.apple.com/v1/catalog/【地区】/apps/【appid】/reviews?l=zh-Hant-TW&offset=10&platform=web&additionalPlatforms=appletv%2Cipad%2Ciphone%2Cmac
写在最后的后面
如有错误的地方欢迎大家指出,有更好的方案或者实践欢迎分享一起讨论!
这篇关于Nodejs使用puppeteer抓取iOS商店后台APP评论的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-04React 19 来了!新的编译器简直太棒了!
- 2025-01-032025年Node.js与PHP大比拼:挑选最适合的后端技术进行现代web开发
- 2025-01-03?? 用 Gemini API、Next.js 和 TailwindCSS 快速搭建 AI 推文生成项目 ??
- 2024-12-31Vue CLI多环境配置学习入门
- 2024-12-31Vue CLI学习入门:一步一步搭建你的第一个Vue项目
- 2024-12-31Vue3公共组件学习入门:从零开始搭建实用组件库
- 2024-12-31Vue3公共组件学习入门教程
- 2024-12-31Vue3学习入门:新手必读教程
- 2024-12-31Vue3学习入门:初学者必备指南
- 2024-12-30Vue CLI多环境配置教程:轻松入门指南