如何使用Playwright批量抓取漫画章节列表?
解决漫画章节批量抓取与元素CSS选择器生成问题
我正在进行漫画内容抓取工作,目前已成功获取单个章节的数据,但始终无法实现批量抓取所有章节并生成章节数组。此前我通过指定单个章节与章节名称的CSS选择器来获取数据,但这种方式并不适用于批量抓取场景。因此我尝试编写函数,获取每个章节元素内所有元素的CSS选择器并打印至控制台,但该功能始终无法正常运行,特此寻求技术帮助。
你的代码存在几个核心问题导致批量抓取和选择器生成失败:
- 章节列表选择器错误:原选择器指向的是章节列表的容器而非单个章节项,循环仅执行一次,无法批量获取所有章节
- CSS选择器生成逻辑缺失:当前仅获取元素的
tagName,并非完整可定位的CSS选择器 - 动态内容等待不充分:目标页面使用
astro-island动态渲染内容,仅等待domcontentloaded可能导致章节元素未完全加载
以下是修正后的代码:
import playwright from "playwright"; // 生成元素的完整CSS选择器 const generateCSSSelector = (element) => { if (element.tagName === 'HTML') return 'html'; if (element.tagName === 'BODY') return 'body'; let selector = element.tagName.toLowerCase(); if (element.id) { selector += `#${element.id}`; return selector; } if (element.className) { const classes = element.className.trim().split(/\s+/).join('.'); selector += `.${classes}`; } // 计算同级别元素中的位置 const siblings = Array.from(element.parentElement.children).filter(el => el.tagName === element.tagName); if (siblings.length > 1) { const index = siblings.indexOf(element) + 1; selector += `:nth-child(${index})`; } return `${generateCSSSelector(element.parentElement)} > ${selector}`; }; (async () => { let browser; let context; try { const url = "https://battwo.com/title/75019-blue-lock"; const yearUrl = "https://www.anime-planet.com/manga/blue-lock"; const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"; browser = await playwright.firefox.launch({ headless: true }); context = await browser.newContext({ userAgent }); const page = await context.newPage(); await page.setViewportSize({ width: 1280, height: 720 }); // 调整视口避免移动端布局影响 page.setDefaultTimeout(180000); // 等待页面完全加载,包括动态内容 await page.goto(url, { waitUntil: "networkidle", timeout: 60000 }); // 等待astro-island内的章节列表渲染完成 await page.waitForSelector("main.px-3 astro-island div div.px-2", { visible: true, timeout: 60000 }); // 抓取年份数据 const yearPage = await context.newPage(); await yearPage.goto(yearUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); await yearPage.waitForSelector("div.pure-1.md-1-5 > span.iconYear", { visible: true }); const year = await yearPage.$eval("div.pure-1.md-1-5 > span.iconYear", (element) => element.innerText.replace(/[-?]/g, "").trim()); // 获取单个章节项的列表(修正选择器) const listItems = await page.$$("main.px-3 astro-island div div.px-2"); const chapters = []; for (const item of listItems) { // 获取章节号和名称(简化选择器) const chapter = await item.$eval("div > a", (element) => element.innerText.trim()); const name = await item.$eval("div > span:nth-child(2)", (element) => element.innerText.trim()); chapters.push({ chapter, name }); // 生成并打印当前章节元素内所有子元素的CSS选择器 const selectors = await item.$$eval("*", (elements) => { return elements.map(el => generateCSSSelector(el)); }); console.log(`章节 ${chapter} 内元素选择器:`, selectors); } const scrapedData = { year, chapters }; console.log("抓取结果:", scrapedData); } catch (e) { console.error("抓取错误:", e); } finally { const delay = Math.floor(Math.random() * (15000 - 8000 + 1)) + 8000; await new Promise((resolve) => setTimeout(resolve, delay)); if (context) await context.close(); if (browser) await browser.close(); console.log("抓取完成,浏览器已关闭"); } })();
关键修改说明:
- 修正章节选择器:使用
main.px-3 astro-island div div.px-2定位单个章节项,确保listItems包含所有章节元素 - 添加CSS选择器生成函数:
generateCSSSelector递归生成元素的完整路径,包含标签、ID、类名和兄弟元素位置,确保选择器唯一可定位 - 优化页面等待逻辑:改用
networkidle等待页面加载完成,确保动态渲染的章节列表完全加载;调整视口尺寸避免移动端布局影响元素选择 - 简化章节数据选择器:利用章节项的相对路径获取数据,避免依赖过于冗长的绝对选择器,提升代码稳定性
内容的提问来源于stack exchange,提问作者moises ramirez
相关产品推荐
相关产品推荐

