Puppeteer无法访问Canvas函数,但Chrome开发工具可以



我不明白为什么我在Chrome开发工具中运行的代码片段可以完美地工作,但我无法让它在Puppeter中工作。作为一个实验,我试图";驱动器";《纽约时报》Letter Boxed游戏的画布:https://www.nytimes.com/puzzles/letter-boxed

在Google Chrome中,我可以通过选择canvas元素,然后提取"__reactInternalInstance";属性(通常该属性的名称类似于"__reactInternalInstance$2C4oug63f700万"(。实现这一点的javascript就在这里:

var myObject = {};
var mycanvas = document.querySelector("#pz-game-root > div > div > div.lb-square-container > canvas")
for (let property in mycanvas) {
if (property.includes("__reactInternalInstance")) {
console.log(`${property}`);
myObject = mycanvas[`${property}`];
}
}
console.log(myObject);
//at this point IN CHROME,
//myObject has access to some of the interactive functions of the game play

puppeteer中几乎相同的代码似乎无法访问任何画布属性,包括"__reactInternalInstance";所有物

const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
//args: ["--renderer","--single-process"],
executablePath: "/usr/bin/google-chrome",
headless: true,
userDataDir: '/tmp'
});
const page = await browser.newPage();
//use a mobile user agent
page.setUserAgent("Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36");
await page.setViewport({ width: 412, height: 915 });
await page.goto('https://www.nytimes.com/puzzles/letter-boxed');
try {
/**
* click the start button on the splash screen
*/
await page.waitForSelector("button.pz-moment__button.primary");
let startButton = await page.$("button.pz-moment__button.primary");
await startButton.click();
await delay(3000);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* myHandle object comes back null,
* no properties are logged,
* and the "Found the canvas" message doesn't print
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
//capture the canvas element
await page.waitForSelector("#pz-game-root > div > div > div.lb-square-container > canvas");
let myHandle = await page.$("#pz-game-root > div > div > div.lb-square-container > canvas",
(canv) => {
console.log("Found the canvas");
for (let property in canv) {
console.log(`${property}`);
}
return canv;
}
);
console.log(myHandle);
} catch (error) {
console.log("Failure occurred");
console.log(error);
}
await browser.close();
})();
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}

我对木偶师不是很精通。有人能告诉我我做错了什么吗?

我不明白为什么我在Chrome开发工具中运行的代码片段能完美工作,但我无法让它在Puppeteer 中工作

这种情况经常发生。网站很复杂,你很少能把浏览器代码复制到Puppeter中并期望它能正常工作。例如,浏览器控制台将暴露Puppeter中不可访问的iframe(但这不是问题所在(。可见性、时间、机器人检测以及原生DOM方法和Puppeteer版本之间的差异导致了各种各样的差异。

然而,这里的主要问题是误解了"玩偶API",这是不容易理解的。page.$不接受回调。它基本上是document.querySelector上的一个包装器,如果选择器是可选的,则返回选择器的ElementHandle,否则为null。

您可能想到了page.$eval,它接受两个参数,一个选择器和一个回调(以及在浏览器中执行时填充回调参数的变量参数(。

即使回调工作正常,请记住,除非监视浏览器控制台,否则console.log调用在Node进程中是不可见的。

return canv;也不起作用。从浏览器到Puppeter的所有返回值都必须是可序列化的,因此DOM元素、React实例等复杂结构将作为空对象返回。

我不确定您最终要实现什么,但如果您计划将数据返回到Node以在那里使用,则需要选择基本的可序列化属性并返回这些属性。否则,您可以返回带有page.evaluateHandle的ElementHandle,或者很可能在画布上执行操作,以便在浏览器上下文中玩游戏。

目前尚不清楚您为什么要深入研究React实例(其中是否隐藏了一些解决方案数据?(,但我通常不推荐这种方法。最好尽可能靠近用户界面和网络。如果React使用了数据,那么它通常可以更可靠、更容易地从网络请求中截取,或者从静态HTML附带的JSON负载中提取。如果你想检查游戏状态或采取行动,更喜欢通过可见的UI元素进行操作。

也就是说,作为概念的证明,我将从React实例返回memoizedProps

const puppeteer = require("puppeteer"); // ^16.2.0
let browser;
(async () => {
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
const ua = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36";
await page.setUserAgent(ua);
await page.setViewport({width: 412, height: 915});
const url = "https://www.nytimes.com/puzzles/letter-boxed";
await page.goto(url, {waitUntil: "domcontentloaded"});
const btn = await page.waitForSelector("button.pz-moment__button.primary");
await btn.click();
const sel = await page.waitForSelector("#pz-game-root canvas");
const memoizedProps = await sel.evaluate(el => 
Object
.entries(el)
.find(([k, v]) => k.startsWith("__reactInternalInstance"))
.pop()
.memoizedProps
);
console.log(memoizedProps);
// => { width: 320, height: 320, style: { width: 320, height: 320 } }
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;

其他建议:

  • 避免死板的、超特定的选择器。#pz-game-root canvas比长链的div > div > div > divs更健壮、适应性更强,不会损失太多精度(我们希望在这个id下有多少<canvas>元素?(。浏览器生成的选择器通常是这种做法的罪魁祸首
  • 通常不需要多次选择内容——waitForSelector返回ElementHandle,这样您就可以对其进行.click().evaluate()
  • 尽量避免睡眠/延迟/超时,几乎总是有利于waitForSelectorwaitForFunctionwaitForResponse等。如果你要睡觉,Puppeter已经给了你page.waitForTimeout,所以你不必像使用delay那样重新实现它
  • page.setUserAgent()返回一个您应该await的承诺。未能awaitPuppeteer中的所有承诺会导致比赛条件和奇怪的、间歇性的错误以及令人困惑的消息

我的博客文章更详细地解释了为什么这些都是应该避免的糟糕做法。

相关内容

最新更新