Puppeteer 迭代 table 个单元格并单击特定单元格
Puppeteer iterate table cells and click specific cells
我希望在 Puppeteer 中遍历 table(日历 table)并单击特定的单元格(日期)以切换其状态(至“离开”)。
我在下面包含了 table 的一个片段。每个 td 单元格包含两个 child divs,一个带有日期编号 (<div class="day_num">
),另一个带有标记为“AWAY”(<div class="day_content">
)。
到目前为止,我已经能够抓取 table,但这不允许我单击实际的单元格,因为抓取只是将 table 内容抓取到一个数组中。
如何遍历所有单元格并根据 child "day_num"
div 中包含的天数单击特定单元格?例如,我希望在下面的示例中单击第 8 天的 td,以切换它的状态。
<table class="calendar">
<tr class="days">
<td class="day">
<div class="day_num">7</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">8</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">9</div>
<div class="day_content">AWAY</div>
</td>
我目前的抓取代码是:
const result = await page.evaluate(() => {
const rows = document.querySelectorAll('.calendar tr td div');
return Array.from(rows, (row) => {
const columns = row.querySelectorAll('div');
return Array.from(columns, (column) => column.innerHTML);
});
});
console.log(result);
结果是:
[
[], [ '1', '' ], [ '2', 'AWAY' ],
[ '3', '' ], [ '4', '' ], [ '5', '' ],
[ '6', '' ], [ '7', '' ], [ '8', '' ],
[ '9', 'AWAY' ], [ '10', '' ], [ '11', '' ],
[ '12', '' ], [ '13', '' ], [ '14', '' ],
[ '15', '' ], [ '16', '' ], [ '17', '' ],
[ '18', '' ], [ '19', '' ], [ '20', '' ],
[ '21', '' ], [ '22', '' ], [ '23', '' ],
[ '24', '' ], [ '25', '' ], [ '26', '' ],
[ '27', '' ], [ '28', '' ], [ '29', '' ],
[ '30', '' ], [], [],
[], []
]
虽然你没有提供实时页面(所以我无法验证任意 JS、可见性和时间不会导致此失败),但我会尝试一下,看看以下是否有效,假设您的 HTML 几乎是静态的:
const puppeteer = require("puppeteer"); // ^13.0.1
let browser;
(async () => {
const html = `
<body>
<table class="calendar">
<tr class="days">
<td class="day">
<div class="day_num">7</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">8</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">9</div>
<div class="day_content">AWAY</div>
</td>
</tr>
</table>
<script>
[...document.querySelectorAll(".day_content")][1]
.addEventListener("click", e => {
e.target.textContent = "CLICKED";
})
;
</script>
</body>
`;
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
await page.setContent(html);
const xp = '//div[contains(@class, "day_num") and text()="8"]';
const [dayEl] = await page.$x(xp);
const dayContent = await dayEl.evaluate(el => {
const dayContent = el.closest(".day").querySelector(".day_content");
dayContent.click();
return dayContent.textContent;
});
console.log(dayContent); // => CLICKED
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
方法是在 class 和文本上使用 XPath 找到您感兴趣的 .day_num
元素,然后弹出树到 .day
元素并向下再次转到关联的 .day_content
元素以单击它。我添加了一个侦听器以在单击时更改文本以验证它确实被单击了。
您也可以在 .day_num
上使用 nextElementSibling
而不是 closest
/querySelector
组合,但这更多地假设了 [=12= 之间的关系] 和 .day_content
元素,并且可能会更脆。
此外,如果文本内容 "8"
可能有空格,您可以在 XPath 中使用 将其放宽一点。 '//div[contains(@class, "day_num") and contains(text(), "8")]'
,冒着误报和选择 "18"
和 "28"
的风险。在这种情况下,正则表达式或树遍历和 trim
可能更合适。断章取义 HTML 的这段摘录很难提出建议。
更进一步,听起来您需要在循环中单击多个元素并且正在努力做到这一点。这是一个适用于 mocked-up 版本网站的尝试:
const puppeteer = require("puppeteer"); // ^13.0.1
let browser;
(async () => {
const html = `
<body>
<table class="calendar">
<tr class="days"></tr>
</table>
<script>
for (let i = 0; i < 30; i++) {
document.querySelector(".days").innerHTML +=
\`<td class="day">
<div class="day_num">${i + 1}</div>
<div class="day_content"></div>
</td>\`
;
}
[...document.querySelectorAll(".day_content")].forEach(e =>
e.addEventListener("click", e => {
e.target.textContent = "AWAY";
})
);
</script>
</body>
`;
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
await page.setContent(html);
const awayDatesInMonth = [5, 12, 18, 20];
for (const day of awayDatesInMonth) {
const xp = `//div[contains(@class, "day_num") and text()="${day}"]`;
const [dayEl] = await page.$x(xp);
const dayContent = await dayEl.evaluate(el =>
el.closest(".day").querySelector(".day_content").click()
);
}
/* or if you can assume the elements are correctly indexed */
const days = await page.$$(".day_content");
for (const day of awayDatesInMonth) {
await days[day-1].evaluate(el => el.click());
}
/* --- */
console.log(await page.content());
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
如果这不起作用,请提供您自己的 mock-up,它能更好地代表您正在使用的原始站点,这样我就可以确定我正在解决相关问题。
我希望在 Puppeteer 中遍历 table(日历 table)并单击特定的单元格(日期)以切换其状态(至“离开”)。
我在下面包含了 table 的一个片段。每个 td 单元格包含两个 child divs,一个带有日期编号 (<div class="day_num">
),另一个带有标记为“AWAY”(<div class="day_content">
)。
到目前为止,我已经能够抓取 table,但这不允许我单击实际的单元格,因为抓取只是将 table 内容抓取到一个数组中。
如何遍历所有单元格并根据 child "day_num"
div 中包含的天数单击特定单元格?例如,我希望在下面的示例中单击第 8 天的 td,以切换它的状态。
<table class="calendar">
<tr class="days">
<td class="day">
<div class="day_num">7</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">8</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">9</div>
<div class="day_content">AWAY</div>
</td>
我目前的抓取代码是:
const result = await page.evaluate(() => {
const rows = document.querySelectorAll('.calendar tr td div');
return Array.from(rows, (row) => {
const columns = row.querySelectorAll('div');
return Array.from(columns, (column) => column.innerHTML);
});
});
console.log(result);
结果是:
[
[], [ '1', '' ], [ '2', 'AWAY' ],
[ '3', '' ], [ '4', '' ], [ '5', '' ],
[ '6', '' ], [ '7', '' ], [ '8', '' ],
[ '9', 'AWAY' ], [ '10', '' ], [ '11', '' ],
[ '12', '' ], [ '13', '' ], [ '14', '' ],
[ '15', '' ], [ '16', '' ], [ '17', '' ],
[ '18', '' ], [ '19', '' ], [ '20', '' ],
[ '21', '' ], [ '22', '' ], [ '23', '' ],
[ '24', '' ], [ '25', '' ], [ '26', '' ],
[ '27', '' ], [ '28', '' ], [ '29', '' ],
[ '30', '' ], [], [],
[], []
]
虽然你没有提供实时页面(所以我无法验证任意 JS、可见性和时间不会导致此失败),但我会尝试一下,看看以下是否有效,假设您的 HTML 几乎是静态的:
const puppeteer = require("puppeteer"); // ^13.0.1
let browser;
(async () => {
const html = `
<body>
<table class="calendar">
<tr class="days">
<td class="day">
<div class="day_num">7</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">8</div>
<div class="day_content"></div>
</td>
<td class="day">
<div class="day_num">9</div>
<div class="day_content">AWAY</div>
</td>
</tr>
</table>
<script>
[...document.querySelectorAll(".day_content")][1]
.addEventListener("click", e => {
e.target.textContent = "CLICKED";
})
;
</script>
</body>
`;
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
await page.setContent(html);
const xp = '//div[contains(@class, "day_num") and text()="8"]';
const [dayEl] = await page.$x(xp);
const dayContent = await dayEl.evaluate(el => {
const dayContent = el.closest(".day").querySelector(".day_content");
dayContent.click();
return dayContent.textContent;
});
console.log(dayContent); // => CLICKED
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
方法是在 class 和文本上使用 XPath 找到您感兴趣的 .day_num
元素,然后弹出树到 .day
元素并向下再次转到关联的 .day_content
元素以单击它。我添加了一个侦听器以在单击时更改文本以验证它确实被单击了。
您也可以在 .day_num
上使用 nextElementSibling
而不是 closest
/querySelector
组合,但这更多地假设了 [=12= 之间的关系] 和 .day_content
元素,并且可能会更脆。
此外,如果文本内容 "8"
可能有空格,您可以在 XPath 中使用 '//div[contains(@class, "day_num") and contains(text(), "8")]'
,冒着误报和选择 "18"
和 "28"
的风险。在这种情况下,正则表达式或树遍历和 trim
可能更合适。断章取义 HTML 的这段摘录很难提出建议。
更进一步,听起来您需要在循环中单击多个元素并且正在努力做到这一点。这是一个适用于 mocked-up 版本网站的尝试:
const puppeteer = require("puppeteer"); // ^13.0.1
let browser;
(async () => {
const html = `
<body>
<table class="calendar">
<tr class="days"></tr>
</table>
<script>
for (let i = 0; i < 30; i++) {
document.querySelector(".days").innerHTML +=
\`<td class="day">
<div class="day_num">${i + 1}</div>
<div class="day_content"></div>
</td>\`
;
}
[...document.querySelectorAll(".day_content")].forEach(e =>
e.addEventListener("click", e => {
e.target.textContent = "AWAY";
})
);
</script>
</body>
`;
browser = await puppeteer.launch({headless: true});
const [page] = await browser.pages();
await page.setContent(html);
const awayDatesInMonth = [5, 12, 18, 20];
for (const day of awayDatesInMonth) {
const xp = `//div[contains(@class, "day_num") and text()="${day}"]`;
const [dayEl] = await page.$x(xp);
const dayContent = await dayEl.evaluate(el =>
el.closest(".day").querySelector(".day_content").click()
);
}
/* or if you can assume the elements are correctly indexed */
const days = await page.$$(".day_content");
for (const day of awayDatesInMonth) {
await days[day-1].evaluate(el => el.click());
}
/* --- */
console.log(await page.content());
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
如果这不起作用,请提供您自己的 mock-up,它能更好地代表您正在使用的原始站点,这样我就可以确定我正在解决相关问题。