返回博客
指南
Sorin-Gabriel MaricaLast updated on Mar 31, 20262 min read

《使用 Go 语言进行网页抓取的终极指南》

《使用 Go 语言进行网页抓取的终极指南》

使用 Go 语言进行网页抓取,是创建快速且强大的抓取工具的绝佳方式。这是因为 Go 语言是处理并发任务的最佳编程语言之一。但在直接动手之前,我必须先向您详细介绍什么是网页抓取,以及它能为您带来哪些帮助。

网页抓取是指从网站中提取数据的过程。虽然可以手动进行,但在处理大量数据时,这种方法并不推荐。本文将探讨如何使用 Go 从零开始构建自己的自动化网页抓取工具。

如果您是初学者,可能会好奇网络爬虫有哪些应用场景。以下列举了其中几个最常见的:

  • 价格比较工具——利用网络爬虫可以构建多种工具,其中最常见且实用的便是价格比较工具。此类工具会从多个来源抓取某产品的价格,并展示最优惠的交易。
  • 机器学习 - 若想构建机器学习模型,你需要训练数据集。虽然有时能找到现成的数据集,但更多时候你需要额外付出努力,亲自获取所需数据。
  • 市场调研——第三种应用场景是从互联网上抓取信息,以了解你的竞争对手是谁以及他们在做什么。这样,你就能紧跟或领先于竞争对手,及时掌握他们可能新增的任何功能。

使用 Go 进行数据抓取需要什么

开始之前,你需要确保能在本地运行 GoLang 代码。若尚未安装 Go,只需安装 Go 即可。关于如何安装 Go 以及如何检查是否已安装,可在此处查看更多详细信息。

另外,您还需要一个集成开发环境(IDE)或您喜欢的文本编辑器来编写代码。我个人偏好使用 Visual Studio Code,但您也可以自由选择任何您认为合适的工具。

就这样。很简单,对吧?现在让我们深入探讨本文的主题——使用 Go 进行网页抓取。

使用 Go 构建网页爬虫

要构建我们的爬虫,首先需要明确目标——即从特定来源收集的一组数据。因此,我选择的爬取主题是:从 npmjs.com 抓取使用关键词“framework”的包中,每周下载量排名前列的包。 您可以在以下页面找到它们:https://www.npmjs.com/search?q=keywords:framework&page=0&ranking=optimal)

检查要抓取页面的内容

要正确进行抓取,在实际提取数据之前,你需要先确定数据的位置。我的意思是,你需要根据页面的 HTML 结构构建 HTML 选择器来查询数据。

要查看页面的 HTML 结构,您可以使用大多数现代浏览器中提供的开发者工具。在 Chrome 中,您可以在页面上右键点击要提取的元素,然后点击“检查页面”。执行此操作后,您将看到类似以下内容:

根据右侧(检查窗口)显示的 HTML 代码,我们可以构建后续使用的选择器。从该页面中,我们仅需提取每个包的 URL。

通过查看 HTML,我们可以发现网站使用的 CSS 类是程序生成的。这使得它们在爬取时不够可靠,因此我们将改用 HTML 标签。在页面上,我们可以看到包位于 <section> 标签中,且指向该包的链接位于该部分第一个 div 中的第一个 div 内。

基于此,我们可以构建以下选择器来提取所有包的链接:section > div:first-child > div:first-child a。在代码中尝试之前,我们可以先通过浏览器的开发者工具测试该选择器。操作方法是进入控制台标签页,并运行 document.querySelectorAll("{{ SELECTOR }}"):

将鼠标悬停在返回的每个节点列表元素上,我们可以看到它们正是我们所寻找的,因此可以使用此选择器。

使用 Go 抓取网页

我们终于开始构建爬虫了!为此,你首先需要创建一个文件夹来存放所有代码。接下来,你需要打开终端窗口(无论是通过 IDE 还是操作系统),并进入该文件夹。

若要在该文件夹中打开终端,使用 Visual Studio Code 时,可点击顶部工具栏中的“终端”→“新建终端”。

现在终端已打开,是时候初始化项目了。你可以通过运行以下命令来完成:

go mod init webscrapingapi.com/my-go-scraper

这将在你的文件夹中生成一个名为 go.mod 的文件,内容如下:

module webscrapingapi.com/my-go-scraper
go 1.19

为了向页面发送请求并从 HTML 中提取选择器,我们将使用 Colly 这个 Go 语言包(更多信息请查阅 Colly 文档)。要安装此包,请运行:

go get github.com/gocolly/colly

现在一切准备就绪,我们只需创建 main.go 文件并编写一些代码即可。以下是从 npmjs 框架列表首页提取所有链接的代码:

package main

import (
    "fmt"
    "github.com/gocolly/colly"
)

func scrape() {
    c := colly.NewCollector()

    // Find and print all links
    c.OnHTML("section > div:first-child > div:first-child a", func(e *colly.HTMLElement) {
        fmt.Println(e.Attr("href"))
    })
    c.Visit("https://www.npmjs.com/search?q=keywords:framework&page=0&ranking=optimal")
}

func main() {
    scrape()
}

如果初看觉得难以理解,别担心,我们将在接下来的段落中逐一分解并进行说明。 

每个 Go 语言文件都应以包名和 Go 将使用的导入语句开头。在此示例中,我们使用的两个包是“fmt”(用于打印刮取到的链接)和“Colly”(用于实际的刮取操作)。

在接下来的部分,我们创建了 scrape() 函数,负责抓取所需的链接。该函数会访问首页,并等待找到我们预设的元素选择器。当符合该选择器的元素出现时,它会立即输出该元素的 href 属性。

最后一部分是 main 函数,这是每次运行 Go 脚本时都会被调用的函数。要执行上述代码,请在终端中运行 go run main.go,你应该会得到以下输出:

如您所见,href 属性中的链接路径是相对路径,因此我们需要在前面添加 npmjs 的 URL。

利用 GoLang 的并发特性提升效率

GoLang 最酷的功能之一就是 GoRoutines。GoRoutines 是由 Go 运行时管理的简单轻量级线程。其优势在于,Go 能帮助我们以闪电般的速度同时抓取多个 URL。

此前我们已提取了 npmjs.com 上关键词“framework”下前 20 个包的链接。现在我们将尝试同时抓取所有这些链接,并提取每个包的每周下载量。为此,我们将使用 GoRoutines 和 WaitGroups。

以下是使用 goroutines 提取每周下载量的最终代码:

package main

import (
    "fmt"
    "github.com/gocolly/colly"
    "sync"
)

func scrapeWeeklyDownloads(url string, wg *sync.WaitGroup) {
    defer wg.Done()

    c := colly.NewCollector()

    // Find and print the weekly downloads value
    c.OnHTML("main > div > div:last-child > div:not([class]) p", func(e *colly.HTMLElement) {
        fmt.Println(fmt.Sprintf("%s - %s", url, e.Text))
    })
    c.Visit(url)
}

func scrape() {
    c := colly.NewCollector()

    var wg sync.WaitGroup

    // Find and print all links
    c.OnHTML("section > div:first-child > div:first-child a", func(e *colly.HTMLElement) {
        wg.Add(1)
        go scrapeWeeklyDownloads(fmt.Sprintf("%s%s", "https://www.npmjs.com", e.Attr("href")), &wg)
    })
    c.Visit("https://www.npmjs.com/search?q=keywords:framework&page=0&ranking=optimal")

    wg.Wait()
}

func main() {
    scrape()
}

现在让我们讨论一下相较于之前的代码,本次新增了哪些内容。首先你会注意到我们导入了一个名为“sync”的新包。它将帮助我们使用 Go 语言的协程,并在程序停止执行前等待所有线程完成。

接下来新增的是名为“scrapeWeeklyDownloads”的函数。该函数接受两个参数:待抓取链接的 URL 以及一个 WaitGroup 指针。该函数的作用是访问给定的 URL,并提取每周下载量(使用选择器 main > div > div:last-child > div:not([class]) p)。

您会注意到最后改动在 scrape 函数中,我们通过 var wg sync.WaitGroup 创建了一个 WaitGroup。在此,对于“包”页面上的每个链接,我们使用 wg.Add(1),随后创建一个 GoRoutine 来调用 scrapeWeeklyDownloads 函数。在函数结尾,通过 wg.Wait() 指令使代码等待直至所有 GoRoutine 执行完毕。 

有关 WaitGroup 的更多信息,请参阅 golang 中的这个示例。 

为何要使用 GoRoutines 和 WaitGroups?

通过在 Go 语言中结合 GoRoutine 和 WaitGroup 实现并发,我们可以构建一个非常高效的爬虫。运行前面的代码示例将返回页面以及每个包的每周下载量。但是,由于我们使用了多线程,这些信息显示的顺序是不可预测的(因为线程的执行速度各不相同)

如果您在 Linux 或 Windows 子系统 Linux (WSL) 上运行代码,可以使用 `time go run main.go` 来查看整个脚本的执行时间。对我来说,执行时间大约在 5 到 6 秒之间。考虑到我们正在抓取 21 个页面(首先是包含所有包的页面,然后是每个包的页面),这已经非常快了。

其他障碍

大多数爬虫通常依赖向页面发送简单的 HTTP 请求来获取所需内容。这种方案虽然可行,但有时网站会通过 JavaScript 渲染来展示信息。这意味着网站最初只会显示部分内容,其余内容则通过 JavaScript 动态加载。

要抓取此类页面,你需要使用ChromeDriver并控制真实的Chrome浏览器。虽然Go语言中存在一些实现方案,但你需要针对这一主题进行额外的研究。

即使解决了 JavaScript 渲染的问题,在抓取网站时仍会遇到一些额外障碍。部分网站可能采用反机器人检测、IP 封禁或验证码等手段来阻止机器人抓取内容。若要继续抓取这些网站,您可以尝试运用一些网络抓取技巧,例如放慢抓取速度并模拟更接近人类的行为。

但若您希望保持爬虫的高速运行,并以简便的方式克服这些障碍,WebScrapingAPI 将是理想之选。WebScrapingAPI 是一款专为网页爬取设计的 API,它通过轮换 IP 地址并规避反机器人检测机制,助您高效完成数据抓取。借助该 API,您既能充分利用 Go 语言带来的极速性能,又能瞬间完成数据抓取。

关于使用 Go 进行网页抓取的总结

网络爬虫是一种高效便捷的互联网数据提取方式,适用于多种场景。您可以选择为机器学习模型提取数据,或利用爬取的数据从零开始构建应用程序。

在并发处理方面,Go 语言堪称业界最佳解决方案之一。借助 Go 语言和 Colly,您可以构建高效的爬虫程序,在极短时间内获取所需数据。一旦熟悉 Go 的语法,使用 Go 进行网页爬取将变得既简单又高效。

关于作者
Sorin-Gabriel Marica, 全栈开发工程师 @ WebScrapingAPI
Sorin-Gabriel Marica全栈开发工程师

索林·马里卡(Sorin Marica)是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。