puppeteer等待页面/DOM更新-响应初始加载后添加的新项



我想使用Puppeter来响应页面更新。页面显示项目,当我打开页面时,随着时间的推移,新项目可能会出现。例如,每10秒添加一个新项目。

我可以使用以下来等待页面初始加载上的项目:

await page.waitFor(".item");
console.log("the initial items have been loaded")

我如何等待/捕获未来的项目?我想实现这样的东西(伪代码):

await page.goto('http://mysite');
await page.waitFor(".item");
// check items (=these initial items)
// event when receiving new items:
// check item(s) (= the additional [or all] items)

您可以使用exposeFunction来公开本地函数:

await page.exposeFunction('getItem', function(a) {
console.log(a);
});

然后,您可以使用page.evaluate创建一个观察者,并监听在父节点内创建的新节点。

这个例子在Stack Overflow中抓取(这只是一个想法,而不是最终的作品)python聊天,并打印在该聊天中创建的新项目。

var baseurl =  'https://chat.stackoverflow.com/rooms/6/python';
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto(baseurl);
await page.exposeFunction('getItem', function(a) {
console.log(a);
});
await page.evaluate(() => {
var observer = new MutationObserver((mutations) => { 
for(var mutation of mutations) {
if(mutation.addedNodes.length) {
getItem(mutation.addedNodes[0].innerText);
}
}
});
observer.observe(document.getElementById("chat"), { attributes: false, childList: true, subtree: true });
});

作为优秀的当前答案(使用evaluate注入MutationObserver并将数据转发到公开的Node函数)的替代方案,Puppeter提供了一个名为page.waitForFunction的高级函数,该函数阻塞任意谓词,并在后台使用MutationObserverrequestAnimationFrame来确定何时重新评估谓词。

在循环中调用page.waitForFunction可能会增加开销,因为每个新的调用都涉及注册新的观察者或RAF。您必须为您的用例配置文件。不过,我不会过早地担心这件事。

也就是说,RAF选项可能会提供比MO更紧密的延迟,因为需要一些额外的CPU周期来不断轮询。

以下网站上有一个提供定期更新提要的最小示例:

const wait = ms => new Promise(r => setTimeout(r, ms));
const r = (lo, hi) => ~~(Math.random() * (hi - lo) + lo);
const randomString = n =>
[...Array(n)].map(() => String.fromCharCode(r(97, 123))).join("");
(async () => {
for (let i = 0; i < 500; i++) {
const el = document.createElement("div");
document.body.appendChild(el);
el.innerText = randomString(r(5, 15));
await wait(r(1000, 5000));
}
})();

const puppeteer = require("puppeteer");
const html = `<!DOCTYPE html>
<html><body><div class="container"></div><script>
const wait = ms => new Promise(r => setTimeout(r, ms));
const r = (lo, hi) => ~~(Math.random() * (hi - lo) + lo);
const randomString = n =>
[...Array(n)].map(() => String.fromCharCode(r(97, 123))).join("")
;
(async () => {
for (;;) {
const el = document.createElement("div");
document.querySelector(".container").appendChild(el);
el.innerText = randomString(r(5, 15));
await wait(r(1000, 5000));
}
})();
</script></body></html>`;
let browser;
(async () => {
browser = await puppeteer.launch({headless: false});
const [page] = await browser.pages();
await page.setContent(html);

for (;;) {
await page.waitForFunction((el, oldLength) =>
el.children.length > oldLength,                           // predicate
{polling: "mutation" /* or: "raf" */, timeout: 10**8},    // wFF options
await page.$(".container"),                               // elem to watch
await page.$eval(".container", el => el.children.length), // oldLength
);
const selMostRecent = ".container div:last-child";
console.log(await page.$eval(selMostRecent, el => el.textContent));
}
})()
.catch(err => console.error(err))
.finally(() => browser?.close());

注意,这个例子是人为的;如果同时向提要添加多个项目,则可以跳过一个项目。抢oldLength以外的所有物品会更安全。您几乎肯定需要调整此代码以匹配提要的特定行为。

另请参阅:

  • 使用puppeteer在page.waitForFunction()内部传递一个函数,该函数显示了封装page.waitForFunction的通用waitForTextChange辅助函数
  • 使用Nodejs实时抓取聊天,这恰当地建议了在API响应填充提要时拦截它们的替代方法(如果可能)

最新更新