如何在 Puppeteers .evaluate() 方法中传递函数?
Posted
技术标签:
【中文标题】如何在 Puppeteers .evaluate() 方法中传递函数?【英文标题】:How to pass a function in Puppeteers .evaluate() method? 【发布时间】:2018-04-28 12:23:19 【问题描述】:每当我尝试传递一个函数时,像这样:
var myFunc = function() console.log("lol"); ;
await page.evaluate(func =>
func();
return true;
, myFunc);
我明白了:
(node:13108) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Evaluation failed: TypeError: func is not a function
at func (<anonymous>:9:9)
(node:13108) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
为什么?如何正确操作?
谢谢!
€:让我澄清一下:我这样做是因为我想先找到一些 DOM 元素并在该函数中使用它们,更像这样(简化):
var myFunc = function(element) element.innerhtml = "baz" ;
await page.evaluate(func =>
var foo = document.querySelector('.bar');
func(foo);
return true;
, myFunc);
【问题讨论】:
这能回答你的问题吗? How can I dynamically inject functions to evaluate using Puppeteer? 【参考方案1】:您不能将函数直接传递给page.evaluate()
,但您可以调用另一个特殊方法(page.exposeFunction
),它将您的函数公开为全局函数(也可作为页面window
对象的属性使用) ,所以当你在page.evaluate()
里面时可以调用它:
var myFunc = function() console.log("lol"); ;
await page.exposeFunction("myFunc", myFunc);
await page.evaluate(async () =>
await myFunc();
return true;
);
请记住page.exposeFunction()
将使您的函数返回一个Promise
,然后,您需要使用async
和await
。发生这种情况是因为您的函数不是running inside your browser,而是在您的nodejs
应用程序中。
-
exposeFunction() does not work after goto()
Why can't I access 'window' in an exposeFunction() function with Puppeteer?
How to use evaluateOnNewDocument and exposeFunction?
exposeFunction remains in memory?
Puppeteer: pass variable in .evaluate()
Puppeteer evaluate function
allow to pass a parameterized funciton as a string to page.evaluate
Functions bound with page.exposeFunction() produce unhandled promise rejections
exposed function queryseldtcor not working in puppeteer
How can I dynamically inject functions to evaluate using Puppeteer?
【讨论】:
【参考方案2】:木偶师issue讨论了类似的问题。
有几种方法可以解决您的问题。第一条规则是保持简单。
评估函数
这是最快的处理方式,你只需传递函数并执行它。
await page.evaluate(() =>
var myFunc = function(element) element.innerHTML = "baz" ;
var foo = document.querySelector('.bar');
myFunc(foo);
return true;
);
预先暴露函数
您可以使用 page.evaluate 或 page.addScriptTag 预先公开该函数
// add it manually and expose to window
await page.evaluate(() =>
window.myFunc = function(element) element.innerHTML = "baz" ;
);
// add some scripts
await page.addScriptTag(path: "myFunc.js");
// Now I can evaluate as many times as I want
await page.evaluate(() =>
var foo = document.querySelector('.bar');
myFunc(foo);
return true;
);
使用元素句柄
page.$(选择器)
您可以将元素句柄传递给 .evaluate 并根据需要进行更改。
const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);
page.$eval
您可以定位一个元素并根据需要进行更改。
const html = await page.$eval('.awesomeSelector', e =>
e.outerHTML = "whatever"
);
诀窍是read the docs 并保持简单。
【讨论】:
第一个解决方案有什么意义,你可以传递函数并执行它。?如果我们想在多个evaluate
调用中使用相同的myFunc
怎么办?
就像下面的答案所说,你不能像使用 page.evaluate
的变量那样将函数传递到页面中。例如:await page.evaluate(async func => /* ... */ , myFunc)
【参考方案3】:
带参数的传递函数
//手动添加并暴露到窗口
await page.evaluate(() =>
window.myFunc = function(element) element.innerHTML = "baz" ;
);
//然后调用上面声明的函数
await page.evaluate((param) =>
myFunc (param);
, param);
【讨论】:
我不应该在page.evaluate(...)
中调用window.myFunc
而不是myFunc
? myFunc
分配给window.myFunc
后如何进入全局命名空间?
你的答案与投票最多的答案有何不同?【参考方案4】:
抛出错误是因为你执行了func();
但func
不是函数。我更新我的答案以回答您更新的问题:
选项 1:在页面上下文中执行您的函数:
var myFunc = function(element) element.innerHTML = "baz" ;
await page.evaluate(func =>
var foo = document.querySelector('.bar');
myFunc(foo);
return true;
);
选项 2:将元素句柄作为参数传递
const myFunc = (element) =>
innerHTML = "baz";
return true;
const barHandle = await page.$('.bar');
const result = await page.evaluate(myFunc, barHandle);
await barHandle.dispose();
`
【讨论】:
不是真的,我想在 .evaluate 函数中评估 myFunc。这样我就可以在浏览器上下文中做一些事情,并使用它在浏览器上下文中调用 myFunc: 之前能够做其他事情并将其传递给函数:await page.evaluate(func => /* 在这里做事情 / func( / 使用这些事情在这里 */ ); return true; , myFunc); 所以你可以这样做:` var myFunc = function(element) element.innerHTML = "baz" ;等待 page.evaluate(() => var foo = document.querySelector('.bar'); myFunc(foo); return true; ); ` 这是错误的。您不能将测试中的全局范围函数传递到 page.evaluate 中。【参考方案5】:// External function to run inside evaluate context
function getData()
return document.querySelector('title').textContent;
function mainFunction(url, extractFunction)
let browser = await puppeteer.launch();
let page = await browser.newPage();
await page.goto(url);
let externalFunction = Object.assign(extractFunction);
let res = await this.page.evaluate(externalFunction)
console.log(res);
// call it here
mainFunction('www.google.com',getData);
【讨论】:
【参考方案6】:创建了一个包装 page.evaluate
的辅助函数:
const evaluate = (page, ...params) => browserFn =>
const fnIndexes = [];
params = params.map((param, i) =>
if (typeof param === "function")
fnIndexes.push(i);
return param.toString();
return param;
);
return page.evaluate(
(fnIndexes, browserFnStr, ...params) =>
for (let i = 0; i < fnIndexes.length; i++)
params[fnIndexes[i]] = new Function(
" return (" + params[fnIndexes[i]] + ").apply(null, arguments)"
);
browserFn = new Function(
" return (" + browserFnStr + ").apply(null, arguments)"
);
return browserFn(...params);
,
fnIndexes,
browserFn.toString(),
...params
);
;
export default evaluate;
获取所有参数并将函数转换为字符串。 然后在浏览器上下文中重新创建函数。 见https://github.com/puppeteer/puppeteer/issues/1474
你可以像这样使用这个函数:
const featuredItems = await evaluate(page, _getTile, selector)((get, s) =>
const items = Array.from(document.querySelectorAll(s));
return items.map(node => get(node));
);
【讨论】:
你在这里用这些代码做什么?怎么用? 这让我可以为浏览器上下文创建模块化函数,将它们导入我的脚本并将它们传递给评估。 IE。 _getTile 可以访问文档,从它自己的 javascript 文件中导入,然后传递给评估方法。通过对函数进行字符串化然后在浏览器上下文中重新创建,您可以将函数传递给 Puppeteer .evaluate() 方法,如问题中所述。【参考方案7】:function stringifyWithFunc(obj)
const str = JSON.stringify(obj, function(key, val)
if (typeof val === "function")
return val + "";
return val;
);
return str;
function parseWithFunction(str)
const obj = JSON.parse(str, function(key, val)
if (typeof val === 'string' && val.includes("function"))
return eval(`($val)`);
return val;
);
return obj;
function testFunc()
console.log(123);
;
const params =
testFunc,
a: 1,
b: null
await page.exposeFunction("parseWithFunction", parseWithFunction);
await pageFrame.$eval(".category-content", (elem, objStr) =>
const params = parseWithFunction(objStr);
params.testFunc()
,
stringifyWithFunc(params)
);
【讨论】:
以上是关于如何在 Puppeteers .evaluate() 方法中传递函数?的主要内容,如果未能解决你的问题,请参考以下文章
如何解决在electron里无法使用puppeteer的evaluate函数
如何在使用 fit_generator 和 evaluate_generator 训练我的网络时绘制 AUC 和 ROC?