简单的 Puppeteer 网页抓取教程
这是使用 Node.js 库 Puppeteer 进行网页抓取的分步指南。
这些年来,网站发生了翻天覆地的变化。抓取现代网站或单页应用程序需要渲染整个页面。它们依赖于懒加载和无限滚动等技术,因此传统的脚本帮不上什么忙。像 Puppeteer 这样的无头浏览器库在抓取动态网站方面发挥着关键作用。
在本分步指南中,您将了解为什么 Puppeteer 是用于网页抓取的如此流行的 Node.js 库。您还将找到一个如何抓取动态页面的真实示例。
什么是 Puppeteer 抓取?
使用 Puppeteer 进行网页抓取是指控制一个无头Chrome 浏览器从目标网站提取数据的过程。Puppeteer 是一个广泛使用的 Node.js 库,由 Google 的一个团队于 2018 年创建。尽管 Puppeteer 相对较新,但它已成为抓取依赖 JavaScript 的网站的热门选择。
Puppeteer 可以自动执行大多数浏览器交互,例如填写表格、移动鼠标、等待页面加载、截屏等操作。
该库由 Chrome 浏览器自动化开发人员维护,因此您将拥有最新的浏览器版本和功能。但是,它仅正确支持 Chrome 和 Chromium;对 Firefox 和 Microsoft Edge 的支持仍处于试验阶段。
使用 Puppeteer 进行网页抓取的优势
Puppeteer比其他无头浏览器库(例如 Selenium)快得多。主要原因是它使用了 Chromium 内置的 DevTools 协议,可以直接控制浏览器。此外,它资源占用低,因此执行速度相对较快。
该库拥有许多强大的插件,例如puppeteer-extra-plugin-stealth和puppeteer-extra-plugin-anonymize-ua,可用于伪造浏览器指纹。例如,您可以轮换请求头或用户代理。此外,您还可以将代理与 Puppeteer 集成。
Puppeteer 相对来说比较容易上手。例如,它不像 Selenium 那样内置集成开发环境 (IDE) 来编写脚本,所以你可以使用自己喜欢的 IDE。这意味着你需要编写的代码更少。此外,安装这个库也很快——只需添加 npm 或 yarn 包管理器并下载软件包即可。
除了丰富的功能外,Puppeteer还拥有完善的文档和庞大的用户社区。因此,如果您在使用过程中遇到任何困难,很快就能找到答案。
Node.js 和 Puppeteer Web 抓取:分步教程
两个链接都包含动态元素。有什么区别?第二个页面用于处理延迟渲染。当页面需要时间加载,或者您需要等待特定条件满足才能提取数据时,这很有用。
硬件需求
导入库
步骤 1.首先,让我们导入必要的元素。
1)导入Puppeteer库。
import puppeteer from 'puppeteer'
2) 由于我们将使用内置的 Node.js 文件系统模块,因此您也需要导入它。
import fs from 'fs'
3)然后,导入 URL,以便您可以抓取它们。
const start_url = 'http://quotes.toscrape.com/js/'
//const start_url = 'http://quotes.toscrape.com/js-delayed/'
设置CSS选择器
步骤 1.在 quotes.toscrape.com/js 页面的任意位置右键单击,然后按“检查”,检查页面源代码。
步骤 2.您需要选择所有引用类对象,并在其中找到以下类的文本:text、quote、author 和 tag。
const quote_elem_selector = '.quote'
const quote_text_selector = '.text'
const quote_author_selector = '.author'
const quote_tag_selector = '.tag'
const next_page_selector = '.next > a'
步骤 3.设置列表,用于记录抓取的引言。
var quotes_list = []
准备刮擦
步骤 1.我们将使用全屏模式,这样你就可以看到正在发生的事情。
async function prepare_browser() {
const browser = await puppeteer.launch({
headless: false,
})
return browser
}
注意:如果您想添加 puppeteer-extra-plugin-stealth 来隐藏您的数字指纹,或者设置代理来绕过基于 IP 的限制,请在此处进行设置。如果您不知道如何使用 Puppeteer 设置代理,我们准备了一份分步教程。
了解如何使用 Puppeteer 设置代理服务器。
步骤 2.然后,编写main()函数。
async function main() {
1)调用setup_browser函数获取浏览器对象。
var browser = await prepare_browser()
var page = await browser.newPage()
2)现在,让我们开始使用start_url字符串进行抓取。
await get_page(page, start_url)
3)抓取完成后关闭浏览器。
await browser.close()
4)在终端窗口打印出JSON输出。
console.log(quotes_list)
}
5)当代码开始运行时,您需要调用main()函数。
main()
使用 Node.js 和 Puppeteer 抓取多个页面
步骤 1.让我们使用 get_page() 函数访问 URL,获取 HTML 输出,然后跳转到下一页。
async function get_page(page, url) {
await page.goto(url)
1) 现在,我们将使用 quote_selector 告诉 Puppeteer 等待内容出现。一旦出现带有 class=quote 的元素,它就会开始抓取。我们将超时值设置为 20 秒。
await page.waitForSelector(quote_elem_selector, {timeout: 20_000})
2)然后,调用scrape函数来解析HTML。
await scrape(page)
3)检查下一页选择器并提取 href 属性以进行抓取。
try {
let next_href = await page.$eval(next_page_selector, el => el.getAttribute('href'))
let next_url = `https://quotes.toscrape.com${next_href}`
console.log(`Next URL to scrape: ${next_url}`)
4)再次调用get_page()函数并将new_url传递给scrape。
await get_page(page, next_url)
} catch {
// Next page button not found, end job
return
}
}
步骤二:现在,我们进入解析部分。我们将使用 scrape() 函数。
async function scrape(page) {
1)找到所有quote元素并把它们放入quote_elements列表中。
let quote_elements = await page.$$(quote_elem_selector)
2)然后,遍历列表以查找每个报价的所有值。
for (let quote_element of quote_elements) {
3)现在,让我们使用选择器找到我们需要的元素并提取它们的文本内容。
let quote_text = await quote_element.$eval(quote_text_selector, el => el.innerText)
let quote_author = await quote_element.$eval(quote_author_selector, el => el.innerText)
let quote_tags = await quote_element.$$eval(quote_tag_selector, els => els.map(el => el.textContent))
//console.log(quote_text)
//console.log(quote_author)
//console.log(quote_tags)
// Putting the output into a dictionary
var dict = {
'author': quote_author,
'text': quote_text,
'tags': quote_tags,
}
5)将字典推入quotes_list以获取输出。
quotes_list.push(dict)
}
}
这是输出:
PS C:\node-projects\scraping> node .\index.js
Next URL to scrape: https://quotes.toscrape.com/js/page/2/
Next URL to scrape: https://quotes.toscrape.com/js/page/3/
Next URL to scrape: https://quotes.toscrape.com/js/page/4/
Next URL to scrape: https://quotes.toscrape.com/js/page/5/
Next URL to scrape: https://quotes.toscrape.com/js/page/6/
Next URL to scrape: https://quotes.toscrape.com/js/page/7/
Next URL to scrape: https://quotes.toscrape.com/js/page/8/
Next URL to scrape: https://quotes.toscrape.com/js/page/9/
Next URL to scrape: https://quotes.toscrape.com/js/page/10/
[
{
author: 'Albert Einstein',
text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
tags: [ 'change', 'deep-thoughts', 'thinking', 'world' ]
},
{
author: 'J.K. Rowling',
text: '“It is our choices, Harry, that show what we truly are, far more than our abilities.”',
tags: [ 'abilities', 'choices' ]
},
{
author: 'Albert Einstein',
text: '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
tags: [ 'inspirational', 'life', 'live', 'miracle', 'miracles' ]
},
将输出保存到 CSV 文件
步骤 1.创建一个函数,将我们抓取和解析后的输出写入 CSV 文件:
function write_to_csv(){
1) 从 quotes_list 对象获取键。这将是 csv 文件的第一行。
var csv = Object.keys(quotes_list[0]).join(', ') + '\n'
2)遍历每个引文字典元素。
quotes_list.forEach(function(quote) {
3)向 CSV 变量中添加新行,并在行末添加换行符。
csv += `${quote['author']}, ${quote['text']}, "${quote['tags']}"\n`
})
4)将输出写入output.csv文件
fs.writeFile('output.csv', csv, (err) => {
if (err)
console.log(err)
else {
console.log("Output written successfully")
}
})
}
步骤 2.编写main()函数。
async function main() {
1)通过调用setup_browser函数初始化浏览器设置并获取浏览器对象。
var browser = await prepare_browser()
var page = await browser.newPage()
2)开始刮擦。
await get_page(page, start_url)
3)抓取完成后关闭浏览器。
await browser.close()
4)在终端窗口打印出输出json。
console.log(quotes_list)
5)将输出写入 CSV。
write_to_csv()
}
这是完整的代码:
import puppeteer from 'puppeteer'
import fs from 'fs'
const start_url = 'http://quotes.toscrape.com/js/'
//const start_url = 'http://quotes.toscrape.com/js-delayed/'
const quote_elem_selector = '.quote'
const quote_text_selector = '.text'
const quote_author_selector = '.author'
const quote_tag_selector = '.tag'
const next_page_selector = '.next > a'
var quotes_list = []
async function prepare_browser() {
const browser = await puppeteer.launch({
headless: false,
})
return browser
}
async function get_page(page, url) {
await page.goto(url)
await page.waitForSelector(quote_elem_selector, {timeout: 20_000})
await scrape(page)
try {
let next_href = await page.$eval(next_page_selector, el => el.getAttribute('href'))
let next_url = `https://quotes.toscrape.com${next_href}`
console.log(`Next URL to scrape: ${next_url}`)
await get_page(page, next_url)
} catch {
return
}
}
async function scrape(page) {
let quote_elements = await page.$$(quote_elem_selector)
for (let quote_element of quote_elements) {
// Here we find the elements by using the selectors and extracting their text content
let quote_text = await quote_element.$eval(quote_text_selector, el => el.innerText)
let quote_author = await quote_element.$eval(quote_author_selector, el => el.innerText)
let quote_tags = await quote_element.$$eval(quote_tag_selector, els => els.map(el => el.textContent))
//console.log(quote_text)
//console.log(quote_author)
//console.log(quote_tags)
var dict = {
'author': quote_author,
'text': quote_text,
'tags': quote_tags,
}
quotes_list.push(dict)
}
}
function write_to_csv(){
var csv = Object.keys(quotes_list[0]).join(', ') + '\n'
quotes_list.forEach(function(quote) {
csv += `${quote['author']}, ${quote['text']}, "${quote['tags']}"\n`
})
//console.log(csv)
fs.writeFile('output.csv', csv, (err) => {
if (err)
console.log(err)
else {
console.log("Output written successfully")
}
})
}
async function main() {
var browser = await prepare_browser()
var page = await browser.newPage()
await get_page(page, start_url)
await browser.close()
console.log(quotes_list)
write_to_csv()
}
main()