如何在Puppeteers .evaluate()方法中传递函数?

时间:2017-11-15 10:14:30

标签: javascript node.js google-chrome puppeteer

每当我尝试传递一个函数时,就像这样:

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);

7 个答案:

答案 0 :(得分:14)

您不能将函数直接传递到page.evaluate()中,但是可以调用另一个特殊方法(page.exposeFunction),该方法会将您的函数公开为全局函数(也可以作为页面的属性使用{ {1}}对象),因此您可以在window内部时调用它:

page.evaluate()

请记住,var myFunc = function() { console.log("lol"); }; await page.exposeFunction("myFunc", myFunc); await page.evaluate(async () => { await myFunc(); return true; }); 将使您的函数返回page.exposeFunction(),然后,您需要使用Promiseasync。发生这种情况是因为您的功能不是running inside your browser,而是在您的await应用程序内部。

  1. exposeFunction() does not work after goto()
  2. Why can't I access 'window' in an exposeFunction() function with Puppeteer?
  3. How to use evaluateOnNewDocument and exposeFunction?
  4. exposeFunction remains in memory?
  5. Puppeteer: pass variable in .evaluate()
  6. Puppeteer evaluate function
  7. allow to pass a parameterized funciton as a string to page.evaluate
  8. Functions bound with page.exposeFunction() produce unhandled promise rejections
  9. exposed function queryseldtcor not working in puppeteer
  10. How can I dynamically inject functions to evaluate using Puppeteer?

答案 1 :(得分:4)

在木偶操纵者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;
});

使用ElementHandle

页。$(选择器)

您可以将元素句柄传递给.evaluate并根据需要进行更改。

const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);

页。$ EVAL

您可以定位一个元素并根据需要进行更改。

const html = await page.$eval('.awesomeSelector', e => {
e.outerHTML = "whatever"
});

诀窍是read the docs并保持简单。

答案 2 :(得分:1)

抛出错误是因为您执行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();

`

答案 3 :(得分:0)

具有参数的传递功能

//手动添加并显示在窗口中

 await page.evaluate(() => {
      window.myFunc = function(element) { element.innerHTML = "baz" };
    });

//,然后调用上面声明的函数

 await page.evaluate((param) => {
         myFunc (param);
    }, param);

答案 4 :(得分:0)

创建了一个包装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));
});

答案 5 :(得分:0)

//  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 :(得分:0)

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)
  );