网络爬虫-地鼠文档:https://www.topgoer.cn/docs/go42/go42-1d1jub3acmm0b

并发的Web爬虫-地鼠文档:https://www.topgoer.cn/docs/gopl-zh/gopl-zh-1d2a1e652l54v

分布式爬虫-地鼠文档:https://www.topgoer.cn/docs/advancedgoprogramming/advancedgoprogramming-1d27tnom3q8pe

Go语言圣经Go语言高级编程:本书涵盖CGO、Go汇编语言、RPC实现、Web框架实现、分布式系统等高阶主题,针对Go语言有一定经验想深入了解Go语言各种高级用法的开发人员。对于刚学习Go语言的读者,建议先从《Go语言圣经》开始系统学习Go语言的基础知识。

【Go语言实战】Go爬虫

静态数据爬取(Go爬取豆瓣TOP250)

引用:https://blog.csdn.net/weixin_45304503/article/details/120390989

首先看豆瓣TOP250的网页请求格式:

1、发送请求

Golang中提供了net/http这个包原生支持requestresponse

1.构造客户端

1
var client http.Client

2.构造GET请求:

1
req, err := http.NewRequest("GET", URL, nil) // URL BODY

2.或者构造POST请求:

Go中提供了一个cookiejar.New的函数方法,用于保留生成Cookie信息,这个是为了一些网站要登陆才能爬取的情况,所以我们登陆完之后,会有一个cookie,这个cookie是存储用户信息的,也就是这个信息是让服务器知道是谁进行这一次的访问!比如说登陆学校的教务处进行爬取课表,因为课表每个人都可能是不同的,所以就需要登陆,让服务器知道这是谁的课表信息,所以就需要在请求头上加上cookie进行伪装爬取。

1
2
3
4
jar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}

构造POST请求的时候,可以把要传输的数据进行封装好,与URL一起构造

1
2
3
4
var client http.Client
Info :="muser="+muserid+"&"+"passwd="+password
var data = strings.NewReader(Info)
req, err := http.NewRequest("POST", URL, data)

3.添加请求头(请求头可以在网页F12中看到,不知道要加什么的话就全部加上)

1
2
3
4
5
6
7
8
9
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")

4.发送请求

1
resp, _:= client.Do(req)  // 发送请求

第一步完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// todo 1. 发送请求
// 1. 构造客户端
client := http.Client{}
// 2. 构造GET请求
URL := "https://movie.douban.com/top250"
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
fmt.Println("构造Get请求失败: ", err)
}
// 3. 添加请求头
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")

// 4. 发送请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败: ", err)
}
defer resp.Body.Close()

2、解析网页(获取网页源码)

方式有多种:1.CSS选择器;2.Xpath语法;3.Regex正则

  1. CSS选择器

github.com/PuerkitoBio/goquery 提供了.NewDocumentFromReader方法进行网页的解析。

1
doc, err := goquery.NewDocumentFromReader(resp.Body)

go get github.com/PuerkitoBio/goquery

下载goquery然后import进来

3、获取节点信息(解析数据)

指的是获取指定根下的内容

我们拿到上一步解析出来的doc之后,可以进行css选择器语法,进行结点的选择。

  • 语法:doc.Find(selector)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 举例:查看豆瓣电影的top1的title
// 下面这句查看网页源码然后右键复制selector,使用Find()方法查看内容(记得后面有.text())
// #content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a > span:nth-child(1)
//title1 := doc.Find("#content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a > span:nth-child(1)").Text()
//fmt.Println("title:", title1)
////////////////////////////////////////////////////////////////////////////////////////
// 循环列表,进行遍历
doc.Find("#content > div > div.article > ol > li"). // 列表
Each(func(i int, s *goquery.Selection) { // 在列表里面继续找
title := s.Find("div > div.info > div.hd > a > span:nth-child(1)").Text() // 电影标题
img := s.Find("div > div.pic > a > img") // img图片,但是img标签在sec属性里面
imgTmp, ok := img.Attr("src") // 获得img中src中的值,不存在ok就为err
info := s.Find("div > div.info > div.bd > p:nth-child(1)").Text() // 电影信息
score := s.Find("div > div.info > div.bd > div > span.rating_num").Text() // 电影评分
quote := s.Find("div > div.info > div.bd > p.quote > span").Text() // 电影评论
if ok {
fmt.Println("---------------------------")
fmt.Println("title:", title)
fmt.Println("imgTmp:", imgTmp)
fmt.Println("info:", info)
fmt.Println("score:", score)
fmt.Println("quote:", quote)
}
})

对数据进行进一步处理

① 将电影信息中的导演、主演、年份等进一步细分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func InfoSpite(info string) (director, actor, year string) {
directorRe, _ := regexp.Compile(`导演:(.*)主演:`) // 正则 .*匹配一行所有
director = string(directorRe.Find([]byte(info)))

actorRe, _ := regexp.Compile(`主演:(.*)`) // 正则 .*匹配一行所有
actor = string(actorRe.Find([]byte(info)))

yearRe, _ := regexp.Compile(`(\d+)`) //正则表达式 \d+匹配数字 == 年份
year = string(yearRe.Find([]byte(info)))
return
}
/*
导演: 罗伯·莱纳 Rob Reiner   主演: 玛德琳·卡罗尔 Madeline Carroll / 卡...
2010 / 美国 / 剧情 喜剧 爱情
==>
director: 导演: 罗伯·莱纳 Rob Reiner   主演:
actor: 主演: 玛德琳·卡罗尔 Madeline Carroll / 卡...
year: 2010

*/

② 添加上分页(一页25 ==> 10页)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
// 豆瓣top250 : https://movie.douban.com/top250/start=?
for i := 0; i < 10; i++ {
fmt.Printf("正在爬取第 %d 页的信息\n", i)
Spider(strconv.Itoa(i * 25))
}
}
func Spider(page string) {
// todo 1. 发送请求
// 构造客户端
client := http.Client{}
// 构造GET请求
URL := "https://movie.douban.com/top250?start=" + page
req, err := http.NewRequest("GET", URL, nil)
...

第三步完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
"fmt"
"github.com/PuerkitoBio/goquery"
"net/http"
"regexp"
"strconv"
)
func main() {
// 豆瓣top250 : https://movie.douban.com/top250
for i := 0; i < 10; i++ {
fmt.Printf("正在爬取第 %d 页的信息\n", i)
Spider(strconv.Itoa(i * 25))
}
}

func Spider(page string) {
// todo 1. 发送请求
// 构造客户端
client := http.Client{}
// 构造GET请求
URL := "https://movie.douban.com/top250?start=" + page
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
fmt.Println("构造Get请求失败: ", err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")

// 发送请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败: ", err)
}
defer resp.Body.Close()

// todo 2. 解析网页
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
fmt.Println("解析失败", err)
}

// todo 3. 获取节点信息
// 举例:查看豆瓣电影的top1的title
// 下面这句查看网页源码然后右键复制selector,使用Find()方法查看内容(记得后面有.text())
// #content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a > span:nth-child(1)
//title1 := doc.Find("#content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a > span:nth-child(1)").Text()
//fmt.Println("title:", title1)
////////////////////////////////////////////////////////////////////////////////////////
// 循环列表,进行遍历
doc.Find("#content > div > div.article > ol > li"). // 列表
Each(func(i int, s *goquery.Selection) { // 在列表里面继续找
title := s.Find("div > div.info > div.hd > a > span:nth-child(1)").Text() // 电影标题
img := s.Find("div > div.pic > a > img") // img图片,但是img标签在sec属性里面
imgTmp, ok := img.Attr("src") // 获得img中src中的值,不存在ok就为err
info := s.Find("div > div.info > div.bd > p:nth-child(1)").Text() // 电影信息
score := s.Find("div > div.info > div.bd > div > span.rating_num").Text() // 电影评分
quote := s.Find("div > div.info > div.bd > p.quote > span").Text() // 电影评论
if ok {
fmt.Println("---------------------------")
fmt.Println("title:", title)
fmt.Println("imgTmp:", imgTmp)
//fmt.Println("info:", info)
director, actor, year := InfoSpite(info)
fmt.Println("director:", director)
fmt.Println("actor:", actor)
fmt.Println("year:", year)
fmt.Println("score:", score)
fmt.Println("quote:", quote)
}
})

// todo 4. 保存信息
}

func InfoSpite(info string) (director, actor, year string) {
directorRe, _ := regexp.Compile(`导演:(.*)主演:`) // 正则 .*匹配一行所有
director = string(directorRe.Find([]byte(info)))

actorRe, _ := regexp.Compile(`主演:(.*)`) // 正则 .*匹配一行所有
actor = string(actorRe.Find([]byte(info)))

yearRe, _ := regexp.Compile(`(\d+)`) //正则表达式 \d+匹配数字 == 年份
year = string(yearRe.Find([]byte(info)))
return
}

4、保存信息

1. 使用原生SQL语句把数据保存Mysql中

①定义一个结构体(字段与数据库表字段相同)

1
2
3
4
5
6
7
8
9
10
// 与数据库表字段相同的结构体
type MovieData struct {
Title string `json:"title"`
Director string `json:"Director"`
Picture string `json:"Picture"`
Actor string `json:"Actor"`
Year string `json:"Year"`
Score string `json:"Score"`
Quote string `json:"Quote"`
}

② 在解析数据时保存进数据结构中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
doc.Find("#content > div > div.article > ol > li"). // 列表
Each(func(i int, s *goquery.Selection) { // 在列表里面继续找
var data MovieData
title := s.Find("div > div.info > div.hd > a > span:nth-child(1)").Text() // 电影标题
img := s.Find("div > div.pic > a > img") // img图片,但是img标签在sec属性里面
imgTmp, ok := img.Attr("src") // 获得img中src中的值,不存在ok就为err
info := s.Find("div > div.info > div.bd > p:nth-child(1)").Text() // 电影信息
score := s.Find("div > div.info > div.bd > div > span.rating_num").Text() // 电影评分
quote := s.Find("div > div.info > div.bd > p.quote > span").Text() // 电影评论
if ok {
director, actor, year := InfoSpite(info)
data.Title = title
data.Director = director
data.Actor = actor
data.Picture = imgTmp
data.Year = year
data.Score = score
data.Quote = quote
fmt.Println(data)
}
})

③ 连接数据库

  • 定义数据库链接参数
1
2
3
4
5
6
7
const (
USERNAME = "root"
PASSWORD = "123456"
HOST = "127.0.0.1"
PORT = "3306"
DBNAME = "spider"
)
  • 连接数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 要先引入mysql的驱动包, 不然会报错
// _ "github.com/jinzhu/gorm/dialects/mysql"

var DB *sql.DB
func InitDB(){ // 数据库初始化
path := strings.Join([]string{USERNAME, ":", PASSWORD, "@tcp(", HOST, ":", PORT, ")/", DBNAME, "?charset=utf8"}, "")
DB, _ = sql.Open("mysql", path)
DB.SetConnMaxLifetime(10)
DB.SetMaxIdleConns(5)
if err := DB.Ping(); err != nil{
fmt.Println("opon database fail")
return
}
fmt.Println("connect success")
}
  • 插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 插入数据库
func InsertData(data MovieData) bool {
tx, err := DB.Begin() // 开启数据库事务
if err != nil {
fmt.Println("开启数据库事务DB.Begin()失败:", err)
return false
}
// 编写sql语句(需要提前把数据准备好)
stmt, err := tx.Prepare("Insert INTO douban_movie(`Title`, `Director`, `Picture`, `Actor`, `Year`, `Score`, `Quote`) VALUES (?, ?, ?, ?, ?, ?, ?)")
if err != nil {
fmt.Println("数据准备tx.Prepare失败:", err)
return false
}
_, err = stmt.Exec(data.Title, data.Director, data.Picture, data.Actor, data.Year, data.Score, data.Quote)
if err != nil {
fmt.Println("数据插入stmt.Exec失败:", err)
return false
}
// 提交事务
tx.Commit()
return true
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package main

import (
"database/sql"
"fmt"
"github.com/PuerkitoBio/goquery"
_ "github.com/jinzhu/gorm/dialects/mysql"
"net/http"
"regexp"
"strconv"
"strings"
)

// 定义数据库连接的常量
const (
USERNAME = "root"
PASSWORD = "123456"
HOST = "127.0.0.1"
PORT = "3306"
DBNAME = "spider"
)

// 与数据库表字段相同的结构体
type MovieData struct {
Title string `json:"title"`
Director string `json:"Director"`
Picture string `json:"Picture"`
Actor string `json:"Actor"`
Year string `json:"Year"`
Score string `json:"Score"`
Quote string `json:"Quote"`
}

var DB *sql.DB

// 初始化数据库连接
func InitDB() {
path := strings.Join([]string{USERNAME, ":", PASSWORD, "@tcp(", HOST, ":", PORT, ")/", DBNAME, "?charset=utf8"}, "")
DB, _ = sql.Open("mysql", path)
DB.SetConnMaxLifetime(10)
DB.SetMaxIdleConns(5)
if err := DB.Ping(); err != nil {
fmt.Println("opon database fail")
return
}
fmt.Println("connect success")
}

func main() {
InitDB()
//豆瓣top250 : https://movie.douban.com/top250
for i := 0; i < 10; i++ {
fmt.Printf("正在爬取第 %d 页的信息\n", i)
Spider(strconv.Itoa(i * 25))
}
}

// 爬虫爬取过程
func Spider(page string) {
// todo 1. 发送请求
// 构造客户端
client := http.Client{}
// 构造GET请求
URL := "https://movie.douban.com/top250?start=" + page
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
fmt.Println("构造Get请求失败: ", err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")

// 发送请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败: ", err)
}
defer resp.Body.Close()

// todo 2. 解析网页
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
fmt.Println("解析失败", err)
}

// todo 3. 获取节点信息
doc.Find("#content > div > div.article > ol > li"). // 列表
Each(func(i int, s *goquery.Selection) { // 在列表里面继续找
var data MovieData
title := s.Find("div > div.info > div.hd > a > span:nth-child(1)").Text() // 电影标题
img := s.Find("div > div.pic > a > img") // img图片,但是img标签在sec属性里面
imgTmp, ok := img.Attr("src") // 获得img中src中的值,不存在ok就为err
info := s.Find("div > div.info > div.bd > p:nth-child(1)").Text() // 电影信息
score := s.Find("div > div.info > div.bd > div > span.rating_num").Text() // 电影评分
quote := s.Find("div > div.info > div.bd > p.quote > span").Text() // 电影评论
if ok {
// todo 4. 保存信息
director, actor, year := InfoSpite(info)
data.Title = title
data.Director = director
data.Actor = actor
data.Picture = imgTmp
data.Year = year
data.Score = score
data.Quote = quote
if InsertData(data) {
fmt.Printf("插入成功:%+v\n", data)
} else {
fmt.Printf("插入失败:%+v\n", data)
}
}
})

}

// 将豆瓣info信息中的导演、演员等信息用 正则表达式 提取出来
func InfoSpite(info string) (director, actor, year string) {
directorRe, _ := regexp.Compile(`导演:(.*)主演:`) // 正则 .*匹配一行所有
director = string(directorRe.Find([]byte(info)))

actorRe, _ := regexp.Compile(`主演:(.*)`) // 正则 .*匹配一行所有
actor = string(actorRe.Find([]byte(info)))

yearRe, _ := regexp.Compile(`(\d+)`) //正则表达式 \d+匹配数字 == 年份
year = string(yearRe.Find([]byte(info)))
return
}

// 插入数据库
func InsertData(data MovieData) bool {
tx, err := DB.Begin() // 开启数据库事务
if err != nil {
fmt.Println("开启数据库事务DB.Begin()失败:", err)
return false
}
// 编写sql语句(需要提前把数据准备好)
stmt, err := tx.Prepare("Insert INTO douban_movie(`Title`, `Director`, `Picture`, `Actor`, `Year`, `Score`, `Quote`) VALUES (?, ?, ?, ?, ?, ?, ?)")
if err != nil {
fmt.Println("数据准备tx.Prepare失败:", err)
return false
}
_, err = stmt.Exec(data.Title, data.Director, data.Picture, data.Actor, data.Year, data.Score, data.Quote)
if err != nil {
fmt.Println("数据插入stmt.Exec失败:", err)
return false
}
// 提交事务
tx.Commit()
return true
}

2. 使用GORM把数据保存到Mysql中

定义GORM模型model

1
2
3
4
5
6
7
8
9
10
11
type NewD struct {
gorm.Model
Title string `gorm:"type:varchar(255);not null;"`
Director string `gorm:"type:varchar(256);not null;"`
Picture string `gorm:"type:varchar(256);not null;"`
Actor string `gorm:"type:varchar(256);not null;"`
Year string `gorm:"type:varchar(256);not null;"`
Score string `gorm:"type:varchar(256);not null;"`
Quote string `gorm:"type:varchar(256);not null;"`
}

连接数据库(初始化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var gormdb *gorm.DB

func InitDBByGORM() {
var err error
path := strings.Join([]string{USERNAME, ":", PASSWORD, "@tcp(", HOST, ":", PORT, ")/", DBNAME, "?charset=utf8"}, "")
gormdb, err = gorm.Open("mysql", path)
if err != nil {
panic(err)
}
_ = gormdb.AutoMigrate(&NewD{})
sqlDB := gormdb.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
fmt.Println("connect success")
}

写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func InsertDataByGORM(data MovieData) bool {
NewA := NewD{
Title: data.Title,
Director: data.Director,
Picture: data.Picture,
Actor: data.Actor,
Year: data.Year,
Score: data.Score,
Quote: data.Quote,
}
err := gormdb.Create(&NewA).Error
if err != nil {
return false
}
return true
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package main

import (
"database/sql"
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"net/http"
"regexp"
"strconv"
"strings"
)

// 定义数据库连接的常量
const (
USERNAME = "root"
PASSWORD = "123456"
HOST = "127.0.0.1"
PORT = "3306"
DBNAME = "spider"
)

// 与数据库表字段相同的结构体
type MovieData struct {
Title string `json:"title"`
Director string `json:"Director"`
Picture string `json:"Picture"`
Actor string `json:"Actor"`
Year string `json:"Year"`
Score string `json:"Score"`
Quote string `json:"Quote"`
}

var DB *sql.DB

// 初始化数据库连接
func InitDBBySQL() {
path := strings.Join([]string{USERNAME, ":", PASSWORD, "@tcp(", HOST, ":", PORT, ")/", DBNAME, "?charset=utf8"}, "")
DB, _ = sql.Open("mysql", path)
DB.SetConnMaxLifetime(10)
DB.SetMaxIdleConns(5)
if err := DB.Ping(); err != nil {
fmt.Println("opon database fail")
return
}
fmt.Println("connect success")
}

var gormdb *gorm.DB

func InitDBByGORM() {
var err error
path := strings.Join([]string{USERNAME, ":", PASSWORD, "@tcp(", HOST, ":", PORT, ")/", DBNAME, "?charset=utf8"}, "")
gormdb, err = gorm.Open("mysql", path)
if err != nil {
panic(err)
}
_ = gormdb.AutoMigrate(&NewD{})
sqlDB := gormdb.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
fmt.Println("connect success")
}

func main() {
//InitDBBySQL()
InitDBByGORM()
//豆瓣top250 : https://movie.douban.com/top250
for i := 0; i < 10; i++ {
fmt.Printf("正在爬取第 %d 页的信息\n", i)
Spider(strconv.Itoa(i * 25))
}
}

// 爬虫爬取过程
func Spider(page string) {
// todo 1. 发送请求
// 构造客户端
client := http.Client{}
// 构造GET请求
URL := "https://movie.douban.com/top250?start=" + page
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
fmt.Println("构造Get请求失败: ", err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")

// 发送请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败: ", err)
}
defer resp.Body.Close()

// todo 2. 解析网页
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
fmt.Println("解析失败", err)
}

// todo 3. 获取节点信息
// 循环列表,进行遍历
doc.Find("#content > div > div.article > ol > li"). // 列表
Each(func(i int, s *goquery.Selection) { // 在列表里面继续找
var data MovieData
title := s.Find("div > div.info > div.hd > a > span:nth-child(1)").Text() // 电影标题
img := s.Find("div > div.pic > a > img") // img图片,但是img标签在sec属性里面
imgTmp, ok := img.Attr("src") // 获得img中src中的值,不存在ok就为err
info := s.Find("div > div.info > div.bd > p:nth-child(1)").Text() // 电影信息
score := s.Find("div > div.info > div.bd > div > span.rating_num").Text() // 电影评分
quote := s.Find("div > div.info > div.bd > p.quote > span").Text() // 电影评论
if ok {
// todo 4. 保存信息
director, actor, year := InfoSpite(info)
data.Title = title
data.Director = director
data.Actor = actor
data.Picture = imgTmp
data.Year = year
data.Score = score
data.Quote = quote
if InsertDataByGORM(data) {
fmt.Printf("插入成功:%+v\n", data)
} else {
fmt.Printf("插入失败:%+v\n", data)
}
}
})

}

// 将豆瓣info信息中的导演、演员等信息用 正则表达式 提取出来
func InfoSpite(info string) (director, actor, year string) {
directorRe, _ := regexp.Compile(`导演:(.*)主演:`) // 正则 .*匹配一行所有
director = string(directorRe.Find([]byte(info)))

actorRe, _ := regexp.Compile(`主演:(.*)`) // 正则 .*匹配一行所有
actor = string(actorRe.Find([]byte(info)))

yearRe, _ := regexp.Compile(`(\d+)`) //正则表达式 \d+匹配数字 == 年份
year = string(yearRe.Find([]byte(info)))
return
}

// 插入数据库
func InsertDataBySQL(data MovieData) bool {
tx, err := DB.Begin() // 开启数据库事务
if err != nil {
fmt.Println("开启数据库事务DB.Begin()失败:", err)
return false
}
// 编写sql语句(需要提前把数据准备好)
stmt, err := tx.Prepare("Insert INTO douban_movie(`Title`, `Director`, `Picture`, `Actor`, `Year`, `Score`, `Quote`) VALUES (?, ?, ?, ?, ?, ?, ?)")
if err != nil {
fmt.Println("数据准备tx.Prepare失败:", err)
return false
}
_, err = stmt.Exec(data.Title, data.Director, data.Picture, data.Actor, data.Year, data.Score, data.Quote)
if err != nil {
fmt.Println("数据插入stmt.Exec失败:", err)
return false
}
// 提交事务
tx.Commit()
return true
}

type NewD struct {
gorm.Model
Title string `gorm:"type:varchar(255);not null;"`
Director string `gorm:"type:varchar(256);not null;"`
Picture string `gorm:"type:varchar(256);not null;"`
Actor string `gorm:"type:varchar(256);not null;"`
Year string `gorm:"type:varchar(256);not null;"`
Score string `gorm:"type:varchar(256);not null;"`
Quote string `gorm:"type:varchar(256);not null;"`
}

func InsertDataByGORM(data MovieData) bool {
NewA := NewD{
Title: data.Title,
Director: data.Director,
Picture: data.Picture,
Actor: data.Actor,
Year: data.Year,
Score: data.Score,
Quote: data.Quote,
}
err := gormdb.Create(&NewA).Error
if err != nil {
return false
}
return true
}

动态数据爬取(Go爬取国王排名评论)

① 动态数据在API里,不在源代码中,需要进行抓包

  • 可以在Headers中的获取请求路径和请求头信息

② http响应是json格式的,需要将json映射到go结构体中

将json转为strcut,获得struct:https://json2struct.mervine.net/

1
2
3
4
5
6
7
8
9
10
11
12
// 使用网站将json转为go的数据结构,然后进行删减,只留下感兴趣的部分
var resultList KingRankResp
// 将json串反序列化为list
_ = json.Unmarshal(bodyTest, &resultList)
// for循环遍历感兴趣的信息(一级评论 + )
for _, commit := range resultList.Data.Replies {
fmt.Println("一级评论", commit.Content.Message)
for _, reply := range commit.Replies {
fmt.Println("二级评论", reply.Content.Message)
}
fmt.Println("########################################################")
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
package main

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

func main() {
// 构造客户端
client := http.Client{}
// 构造Get请求
URL := "https://api.bilibili.com/x/v2/reply/main?csrf=9a70a0c269d742c67c583eba29fa37b9&mode=3&next=0&oid=634501161&plat=1&type=1"
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
fmt.Println("req err", err)
}
// 添加请求头
req.Header.Set("authority", "api.bilibili.com")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("referer", "https://www.bilibili.com/bangumi/play/ss39462?spm_id_from=333.337.0.0")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
// 发送请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败", err)
}
defer resp.Body.Close()
// 读取响应json到内存bodyTest中
bodyTest, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("io err", err)
}
var resultList KingRankResp
_ = json.Unmarshal(bodyTest, &resultList)
for _, commit := range resultList.Data.Replies {
fmt.Println("一级评论", commit.Content.Message)
for _, reply := range commit.Replies {
fmt.Println("二级评论", reply.Content.Message)
}
fmt.Println("########################################################")
}
}

type KingRankResp struct {
Code int64 `json:"code"`
Data struct {
Replies []struct {
Content struct {
Emote struct {
Doge struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[doge]"`
妙啊 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[妙啊]"`
捂眼 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[捂眼]"`
滑稽 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[滑稽]"`
疑惑 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[疑惑]"`
笑哭 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[笑哭]"`
羞羞 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[羞羞]"`
藏狐 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[藏狐]"`
} `json:"emote"`
JumpURL struct {
希琳王妃 struct {
AppName string `json:"app_name"`
AppPackageName string `json:"app_package_name"`
AppURLSchema string `json:"app_url_schema"`
ClickReport string `json:"click_report"`
ExposureReport string `json:"exposure_report"`
Extra struct {
GoodsClickReport string `json:"goods_click_report"`
GoodsCmControl int64 `json:"goods_cm_control"`
GoodsExposureReport string `json:"goods_exposure_report"`
GoodsShowType int64 `json:"goods_show_type"`
IsWordSearch bool `json:"is_word_search"`
} `json:"extra"`
IconPosition int64 `json:"icon_position"`
IsHalfScreen bool `json:"is_half_screen"`
MatchOnce bool `json:"match_once"`
PcURL string `json:"pc_url"`
PrefixIcon string `json:"prefix_icon"`
State int64 `json:"state"`
Title string `json:"title"`
Underline bool `json:"underline"`
} `json:"希琳王妃"`
梶裕贵 struct {
AppName string `json:"app_name"`
AppPackageName string `json:"app_package_name"`
AppURLSchema string `json:"app_url_schema"`
ClickReport string `json:"click_report"`
ExposureReport string `json:"exposure_report"`
Extra struct {
GoodsClickReport string `json:"goods_click_report"`
GoodsCmControl int64 `json:"goods_cm_control"`
GoodsExposureReport string `json:"goods_exposure_report"`
GoodsShowType int64 `json:"goods_show_type"`
IsWordSearch bool `json:"is_word_search"`
} `json:"extra"`
IconPosition int64 `json:"icon_position"`
IsHalfScreen bool `json:"is_half_screen"`
MatchOnce bool `json:"match_once"`
PcURL string `json:"pc_url"`
PrefixIcon string `json:"prefix_icon"`
State int64 `json:"state"`
Title string `json:"title"`
Underline bool `json:"underline"`
} `json:"梶裕贵"`
} `json:"jump_url"`
MaxLine int64 `json:"max_line"`
Members []interface{} `json:"members"`
Message string `json:"message"`
} `json:"content"`
Replies []struct {
Content struct {
AtNameToMid struct {
Mr_Sinsimito int64 `json:"Mr-Sinsimito"`
TroubleBear麻烦熊 int64 `json:"TroubleBear麻烦熊"`
Wmgiii int64 `json:"WMGIII"`
索然無味 int64 `json:"_索然無味"`
北邙流浪 int64 `json:"北邙流浪"`
可怜体无比 int64 `json:"可怜体无比"`
天选之人嗷嗷 int64 `json:"天选之人嗷嗷"`
姆莱先生 int64 `json:"姆莱先生"`
斯桉山 int64 `json:"斯桉山"`
新来的给我站住 int64 `json:"新来的给我站住"`
枫叶秦 int64 `json:"枫叶秦"`
海盗战天下第几 int64 `json:"海盗战天下第_几"`
甲烷冰 int64 `json:"甲烷冰"`
籁lie int64 `json:"籁lie"`
节奏裂开来 int64 `json:"节奏裂开来"`
闪子哥丷 int64 `json:"闪子哥丷"`
青汁愈人 int64 `json:"青汁愈人"`
魔法达比 int64 `json:"魔法达比"`
鱼蛋占 int64 `json:"鱼蛋占"`
} `json:"at_name_to_mid"`
Emote struct {
Doge struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[doge]"`
保佑 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[保佑]"`
妙啊 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[妙啊]"`
思考 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[思考]"`
打call struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[打call]"`
疑惑 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[疑惑]"`
笑哭 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[笑哭]"`
藏狐 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[藏狐]"`
辣眼睛 struct {
Attr int64 `json:"attr"`
ID int64 `json:"id"`
JumpTitle string `json:"jump_title"`
Meta struct {
Size int64 `json:"size"`
} `json:"meta"`
Mtime int64 `json:"mtime"`
PackageID int64 `json:"package_id"`
State int64 `json:"state"`
Text string `json:"text"`
Type int64 `json:"type"`
URL string `json:"url"`
} `json:"[辣眼睛]"`
} `json:"emote"`
JumpURL struct{} `json:"jump_url"`
MaxLine int64 `json:"max_line"`
Members []struct {
Avatar string `json:"avatar"`
FaceNftNew int64 `json:"face_nft_new"`
IsSeniorMember int64 `json:"is_senior_member"`
LevelInfo struct {
CurrentExp int64 `json:"current_exp"`
CurrentLevel int64 `json:"current_level"`
CurrentMin int64 `json:"current_min"`
NextExp int64 `json:"next_exp"`
} `json:"level_info"`
Mid string `json:"mid"`
Nameplate struct {
Condition string `json:"condition"`
Image string `json:"image"`
ImageSmall string `json:"image_small"`
Level string `json:"level"`
Name string `json:"name"`
Nid int64 `json:"nid"`
} `json:"nameplate"`
OfficialVerify struct {
Desc string `json:"desc"`
Type int64 `json:"type"`
} `json:"official_verify"`
Pendant struct {
Expire int64 `json:"expire"`
Image string `json:"image"`
ImageEnhance string `json:"image_enhance"`
ImageEnhanceFrame string `json:"image_enhance_frame"`
Name string `json:"name"`
Pid int64 `json:"pid"`
} `json:"pendant"`
Rank string `json:"rank"`
Senior struct{} `json:"senior"`
Sex string `json:"sex"`
Sign string `json:"sign"`
Uname string `json:"uname"`
Vip struct {
AccessStatus int64 `json:"accessStatus"`
AvatarSubscript int64 `json:"avatar_subscript"`
DueRemark string `json:"dueRemark"`
Label struct {
BgColor string `json:"bg_color"`
BgStyle int64 `json:"bg_style"`
BorderColor string `json:"border_color"`
ImgLabelURIHans string `json:"img_label_uri_hans"`
ImgLabelURIHansStatic string `json:"img_label_uri_hans_static"`
ImgLabelURIHant string `json:"img_label_uri_hant"`
ImgLabelURIHantStatic string `json:"img_label_uri_hant_static"`
LabelTheme string `json:"label_theme"`
Path string `json:"path"`
Text string `json:"text"`
TextColor string `json:"text_color"`
UseImgLabel bool `json:"use_img_label"`
} `json:"label"`
NicknameColor string `json:"nickname_color"`
ThemeType int64 `json:"themeType"`
VipDueDate int64 `json:"vipDueDate"`
VipStatus int64 `json:"vipStatus"`
VipStatusWarn string `json:"vipStatusWarn"`
VipType int64 `json:"vipType"`
} `json:"vip"`
} `json:"members"`
Message string `json:"message"`
} `json:"content"`
} `json:"replies"`
} `json:"replies"`
} `json:"data"`
}


并发爬取

并发爬取豆瓣TOP250

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 单线程爬取
func main() {
InitDBBySQL()
for i := 0; i < 10; i++ {
fmt.Printf("正在爬取第 %d 页的信息\n", i)
Spider(strconv.Itoa(i * 25))
}
}
// 并发爬取(10个go程,每个go程爬一页)
func main() {
InitDB()
ch := make(chan bool)
for i := 0; i < 10; i++ {
go Spider(strconv.Itoa(i*25), ch)
}
for i := 0; i < 10; i++ {
<-ch
}
}

func Spider(page string, ch chan bool){
....
if ch != nil {
ch <- true
}
}

gorequest库

使用原生的库需要写很多的代码,那有没有更简洁一些的写法?

已经有人把原生的 net/http 库,进一步的进行了封装,形成了这样一个库:gorequest.

gorequest 文档

对外暴露的接口非常的简单:

1
resp, body, errs := gorequest.New().Get("http://example.com/").End()

一行代码即可完成一次请求。

Post 的请求也可以比较简便的完成:

1
2
3
4
5
request := gorequest.New()
resp, body, errs := request.Post("http://example.com").
Set("Notes","gorequst is coming!").
Send(`{"name":"backy", "species":"dog"}`).
End()

上述两种方式,按照自己喜好选择,可以获取到网页源代码。此为第一步。