const scraperObject = {
url: 'http://book-store.com',
scraper(browser){
let page = browser.newPage();
page.goto(this.url);
page.waitForSelector('.book-container');
page.waitForSelector('.book');
//TODO: save book data after this
});
}
}
const scraperObject = {
url: 'http://book-store.com',
async scraper(browser){
let page = await browser.newPage();
await page.goto(this.url);
await page.waitForSelector('.book-container');
await page.waitForSelector('.book');
//TODO: save book data after this
});
}
}
2条答案
按热度按时间jjhzyzn01#
对于初学者,我建议阅读Node.js中的单线程非阻塞IO模型是如何工作的。该线程激发了Node为实现并发而提供的回调和基于promise的模型。
每当Node进程需要访问进程外资源(如文件系统或网络套接字)时(如Puppeteer与其连接的浏览器通信),有两个选项:
1.阻塞整个进程并等待响应,就像
fs.readFileSync
一样。1.使用promise或callback来获得响应通知并处理其他事情,就像
fs.readFile
(通过callback或fs.promises
)和Puppeteer一样。第一个选项是一个糟糕的选择,唯一的优点是更容易编写语法。阻塞线程等待资源就像订购比萨饼,然后什么也不做,直到比萨饼到达。你还不如在等待的时候看书或浇水。
从历史上看,回调最初是在Node中编写并发代码的唯一方法。最终,promise和
then
出现了,它们更好,但仍然带来了可读性负担。随着async
/await
的出现,编写读起来像同步代码的异步代码不再困难(尽管async
范例并非没有自己的脚炮,特别是在错误处理方面).同步API(如fs
的__Sync
函数)是异步API的别名,这是历史遗留的产物。page.waitForSelectorSync
、page.$evalSync
等现在,可以理解Puppeteer的异步API在简单的直线脚本中毫无意义,因为您的Node进程在等待响应时没有任何其他事情要做,但是必须为每个调用输入
await
是API可用设计选项中最不邪恶的。即使脚本是一个单一的直线代码序列,也不能简单地使用promise。没有
await
,操作/结果的顺序变得不确定,因为每个promise都是并发运行的,彼此独立。这种交错在顺序代码中是无意的,但在需要并发的情况下是一个有用的工具(例如,Express Web服务器调用Puppeteer作为处理请求的一部分),或者当您需要等待与操作顺序不一致的事件时,例如常见的“单击并等待导航”模式:字符串
对于异步API的作者来说,几乎所有的调用都是访问网络资源,就像Puppeteer一样,选项是:
1.编写和维护两个版本的API,一个同步版本和一个异步版本。Playwright Python可以做到这一点,但这种模式比Node更适合Python架构(Playwright Node只有P2C API)。除了Node的核心fs模块,我想不出任何在Node中维护两个API的包的例子。
1.编写和维护一个同步的API仅仅是为了满足简单的用例,而代价是让库对任何关心并发性的人来说几乎是不可用的。显然,这是一个可怕的设计,就像强迫每个订购比萨饼的人(在上面的真实世界的例子中)在它到达之前什么都不做一样。
1.编写并维护一个异步API,并使那些不关心特定程序中并发性的客户端必须在所有调用之前编写
await
,即使他们没有利用并发性(或者是利用了,但没有意识到)。顺便说一句,浏览器是一个独立的进程,这一事实往往会导致 puppet 师初学者的各种困惑。例如,数据被序列化和重新序列化的事实(转换为字符串)在每次调用
page.evaluate
时(和家族)意味着你不能在跨层传递像DOM节点这样的复杂结构,进程间隙。您无法从evaluate
回调的主体访问在Node中定义的变量,除非将它们作为参数传递给evaluate
调用,并且这些变量需要能够正确响应JSON.stringify()
(也就是说,是可序列化的)。就在这篇文章发表前13个小时,有人问node.js puppeteer "document is not defined"。他们试图访问Node内部的浏览器进程
document
对象。如果你在Windows上,试着运行一个不关闭浏览器的简单Puppeteer Node脚本,然后看看你的任务管理器。在Linux上,你可以运行
ps -a
。你会看到有一个Chromium浏览器和一个Node进程。这两个进程通过一个套接字进行通信,其具有比帧内更高的延迟,每个Puppeteer调用都提供了一个并发的机会,如果Puppeteer的API是同步的,那么这个机会就会丢失。理解进程间的差距对于Puppeteer的成功至关重要,因为它激发了API调用为什么是异步的,并有助于澄清哪个代码在哪个进程中执行。
ia2d9nvy2#
async
对于数据获取/抓取非常重要。你可以想象一下这种情况,你有一个元素是book-container
,但是在book-container
中,它将有book
数据稍后在UI上通过API获取。字符串
有了这个代码片段,它将像这样运行
page.goto(this.url)
转到具有特定URL的页面page.waitForSelector('.book-container')
这里没有.book-container
,所以它会尝试立即获取.book-container
元素(当然,它不会在那里,因为页面可能由于一些网络问题而仍然在加载)page.waitForSelector('.book')
类似地,它试图立即获取图书数据(即使book-container
还没有在HTML中)为了解决这个问题,我们应该让JavaScript为HTML中的元素设置WAIT。
型
再用
async/await
解释一下。page.goto(this.url)
转到具有特定URL的页面并等待页面加载page.waitForSelector('.book-container')
等待.book-container
元素出现在HTML中page.waitForSelector('.book')
等待.book
元素出现在HTML中(我们可以理解为API的数据响应)