先决条件
如果您尚未搭建 Node.js 环境,请前往其官方网站下载适用于您操作系统的最新版本。随后创建一个新目录,并运行以下命令初始化项目:
npm init -y
我们将使用 TypeScript 编写代码。作为 JavaScript 的超集,它增加了可选的静态类型检查及其他功能。这对于大型项目非常有用,并能帮助您更早地发现错误。您需要将其添加到项目的开发依赖项中,并初始化其配置文件:
npm install typescript -save-dev npx tsc -init
请确保在新生成的 tsconfig.json 文件中,将 “outDir” 属性设置为 “dist”,因为我们计划将 TypeScript 代码与编译后的代码分开存放。
最后,以下命令将 Puppeteer 添加到项目依赖中:
npm install puppeteer
Puppeteer 是一个 Node.js 库,它提供了一个用于控制无头 Chrome 浏览器的高级 API,可用于网页抓取和自动化任务
数据来源
在本教程中,我们选择抓取葡萄牙马德拉群岛的房产信息:https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15。 请务必在 URL 中添加入住和退房日期,以确保获取到完整的房源信息。
本指南涵盖以下房产数据的提取:
- 名称
- URL
- 实际地址
- 价格
- 评分和评论数量
- 缩略图
您可以在下方的截图中看到这些元素已被高亮显示:
通过在这些元素上打开开发者工具,您将能够看到我们将用于定位 HTML 元素的 CSS 选择器。如果您对 CSS 选择器的运作机制还不太熟悉,欢迎查阅这篇入门指南。
数据解析
由于所有房源列表的结构和数据都相同,我们可以在算法中提取整个房源列表的所有信息。运行脚本后,我们可以遍历所有结果并将它们合并为一个列表。
初次浏览 HTML 文档时,您可能已经注意到 Booking 网站结构相当复杂,且类名大多是随机生成的。
幸运的是,该网站并非完全依赖类名,我们可以利用特定属性的值作为提取依据。在上图截图中,我们标出了房产缩略图、名称和 URL 的获取路径。
import puppeteer from 'puppeteer';
async function scrapeBookingData(booking_url: string): Promise<void> {
// Launch Puppeteer
const browser = await puppeteer.launch({
headless: false,
args: ['--start-maximized'],
defaultViewport: null
})
const page = await browser.newPage()
// Navigate to the channel URL
await page.goto(booking_url)
// Extract listings name
const listings_name = await page.evaluate(() => {
const names = document.querySelectorAll('div[data-testid="title"]')
const names_array = Array.from(names)
return names ? names_array.map(n => n.textContent) : []
})
console.log(listings_name)
// Extract listings location
const listings_location = await page.evaluate(() => {
const locations = document.querySelectorAll('a[data-testid="title-link"]')
const locations_array = Array.from(locations)
return locations ? locations_array.map(l => l.getAttribute('href')) : []
})
console.log(listings_location)
// Extract listings thumbnail
const listings_thumbnail = await page.evaluate(() => {
const thumbnails = document.querySelectorAll('[data-testid="image"]')
const thumbnails_array = Array.from(thumbnails)
return thumbnails ? thumbnails_array.map(t => t.getAttribute('src')) : []
})
console.log(listings_thumbnail)
await browser.close()
}
scrapeBookingData("https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15")
我们使用 Puppeteer 打开了一个浏览器实例,创建新页面,导航至目标 URL,提取上述数据,然后关闭浏览器。为了便于可视化调试,我使用了浏览器的非无头模式。
如上所述,得益于为 HTML 元素分配了唯一值的“data-testid”属性,这些数据很容易获取。运行以下命令以执行脚本:
npx tsc && node dist/index.js
终端应显示 3 条大小相同的列表结果,分别代表当前页面上所有房产的名称、URL 和缩略图。
在 HTML 文档的下一部分,我们突出了房产的地址、评分和评论数量。
// Extract listings address
const listings_address = await page.evaluate(() => {
const addresses = document.querySelectorAll('[data-testid="address"]')
const addresses_array = Array.from(addresses)
return addresses ? addresses_array.map(a => a.textContent) : []
})
console.log(listings_address)
// Extract listings rating and review count
const listings_rating = await page.evaluate(() => {
const ratings = document.querySelectorAll('[data-testid="review-score"]')
const ratings_array = Array.from(ratings)
return ratings ? ratings_array.map(r => r.textContent) : []
})
console.log(listings_rating)
与之前一样,我们使用了“data-testid”属性。再次运行脚本应会显示另外 2 个列表,与之前的列表形式相同。
最后,在文档的末段,我们提取了房产的价格。代码与之前操作并无二致:
// Extract listings price
const listings_price = await page.evaluate(() => {
const prices = document.querySelectorAll('[data-testid="price-and-discounted-price"]')
const prices_array = Array.from(prices)
return prices ? prices_array.map(p => p.textContent) : []
})
console.log(listings_price)
为了便于对提取的数据进行后续处理,我们将把生成的列表合并为一个。
// Group the lists
const listings = []
for (let i = 0; i < listings_name.length; i++) {
listings.push({
name: listings_name[i],
url: listings_location[i],
address: listings_address[i],
price: listings_price[i],
ratings: listings_rating[i],
thumbnails: listings_thumbnail[i]
})
}
console.log(listings)
最终结果应如下所示:
[
{
name: 'Pestana Churchill Bay',
url: 'https://www.booking.com/hotel/pt/pestana-churchill-bay.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=1&hapos=1&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=477957801_262227867_0_1_0&highlighted_blocks=477957801_262227867_0_1_0&matching_block_id=477957801_262227867_0_1_0&sr_pri_blocks=477957801_262227867_0_1_0__18480&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Câmara de Lobos',
price: '911 lei',
ratings: '9.0Wonderful 727 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/202313893.webp?k=824dc3908c4bd3e80790ce011f763f10fd4064dcb5708607f020f2e7c92d130e&o=&s=1'
},
{
name: 'Hotel Madeira',
url: 'https://www.booking.com/hotel/pt/madeira-funchal.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=2&hapos=2&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=57095605_262941681_2_1_0&highlighted_blocks=57095605_262941681_2_1_0&matching_block_id=57095605_262941681_2_1_0&sr_pri_blocks=57095605_262941681_2_1_0__21200&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Se, Funchal',
price: '1,045 lei',
ratings: '8.3Very Good 647 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/364430623.webp?k=8c1e510da2aad0fc9ff5731c3874e05b1c4cceec01a07ef7e9db944799771724&o=&s=1'
},
{
name: 'Les Suites at The Cliff Bay - PortoBay',
url: 'https://www.booking.com/hotel/pt/les-suites-at-the-cliff-bay.html?aid=304142&label=gen173nr-1FCAQoggJCFnNlYXJjaF9tYWRlaXJhIGlzbGFuZHNIMVgEaMABiAEBmAExuAEXyAEM2AEB6AEB-AEDiAIBqAIDuAK9luydBsACAdICJGViMWY2MmRjLWJhZmEtNGZhZC04MDAyLWQ4MmU3YjU5MTMwZtgCBeACAQ&ucfs=1&arphpl=1&checkin=2023-01-13&checkout=2023-01-15&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&hpos=3&hapos=3&sr_order=popularity&srpvid=42cc81de452009eb&srepoch=1673202494&all_sr_blocks=395012401_247460894_2_1_0&highlighted_blocks=395012401_247460894_2_1_0&matching_block_id=395012401_247460894_2_1_0&sr_pri_blocks=395012401_247460894_2_1_0__100000&tpi_r=2&from_sustainable_property_sr=1&from=searchresults#hotelTmpl',
address: 'Sao Martinho, Funchal',
price: '4,928 lei',
ratings: '9.5Exceptional 119 reviews',
thumbnails: 'https://cf.bstatic.com/xdata/images/hotel/square200/270120962.webp?k=68ded1031f5082597c48eb25c833ea7fcedc2ec2bc5d555adfcac98b232f9745&o=&s=1'
}
]替代方案
尽管本教程至此看似简单明了,但我们必须提及网络爬虫中常见的注意事项,尤其是在您希望扩大项目规模时。
如今,网站会采用各种机器人检测技术并收集浏览器数据,以此来防止或拦截自动化流量。Booking.com 也不例外。该网站利用 PerimeterX 防护机制,会对您的 IP 地址进行检查并收集多项信息:
- Navigator对象的属性(deviceMemory、languages、platform、userAgent、webdriver等)
- 字体和插件枚举
- 屏幕尺寸检测
- 以及更多信息。
应对这些挑战的一个解决方案是使用爬取 API,它提供了一种简单可靠的方式,让您无需构建和维护自己的爬虫程序,即可从 Booking.com 等网站获取数据。
WebScrapingAPI 正是这样一款产品,它利用代理轮换来绕过 CAPTCHA,并通过随机化浏览器数据来模拟真实用户。开始使用时,只需注册一个账户,然后从仪表盘获取您的 API 密钥。该密钥用于验证您的请求。
若要快速在现有的 Node.js 项目中测试该 API,我们可以使用其对应的 SDK。只需运行以下命令:
npm install webscrapingapi
现在,您只需将之前的 CSS 选择器调整为 API 格式即可。提取规则功能让您只需进行微调即可解析数据,使其成为您网络爬虫工具箱中的强大工具。
import webScrapingApiClient from 'webscrapingapi';
const client = new webScrapingApiClient("YOUR_API_KEY");
async function exampleUsage() {
const api_params = {
'render_js': 1,
'proxy_type': 'datacenter',
'timeout': 60000,
'extract_rules': JSON.stringify({
names: {
selector: 'div[data-testid="title"]',
output: 'text',
all: '1'
},
locations: {
selector: 'a[data-testid="title-link"]',
output: '@href',
all: '1'
},
addresses: {
selector: '[data-testid="address"]',
output: 'text',
all: '1'
},
prices: {
selector: '[data-testid="price-and-discounted-price"]',
output: 'text',
all: '1'
},
ratings: {
selector: '[data-testid="review-score"]',
output: 'text',
all: '1'
},
thumbnails: {
selector: '[data-testid="image"]',
output: '@src',
all: '1'
}
})
}
const URL = "https://www.booking.com/searchresults.en-us.html?ss=Madeira+Islands&checkin=2023-01-13&checkout=2023-01-15"
const response = await client.get(URL, api_params)
if (response.success) {
// Group the lists
const listings = []
for (let i = 0; i < response.response.data.names.length; i++) {
listings.push({
name: response.response.data.names[i],
url: response.response.data.locations[i],
address: response.response.data.addresses[i],
price: response.response.data.prices[i],
ratings: response.response.data.ratings[i],
thumbnails: response.response.data.thumbnails[i]
})
}
console.log(listings)
} else {
console.log(response.error.response.data)
}
}
exampleUsage();结论
在本教程中,我们介绍了如何使用 Node.js 和 Puppeteer 抓取 Booking.com 的基础知识。我们向您展示了如何配置环境并提取葡萄牙马德拉岛的房源详情。不过,这些技术和概念同样适用于其他网站和数据源。
对于企业和数据科学家而言,网页抓取都是一种极其有用的工具。通过从 Booking.com 收集数据,您可以获得关于酒店业的宝贵见解、评估竞争对手等。但请务必注意,网页抓取可能违反某些网站的使用条款,因此在操作前务必查阅具体政策。
虽然您可以自行开发网络爬虫,但使用专业服务通常是更安全、更高效的选择,对于大型项目而言尤其如此。专业的爬虫服务具备专业知识和资源,能够应对可能出现的任何挑战,并提供高质量的结果。
希望您喜欢这篇教程,并已掌握在 Node.js 环境下从 Booking.com 收集有价值数据的技能。感谢阅读!




