Playwright 网页抓取:使用 Node.js 的分步教程
想要练习使用基于 JavaScript 的网站的技能吗?我们将向您展示如何操作。
从依赖动态元素或复杂的反机器人系统的网站收集数据并非易事。在提取信息之前,您需要像真实用户一样呈现整个页面。无头浏览器库 Playwright 非常适合这项工作。
在本分步指南中,您将了解 Playwright 为何是用于网页抓取 JavaScript 渲染网站的热门 Node.js 库。您还可以通过真实示例练习您的技能。
什么是使用 Playwright 进行网页抓取?
使用 Playwright 进行网页抓取是指从 JavaScript 渲染的网站收集数据的过程。该工具通过编程方式控制一个无头浏览器。由于它没有像标签栏这样的用户界面,Playwright 无需加载任何可视元素。这在网页抓取时可以节省大量资源。
Playwright 是微软于 2018 年开发的一个相对较新的库。它用于自动执行不同浏览器上的操作:模拟滚动、单击、下载 - 所有可以用鼠标执行的操作。Playwright 让您在无头和有头模式下完全控制浏览器。最重要的是,它能够呈现 JavaScript,而常规 HTTP 库无法做到这一点。这使得 Playwright 成为从现代网站抓取动态内容的强大工具。
为什么要使用 Playwright 进行网页抓取?
Playwright 用于网页抓取有以下几个原因:
- 跨浏览器支持。 该库可以模拟 Chromium、Firefox 和 WebKit。
- 跨语言支持。 Playwright 支持 JavaScript、Python、Java、TypeScript 和 .NET。
- 与任何操作系统一起使用。 您可以在 Windows、Linux 或 macOS 上使用 Playwrit。
- 支持异步和同步方法。 剧作家是 默认情况下是异步的;它允许您发出并发请求并并行抓取多个页面。或者,您可以一次发出一个请求以降低复杂性。
- 很好的表演者。 该库使用在抓取数据时保持打开状态的 WebSocket 连接。因此您可以一次发送多个请求。这极大地提高了性能。
- 非常适合欺骗浏览器指纹。 Playwright 拥有像 playwright-extra 这样的软件包,可以防止机器人检测。
- 好的文档。 尽管 Playwright 是网络抓取领域的新手,但它拥有丰富的文档和示例。
Node.js 和 Playwright 网页抓取:分步教程
在本教程中,我们将抓取电影年份、片名、提名、奖项以及电影是否获得最佳影片奖。您将练习一些网页抓取技能,例如:
- 抓取单个页面;
- 处理多页;
- 等待元素加载;
- 通过点击按钮加载动态内容;
- 刮桌子;
- 处理错误;
- 将输出写入.json 格式。
硬件需求
导入库
第一步:导入必要元素:剧本库和文件系统。这将允许您在计算机上进行创作,并稍后输出结果。
import playwright from 'playwright'
import fs from 'fs'
步骤 2.然后,输入要抓取的 URL,并创建films_list来保存输出。
const url = 'https://www.scrapethissite.com/pages/ajax-javascript/'
var films_list = []
准备刮擦
步骤 1.在本网页抓取示例中,我们将使用 Chromium 浏览器。为了让您了解浏览器的运行方式,我们将通过将headless设置为false来使用 headful 模式。
async function prepare_browser() {
const browser = await playwright.chromium.launch({
headless: false,
})
return browser
}
步骤 2.现在,我们来编写main()函数来创建浏览器。然后,我们将使用创建的浏览器上下文打开一个新页面,并将其传递给名为get_page()的函数。这将开始抓取网页内容。
async function main() {
const browser = await prepare_browser()
const context = await browser.newContext()
const page = await context.newPage()
await get_page(page, url)
await browser.close()
}
main()
抓取单个或多个页面
从单个页面收集数据
第一步:现在,让我们抓取一个页面:
- 页面.goto() 告诉浏览器转到该 URL。
- 为了找到并点击页面上的年份按钮,我们需要创建一个 year_btn_selector 包含这些元素的 CSS 选择器的变量。这将允许您加载内容。右键单击页面上的任意位置并选择“检查”,检查页面源代码。
async function get_page(page, url) {
await page.goto(url)
const year_btn_selector = '.year-link'
步骤 2.然后,告诉 Playwright 等待,直到屏幕上至少出现一个按钮元素。我们设置超时时间为 20 秒。一旦出现一个元素,我们就可以认为页面已加载完毕并开始抓取。如果等待超时,您可以实现额外的重新加载或重试功能来解决问题。
await page.locator(year_btn_selector).first().waitFor({'timeout': 20_000})
从多个页面收集数据
步骤 3.现在,我们可以遍历所有按钮并获取它们的内容:
- 我们将使用相同的变量 year_btns。它存储我们使用 CSS 选择器获取的按钮信息。然后,我们将遍历按钮以获取其内容。
为了收集每个表格每年(2010-2015 年)的数据,我们将创建一个名为scrape_table的函数。该函数将浏览器“页面”和“年份”变量作为参数。在此上下文中,“年份”变量对应于代表特定年份的按钮。
for (let year of year_btns) {
await scrape_table(page, year)
}
整个功能:
async function get_page(page, url) {
await page.goto(url)
const year_btn_selector = '.year-link'
await page.locator(year_btn_selector).first().waitFor({'timeout': 20_000})
let year_btns = await page.locator(year_btn_selector).all()
for (let year of year_btns) {
await scrape_table(page, year)
}
}
刮擦桌子
步骤 1.首先,从按钮获取文本“2015”,并将其保存在year_text变量中。
然后,告诉 Playwright 点击年份按钮并等待内容出现。这次我们要定位一张桌子。
async function scrape_table(page, year){
let year_text = await year.textContent()
await year.click()
const table_selector = 'table.table'
await page.locator(table_selector).waitFor({'timeout': 20_000})
步骤 2.表格出现后,我们可以抓取所有行。表格行(<tr>)的类名为“film”,因此我们需要选中它。
let table_rows = await page.locator('.film').all()
获取数据
步骤一:现在,让我们遍历表格的每一行,并使用每列的 CSS 选择器获取电影信息。这些信息存储在一个名为film_info 的字典中,其中包括电影的上映年份。
for (let row of table_rows) {
let film_info = {
'film-year': year_text,
'film-title': await row.locator('.film-title').textContent(),
'film-nominations': await row.locator('.film-nominations').textContent(),
'film-awards': await row.locator('.film-awards').textContent(),
}
步骤 2.然后,检查该行中是否存在图标(<i>)元素。如果存在,则表示该影片获得了最佳影片奖。因此,我们需要在字典中添加另一个键film_best_picture,并将其值设置为“true”,否则设置为“false”。
if (await row.locator('i').count()>0){
film_info['film-best-picture'] = true
} else {
film_info['film-best-picture'] = false
}
步骤 3.之后,在控制台中打印结果以查看输出。film_info字典会被添加到films_list列表(我们最开始创建的列表)中。
console.log(film_info)
films_list.push(film_info)
}
整个功能:
async function scrape_table(page, year){
let year_text = await year.textContent()
await year.click()
const table_selector = 'table.table'
await page.locator(table_selector).waitFor({'timeout': 20_000})
let table_rows = await page.locator('.film').all()
for (let row of table_rows) {
let film_info = {
'film-year': year_text,
'film-title': await row.locator('.film-title').textContent(),
'film-nominations': await row.locator('.film-nominations').textContent(),
'film-awards': await row.locator('.film-awards').textContent(),
}
if (await row.locator('i').count()>0){
film_info['film-best-picture'] = true
} else {
film_info['film-best-picture'] = false
}
console.log(film_info)
films_list.push(film_info)
}
}
JSON 输出
现在抓取部分已经完成,我们可以以结构化的 .json 格式打印出对象列表(films_list )。
write_output()函数会将抓取的数据写入 .json 文件。其工作原理如下:
function write_output() {
fs.writeFile('output.json', JSON.stringify(films_list, null, 2), (err) => {
if (err) {
console.log(err)
} else {
console.log("Output written successfully")
}
})
}
- JSON.stringify(films_list,null,2):转换 影片列表 转换为 JSON 格式的字符串,并缩进 2 个空格以提高可读性。
- fs.writeFile('输出.json',...):将 JSON 格式的字符串写入名为“output.json”的文件。
- (错误)=> {…}: 处理写入过程中任何错误的回调函数。
- console.log(错误):如果发生错误。
- console.log(“输出写入成功”):如果写入过程顺利完成且没有错误。
完整主要功能:
async function main() {
const browser = await prepare_browser()
const context = await browser.newContext()
const page = await context.newPage()
await get_page(page, url)
await browser.close()
write_output()
}
这是完整的代码:
import playwright from 'playwright'
import fs from 'fs'
const url = 'https://www.scrapethissite.com/pages/ajax-javascript/'
var films_list = []
async function prepare_browser() {
const browser = await playwright.chromium.launch({
headless: false,
})
return browser
}
async function scrape_table(page, year){
let year_text = await year.textContent()
await year.click()
const table_selector = 'table.table'
await page.locator(table_selector).waitFor({'timeout': 20_000})
let table_rows = await page.locator('.film').all()
for (let row of table_rows) {
let film_info = {
'film-year': year_text,
'film-title': await row.locator('.film-title').textContent(),
'film-nominations': await row.locator('.film-nominations').textContent(),
'film-awards': await row.locator('.film-awards').textContent(),
}
if (await row.locator('i').count()>0){
film_info['film-best-picture'] = true
} else {
film_info['film-best-picture'] = false
}
console.log(film_info)
films_list.push(film_info)
}
}
async function get_page(page, url) {
await page.goto(url)
const year_btn_selector = '.year-link'
await page.locator(year_btn_selector).first().waitFor({'timeout': 20_000})
let year_btns = await page.locator(year_btn_selector).all()
for (let year of year_btns) {
await scrape_table(page, year)
}
}
function write_output() {
fs.writeFile('output.json', JSON.stringify(films_list, null, 2), (err) => {
if (err) {
console.log(err)
} else {
console.log("Output written successfully")
}
})
}
async function main() {
const browser = await prepare_browser()
const context = await browser.newContext()
const page = await context.newPage()
await get_page(page, url)
await browser.close()
write_output()
}
main()
剧作家的替代品
虽然 Playwright 是一个功能强大的抓取动态元素的库,但它面临着激烈的竞争。
Puppeteer 是另一款出色的 JavaScript 渲染网站工具。它由 Google 提供支持,并且拥有更大、更活跃的社区。
我们比较了两家知名公司创建的两个 Node.js 无头浏览器库。选择更适合网页抓取的方法。
Selenium 是业界的老牌企业,因此它支持更多的编程语言,拥有更大的支持社区,并且可与您能想到的任何浏览器兼容。
看看这两个流行的无头浏览器库的比较结果。
如果您想尝试使用 Node.js 抓取静态页面,我们建议您使用 axios 和 Cheerio。前者是最流行的 Node.js HTTP 客户端之一,可以抓取页面。后者是一个强大的数据下载解析器。
使用 Node.js 进行网页抓取的分步指南:两个示例。