在网页自动化数据抓取的过程中,很多时候我们需要面对一些复杂的场景,比如需要处理动态加载的数据、分页抓取、条件筛选和复选框等复杂的用户交互元素。本文将结合具体案例,总结如何设计一个通用的自动化抓取方案,以抓取特定地区的招采数据为例,详细探讨如何在动态网页中操作元素、处理分页、并确保数据的完整性和可靠性。
本次任务要求实现以下几个功能点:
为实现这些需求,我们主要使用了 JavaScript 的异步编程(async/await)和 DOM 操作的方法,通过等待页面元素加载、点击事件、选择框勾选等方式来获取数据。
我们将整个流程划分为几个主要步骤:
getPage:控制整个抓取过程,包括点击操作、页面数据获取、分页数据循环和筛选条件清空。retrieveCardData:处理分页逻辑,在每页抓取数据,并控制翻页按钮的更新。waitForElement:通过异步轮询检查元素是否加载完成,确保元素可操作性。以下是整个代码的详细设计及其各个函数的功能:
getNextRegconst reg = ['河北省', '天津市', '钓鱼岛']; // 定义地区数组
let moreButtonClicked = false; // 标记“更多”按钮是否已经点击过
function getNextReg(currentRegion) {
const currentIndex = reg.indexOf(currentRegion);
return currentIndex < reg.length - 1 ? reg[currentIndex + 1] : null;
}getNextReg 通过获取当前地区的索引,返回下一个地区。当到达数组末尾时返回 null,以便在 getPage 中判断是否继续抓取。
getPage该函数控制了每个地区的抓取流程,包括:点击“更多”按钮、勾选地区复选框、点击搜索按钮获取数据、循环翻页抓取当前地区全部数据,最后清除筛选条件准备进入下一个地区。
async function getPage(page, region) {
console.log(`开始处理地区: ${region}`);
if (!moreButtonClicked) {
await waitForElement('div.read-more__toggle__text');
const moreButtonContainers = document.querySelectorAll('div.read-more__toggle__text');
for (const div of moreButtonContainers) {
const span = div.querySelector('span.left-text');
if (span && span.innerText.trim() === "更多") {
span.click();
console.log('已点击“更多”按钮');
moreButtonClicked = true;
await new Promise(resolve => setTimeout(resolve, 1000)); // 确保点击生效
break;
}
}
}
// 等待页面元素加载
await waitForElement('input[type="radio"]');
await waitForElement('.checkbox-content-text .checkbox-name');
await waitForElement('.card_box');
// 设置为第二个单选项
const radioOptions = document.querySelectorAll('input[type="radio"]');
if (radioOptions.length >= 2 && !radioOptions[1].checked) {
radioOptions[1].checked = true;
radioOptions[1].dispatchEvent(new Event('change', { bubbles: true }));
}
// 选择地区
let labelElement = Array.from(document.querySelectorAll('.checkbox-content-text .checkbox-name'))
.find(el => el.textContent.includes(region));
if (labelElement) {
const checkbox = labelElement.closest('.cascader-menu-item').querySelector('input[type="checkbox"]');
if (!checkbox.closest('.el-checkbox__input').classList.contains('is-checked')) {
checkbox.click();
console.log(`${region} 已被选中`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
const search_btn = document.querySelector('.search-btn');
if (search_btn) {
search_btn.click();
console.log('点击搜索按钮');
await new Promise(resolve => setTimeout(resolve, 1000));
}
await retrieveCardData(page);
const clearButton = document.querySelector('.filter-clear');
if (clearButton) {
clearButton.click();
console.log('已点击清空按钮,重置筛选条件');
await new Promise(resolve => setTimeout(resolve, 1000));
}
const nextRegion = getNextReg(region);
if (nextRegion) {
console.log(`准备处理下一个地区: ${nextRegion}`);
await getPage(1, nextRegion);
}
}retrieveCardData,完成当前地区的所有分页数据抓取。retrieveCardDataretrieveCardData 函数的作用是从当前页开始,循环翻页直到末页为止,获取所有分页数据。
async function retrieveCardData(page) {
while (true) {
if (page >= 20) break;
console.log(`当前是第 ${page} 页`);
await waitForElement('.card_box');
const cardItems = document.querySelectorAll('.card_box');
const cardDataList = Array.from(cardItems).map(card => ({
招采单位: card.querySelector('.field-item .company-name .content-wrap')?.innerText.trim() || '未找到',
标的物: card.querySelector('.subject-matter-list-item')?.innerText.trim() || '未找到',
发布时间: card.querySelector('.properties .value')?.innerText.trim() || '未找到',
联系方式: card.querySelector('.field-item.field-card .value')?.innerText.trim() || '未找到'
}));
console.log(`当前页面的卡片数量: ${cardItems.length}`);
console.log(cardDataList);
const pageButtons = document.querySelectorAll('.el-pager .number');
let needClick = Array.from(pageButtons).find(button => button.textContent.trim() === `${page + 1}`);
if (needClick) {
needClick.click();
console.log(`切换到第 ${needClick.textContent.trim()} 页`);
page += 1;
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待翻页完成
} else {
break;
}
}
}.card_box 元素,提取该页的所有招采信息。waitForElement在动态页面抓取中,waitForElement 是确保每次页面加载完成的重要手段。它通过轮询判断元素是否加载,避免了页面未加载完成就操作的错误。
async function waitForElement(selector, timeout = 5000) {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (document.querySelector(selector)) {
clearInterval(interval);
resolve();
} else if (Date.now() - startTime > timeout) {
clearInterval(interval);
reject(`等待元素 ${selector} 超时`);
}
}, 500);
});
}该函数通过一个定时器每 500 毫秒检测一次指定选择器的元素是否存在,直到加载成功或超
时。这样可以保证后续代码仅在元素加载后执行,减少错误。
在动态页面的数据抓取中,处理复杂的交互、动态数据加载和分页逻辑是一个挑战。本文以多地区招采信息抓取为例,通过 JavaScript 的异步编程、页面元素操作和分页循环处理,实现了一个较为完整的数据抓取方案。主要的技术亮点包括:
通过这些技巧,我们可以在类似的动态数据抓取项目中设计更加灵活、高效的解决方案,确保数据抓取的完整性和准确性。