执行 js 函数
page.evaluate(pageFunction[, ...args])
- pageFunction 在页面环境中执行的函数
- args 参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| const puppeteer = require("puppeteer"); async function init() { const browser = await puppeteer.launch({ headless: false, args: ['--proxy-server="direct://"', "--proxy-bypass-list=*"], }); const page = await browser.newPage();
await page.evaluate(async () => { console.log("hello world"); }); } init();
|
可以通过 page.evaluate 给页面环境中的函数传参
不过不能传 Function ,会变成 undefined
1 2 3 4 5 6 7 8 9 10 11 12 13
| const args = [ "a", 1, { a: 1 }, [1, 2], function() { console.log("foo"); }, ]; await page.evaluate(async (...args) => { console.log(...args); }, ...args);
|
还可以传 ElementHandle 实例,然后在页面环境里直接操作对应的 dom
1 2 3 4
| const bodyHandle = await page.$("body"); await page.evaluate(async body => { console.log(body); }, bodyHandle);
|
注意不能返回函数, 会变成空对象
1 2 3 4 5 6
| const result = await page.evaluate(async (...args) => { return function() { console.log("foo"); }; }); console.log(result, typeof result);
|
同样也不能返回 Dom
1 2 3 4
| const result = await page.evaluate(async (...args) => { return document.body; }); console.log(result, typeof result);
|
另外官方文档称,不能被序列化的返回值会变成 undefined.
调用外部函数
不能像这样在 evaluate 调用外部函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const puppeteer = require("puppeteer");
async function init() { const browser = await puppeteer.launch({ headless: true, args: ['--proxy-server="direct://"', "--proxy-bypass-list=*"], }); const page = await browser.newPage();
function say() { console.log("hello world"); } page.evaluate(() => { say(); }); }
init();
|
也不能将函数当作参数传给 page.evaluate
1 2 3 4
| page.evaluate(say => { console.log(say); say(); }, say);
|
最简单的解决办法就是把 function 写在 evalute 里
1 2 3 4 5 6 7
| await page.evaluate(() => { function say() { console.log("hello world"); } console.log(say); say(); });
|
但是有时没法这么干,对于这种状况有两种解决办法:
page.addScriptTag
page.addScriptTag 会将 content 中的字符内容作为 script 添加到 head 中.
1 2 3 4
| await page.addScriptTag({ content: `${say}` }); page.evaluate(() => { say(); });
|
注意,如果是箭头函数,别忘了加上函数名,否则会报错.
1 2 3 4
| const say = () => { console.log("hello world"); }; await page.addScriptTag({ content: `const say = ${say}` });
|
作为对比, 如果没有在 content 中 添加 "const say =" 的话会报错,效果如下
1 2 3 4 5
| await page.addScriptTag({ content: `${say}` }); const say = () => { console.log("hello world"); };
|
对于对象方法, 则貌似没有什么好的解决办法.因为对象方法转成字符串之后是没有 function 关键字.
即使在前头加上"const say =" 之类的字符,也无法作为方法来使用.
不要这样写
1 2 3
| const obj = { method() {} }
|
替代的解决办法:
1 2 3
| const obj = { method: function() {} }
|
page.exposeFunction
page.exposeFunction 会在 window 下绑定一个同名函数.在 evaluate 中调用同名函数时, 这个函数会在 node.js 环境下执行,并返回一个 Promise 对象.
1 2 3 4 5 6 7 8 9
| async function say() { console.log("hello world"); } await page.exposeFunction("say", say); await page.evaluate(() => { console.log(say); say(); });
|
evaluate 中的 console.log(say) 输出的效果
绑定的函数会在 nodejs 环境下运行,然后将结果包在 Promise 中返回.
因此 hello world 输出在命令行里, 而不再 chromium 浏览器中.
也不能传递 Dom Function 之类无法序列化的对象作为参数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| async function say(...args) { console.log(args); } await page.exposeFunction("say", say); await page.evaluate(() => { say( "a", 1, [1, 2], { a: 1 }, document.body, () => { console.log("foo"); }, function() { console.log("bar"); } ); });
|
不要忘了在 page.exposeFunction 前加 await ,否则可能会报错.
1 2 3 4 5 6 7
| page.exposeFunction("say", say); await page.evaluate(() => { console.log(say); say(); });
|
exposeFunction 只接受函数, 例如把 path 模块传进去,然后调用 path.join() 的话会报错.
1 2 3 4 5 6 7
| const path = require("path"); await page.exposeFunction("path", path); await page.evaluate(() => { console.log(path, path.join); path.join("a", "b"); });
|
解决办法,用啥传啥
1 2 3 4 5 6
| const path = require("path"); await page.exposeFunction("join", path.join); await page.evaluate(async () => { const p = await join("a", "b"); console.log(p); });
|
exposeFunction 函数实质上在外部的 Node 环境下运行, 因此可以使用 bind 绑定函数的 this/柯里化函数.(page.addScriptTag 不能这样作)
1 2 3 4 5 6 7 8
| function outerFunc(a, b) { console.log(a, b); return [a, b] }; await page.exposeFunction('outFunc', outerFunc.bind(this, 1)); await page.evaluate(async () => { outerFunc(2); })
|
务必注意,网页内的 iframe 可能会导致 await page.exposeFunction 卡死
参考
官方文档 page.evaluate
javascript - How can I dynamically inject functions to evaluate using Puppeteer? - Stack Overflow