
puppeteer是一个node库,它提供了一个高级api来通过devtools协议控制chrome或chromium。这意味着你可以像人类用户一样,通过代码模拟浏览器行为,例如导航页面、点击按钮、填写表单,以及最重要的——从动态加载的网页中提取数据。对于像tripadvisor这样大量使用javascript渲染内容的网站,puppeteer是进行网页抓取的理想工具。
在开始之前,请确保你已安装Node.js,并通过npm安装了Puppeteer:
npm init -y npm install puppeteer
网页抓取的核心挑战在于准确地识别和定位目标数据所在的HTML元素。许多网站,特别是动态内容网站,会频繁更新其HTML结构或使用动态生成的类名,这使得选择器容易失效。因此,构建稳定且具有韧性的CSS选择器至关重要。
以TripAdvisor的景点列表页为例,我们需要抓取每个景点卡片的标题、链接、图片和描述。
初始错误与修正:
初学者常犯的错误是使用过于泛化或依赖动态类名的选择器。例如,尝试使用#lithium-root .jemSU或.VLKGO等类名作为主选择器,但这些类名可能随时改变,或者在DOM中并非唯一标识。
正确的策略是寻找更稳定的HTML结构。观察TripAdvisor页面,每个景点通常被包裹在一个<article>标签内。这是一个语义化的标签,通常用于表示独立的、可分发的内容单元,因此它是一个相对稳定的容器。
获取标题和链接:
在<article>内部,标题通常位于一个链接(<a>标签)内,并且这个链接可能没有额外的类名,或者其父元素有一个稳定的类名,例如.VLKGO。
代码示例:获取标题
以下代码片段演示了如何使用page.$$eval或page.evaluate来获取页面上所有景点卡片的标题。
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: true }); // 生产环境建议设置为true
const page = await browser.newPage();
const url = 'https://www.tripadvisor.com/Attractions-g297476-Activities-c42-Cartagena_Cartagena_District_Bolivar_Department.html';
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.waitForSelector('main', { timeout: 10000 }); // 等待主内容区加载
// 方法一:使用page.evaluate和querySelectorAll
const titles1 = await page.evaluate(() =>
Array.from(document.querySelectorAll('article .VLKGO a:not([class])'), (e) => ({
title: e.innerText.split('.').pop().trim() // 移除序号,清理文本
}))
);
console.log('Titles (Method 1):', titles1);
// 方法二:使用page.$$eval
const titles2 = await page.$$eval('article .VLKGO span > div', el =>
el.map(x => x.textContent.split('.').pop().trim())
);
console.log('Titles (Method 2):', titles2);
await browser.close();
})().catch(err => console.error(err));为了获取每个景点卡片的完整信息,我们将采用更细致的策略:首先定位所有景点卡片的容器(<article>),然后遍历每个容器,在其内部提取所需的各个字段。
完整抓取流程:
示例代码:抓取标题、链接、图片、描述、价格等
const puppeteer = require("puppeteer");
let browser; // 声明浏览器实例以便在finally块中关闭
(async () => {
browser = await puppeteer.launch({ headless: true }); // 建议生产环境设为true
const page = await browser.newPage();
const url = 'https://www.tripadvisor.com/Attractions-g297476-Activities-c42-Cartagena_Cartagena_District_Bolivar_Department.html';
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.waitForSelector('main', { timeout: 10000 }); // 等待主要内容区加载
// 获取所有景点卡片的元素句柄
let places = await page.$$('article');
let data = [];
for (let place of places) {
let header = {};
let image = '';
let desc = '';
let by = {};
let price = '';
let priceTxt = '';
try {
// 提取标题和链接
header = await place.$eval('.VLKGO a:not([class])', el => {
// 移除标题前的序号(如 "1.")
const name = el.textContent.split('.').pop().trim();
const link = el.getAttribute('href');
return { name, link: `https://www.tripadvisor.com${link}` }; // 拼接完整链接
});
} catch (e) {
console.warn("Title/Link not found for a card:", e.message);
// 可以选择跳过或赋予默认值
}
try {
// 提取图片URL,通常在srcset中包含多尺寸图片,取最大的
image = await place.$eval('picture > img[srcset]', el => {
const srcset = el.getAttribute('srcset');
if (srcset) {
// 获取最后一个(通常是最大尺寸)的图片URL,并移除' 2x'等描述
return srcset.split(',').pop().replace(/2x/gi, '').trim();
}
return '';
});
} catch (e) {
console.warn("Image not found for a card:", e.message);
}
try {
// 提取描述
desc = await place.$eval('a:not([class]) > div > span', el => el.textContent.trim());
} catch (e) {
console.warn("Description not found for a card:", e.message);
}
try {
// 提取提供者/作者信息
by = await place.$eval('.VLKGO div > div > div > a', el => {
const name = el.textContent.replace('By ', '').trim();
const link = el.getAttribute('href');
return { name, link: `https://www.tripadvisor.com${link}` };
});
} catch (e) {
// 提供者信息可能不是所有卡片都有
// console.warn("Provider info not found for a card:", e.message);
}
try {
// 提取价格(如果存在)
price = await place.$eval('[data-automation=cardPrice]', el => el.textContent.trim());
} catch (e) {
// 价格信息可能不是所有卡片都有
}
try {
// 提取价格文本(如 "起" 或 "每人")
priceTxt = await place.$eval('div:nth-child(1) > div:nth-child(3):not([class])', el => el.textContent.trim());
} catch (e) {
// 价格文本可能不是所有卡片都有
}
// 将提取到的数据添加到结果数组
data.push({
name: header.name || 'N/A',
link: header.link || 'N/A',
desc: desc || 'N/A',
image: image || 'N/A',
price: price || 'N/A',
priceTxt: priceTxt || 'N/A',
by: by.name ? by : { name: 'N/A', link: 'N/A' }
});
}
console.log(JSON.stringify(data, null, 2)); // 打印结构化的JSON数据
})().catch(err => console.error("An error occurred:", err)).finally(() => {
// 确保浏览器在任何情况下都被关闭
if (browser) {
browser.close();
}
});通过本教程,我们学习了如何使用Puppeteer从TripAdvisor这样的动态网站抓取结构化数据。关键在于理解Puppeteer的工作原理,构建稳定的CSS选择器,并采用分步遍历的方式提取多字段信息。掌握这些技术,你将能够应对各种复杂的网页抓取挑战,为数据分析、市场研究等提供有力支持。记住,在进行任何网页抓取活动时,始终要遵守道德规范和法律法规。
以上就是使用Puppeteer高效抓取TripAdvisor景点数据:完整指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号