0%

go 爬虫练习

分析网站

image-20230620190153496

  • 点击下载图片时,经过分析最终的下载地址的n数量减一,为正则提取或者图片查找做准备http://i.52desktop.cn:81/upimg/allimg/{n}/201912415221023477802.jpg

image-20230620190505925

单线程

  • hello_work\craw\Craw1.go
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
// hello_work\craw\Craw1.go
package crawl

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
)

var (
// 正则查找图片的地址
reImg = `http://i.52desktop.cn:81/upimg/allimg/[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
)

// 读取链接中所有的内容
func GetPageStr(url string) (pageStr string) {

resp, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return
}
defer resp.Body.Close()
// 读取页面内容
pagebytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("ioutil.ReadAll error", err)
return
}
// 字节转换为字符串
pageStr = string(pagebytes)
return pageStr
}
// 下载图片,核心内容是获取图片的reader、write对象
func downloadImg(savePath string, url string) error {

v, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return err
}
defer v.Body.Close()
fileName := path.Base(url) // 获取文件名
// 获得get请求响应的reader对象
reader := bufio.NewReaderSize(v.Body, 32*1024)
// 创建单个文件保存下来
file, err := os.Create(savePath + fileName)
if err != nil {
fmt.Println("ioutil.WriteFile error", err)
return err
}
// // 获得文件的writer对象
writer := bufio.NewWriter(file)

written, err := io.Copy(writer, reader)
if err != nil {
fmt.Println("io.Copy error", err)
return err
}
fmt.Println(fileName, "download is success, Total length:", written)
return nil

}
// 给main.go进行调用的外部地址
func GetCrawl1Img() {
start := time.Now()
for i := 2; i <= 21; i++ {
// 根据观察规则,发现下载图片地址网站
url := "http://www.52desktop.cn/html/DLZM/KPBZ/20191205/15898_" + strconv.Itoa(i) + ".html"
pageStr := GetPageStr(url)
re := regexp.MustCompile(reImg)
results := re.FindAllStringSubmatch(pageStr, -1) // -1 表示搜索所有可能的匹配项。
for _, result := range results {
if strings.Contains(result[0], "20191204") { // 过滤条件读取图片地址
downloadImg("E:\\proj\\gowork\\img\\", result[0])

}

}

}
elapse := time.Since(start)
fmt.Println("elapsed time is : ", elapse, "s")

}

  • hello_work/main.go
1
2
3
4
5
6
7
8
9
10
11
// hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/crawl"
)

func main() {
crawl.GetCrawl1Img()
}

  • 调用,共下载了20张图片,耗时20秒
1
2
3
4
5
6
7
 E:\proj\gowork> go run .\hello_work\main.go
201912415221023477802.jpg download is success, Total length: 344218
201912415221023477802.jpg download is success, Total length: 344218
201912415221026577803.jpg download is success, Total length: 342307
...
2019124152210921778021.jpg download is success, Total length: 340471
elapsed time is : 20.861619s

并发爬虫

思路

  • 初始化一个数据管道
  • 爬虫写出:创建多个协程用于添加图片
  • 任务统计协程:检查多个个任务是否都完成,完成则关闭数据管道
  • 下载协程:从管道里读取链接并下载

代码

  • hello_work/crawl/Crawl2.go
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
package crawl

import (
"fmt"
"io/ioutil"
"net/http"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
)

var (
// 正则提取表达式
geReImg = `http://i.52desktop.cn:81/upimg/allimg/[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`

// 存放图片链接的数据管道
chanImageUrls chan string
// 启动多个goroutine,sync.WaitGroup来实现goroutine的同步
waitGroup sync.WaitGroup
// 用于监控协程
chanTask chan string
// 监控协程总数
chanTaskCount = 23
// 监控当前的下载任务是否完成的总数
count int
)

// 爬取图片链接,获取当前页的所有图片链接
func getImgs(url string) (urls []string) {
pageStr := GetPageStr(url)
re := regexp.MustCompile(geReImg)
results := re.FindAllStringSubmatch(pageStr, -1) // -1 表示搜索所有可能的匹配项。
fmt.Printf("共找到%d条结果\n", len(results))
for _, result := range results {
url := result[0]
if strings.Contains(result[0], "20191204") { // 过滤条件读取图片地址
urls = append(urls, url)
// fmt.Println(url)

}
}
return urls

}

func getImgUrls(url string) {
urls := getImgs(url)
// 遍历切片里所有链接,存入数据管道
for _, url := range urls {
// 存放图片链接的数据管道,发送图片链接
chanImageUrls <- url
}
// 标识当前协程完成
// 每完成一个任务,写一条数据
// 用于监控协程知道已经完成了几个任务
chanTask <- url
waitGroup.Done()

}

// 任务统计
func CheckOk() {
for {
// 从chanTask中接收值并赋值给变量,url
url := <-chanTask
fmt.Printf("%s 完成了爬虫任务\n", url)
count++
// 任务统计个协程是否全部完成,完成了久关闭通道
// 因为的我循环起始地址的索引为2,所以这里减去2
if count == chanTaskCount-2 {
close(chanImageUrls)
break
}
}
waitGroup.Done()

}
func downloadFile(savePath string, url string) {

v, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return
}
defer v.Body.Close()
fileName := savePath + path.Base(url) // 获取文件名
bytes, err := ioutil.ReadAll(v.Body)
if err != nil {
fmt.Println("io.ReadAll error", err)
return
}
// 直接写入内容,比Crawl1.go代码更简单
err = ioutil.WriteFile(fileName, bytes, 0666)
if err != nil {
fmt.Println("ioutil.WriteFile error", err)
return
}
fmt.Println(fileName, " download is success")

}

// 下载协程,从hanImageUrls管道中读取链接下载,读取的数据来自于这里函数getImgUrls,对chanImageUrls的写入
func DownloadImg() {
for url := range chanImageUrls {
downloadFile("E:\\proj\\gowork\\img\\", url)
}
waitGroup.Done()
}

func GetCrawl2Img() {
// 初始化管道
chanImageUrls = make(chan string, 100)
chanTask = make(chan string, chanTaskCount)
start := time.Now()

for i := 2; i < chanTaskCount; i++ {
waitGroup.Add(1)
// 根据观察规则,发现下载图片地址网站
go getImgUrls("http://www.52desktop.cn/html/DLZM/KPBZ/20191205/15898_" + strconv.Itoa(i) + ".html")

}
// 任务统计个协程是否全部完成
waitGroup.Add(1)
go CheckOk()
// 创建一个新的协程来执行 Wait 后面的操作
go func() {
// 等待所有协程完成
waitGroup.Wait()

// 执行一些操作
fmt.Println("All workers have completed.")
}()
// 下载协程,从管道中读取链接下载
for i := 0; i < 5; i++ {
waitGroup.Add(1)
go DownloadImg()

}

waitGroup.Wait()
elapse := time.Since(start)
fmt.Println("elapsed time is : ", elapse, "s")

}


  • hello_work/main.go
1
2
3
4
5
6
7
8
9
10
package main

import (
"example.com/hello_work/hello_work/crawl"
)

func main() {
// crawl.GetCrawl1Img()
crawl.GetCrawl2Img()
}
  • 调用,下载了20张图片,共耗时7秒,大幅提升下载速度
1
2
3
4
5
6
PS E:\proj\gowork> go run .\hello_work\main.go
E:\proj\gowork\img\201912415221040677807.jpg download is success
E:\proj\gowork\img\201912415221040677807.jpg download is success
E:\proj\gowork\img\2019124152210546778011.jpg download is success
All workers have completed.
elapsed time is : 7.2002013s s

image-20230620203609304

总结

  • 代码主要来自这里

  • 其中CheckOk代码中的count计算不正确,无法关闭close(chanImageUrls),导致waitGroup无法关闭,引起阻塞,本次代码中已经修复