使用Puppeteer爬虫时getElementsByClassName返回异常集合求助
我明白你遇到的麻烦了——用page.evaluate返回DOM元素数组时,拿到的不是预期的HTML元素,而是一堆带__ko__标记的奇怪对象。这其实是Puppeteer的一个常见坑,和目标网站用React(看起来还额外用到了Knockout框架)有关,但核心原因是浏览器端的DOM元素无法直接序列化并传递到Node.js环境中。
问题根源
当你在page.evaluate里返回DOM元素(哪怕转成数组)时,Puppeteer会尝试把这些浏览器端的对象序列化(转成JSON格式)传回Node端。但DOM元素是复杂的浏览器专属对象,包含大量不可序列化的属性和方法,序列化过程中只会保留一些可枚举的属性——这里的__ko__就是Knockout框架附加在DOM元素上的标记属性,所以最终你看到的就是这些奇怪的对象,而非真正的DOM元素引用。
解决方案
根据你的需求,有几个实用的解决思路:
1. 在浏览器端提取需要的数据,返回结构化结果
如果你只是需要元素里的文本、链接、HTML内容等,直接在page.evaluate里把这些数据提取出来,返回普通的JSON对象或数组,就能正常传递到Node端了。
比如,获取每个opportunity元素的完整HTML:
(async () => { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); const url = "https://recruiting2.ultipro.com/PUB1004PSCU/JobBoard/d433f5c3-37c8-4bcf-a3af-248a707c7d31/?q=&o=postedDateDesc"; await page.goto(url, {timeout: 0, waitUntil: 'networkidle0'}); // 在浏览器端提取每个元素的outerHTML const opportunitiesHTML = await page.evaluate(() => { return Array.from(document.getElementsByClassName("opportunity")).map(elem => elem.outerHTML); }); console.log(opportunitiesHTML); opportunitiesHTML.forEach(html => { console.log(html); // 这里可以用cheerio等工具解析HTML处理数据 }); await browser.close(); } catch (err) { console.error(err); } })();
2. 使用Puppeteer的$$方法获取ElementHandle数组
如果你需要在Node端操作DOM元素(比如点击、获取属性),可以用Puppeteer提供的page.$$(selector)方法,它会返回ElementHandle对象数组——这些对象是浏览器端DOM元素的代理,能在Node端调用相关方法:
(async () => { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); const url = "https://recruiting2.ultipro.com/PUB1004PSCU/JobBoard/d433f5c3-37c8-4bcf-a3af-248a707c7d31/?q=&o=postedDateDesc"; await page.goto(url, {timeout: 0, waitUntil: 'networkidle0'}); // 获取ElementHandle数组 const opportunityElements = await page.$$(".opportunity"); for (const elem of opportunityElements) { // 比如获取元素文本内容 const text = await elem.evaluate(el => el.textContent); console.log(text); // 也可以直接调用元素方法,比如elem.click() } await browser.close(); } catch (err) { console.error(err); } })();
3. 在evaluate里直接处理元素逻辑
如果你的crawlOpo函数是处理DOM元素的逻辑,直接把这个函数放到page.evaluate里执行,避免传递DOM元素:
(async () => { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); const url = "https://recruiting2.ultipro.com/PUB1004PSCU/JobBoard/d433f5c3-37c8-4bcf-a3af-248a707c7d31/?q=&o=postedDateDesc"; await page.goto(url, {timeout: 0, waitUntil: 'networkidle0'}); // 在浏览器端直接处理每个元素,返回结构化结果 const results = await page.evaluate(() => { const opportunities = Array.from(document.getElementsByClassName("opportunity")); return opportunities.map(opo => { // 把crawlOpo的逻辑写在这里,提取需要的数据 return { title: opo.querySelector(".job-title")?.textContent || "", location: opo.querySelector(".location")?.textContent || "", postedDate: opo.querySelector(".posted-date")?.textContent || "" }; }); }); console.log(results); await browser.close(); } catch (err) { console.error(err); } })();
总结
核心要点就是:不要试图把浏览器端的DOM元素直接传回Node.js环境,要么在浏览器端提取好数据再返回,要么用Puppeteer的ElementHandle来代理操作。你看到的__ko__对象只是DOM元素序列化后的“残次品”,不是真正的元素引用。
内容的提问来源于stack exchange,提问作者Charles L.

