0

0

Golang实现简单爬虫怎么做 组合net/http与goquery解析HTML

P粉602998670

P粉602998670

发布时间:2025-08-29 08:34:01

|

683人浏览过

|

来源于php中文网

原创

答案:使用Golang实现爬虫需先用net/http发送请求并处理错误、超时和重定向,再通过goquery结合CSS选择器解析HTML提取数据,最后利用goroutine和channel实现并发抓取,配合WaitGroup同步,数据可存为文件或数据库。

golang实现简单爬虫怎么做 组合net/http与goquery解析html

用Golang实现一个简单的爬虫,核心思路其实就是两步:先用标准库

net/http
发出HTTP请求,获取网页的HTML内容;接着,利用
goquery
这个库来解析HTML,像jQuery一样方便地定位和提取你想要的数据。这套组合拳下来,处理大部分静态网页数据抓取的需求,基本就够用了。

解决方案

说实话,第一次用Go写爬虫,

net/http
的简洁性让我有点惊喜。它把HTTP请求的复杂性封装得很好,你只需要关心你想请求什么URL,以及如何处理返回的数据。而当HTML内容到手后,传统的字符串匹配或者正则表达,面对复杂的网页结构简直是噩梦。这时候,
goquery
就成了救星,它把前端开发中熟悉的CSS选择器带到了后端,大大提升了开发效率和代码的可读性。

下面是一个基础的实现,展示了如何抓取一个网页的标题和所有链接:

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {
    // 目标URL,这里以一个示例网站为例,实际使用时请替换
    url := "http://example.com" // 请替换成实际可访问的URL

    // 发送HTTP GET请求
    resp, err := http.Get(url)
    if err != nil {
        log.Fatalf("请求URL失败: %v", err)
        return
    }
    defer resp.Body.Close() // 确保在函数结束时关闭响应体

    // 检查HTTP状态码
    if resp.StatusCode != http.StatusOK {
        log.Fatalf("HTTP请求失败,状态码: %d %s", resp.StatusCode, resp.Status)
        return
    }

    // 使用goquery解析HTML文档
    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        log.Fatalf("解析HTML文档失败: %v", err)
        return
    }

    // 提取网页标题
    title := doc.Find("title").Text()
    fmt.Printf("网页标题: %s\n", title)

    fmt.Println("\n所有链接:")
    // 遍历所有a标签,提取href属性和链接文本
    doc.Find("a").Each(func(i int, s *goquery.Selection) {
        href, exists := s.Attr("href")
        if exists {
            linkText := strings.TrimSpace(s.Text())
            // 简单过滤空链接文本,或只显示非锚点链接
            if linkText != "" && !strings.HasPrefix(href, "#") {
                fmt.Printf("- 链接 %d: %s (%s)\n", i+1, linkText, href)
            }
        }
    })

    // 尝试提取某个特定元素,比如第一个段落
    firstParagraph := doc.Find("p").First().Text()
    if firstParagraph != "" {
        fmt.Printf("\n第一个段落内容: %s\n", strings.TrimSpace(firstParagraph))
    } else {
        fmt.Println("\n未找到任何段落。")
    }
}

这段代码展示了最基本的爬取和解析流程。从请求到错误处理,再到用

goquery
定位元素,一切都显得相当直观。

立即学习go语言免费学习笔记(深入)”;

Golang爬虫如何处理HTTP请求错误与重定向?

在实际的爬虫开发中,网络波动或者目标网站的反爬机制,常常让请求变得不可靠。光是简单的

http.Get()
可能不够用,我们需要更精细的控制。

首先是错误处理。除了检查

http.Get()
返回的错误,我们还需要关注
resp.StatusCode
。一个非200的状态码(比如404 Not Found,403 Forbidden,500 Internal Server Error)意味着请求没有成功,这时候应该根据具体情况决定是重试、记录日志还是直接跳过。

超时设置也是个关键点。默认的HTTP请求可能不会设置超时,导致程序长时间阻塞。我们可以创建一个自定义的

http.Client
来配置超时:

client := &http.Client{
    Timeout: 10 * time.Second, // 设置10秒的请求超时
}
resp, err := client.Get(url)
// ... 后续处理

User-Agent的设置也挺重要。很多网站会根据User-Agent来判断请求来源,如果发现是爬虫,可能会直接拒绝。模拟浏览器行为,设置一个常见的User-Agent头是个好习惯:

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    log.Fatalf("创建请求失败: %v", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124124 Safari/537.36")

client := &http.Client{}
resp, err := client.Do(req)
// ... 后续处理

至于重定向,

net/http
http.Client
默认是会自动处理3xx重定向的。但有时候,我们可能需要禁用重定向,或者在重定向发生时执行一些自定义逻辑。比如,你想知道最终重定向到的URL,或者想限制重定向的次数。这可以通过设置
http.Client
CheckRedirect
字段来实现:

client := &http.Client{
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        if len(via) >= 10 { // 限制重定向次数,防止无限循环
            return errors.New("stopped after 10 redirects")
        }
        fmt.Printf("重定向到: %s (原URL: %s)\n", req.URL.String(), via[0].URL.String())
        return nil // 返回nil表示允许重定向
    },
}
// 如果想禁用重定向,直接返回http.ErrUseLastResponse即可
// CheckRedirect: func(req *http.Request, via []*http.Request) error {
//     return http.ErrUseLastResponse
// }

这些细节的考量,能让你的爬虫在面对复杂网络环境时,显得更加健壮和可靠。

goquery在复杂HTML结构中如何精准定位元素?

说完了请求,接下来的重头戏自然是数据的提取。

goquery
之所以好用,很大程度上是因为它对CSS选择器的支持。如果你熟悉前端开发,那么
goquery
的API几乎是无缝衔接。

要精准定位元素,关键在于灵活运用CSS选择器。

Remove.bg
Remove.bg

AI在线抠图软件,图片去除背景

下载
  • 基本选择器:
    • tagName
      :选择所有指定标签的元素,比如
      "div"
      "p"
    • .className
      :选择所有带有指定class的元素,比如
      ".product-title"
    • #idName
      :选择带有指定ID的元素,比如
      "#main-content"
  • 组合选择器:
    • parent child
      :后代选择器,选择
      parent
      元素下的所有
      child
      元素。例如
      "div p"
      选择所有在
      div
      标签内的
      p
      标签。
    • parent > child
      :子元素选择器,选择
      parent
      元素的直接
      child
      元素。例如
      "ul > li"
    • tag.className
      :同时匹配标签和class,例如
      "span.price"
    • [attribute=value]
      :属性选择器,选择带有特定属性和值的元素。例如
      "a[target=_blank]"
  • 伪类选择器:
    • :nth-child(n)
      :选择父元素下的第n个子元素。
    • :first-child
      ,
      :last-child
      :选择第一个/最后一个子元素。

goquery
Find()
方法就是用来接收这些CSS选择器的。它会返回一个
*goquery.Selection
对象,这个对象代表了所有匹配到的元素集合。你可以继续在这个
Selection
对象上调用
Find()
进行链式操作,从而深入到更复杂的嵌套结构中。

举个例子,假设你有一个产品列表,每个产品在一个

div
中,
div
有一个
class="product-item"
,产品标题在内部的一个
h3
标签里,链接在
h3
里的
a
标签里:

要提取产品标题和价格,你可以这么做:

doc.Find(".product-item").Each(func(i int, s *goquery.Selection) {
    // 在当前产品项的Selection中查找标题和价格
    title := s.Find(".product-title a").Text()
    href, _ := s.Find(".product-title a").Attr("href")
    price := s.Find(".price").Text()

    fmt.Printf("产品 %d: 标题=%s, 链接=%s, 价格=%s\n", i+1, title, href, price)
})

这里的关键是

s.Find()
,它是在当前迭代的
product-item
元素内部进行查找,而不是从整个文档的根部开始,这大大提高了定位的准确性。

此外,

goquery
还提供了
First()
,
Last()
,
Eq(index)
等方法来获取
Selection
集合中的特定元素,以及
Text()
用于获取元素的文本内容,
Attr(name)
用于获取元素的属性值。这些方法组合起来,足以应对绝大多数的HTML解析场景。

如何让Golang爬虫更健壮:并发与数据存储策略?

一个简单的爬虫可能只抓取一两个页面,但如果面对成千上万的页面,甚至需要持续抓取,那么并发和数据存储就成了绕不开的话题。

Go语言天生就是为并发而设计的,

goroutine
channel
是其并发模型的核心。利用它们,我们可以轻松地实现并发抓取,显著提高爬取效率。

基本的并发抓取思路是:

  1. 生产者-消费者模型:一个或多个
    goroutine
    负责生成待抓取的URL(生产者),将URL发送到一个
    channel
    中。
  2. 工作池模型:多个
    goroutine
    作为消费者,从
    channel
    中接收URL,然后并发地执行抓取和解析任务。
  3. 结果收集:抓取到的数据也可以通过另一个
    channel
    发送给一个专门的
    goroutine
    进行统一处理或存储。
  4. 同步等待:使用
    sync.WaitGroup
    来等待所有抓取
    goroutine
    完成任务。
// 这是一个简化的并发抓取框架示例
func worker(id int, urls <-chan string, results chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    for url := range urls {
        fmt.Printf("工作者 %d 正在抓取: %s\n", id, url)
        // 模拟抓取和解析
        // resp, err := http.Get(url)
        // doc, err := goquery.NewDocumentFromReader(resp.Body)
        // ... 实际的抓取解析逻辑
        time.Sleep(time.Millisecond * 500) // 模拟网络延迟和处理时间
        results <- fmt.Sprintf("抓取完成: %s", url)
    }
}

func main() {
    // ... 前面省略的导入和主函数开头
    urlsToCrawl := []string{
        "http://example.com/page1",
        "http://example.com/page2",
        "http://example.com/page3",
        // ... 更多URL
    }

    numWorkers := 5 // 设定并发工作者数量
    urls := make(chan string, len(urlsToCrawl))
    results := make(chan string, len(urlsToCrawl))
    var wg sync.WaitGroup

    // 启动工作者goroutine
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, urls, results, &wg)
    }

    // 将URL发送到urls channel
    for _, url := range urlsToCrawl {
        urls <- url
    }
    close(urls) // 关闭urls channel,通知worker没有更多URL了

    // 等待所有worker完成
    wg.Wait()
    close(results) // 关闭results channel

    // 收集并处理结果
    for res := range results {
        fmt.Println(res)
    }
    fmt.Println("所有抓取任务完成。")
}

当然,并发抓取还需要考虑速率限制(避免对目标网站造成过大压力,甚至被封禁IP)、错误重试机制IP代理池等,这些都是让爬虫更健壮的进阶话题。

数据存储方面,取决于你的需求和数据量:

  • 简单场景:如果数据量不大,直接打印到控制台,或者保存到TXT文件CSV文件(使用
    encoding/csv
    包)或JSON文件(使用
    encoding/json
    包)都是不错的选择。CSV适合结构化表格数据,JSON适合半结构化数据。
  • 中等规模:可以考虑使用SQLite。它是一个轻量级的嵌入式数据库,不需要独立的服务器进程,直接以文件形式存在,非常适合本地开发和中小型爬虫项目。Go有成熟的SQLite驱动。
  • 大规模和持久化:对于需要长期存储、支持复杂查询和高并发读写的数据,关系型数据库(如PostgreSQL、MySQL)或NoSQL数据库(如MongoDB、Redis)是更好的选择。Go的标准库
    database/sql
    配合相应的驱动可以很方便地操作这些数据库。

选择哪种存储方式,最终还是取决于你爬取的数据量、数据结构以及后续如何使用这些数据。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

180

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

340

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

393

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

197

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

191

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

253

2025.06.17

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

CSS教程
CSS教程

共754课时 | 22.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号