Go语言爬虫实战
目标
- 利用go语言爬取指定网站的图片。
- 实现爬取网站任意页面所有所需的图片。
- 实现使用go语言线程池开启多个线程爬取图片内容。
- 最后实现创建多个文件夹存储图片。
爬取网站图片
步骤
-
对指定URL发去GET请求,获取对应的响应。
resp, err := http.Get(url)
-
通过返回的响应获取网站的Html文本内容
BodyData, err := io.ReadAll(resp.Body)
-
通过观察Html文本中图片的地址,并写出对应的正则表达式,匹配所有符合的图片信息。
-
细节:通过浏览器的开发者模式,可以更快找到图片的地址
-
reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
-
-
保存正则表达式的匹配结果,并对其发起GET请求获取图片资源信息。
-
//创建正则表达式的对象 compile, err := regexp.Compile(reImg) //根据网站得Html内容匹配符合条件的结果,-1的意思是匹配所有结果。正数则表示匹配对应数字的结果 allResult := compile.FindAllString(string(BodyData), -1) //获取图片资源 for i, resultUrl := range allResult {...}
-
-
保存图片到指定的文件夹
-
//获取图片信息 data, err := io.ReadAll(get.Body) //创建指定文件夹 mkdirPath := "./img/" + "img_" + strconv.Itoa(num) + "/" os.MkdirAll(mkdirPath, os.ModePerm) //创建文件保存图片信息 file, err := os.OpenFile(mkdirPath+"cutImg_"+name, os.O_CREATE|os.O_RDWR, os.ModePerm) //将图片信息写入文件 _, err = file.Write(data)
-
实现爬取网站任意页面
思路
-
可以通过对网站的观察我们可以发现网站各个页面之间微小的变化,然后将需要爬取的网页存储在一个切片当中,之后重复第一步即可。
-
例如:
https://desk.3gbizhi.com/deskFJ/
该网站的网页信息,通过点击翻页可以发现一些规律https://desk.3gbizhi.com/deskFJ/index_1.html
第一页https://desk.3gbizhi.com/deskFJ/index_2.html
第二页https://desk.3gbizhi.com/deskFJ/index_3.html
第三页- 所以这里我们只需改变后面的数字即可获取对应页数的网页信息,并开始爬取图片信息。
-
代码
var start int var end int fmt.Printf("请输入爬取的开始页数:") fmt.Scanf("%d\n", &start) fmt.Printf("请输入爬取的结束页数:") fmt.Scanf("%d\n", &end) reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))` for i := 0; i < end-start+1; i++ { ... urls[i].Url = "https://desk.3gbizhi.com/deskFJ/index_" + strconv.Itoa(urls[i].Id) + ".html" ... }
线程池开启多个线程
- 可以查看往期文章
- https://editor.csdn.net/md/?articleId=137082930
创建多个文件夹存储图片
-
创建文件夹
os.MkdirAll(mkdirPath, os.ModePerm)
-
在存储图片的时候,获取图片的后缀以及获取图片原名称来命名图片
-
//截取名字和后缀 index := strings.LastIndex(resultUrl, "/") name := resultUrl[index+1:]
-
项目结构图片
项目代码
//pool.go
package worker
import (
"log"
"math"
"sync"
)
// Args 参数结构体
type Args struct {
Url string
ReImg string
Id int
}
// Task 定义任务函数类型
type Task func(num int, url string, reImg string) interface{}
//type Task func() interface{}
type Pool struct {
worker int
tasks *Queue
events chan struct{}
results chan interface{}
wg sync.WaitGroup
}
// NewPool 创建pool
func NewPool(worker int) *Pool {
return &Pool{
worker: worker,
tasks: NewQueue(-1),
events: make(chan struct{}, math.MaxInt),
results: make(chan interface{}, worker*2),
wg: sync.WaitGroup{},
}
}
// AddTasks 任务添加
func (p *Pool) AddTasks(task Task) {
err := p.tasks.Push(task)
if err != nil {
log.Println(err)
return
}
p.events <- struct{}{}
}
// Start 启动工作池
func (p *Pool) Start(urls []Args) chan interface{} {
var index = -1
var IndexLock sync.Mutex
for i := 0; i < p.worker; i++ {
p.wg.Add(1)
go func() {
for range p.events {
task, err := p.tasks.Pop()
if err != nil {
log.Println(err)
continue
}
IndexLock.Lock()
index++
IndexLock.Unlock()
if task, ok := task.(Task); ok {
p.results <- task(urls[index].Id, urls[index].Url, urls[index].ReImg)
}
}
p.wg.Done()
}()
}
return p.results
}
// Wait 关闭池子
func (p *Pool) Wait() {
close(p.events)
p.wg.Wait()
close(p.results)
}
//queue.go
package worker
import (
"fmt"
"sync"
)
type Queue struct {
elements []interface{}
lock sync.Mutex
limit int
}
// NewQueue 创建队列
func NewQueue(limit int) *Queue {
return &Queue{
elements: make([]interface{}, 0, 1024),
lock: sync.Mutex{},
limit: limit,
}
}
// Push 入队
func (q *Queue) Push(task interface{}) error {
if q.limit != -1 && len(q.elements) >= q.limit {
return fmt.Errorf("队列已满,请等待")
}
q.lock.Lock()
defer q.lock.Unlock()
q.elements = append(q.elements, task)
return nil
}
// Pop 出队
func (q *Queue) Pop() (interface{}, error) {
if len(q.elements) == 0 {
return nil, fmt.Errorf("队列以空,等带任务入队")
}
task := q.elements[0]
q.elements = q.elements[1:]
return task, nil
}
//main.go
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"src/worker"
"strconv"
"strings"
"time"
)
// 爬虫流程
// 1.对网站发送Get请求
// 2.读取网站信息
// 3.提取图片路径
// 4.下载图片,保存起来
func main() {
var start int
var end int
fmt.Printf("请输入爬取的开始页数:")
fmt.Scanf("%d\n", &start)
fmt.Printf("请输入爬取的结束页数:")
fmt.Scanf("%d\n", &end)
pool := worker.NewPool(5)
reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
urls := make([]worker.Args, end-start+1)
for i := 0; i < end-start+1; i++ {
urls[i].ReImg = reImg
urls[i].Id = start + i
urls[i].Url = "https://desk.3gbizhi.com/deskFJ/index_" + strconv.Itoa(urls[i].Id) + ".html"
pool.AddTasks(GetUrlImage)
}
pool.Start(urls)
pool.Wait()
fmt.Printf("爬取结束!")
}
func GetUrlImage(num int, url string, reImg string) interface{} {
resp, err := http.Get(url)
HandleError(err)
//读取
BodyData, err := io.ReadAll(resp.Body)
HandleError(err)
//解析数据
compile, err := regexp.Compile(reImg)
HandleError(err)
allResult := compile.FindAllString(string(BodyData), -1)
if allResult == nil {
fmt.Printf("%d号线程找不到对应数据\n", num)
return nil
}
fmt.Printf("%d号线程已经获取到%d个图片路径,准备下载\n", num, len(allResult))
for i, resultUrl := range allResult {
//截取名字
index := strings.LastIndex(resultUrl, "/")
name := resultUrl[index+1:]
get, err := http.Get(resultUrl)
HandleError(err)
//下载图片
now := time.Now()
data, err := io.ReadAll(get.Body)
HandleError(err)
fmt.Println("图片耗时:", time.Now().Sub(now))
mkdirPath := "./img/" + "img_" + strconv.Itoa(num) + "/"
os.MkdirAll(mkdirPath, os.ModePerm)
file, err := os.OpenFile(mkdirPath+"cutImg_"+name, os.O_CREATE|os.O_RDWR, os.ModePerm)
HandleError(err)
_, err = file.Write(data)
HandleError(err)
fmt.Printf("成功下载图片%d\n", i)
}
return nil
}
// HandleError 错误
func HandleError(err error) {
if err != nil {
log.Println(err)
return
}
}
(mkdirPath, os.ModePerm)
file, err := os.OpenFile(mkdirPath+“cutImg_”+name, os.O_CREATE|os.O_RDWR, os.ModePerm)
HandleError(err)
_, err = file.Write(data)
HandleError(err)
fmt.Printf(“成功下载图片%d\n”, i)
}
return nil
}
// HandleError 错误
func HandleError(err error) {
if err != nil {
log.Println(err)
return
}
}