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 格式。
硬件需求
导入库
步骤 1。 导入必要的元素:Playwright 库和文件系统。它将允许您在计算机上工作并稍后写入输出。
import playwright from 'playwright'
import fs from 'fs'
步骤 2。 然后,输入要抓取的 URL 并创建 影片列表 这将保存输出。
const url = 'https://www.scrapethissite.com/pages/ajax-javascript/'
var films_list = []
准备刮擦
步骤 1。 在此网页抓取示例中,我们将使用 Chromium 浏览器。为了让您了解浏览器的运行情况,我们将通过指定以下方式使用 headful 模式: 无头 至 false.
async function prepare_browser() {
const browser = await playwright.chromium.launch({
headless: false,
})
return browser
}
步骤 2。 现在,让我们写 主() 函数来创建浏览器。然后,我们将使用创建的浏览器上下文打开一个新页面,并将其传递给名为 获取页面(). 它将开始抓取。
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()
抓取单个或多个页面
从单个页面收集数据
步骤 1。 现在,让我们抓取一个页面:
- 页面.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。 当表格出现时,我们可以抓取所有行。表格行 () 的类别是“电影”,所以我们需要选择它。
let table_rows = await page.locator('.film').all()
获取数据
步骤 1。 现在,让我们遍历表格行并使用每列的 CSS 选择器获取电影信息。信息存储在 影片信息 词典,包括出版年份。
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。 然后,检查图标() 元素在该行中存在。如果存在,则该电影获得了最佳影片奖。因此,我们需要添加另一个键 最佳影片 添加到字典中并赋予其“true”值,否则为“false”。
if (await row.locator('i').count()>0){
film_info['film-best-picture'] = true
} else {
film_info['film-best-picture'] = false
}
步骤 3。 之后,在控制台打印出结果,查看输出。 影片信息 字典被推入 影片列表 列表(我们最开始创建的列表)。
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 格式。
此 写入输出() 函数将处理将抓取的数据写入 .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 进行网页抓取的分步指南:两个示例。