Go1.19 爬虫框架:简化站点模板的自动化抓取

引言

网络爬虫(Web Scraper)是从网站自动提取数据的工具。它们被广泛用于数据采集、搜索引擎优化、市场调查等领域。本文将详细介绍如何使用 Go 1.19 实现一个简化的站点模板自动化抓取工具,帮助开发者高效地进行数据采集。

目录

  1. 环境准备
  2. 网络爬虫的基本概念
  3. Go 爬虫框架选型
  4. 设计爬虫的基本流程
  5. 实现简单的网页爬虫
  6. 解析 HTML 内容
  7. 爬虫的并发处理
  8. 数据存储
  9. 错误处理和重试机制
  10. 实战案例:抓取新闻网站
  11. 高级功能与优化
  12. 结论

1. 环境准备

在开始之前,请确保你的系统上已经安装了 Go 1.19。可以通过以下命令检查 Go 的版本:

go version

如果尚未安装 Go,可以从 Go 官方网站 下载并安装最新版本。

2. 网络爬虫的基本概念

网络爬虫的基本工作流程如下:

  1. 发送请求:向目标网页发送 HTTP 请求。
  2. 获取响应:接收服务器返回的 HTTP 响应。
  3. 解析内容:从响应中提取所需数据。
  4. 存储数据:将提取的数据保存到本地文件或数据库。
  5. 处理链接:提取网页中的链接,继续抓取其他页面。

3. Go 爬虫框架选型

在 Go 语言中,有多个流行的爬虫框架,例如:

  • Colly:一个快速和优雅的爬虫框架,提供了丰富的功能和良好的性能。
  • Goquery:一个类似 jQuery 的库,用于解析和操作 HTML 文档。
  • HTTP 客户端:标准库的 net/http 包,可以满足大部分简单的 HTTP 请求需求。

本文将主要使用 Colly 和 Goquery 进行网页爬取和内容解析。

4. 设计爬虫的基本流程

我们将设计一个简化的站点模板自动化抓取工具,其基本流程如下:

  1. 初始化爬虫配置。
  2. 发送 HTTP 请求,获取网页内容。
  3. 使用 Goquery 解析 HTML 内容,提取所需数据。
  4. 保存数据到本地文件或数据库。
  5. 处理错误和重试机制。
  6. 使用并发处理提高抓取效率。

5. 实现简单的网页爬虫

首先,创建一个新的 Go 项目:

mkdir go_scraper
cd go_scraper
go mod init go_scraper

然后,安装 Colly 和 Goquery:

go get -u github.com/gocolly/colly
go get -u github.com/PuerkitoBio/goquery

接下来,编写一个简单的爬虫来抓取网页内容:

package main

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

func main() {
    // 创建一个新的爬虫实例
    c := colly.NewCollector()

    // 设置请求时的回调函数
    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Visiting", r.URL.String())
    })

    // 设置响应时的回调函数
    c.OnResponse(func(r *colly.Response) {
        fmt.Println("Visited", r.Request.URL)
        fmt.Println("Response:", string(r.Body))
    })

    // 设置错误处理的回调函数
    c.OnError(func(r *colly.Response, err error) {
        fmt.Println("Error:", err)
    })

    // 设置HTML解析时的回调函数
    c.OnHTML("title", func(e *colly.HTMLElement) {
        fmt.Println("Title:", e.Text)
    })

    // 开始爬取
    c.Visit("http://example.com")
}

运行以上代码,将会抓取 http://example.com 的内容并打印网页标题。

6. 解析 HTML 内容

为了从网页中提取所需的数据,我们需要使用 Goquery 解析 HTML 内容。以下示例展示了如何使用 Goquery 提取网页中的链接和文本:

package main

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

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

    c.OnHTML("body", func(e *colly.HTMLElement) {
        e.DOM.Find("a").Each(func(index int, item *goquery.Selection) {
            link, _ := item.Attr("href")
            text := item.Text()
            fmt.Printf("Link #%d: %s (%s)\n", index, text, link)
        })
    })

    c.Visit("http://example.com")
}

7. 爬虫的并发处理

为了提高爬虫的效率,我们可以使用 Colly 的并发功能:

package main

import (
    "fmt"
    "github.com/gocolly/colly"
    "github.com/PuerkitoBio/goquery"
    "log"
    "time"
)

func main() {
    c := colly.NewCollector(
        colly.Async(true), // 启用异步模式
    )

    c.Limit(&colly.LimitRule{
        DomainGlob:  "*",
        Parallelism: 2, // 设置并发数
        Delay:       2 * time.Second,
    })

    c.OnHTML("body", func(e *colly.HTMLElement) {
        e.DOM.Find("a").Each(func(index int, item *goquery.Selection) {
            link, _ := item.Attr("href")
            text := item.Text()
            fmt.Printf("Link #%d: %s (%s)\n", index, text, link)
            c.Visit(e.Request.AbsoluteURL(link))
        })
    })

    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Visiting", r.URL.String())
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Println("Error:", err)
    })

    c.Visit("http://example.com")

    c.Wait() // 等待所有异步任务完成
}

8. 数据存储

将抓取的数据保存到本地文件或数据库中。这里以 CSV 文件为例:

package main

import (
    "encoding/csv"
    "fmt"
    "github.com/gocolly/colly"
    "github.com/PuerkitoBio/goquery"
    "log"
    "os"
    "time"
)

func main() {
    file, err := os.Create("data.csv")
    if err != nil {
        log.Fatalf("could not create file: %v", err)
    }
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush()

    c := colly.NewCollector(
        colly.Async(true),
    )

    c.Limit(&colly.LimitRule{
        DomainGlob:  "*",
        Parallelism: 2,
        Delay:       2 * time.Second,
    })

    c.OnHTML("body", func(e *colly.HTMLElement) {
        e.DOM.Find("a").Each(func(index int, item *goquery.Selection) {
            link, _ := item.Attr("href")
            text := item.Text()
            fmt.Printf("Link #%d: %s (%s)\n", index, text, link)
            writer.Write([]string{text, link})
            c.Visit(e.Request.AbsoluteURL(link))
        })
    })

    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Visiting", r.URL.String())
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Println("Error:", err)
    })

    c.Visit("http://example.com")

    c.Wait()
}

9. 错误处理和重试机制

为了提高爬虫的稳定性,我们需要处理请求错误并实现重试机制:

package main

import (
    "fmt"
    "github.com/gocolly/colly"
    "github.com/PuerkitoBio/goquery"
    "log"
    "os"
    "time"
)

func main() {
    file, err := os.Create("data.csv")
    if err != nil {
        log.Fatalf("could not create file: %v", err)
    }
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush()

    c := colly.NewCollector(
        colly.Async(true),
        colly.MaxDepth(1),
    )

    c.Limit(&colly.LimitRule{
        DomainGlob:  "*",
        Parallelism: 2,
        Delay:       2 * time.Second,
    })

    c.OnHTML("body", func(e *colly.HTMLElement) {
        e.DOM.Find("a").Each(func(index int, item *goquery.Selection) {
            link, _ := item.Attr("href")
            text := item.Text()
            fmt.Printf("Link #%d: %s (%s)\

n", index, text, link)
            writer.Write([]string{text, link})
            c.Visit(e.Request.AbsoluteURL(link))
        })
    })

    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Visiting", r.URL.String())
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Println("Error:", err)
        // 重试机制
        if r.StatusCode == 0 || r.StatusCode >= 500 {
            r.Request.Retry()
        }
    })

    c.Visit("http://example.com")

    c.Wait()
}

10. 实战案例:抓取新闻网站

以下示例展示了如何抓取新闻网站的标题和链接,并保存到 CSV 文件中:

package main

import (
    "encoding/csv"
    "fmt"
    "github.com/gocolly/colly"
    "log"
    "os"
    "time"
)

func main() {
    file, err := os.Create("news.csv")
    if err != nil {
        log.Fatalf("could not create file: %v", err)
    }
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush()

    writer.Write([]string{"Title", "Link"})

    c := colly.NewCollector(
        colly.Async(true),
    )

    c.Limit(&colly.LimitRule{
        DomainGlob:  "*",
        Parallelism: 5,
        Delay:       1 * time.Second,
    })

    c.OnHTML(".news-title", func(e *colly.HTMLElement) {
        title := e.Text
        link := e.ChildAttr("a", "href")
        writer.Write([]string{title, e.Request.AbsoluteURL(link)})
        fmt.Printf("Title: %s\nLink: %s\n", title, e.Request.AbsoluteURL(link))
    })

    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Visiting", r.URL.String())
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Println("Error:", err)
        if r.StatusCode == 0 || r.StatusCode >= 500 {
            r.Request.Retry()
        }
    })

    c.Visit("http://example-news-site.com")

    c.Wait()
}

11. 高级功能与优化

使用代理

为了避免被目标网站屏蔽,可以使用代理:

c.SetProxy("http://proxyserver:port")

用户代理伪装

通过设置用户代理,伪装成不同的浏览器:

c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

分布式爬虫

可以使用 Colly 的扩展库 Colly-Redis 实现分布式爬虫:

import (
    "github.com/gocolly/redisstorage"
)

func main() {
    c := colly.NewCollector()
    redisStorage := &redisstorage.Storage{
        Address:  "localhost:6379",
        Password: "",
        DB:       0,
        Prefix:   "colly",
    }
    c.SetStorage(redisStorage)
}

动态网页抓取

对于动态网页,可以使用无头浏览器,如 chromedp:

import (
    "context"
    "github.com/chromedp/chromedp"
)

func main() {
    ctx, cancel := chromedp.NewContext(context.Background())
    defer cancel()

    var res string
    err := chromedp.Run(ctx,
        chromedp.Navigate("http://example.com"),
        chromedp.WaitVisible(`#some-element`),
        chromedp.InnerHTML(`#some-element`, &res),
    )

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(res)
}

12. 结论

通过本文的详细介绍,我们学习了如何使用 Go 1.19 实现一个简化的站点模板自动化抓取工具。我们从基础的爬虫设计流程开始,逐步深入到 HTML 解析、并发处理、数据存储和错误处理等关键环节,并通过具体的代码示例展示了如何抓取和处理网页数据。

Go 语言强大的并发处理能力和丰富的第三方库,使其成为构建高效、稳定的网络爬虫的理想选择。通过不断优化和扩展,可以实现更复杂和高级的爬虫功能,为各种数据采集需求提供解决方案。

希望本文能为你在 Go 语言下实现网络爬虫提供有价值的参考,并激发你在这一领域进行更多探索和创新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777375.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MAX98357、MAX98357A、MAX98357B小巧、低成本、PCM D类IIS放大器,具有AB类性能中文说明规格书

前言: MAX98357A支持标准I2S数据,MAX98357B支持左对齐数字音频数据。两个版本均支持8通道TDM音频数据。 IIS数字功放MAX98357开发板/评估系统 MAX98357 WLP-9(1.347x1.437mm)封装的外观和丝印AKM MAX98357 TQFN-16-EP(3x3mm)封装的外观和丝印AKK 引脚说…

Rust: polars行遍历,从dataframe到struct及Bar设计比较

pandas提供了iterrows()、itertuples()、apply等行遍历的方式,还是比较方便的。 polars的列操作功能非常强大,这个在其官网上有详细的介绍。由于polars底层的arrow是列存储模式,行操作效率低下,官方也不推荐以行方式进行数据操作。…

谷粒商城 - 树形菜单递归流查询、三级分类数据查询性能优化、Jmter 性能压测

目录 树形分类菜单(递归查询,强扩展) 1)需求 2)数据库表设计 3)实现 4)关于 asSequence 优化 性能压测 1)Jmeter 安装使用说明 2)中间件对性能的影响 三级分类数…

昇思25天学习打卡营第5天|GAN图像生成

文章目录 昇思MindSpore应用实践基于MindSpore的生成对抗网络图像生成1、生成对抗网络简介零和博弈 vs 极大极小博弈GAN的生成对抗损失 2、基于MindSpore的 Vanilla GAN3、基于MindSpore的手写数字图像生成导入数据数据可视化模型训练 Reference 昇思MindSpore应用实践 本系列…

01 企业网站架构部署于优化之Web基础与HTTP协议

目录 1.1 Web基础 1.1.1 域名和DNS 1. 域名的概念 2. Hosts文件 3. DNS 4. 域名注册 1.1.2 网页与HTML 1. 网页概述 2. HTML概述 3. HTML基本标签 4. 网站和主页 5. Web1.0与Web2.0 1.1.3 静态网页与动态网页 1. 静态网页 2. 动态网页 3. 动态网页语言 1.2 HTTP协议 1…

如何快速开展每日待办工作 待办任务高效管理

每天,我们都需要处理大量的待办工作,如何高效有序地开展这些工作成为了我们必须要面对的问题。仅仅依靠个人的记忆和脑力去管理这些繁杂的事务,显然是一项艰巨的挑战。在这个时候,如果能有一款实用的待办工具来辅助我们&#xff0…

7年跨境业务资深从业者的代理IP使用经验分享

作为一个拥有7年跨境业务经验的资深从业者,今天大家分享一下在使用各种代理IP服务中的宝贵经验。无论你是新手还是老手,这篇文章都能为你带来一些新的启发和实用技巧。 在我刚开始跨境业务的那几年,最大的挑战之一就是如何跨境访问&#xff0…

ORB 特征点提取

FAST关键点 选取像素p,假设它的亮度为Ip; . 设置一个阈值T(比如Ip的20%); 以像素p为中心,选取半径为3的圆上的16个像素点; 假如选取的圆上,有连续的N个点的亮度大于IpT或小于…

CSS实现图片裁剪居中(只截取剪裁图片中间部分,图片不变形)

1.第一种方式:(直接给图片设置:object-fit:cover;) .imgbox{width: 100%;height:200px;overflow: hidden;position: relative;img{width: 100%;height: 100%; //图片要设置高度display: block;position: absolute;left: 0;right…

【Python基础篇】你了解python中运算符吗

文章目录 1. 算数运算符1.1 //整除1.2 %取模1.3 **幂 2. 赋值运算符3. 位运算符3.1 &&#xff08;按位与&#xff09;3.2 |&#xff08;按位或&#xff09;3.3 ^&#xff08;按位异或&#xff09;3.4 ~&#xff08;按位取反&#xff09;3.5 <<&#xff08;左移&#…

【JavaWeb程序设计】JSP编程II

目录 一、输入并运行下面的import_test.jsp页面 1.1 代码运行结果 1.2 修改编码之后的运行结果 二、errorPage属性和isErrorPage属性的使用 2.1 下面的hello.jsp页面执行时将抛出一个异常&#xff0c;它指定了错误处理页面为errorHandler.jsp。 2.1.2 运行截图 2.2 下面…

压测工具---Ultron

压测工具&#xff1a;Ultron 类型&#xff1a;接口级和全链路 接口级 对于接口级别的压测我们可以进行 http接口压测、thrift压测、redis压测、kafka压测、DDMQ压测、MySQL压测等&#xff0c;选对对应的业务线、选择好压测执行的时间和轮数就可以执行压测操作了 全链路 对…

Java新特性梳理——Java15

highlight: xcode theme: vuepress 概述 2020 年 9 月 15 日&#xff0c;Java 15 正式发布&#xff0c;(风平浪静的一个版本)共有 14 个 JEP&#xff0c;是时间驱动形式发布的第六个版本。相关文档&#xff1a;https://openjdk.java.net/projects/jdk/15/ 语法层面变化 密封类 …

【机器学习】基于密度的聚类算法:DBSCAN详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 基于密度的聚类算法&#xff1a;DBSCAN详解引言DBSCAN的基本概念点的分类聚类过…

JVM原理(十七):JVM虚拟机即时编译器详解

编译器无论在何时、在何种状态下把Class文件转换成与本地基础设施相关的二进制机器码&#xff0c;他都可以视为整个编译过程的后端。 后端编译器编译性能的好坏、代码优化质量的高低却是衡量一款商用虛拟机优秀与否的关键指标之一。 1. 即时编译器 即时编译器是一个把Java的…

19.【C语言】初识指针(重难点)

内存&#xff1a;所有程序的运行在内存中 用Cheat Engine查看任意程序的内存(16进制&#xff09;&#xff1a; 显示大量的数据 想要定位某个数字 &#xff0c;需要知道地址(类比二维坐标) 如F8的地址为00BCB90008,所以是00BCB908(偏移) ctrlG 则有 内存单元的说明&#xff1…

动态颤抖的眼睛效果404页面源码

动态颤抖的眼睛效果404页面源码&#xff0c; 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 动态颤抖的眼睛效果404页面源码

Portainer 是一个开源的容器管理平台-非常直观好用的Docker图形化项目

在这个容器化技术大行其道的时代&#xff0c;Docker和Kubernetes几乎成了技术圈的新宠。可是管理起容器来&#xff0c;有时候还是有点头大。命令行操作对于某些小伙伴来说&#xff0c;可能还是有点不太友好。 今天开源君分享一个叫 Portainer 的开源项目&#xff0c;一个用来简…

AI大模型时代的存储发展趋势

从2022年下半年&#xff0c;大模型和AIGC这两个词变得极其火热&#xff0c;而GPU的市场也是一卡难求。对于这种迷乱和火热&#xff0c;让我想起了当年的比特币挖矿和IPFS。似乎世界一年一个新风口&#xff0c;比特币、元宇宙、NFT、AIGC&#xff0c;金钱永不眠&#xff0c;IT炒…

【React】React18 Hooks 之 useReducer

目录 useReducer案例1&#xff1a;useReducer不带初始化函数案例2&#xff1a;useReducer带初始化函数注意事项1&#xff1a;dispatch函数不会改变正在运行的代码的状态注意事项2&#xff1a;获取dispatch函数触发后 JavaScript 变量的值注意事项3&#xff1a;触发了reducer&am…