Puppeteer ProtocolError求助:等待选择器失败的解决方法
问题代码
async function sendRequest(page: Page, url: string) { await Promise.all([ page.waitForNavigation(), page.goto(url), page.waitForSelector("div#header"), ]); }
抛出的错误信息
ProtocolError: Waiting for selector
div#headerfailed: Protocol error (Runtime.callFunctionOn): Argument should belong to the same JavaScript world as target object
at /home/mble/Code/repos/pluget/services/spigotScraper/node_modules/puppeteer-core/src/common/Connection.ts:400:16
at new Promise ()
at CDPSessionImpl.send (/home/mble/Code/repos/pluget/services/spigotScraper/node_modules/puppeteer-core/src/common/Connection.ts:396:12)
at ExecutionContext._ExecutionContext_evaluate (/home/mble/Code/repos/pluget/services/spigotScraper/node_modules/puppeteer-core/src/common/ExecutionContext.ts:274:44)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at WaitTask.rerun (/home/mble/Code/repos/pluget/services/spigotScraper/node_modules/puppeteer-core/src/common/WaitTask.ts:123:26)
[ERROR] 18:53:05 ProtocolError: Waiting for selectordiv#headerfailed: Protocol error (Runtime.callFunctionOn): Argument should belong to the same JavaScript world as target object
问题原因
用Promise.all并行执行三个操作时会触发竞态问题:page.waitForSelector可能在页面导航未完成、JavaScript上下文还未稳定时就开始等待,导致它绑定的上下文和页面当前的JavaScript世界不匹配。另外page.goto本身就会返回导航完成的Promise,额外并行调用page.waitForNavigation完全没必要,反而会加重上下文冲突的概率。
修复方案
方案一:最简洁的正确顺序
直接用page.goto的参数控制加载完成,之后再等待选择器:
async function sendRequest(page: Page, url: string) { // 等待页面加载到网络空闲状态(确保资源加载完成) await page.goto(url, { waitUntil: 'networkidle0' }); // 导航完成后再等待目标选择器出现 await page.waitForSelector("div#header"); }
方案二:处理页面跳转场景
如果目标页面会触发跳转(比如点击后跳转到新页面),需要先监听导航再发起操作:
async function sendRequest(page: Page, url: string) { // 先注册导航等待 const navigationPromise = page.waitForNavigation({ waitUntil: 'networkidle0' }); // 发起页面访问 await page.goto(url); // 等待导航完成 await navigationPromise; // 再等待选择器 await page.waitForSelector("div#header"); }
方案三:指定JavaScript世界(针对多上下文场景)
如果页面包含iframe或者自定义隔离的JavaScript上下文,需要明确指定选择器等待的主上下文:
async function sendRequest(page: Page, url: string) { await page.goto(url, { waitUntil: 'networkidle0' }); // 获取主页面的执行上下文 const mainContext = page.mainFrame().executionContext(); // 在指定上下文中等待选择器 await page.waitForSelector("div#header", { context: mainContext }); }
内容的提问来源于stack exchange,提问作者Maciej Bledkowski

