我的Go+语言初体验——(4)零基础学习 Go+ 爬虫

youcans_ 社区中级贡献者
人工智能领域优质创作者
博客专家认证
2021-12-01 11:18:47

我的Go+语言初体验——(4)零基础学习 Go+ 爬虫

[“我的Go+语言初体验” | 征文活动进行中......]

Go+ 语言非常适合编写爬虫程序,具有并发机制完善、并发数量大、占用资源少、运行速度快、部署方便的优点。
本文结合官方文档与 Go 语言的资料,循序渐进介绍 Go+ 爬虫编程,通过多个完整例程带你学习和编写爬虫程序。
本文全部例程已在 Go+ 环境下进行了测试。

 

1. 为什么用 Go+ 语言编写爬虫

网络爬虫的工作原理是通过检查 web 页面的 HTML 内容,并基于内容执行某种类型的行动。特别是提取当前页面的数据,以及通过抓取和分析暴露的链接,按照队列去爬取页面数据。

Go+ 语言非常适合编写爬虫程序,并且具有独特的优势:

  • 并发机制完善
  • 并发数量大
  • 占用资源少
  • 运行速度快
  • 部署方便

 

2. http.Get 方法实现简单请求

2.1 http.Get 方法说明

net 包封装了网络相关的功能,最常用的是 http 和 url。Go+ 语言可以使用 net/http 包实现请求网页。

http.Get 方法的基本语法如下:

resp, err := http.Get("http://example.com/")

参数:请求的目标 URL

向服务器发送一个 http get 请求,得到 response。

2.2 http.Get 获得网页的 html 源文件

我们首先用 http.Get 编写一个简单的爬虫程序。

【例程 1】http.Get 获得网页的 html 源文件

// 1. http.Get 获得网页的 html 源文件
package main
import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	resp, _ := http.Get("http://www.baidu.com")
	defer resp1.Body.Close()
	contents, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(contents))
}

运行【例程1】,可以抓取百度首页的 html 源文件。

数据太多,看不清楚?没有关系,我们可以把抓取的数据保存下来。

2.3 异常处理和网页保存

网络活动十分复杂,对网站的访问不一定都会成功,因此需要对爬取过程中的异常进行处理,否则爬虫在遇到异常时就会因发生运行错误。

  • 导致异常的原因主要有:
  • 链接不上服务器
  • 远程URL不存在
  • 无网络
  • 触发了 HTTPError

我们以 CSDN 热榜网页为例,说明异常处理和数据保存,并将爬取的网页保存在 .vscode 目录下的 page01.html 文件中。

【例程 2】异常处理和网页保存

// 2. 抓取网页,异常处理和数据保存
package main
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	resp, err := http.Get("https://blog.csdn.net/rank/list")
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		fmt.Println(resp.StatusCode)
	}

	f, err := os.OpenFile("csdnPage01.html", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		panic(err)
		return
	}
	defer f.Close()

	buf := make([]byte, 1024)
	for {
		n, _ := resp.Body.Read(buf)
		if 0 == n {
			break
		}
		f.WriteString(string(buf[:n]))
	}
}

加入异常处理后,可以处理网页访问中发生的异常,可以保证从服务器成功获取网页。

但是,我们真的抓到了所要的网页吗?打开 csdnPage01.html 文件,如下图所示。

【例程 2】确实成功抓取了 CSDN 热榜页面中的一些内容,但关键的热榜文章的具体内容并没有抓到。没有关系,我们一步步来尝试。

 

3. client.Get 方法实现发起请求

3.1 Client 对象说明

Client 方法是 http 包内部发起请求的组件,可以控制请求的超时、重定向和其它设置。

Client 的定义如下:

type Client struct {
    Transport     RoundTripper // 超时控制管理
    CheckRedirect func(req *Request, via []*Request) error // 控制重定向
    Jar           CookieJar // 管理 Cookie 的对象
    Timeout       time.Duration // 限制建立连接的时间
}

Client 的简单调用方法与  http.Get 方法类似,例如:

resp, err := client.Get("http://example.com")

// 参数:请求的目标 URL

3.2 控制 HTTP 客户端的头结构

更加常用地,要在请求的时候设置头参数、cookie、证书验证等参数,可以在使用 Client 时,先构造一个 Request 再调用 Client.Do() 方法。

【例程 3】控制 HTTP 客户端的头结构

// 3. 控制 HTTP 客户端的头结构
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func checkError(err error) {
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	}
}

func main() {
	url := "https://blog.csdn.net/youcans"  // 生成 url
	client := &http.Client{}  // 生成 client
	req, err := http.NewRequest("GET", url, nil)  // 提交请求
	checkError(err)  // 异常处理

	// 自定义Header
	cookie_str := "your cookie" //  从浏览器复制的 cookie 字符串
	req.Header.Set("Cookie", cookie_str)
	userAgent_str := "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
	req.Header.Set("User-Agent", userAgent_str)  // 生成 User-Agent

	resp, err := client.Do(req)  // 处理返回结果
	checkError(err)
	defer resp.Body.Close()  // 关闭相关链接

    // 状态码检验 (http.StatusOK=200)
	if resp.StatusCode == http.StatusOK {
		fmt.Println(resp.StatusCode)
	}

	/*
		contents, err := ioutil.ReadAll(resp.Body)
		checkError(err)
		fmt.Println(string(contents))
	*/

	f, err := os.OpenFile("csdnPage02.html", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		panic(err)
		return
	}
	defer f.Close()

	buf := make([]byte, 1024)
	for {
		n, _ := resp.Body.Read(buf)
		if 0 == n {
			break
		}
		f.WriteString(string(buf[:n]))
	}
}

【例程 3】成功抓取了 CSDN 网站的 youcans 页面。打开 csdnPage01.html 文件,显示如下:

3.3 解析网页数据

对于获取的网页源代码需要进行解析,以得到我们需要的数据。

依据响应的不同类型,可以选择不同的方法:

- 对于 html 格式的数据,可以选择正则表达式或者 Css 选择器获取所需的内容;

- 对于 json 格式的数据,可以使用 encoding/json 库对获取的数据反序列化,获取所需的内容。

正则表达式是进行模式匹配和文本操纵的的工具。Go+ 语言通过 regexp 包为正则表达式提供支持,采用 RE2 语法,与 Go、Python 语言的正则一致。 

【函数说明】func MustCompile(str string) \*Regexp

通过 MustCompile 可以创建一个 Regexp 对象

MustCompile 用来解析正则表达式 str 是否合法

如果合法,则返回一个 Regexp 对象;如果不合法,则抛出异常

例如:

rp :=regexp.MustCompile(`<div class="hd">(.*?)</div>`)
// 查找并返回以 <div class="hd"> 开头,以 </div> 结尾的字符串
titleRe := regexp.MustCompile(`<span class="title">(.*?)</span>`)
// 查找并返回以 <span class="title"> 开头,以 </span> 结尾的字符串

【函数说明】func (re \*Regexp) FindAllStringSubmatch(s string, n int) string

在 s 中查找 re 中编译好的正则表达式,并返回所有匹配的内容

同时返回子表达式匹配的内容

【函数说明】func (re \*Regexp) FindStringSubmatchIndex(s string) []int  

在 s 中查找 re 中编译好的正则表达式,并返回第一个匹配的位置

同时返回子表达式匹配的位置

【函数说明】func (re \*Regexp) FindStringSubmatch(s string) []string

在 s 中查找 re 中编译好的正则表达式,并返回第一个匹配的内容

同时返回子表达式匹配的内容

【例程 4】解析网页数据

// 4. 解析网页数据
package main
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// 异常处理
func checkError(err error) {
	if err != nil {
		fmt.Printf("%s", err)
		panic(err)
	}
}

// URL 请求
func fetch(url string) string {
	fmt.Println("Fetch Url", url)
	client := &http.Client{}                     // 生成 client
	req, err := http.NewRequest("GET", url, nil) // 提交请求
	checkError(err)

	// 自定义 Header
	userAgent_str := "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; http://www.baidu.com)"
	req.Header.Set("User-Agent", userAgent_str) // 生成 User-Agent
	resp, err := client.Do(req)                 // 处理返回结果
	checkError(err)
	defer resp.Body.Close()

	// 状态码检验 (http.StatusOK=200)
	if resp.StatusCode == 200 {
		body, err := ioutil.ReadAll(resp.Body) // 读取resp的body内容
		checkError(err)
		return string(body)
	} else {
		fmt.Printf("%s", err)
		return ""
	}
}

// 解析页面
func parseUrls(url string, f *os.File) {
	//func parseUrls(url string) {
	body := fetch(url)
	body = strings.Replace(body, "\n", "", -1) // 去除 body 内容中的回车符
	rp := regexp.MustCompile(`<div class="hd">(.*?)</div>`)
	titleRe := regexp.MustCompile(`<span class="title">(.*?)</span>`)
	idRe := regexp.MustCompile(`<a href="https://movie.douban.com/subject/(\d+)/"`)
	items := rp.FindAllStringSubmatch(body, -1) // 解析符合正则表达式的结果
	for _, item := range items {
		idItem := idRe.FindStringSubmatch(item[1])[1]       // 找第一个符合的结果 ID
		titleItem := titleRe.FindStringSubmatch(item[1])[1] // 找第一个符合的结果 TITLE
		//fmt.Println(idItem, titleItem)
		_, err := f.WriteString(idItem + "\t" + titleItem + "\n")
		checkError(err)
	}
	return
}

func main() {
	f, err := os.Create("topMovie.txt") // 创建文件
	defer f.Close()

	_, err = f.WriteString("ID\tTitle\n") // 描述字段
	checkError(err)

	start := time.Now()
	// 抓取全部 Top250
	for i := 0; i < 10; i++ {
		parseUrls("https://movie.douban.com/top250?start="+strconv.Itoa(25*i), f)
	}  // 把数字转成字符串

	elapsed := time.Since(start)
	fmt.Printf("Took %s", elapsed)
}

【例程 4】中的 URL 请求获取网页数据的程序内容与【例程3】的方法相同,只是将其封装为函数 fetch() 以方便使用。

【例程 4】从豆瓣抓取了 Top250 影片的信息,运行后控制台显示内容为:

> Fetch Url https://movie.douban.com/top250?start=0
> Fetch Url https://movie.douban.com/top250?start=25
> Fetch Url https://movie.douban.com/top250?start=50
> Fetch Url https://movie.douban.com/top250?start=75
> Fetch Url https://movie.douban.com/top250?start=100
> Fetch Url https://movie.douban.com/top250?start=125
> Fetch Url https://movie.douban.com/top250?start=150
> Fetch Url https://movie.douban.com/top250?start=175
> Fetch Url https://movie.douban.com/top250?start=200
> Fetch Url https://movie.douban.com/top250?start=225

文件 topMovie.txt 保存的内容为:

> ID    Title
> 1292052    肖申克的救赎
> 1291546    霸王别姬
> 1292720    阿甘正传
> ...
> 1292528    猜火车
> 1307394    千年女优

说明:【例程4】参考了资深工程师 【Golang编程】的文章:[用Golang写爬虫(一)],特此致谢!作者进行了改编,并在 Go+ 环境进行了测试。

 

4. 总结

Go+ 语言非常适合编写爬虫程序,这也是 Go+ 的优势领域。

本文结合官方文档与 Go 语言的资料,循序渐进介绍 Go+ 爬虫编程,通过多个完整例程带你学习和编写爬虫程序。

本文中的全部例程都已在 Go+ 环境下进行调试和运行测试。

高并发是 Go+ 的重要机制和优点,在爬虫中也能非常有效,我们将在后续文中进行学习。


【本节完】

 

版权声明:

原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/121644252)

【例程4】参考了资深工程师 “Golang编程”的文章:[用Golang写爬虫(一)],作者进行了改编,特此致谢。

 

Copyright 2021 youcans, XUPT

Crated:2021-11-30

**欢迎关注『我的Go+语言初体验』系列,持续更新中…**

> [我的Go+语言初体验——(1)超详细安装教程](https://blog.csdn.net/youcans/article/details/121584358)
> [我的Go+语言初体验——(2) IDE 详细安装教程](https://blog.csdn.net/youcans/article/details/121601845)
> [我的Go+语言初体验——(3)Go+ 数据类型](https://blog.csdn.net/youcans/article/details/121619284)
> [我的Go+语言初体验——(4)零基础学习 Go+ 爬虫](https://blog.csdn.net/youcans/article/details/121644252)

[“我的Go+语言初体验” | 征文活动进行中......]

...全文
148 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

1,019

社区成员

发帖
与我相关
我的任务
社区描述
Go+ 官方开发者社区。我们希望向广大的开发者和数据科学家介绍 Go+ 的定位和意义,并邀请更多开发者一起贡献代码、共建 Go+ 生态。 Go+ 官网:https://goplus.org/
其他 企业社区
社区管理员
  • Go+
  • 杨东杰
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

本社区为 Go+ 官方开发者社区。我们希望向广大的开发者和数据科学家介绍 Go+ 的定位和意义,并邀请更多开发者一起贡献代码、共建 Go+ 生态。

Go+ 官网:https://goplus.org/
GitHub地址:https://github.com/goplus/gop

试试用AI创作助手写篇文章吧