使用 JavaScript 和 Node.js 进行网页抓取指南

我们将讨论为什么前端 JavaScript 不是进行网络爬虫的最佳选择,并教您如何从头开始构建一个 Node.js 爬虫。
7 min read

本文将讨论:

使用前端 JavaScript 进行网页抓取

对于网页抓取而言,前端 JavaScript 是一种带有局限性的解决方案。首先,这是由于必须直接从浏览器控制面板运行 JavaScript 的网页抓取脚本。这并不是一种可以通过编程方式执行的操作。

特别是,可以从控制面板的页面抓取数据,如下所示:

其次,如果想要从其他网页抓取数据,则必须通过 AJAX 进行下载。但是不要忘记,网络浏览器对 AJAX 采用同源政策。因此,在使用前端 JavaScript 的情况下,仅限访问同一来源内的网页。

让我们举一个简单的例子来理解这意味着什么。假设当前正在访问来自 brightdata.com的页面。然后,前端 JavaScript 网页抓取脚本只能下载 brightdata.com 域下的网页。

请注意,这并不意味着 JavaScript 不是一款优秀的网络爬取技术。实际上,Node.js 允许在服务器上运行 JavaScript,并且可以避免上述两项局限。

现在让我们了解如何使用 Node.js 构建 JavaScript 网页抓取工具。

先决条件

在开始开发 Node.js 网页抓取应用程序之前,需要满足以下先决条件列表:

  • 带有 npm 8+ 的 Node.js 18+:包括 npm 在内的任何 Node.js 18+ 的长期支持(LTS)版本均可。本教程以 Node.js 18.12 和 npm 8.19 为基础,该版本即为撰写本文时最新 LTS 版本的 Node.js。
  • 支持 JavaScript 的集成开发环境(IDE):IntelliJ IDEA 的社区版是本教程所选择的 IDE,不过,任何其他支持 JavaScript 和 Node.js 的 IDE 也都可以。

点击上方链接,按照安装向导进行所需的一切设置。可以通过在终端中启动以下命令来验证 Node.js 是否已正确安装:

node -v

应返回的内容类似如下:

v18.12.1

同样,通过以下操作可以验证 npm 是否已正确安装

npm -v 

应返回的字符串如下所示:

8.19.2

上面的两个命令分别指示计算机上全局可用的 Node.js 和 npm 版本。

大功告成!要想查看如何在 Node.js 中执行 JavaScript 网页抓取,当前已准备就绪!

适用于 Node.js 的最佳 JavaScript 网页抓取库

让我们来探索一下 Node.js 中最适合网页抓取的 JavaScript 库:

  • Axios – 该库使用便捷,可协助在 JavaScript 中发出 HTTP 请求。Axios 在浏览器和 Node.js 中均可使用,它是目前可供使用的最为热门的 JavaScript HTTP 客户端之一。
  • Cheerio – 作为轻量级库,提供类似 jQuery 的应用程序接口(API)来浏览 HTML 和 XML 文档。可以使用 Cheerio 来解析 HTML 文档,选择 HTML 元素,然后从中提取数据。换言之,Cheerio 可以提供高级网页抓取 API。
  • Selenium – 支持多种编程语言的库,可用于为 Web 应用程序构建自动测试。也可以利用其无头浏览器功能来进行网页抓取。详情请参阅我们的 Selenium 网页抓取指南。
  • Playwright – 一款用于为微软开发的网络应用程序创建自动测试脚本的工具。它提供了一种指示浏览器执行特定操作的方法。因此,可以使用 Playwright 进行网页抓取,作为一种无头浏览器解决方案。
  • Puppeteer – 一款用于自动测试 Google 开发的网络应用程序的工具。Puppeteer 建立在 Chrome DevTools 协议之上。与 Selenium 和 Playwright 相同,它允许像真人用户一样以编程方式与浏览器进行交互。详细了解 Selenium 和 Puppeteer 之间的区别。

在 Node.js 中构建 JavaScript 网页抓取工具

接下来将学习如何在 Node.js 中构建 JavaScript 网页抓取工具,以便自动从网站提取数据。具体而言,目标网页即为 Bright Data 主页。Node.js 网页抓取过程的目标在于从页面选择感兴趣的 HTML 元素,从中检索数据,并将所抓取的数据转换为更加实用的格式。

在撰写本文时,Bright Data 主页如下所示:

正如所注意到的,Bright Data 主页包含大量不同格式的数据和信息,从文字描述到图像不等。此外,它还包含很多各种用途的链接。下面将学习如何检索所有这些数据。

现在让我们在分步教程中了解如何使用 Node.js 抓取数据!

第 1 步:设置 Node.js 项目

首先,使用以下命令创建包含 Node.js 网页抓取项目的文件夹:

mkdir web-scraper-nodejs

现在应出现一个空白的 web-scraper-nodejs 目录。请注意,可以为项目文件夹命名任何想要的名称。使用以下命令进入文件夹:

cd web-scraper-nodejs

现在,使用以下命令初始化 npm 项目:

npm init -y

该命令将设置一个全新的 npm 项目。请注意,要让 npm 在不经过交互式过程的情况下初始化默认项目,必须使用 -y 标志。如果省略了 -y 标志,则终端会询问一些问题。

web-scraper-nodejs 现在应该包含 package.json,如下所示:

  { "name": "web-scraper-nodejs", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

现在,在项目的根文件夹中创建 index.js 文件,并将其初始化如下:

// index.js

console.log("Hello, World!")

此 JavaScript 文件将包含 Node.js 网页抓取逻辑。

打开 package.json 文件,并在 scripts 部分添加以下脚本:

"start": "node index.js"

现在可以在终端中运行以下命令来启动 Node.js 脚本:

npm run start

这一操作应返回的内容如下:

Hello, World!

这意味着 Node.js 应用程序运行正常。现在,在集成开发环境(IDE)中打开项目,准备好在 Node.js 中写入一些抓取逻辑!

如果是 IntelliJ IDEA 用户,则应看到以下内容:

第 2 步:安装 Axios 和 Cheerio

现在即可在 Node.js 中安装实现网页抓取工具所需的附属项。要确定应该采用哪种 JavaScript 网页抓取库,请访问目标网页,右键单击空白部分,然后选择“检查”(Inspect)选项。此操作应该会打开浏览器的 DevTools 窗口。在“网络”(Network)选项卡中,查看 “Fetch/XHR” 部分。

来自目标页面的 AJAX 请求并未显示重要数据;相反,所需信息直接存在于网页的源代码中,这通常适用于在服务器端渲染的站点。这意味着该页面不需要 JavaScript 来显示内容或获取数据,因此无需使用无头浏览器进行抓取。为了避免使用浏览器带来的额外负载,更简单的解决方案是将 Cheerio 与 Axios 一起使用,这样可以更高效地运行,且避免不必要的复杂性。

因此,可以使用以下命令安装 CheerioAxios

npm install cheerio axios

然后,通过在 index.js 添加以下两行代码来导入 CheerioAxios

// index.js

const cheerio = require("cheerio")
const axios = require("axios")

现在,让我们用代码编写一个使用 Cheerio 和 Axios 执行网页抓取的 Node.js 网页抓取脚本!

第 3 步:下载目标网站

通过以下几行代码使用 Axios 连接到目标网站:

// downloading the target web page 
// by performing an HTTP GET request in Axios
const axiosResponse = await axios.request({
    method: "GET",
    url: "https://brightdata.com",
})

由于成功使用了 Axios 的 request() 方法,可以执行任何 HTTP 请求。具体而言,如果想下载网页的源代码,则必须对其 URL 执行 HTTP GET 请求。通常,Axios 会立即返回 Promise。可以等待 Promise 并使用 await 关键字同步获取其值。

请注意,如果 request() 失败,则显示“错误” 。出现这种情况可能有多种原因,可能包括无效 URL 或服务器暂不可用等。另外,别忘了防抓取措施目前已多有实施。其中一种最常见的方法就是阻止未包含有效用户代理(User-Agent) HTTP 标头的请求。进一步了解用于网页抓取的User-Agent

默认情况下,Axios 将使用以下 User-Agent

axios <axios_version>

这并非浏览器所使用的 User-Agent 的外观。因此,防抓取技术可能会检测到并屏蔽 Node.js 网页抓取工具。

通过向传递给 request() 的对象添加以下属性,在 Axios 中设置有效的 User-Agent 标头:

headers: {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
}

标头属性允许在 Axios 中设置任何 HTTP 标头。

index.js 文件当前应显示如下:

// index.js

const cheerio = require("cheerio")
const axios = require("axios")

async function performScraping() {
    // downloading the target web page
    // by performing an HTTP GET request in Axios
    const axiosResponse = await axios.request({
        method: "GET",
        url: "https://brightdata.com",
        headers: {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
        }
    })
}

performScraping()

请注意,只能在标有 async 的函数中使用 await。正因如此,必须将 JavaScript 网页抓取逻辑嵌入到 async performScraping() 函数中。

现在,让我们花点时间分析目标网页,以此来定义网页抓取策略。

第 4 步:检查 HTML 页面

如果查看 Bright Data 主页,则会看到可以应用 Bright Data 的行业列表。这些可以作为值得抓取的有趣数据。

右键单击以下 HTML 元素之一并选择“检查”(Inspect):

通过分析所选节点的 HTML 代码,将会看到该卡片是一个 HTML 元素。具体而言,此 包含:

  1. 一个
    HTML 元素,其中包含与行业字段相关的图像
  2. 一个 HTML 元素,其中包含行业字段的名称

现在,注意表征这些 HTML 元素的层叠样式表(CSS)类别。通过使用这些类别,将能够定义从 DOM 中选择这些 HTML 元素所需的 CSS 选择器。详细来说,需要注意到 .e-container 卡片包含在 .elementor-element-7a85e3a8

之中。然后,给定一张卡片,可以使用以下 CSS 选择器提取其所有相关数据:

  1. .elementor-image-box-img img
  2. .elementor-image-box-content .elementor-image-box-title

同理,可以应用相同的逻辑来定义所需的 CSS 选择器:

  • 提取 Bright Data 成为行业领导者的原因。
  • 选择 Bright Data 所提供的客户体验成为市场上最佳客户体验的原因。

换句话说,目标网页有三个抓取目标:

  1. 有关可以应用 Bright Data 的行业数据。
  2. 有关 Bright Data 之所以成为行业领导者的数据。
  3. 有关 Bright Data 为何提供业内最佳客户体验的数据。

第 5 步:使用 Cheerio 选择 HTML 元素

Cheerio 提供了几种从网页中选择 HTML 元素的方法。但是,首先必须使用以下命令初始化 Cheerio:

// parsing the HTML source of the target web page with Cheerio
const $ = cheerio.load(axiosResponse.data)

Cheerio 的 load() 方法接受字符串形式的 HTML 内容。请注意,Axios 响应对象在数据属性中包含由 HTTP 请求返回的数据。在这种情况下,数据将存储服务器所返回网页的 HTML 源代码。因此,可将 axiosResponse.data 传递给 load() 来初始化 Cheerio。

由于 Cheerio 的语法与 jQuery 基本相同,因此应调用 Cheerio 变量 $。这样,就能从互联网上复制 jQuery 代码段。

可以通过使用 Cheerio 的类别来选择 HTML 元素:

const htmlElement = $(".elementClass")

同样,可以通过 ID 检索 HTML 元素,方法如下:

const htmlElement = $("#elementId")

具体而言,可以通过将任何有效的 CSS 选择器传递给 $ 来选择 HTML 元素,如同 jQuery 中的操作一样。还可以使用 find() 方法连接选择逻辑:

// retrieving the list of industry cards
const industryCards = $(".elementor-element-7a85e3a8").find(".e-container")

find() 允许访问由 CSS 选择器筛选的当前 HTML 元素的后代。然后,可以使用 each() 方法对 Cheerio 节点列表进行迭代,如下所示:

// iterating over the list of industry cards
$(".elementor-element-7a85e3a8")
    .find(".e-container")
    .each((index, element) => {
         // scraping logic...
    })

接下来让我们学习如何使用 Cheerio 从感兴趣的 HTML 元素中提取数据。

第 6 步:使用 Cheerio 从目标网页抓取数据

可以扩展前文显示的逻辑,从选定的 HTML 元素中提取所需数据,如下所示:

// initializing the data structure
// that will contain the scraped data
const industries = []

// scraping the "Learn how web data is used in your market" section
$(".elementor-element-7a85e3a8")
    .find(".e-container")
    .each((index, element) => {
        // extracting the data of interest
        const pageUrl = $(element).attr("href")
        const image = $(element).find(".elementor-image-box-img img").attr("data-lazy-src")
        const name = $(element).find(".elementor-image-box-content .elementor-image-box-title").text()

        // filtering out not interesting data
        if (name && pageUrl) {
            // converting the data extracted into a more
            // readable object
            const industry = {
                url: pageUrl,
                image: image,
                name: name
            }

            // adding the object containing the scraped data
            // to the industries array
            industries.push(industry)
        }
    })

该 Node.js 网页抓取代码段从 Bright Data 主页中选择了所有行业卡片。然后,它将遍历所有 HTML 卡片元素。针对每张卡片,它会抓取与卡片、图片和行业名称相关的网页 URL。由于成功使用了 Cheerio 的 attr()text() 方法,可以对 HTML 属性值和文本进行分别检索。最后,它将抓取的数据存储在一个对象中,并将其添加到 industries 数组中。

each() 循环的末尾,industries 将包含与第一个抓取目标相关的所有数据。现在让我们了解一下如何实现另外两个目标。

同样,可以按如下方式抓取数据以证明 Bright Data 作为行业领导者的原因:

const marketLeaderReasons = []

// scraping the "What makes Bright Data
// the undisputed industry leader" section
$(".elementor-element-ef3e47e")
    .find(".elementor-widget")
    .each((index, element) => {
        const image = $(element).find(".elementor-image-box-img img").attr("data-lazy-src")
        const title = $(element).find(".elementor-image-box-title").text()
        const description = $(element).find(".elementor-image-box-description").text()

        const marketLeaderReason = {
            title: title,
            image: image,
            description: description,
        }

        marketLeaderReasons.push(marketLeaderReason)
    })

最后,则可以通过以下方式来抓取有关 Bright Data 为何提供出色客户体验的数据:

const customerExperienceReasons = []
// scraping the "The best customer experience in the industry" section
$(".elementor-element-288b23cd .elementor-text-editor")
    .find("li")
    .each((index, element) => {
        const title = $(element).find("strong").text()
        // since the title is part of the text, you have
        // to remove it to get only the description
        const description = $(element).text().replace(title, "").trim()

        const customerExperienceReason = {
            title: title,
            description: description,
        }

        customerExperienceReasons.push(customerExperienceReason)
    })

恭喜!你已经学会了如何达成 Node.js 网页抓取的全部三个目标!

请记住,可以通过点击当前页面中发现的链接从其他网页抓取数据。这正是网络爬取的目的所在。因此,也可以定义网页抓取逻辑来从这些页面提取数据。

industriesmarketLeaderReasonscustomerExperienceReasons 将把所有抓取的数据存储在 JavaScript 对象中。让我们学习如何将其转换为更为实用的格式。

第 7 步:将提取的数据转换为 JSON

就 JavaScript 而言,JSON 是其中一种最佳数据格式。这是由于 JSON 源自 JavaScript,是 API 通常用于接受或返回数据的格式。因此,很可能必须将 JavaScript 抓取的数据转换为 JSON。可通过以下逻辑轻松达成这一目标:

// trasforming the scraped data into a general object
const scrapedData = {
    industries: industries,
    marketLeader: marketLeaderReasons,
    customerExperience: customerExperienceReasons,
}

// converting the scraped data object to JSON
const scrapedDataJSON = JSON.stringify(scrapedData)

首先,必须创建一个包含所有抓取数据的 JavaScript 对象。然后,可以使用 JSON.stringify() 将该 JavaScript 对象转换为 JSON。

scrapedDataJSON 将包含以下 JSON 数据:

{
  "industries": [
    {
      "url": "https://brightdata.com/use-cases/ecommerce",
      "image": "https://brightdata.com/wp-content/uploads/2022/07/E_commerce.svg",
      "name": "E-commerce"
    },

    // ...

    {
      "url": "https://brightdata.com/use-cases/data-for-good",
      "image": "https://brightdata.com/wp-content/uploads/2022/07/Data_for_Good_N.svg",
      "name": "Data for Good"
    }
  ],
  "marketLeader": [
    {
      "title": "Most reliable",
      "image": "https://brightdata.com/wp-content/uploads/2022/01/reliable.svg",
      "description": "Highest quality data, best network uptime, fastest output "
    },

    // ...

    {
      "title": "Most efficient",
      "image": "https://brightdata.com/wp-content/uploads/2022/01/efficient.svg",
      "description": "Minimum in-house resources needed"
    }
  ],
  "customerExperience": [
    {
      "title": "You ask, we develop",
      "description": "New feature releases every day"
    },

    // ...

    {
      "title": "Tailored solutions",
      "description": "To meet your data collection goals"
    }
  ]
}

恭喜!我们从连接网站起步,如今已经可以抓取其数据并将其转换为 JSON。现在已经准备好查看完整的网页抓取 Node.js 脚本了。

小结

Node.js 网页抓取工具整体如下所示:

// index.js

const cheerio = require("cheerio")
const axios = require("axios")

async function performScraping() {
    // downloading the target web page
    // by performing an HTTP GET request in Axios
    const axiosResponse = await axios.request({
        method: "GET",
        url: "https://brightdata.com/",
        headers: {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
        }
    })

    // parsing the HTML source of the target web page with Cheerio
    const $ = cheerio.load(axiosResponse.data)

    // initializing the data structures
    // that will contain the scraped data
    const industries = []
    const marketLeaderReasons = []
    const customerExperienceReasons = []

    // scraping the "Learn how web data is used in your market" section
    $(".elementor-element-7a85e3a8")
        .find(".e-container")
        .each((index, element) => {
            // extracting the data of interest
            const pageUrl = $(element).attr("href")
            const image = $(element).find(".elementor-image-box-img img").attr("data-lazy-src")
            const name = $(element).find(".elementor-image-box-content .elementor-image-box-title").text()

            // filtering out not interesting data
            if (name && pageUrl) {
                // converting the data extracted into a more
                // readable object
                const industry = {
                    url: pageUrl,
                    image: image,
                    name: name
                }

                // adding the object containing the scraped data
                // to the industries array
                industries.push(industry)
            }
        })

    // scraping the "What makes Bright Data
    // the undisputed industry leader" section
    $(".elementor-element-ef3e47e")
        .find(".elementor-widget")
        .each((index, element) => {
            // extracting the data of interest
            const image = $(element).find(".elementor-image-box-img img").attr("data-lazy-src")
            const title = $(element).find(".elementor-image-box-title").text()
            const description = $(element).find(".elementor-image-box-description").text()

            // converting the data extracted into a more
            // readable object
            const marketLeaderReason = {
                title: title,
                image: image,
                description: description,
            }

            // adding the object containing the scraped data
            // to the marketLeaderReasons array
            marketLeaderReasons.push(marketLeaderReason)
        })

    // scraping the "The best customer experience in the industry" section
    $(".elementor-element-288b23cd .elementor-text-editor")
        .find("li")
        .each((index, element) => {
            // extracting the data of interest
            const title = $(element).find("strong").text()
            // since the title is part of the text, you have
            // to remove it to get only the description
            const description = $(element).text().replace(title, "").trim()

            // converting the data extracted into a more
            // readable object
            const customerExperienceReason = {
                title: title,
                description: description,
            }

            // adding the object containing the scraped data
            // to the customerExperienceReasons array
            customerExperienceReasons.push(customerExperienceReason)
        })

    // trasforming the scraped data into a general object
    const scrapedData = {
        industries: industries,
        marketLeader: marketLeaderReasons,
        customerExperience: customerExperienceReasons,
    }

    // converting the scraped data object to JSON
    const scrapedDataJSON = JSON.stringify(scrapedData)

    // storing scrapedDataJSON in a database via an API call...
}

performScraping()

如此处所示,通过不到 100 行代码即可在 Node.js 中构建一个网页抓取工具。通过使用 Cheerio 和 Axios,可以下载 HTML 网页,对其进行解析,然后自动检索其所有数据。随后,可以将抓取数据轻松转换为 JSON。这正是 Node.js 网页抓取的全部内容。

使用以下命令在 Node.js 启动网页抓取工具:

npm run start

确实如此!你刚刚学会了如何在 Node.js 执行 JavaScript 网页抓取!

结语

本教程介绍了为什么使用 JavaScript 在前端进行网页抓取是一种具有局限性的解决方案,以及为什么 Node.js 可以作为一种更佳选择。此外,还研究了创建 Node.js 网页抓取脚本需要哪些先决条件,以及如何使用 JavaScript 从网页抓取数据。具体学习了如何根据真实示例,使用 Cheerio 和 Axios 在 Node.js 中创建 JavaScript 网页抓取应用程序。正如你所了解的那样,使用 Node.js 只需几行代码即可进行网页抓取。

不过仍需谨记,网页抓取可能并不那么轻松。这是因为可能仍然需要应对诸多挑战。详细来说,防抓取和反机器人解决方案已变得日益普遍。不过好在,可以使用 Bright Data 提供的下一代高级网页抓取工具轻松避免所有这一切。是否不太愿意处理网页抓取?则请浏览我们的数据集

如果想进一步了解如何避免遭到屏蔽,可以从多种可用代理服务 中选用一种网络代理,或者开始使用高级 Web 解锁器