云翌通信祝君万事皆圆满共享中秋乐
446
2022-05-29
中秋节快到了,确定不爬点月饼送岳母娘?
最近在学Go时,发现Go语言写爬虫好像也不错,恰逢中秋节,于是想爬点月饼的图片玩玩,各位也可以爬点送岳母娘啊~
温馨提示:本文是Go爬虫的教学博文,不会讨论过多有关Go语言写爬虫的重难点,不要担心看不懂,我也会介绍本文中用到的所有知识....如果是大佬,就此止步吧~ 也可以给本菜鸟点个赞再走~
一、获取页面图片链接
我们这里先介绍如何获取一个页面里面的图片链接。原理很简单,就是先利用我们编写的GetHtml函数获取页面源代码,然后利用正则表达式获取图片链接,然后将链接保存到字符串数组里面。
下面展示GetHtml函数:
func GetHtml(url string) string { resp, _ := http.Get(url) defer resp.Body.Close() bytes, _ := ioutil.ReadAll(resp.Body) html := string(bytes) return html }
本函数需要注意的是,需要延时关闭resp.Body。
下面展示GetPageImgurls函数:
func GetPageImgurls(url string) []string { html := GetHtml(url) re := regexp.MustCompile(ReImg) rets := re.FindAllStringSubmatch(html, -1) imgUrls := make([]string, 0) for _, ret := range rets { imgUrl := "https://www.yuebing.com/"+ret[1] imgUrls = append(imgUrls, imgUrl) } return imgUrls }
因为爬取到的路径是相对路径,所以需要将相对路径前面加上域名、协议等信息形成绝对路径存入字符串数组中,便于以后下载图片。
二、实现同步下载功能
接着我们来实现同步下载功能,我们是将图片以时间戳命名保存到硬盘中。
下面展示DownloadImg函数:
func DownloadImg(url string) { resp, _ := http.Get(url) defer resp.Body.Close() filename := `E:\code\src\day4\爬取图片\img\`+strconv.Itoa(int(time.Now().UnixNano()))+".jpg" imgBytes, _ := ioutil.ReadAll(resp.Body) err := ioutil.WriteFile(filename, imgBytes, 0644) if err == nil{ fmt.Println(filename+"下载成功!") }else{ fmt.Println(filename+"下载失败!") } }
ioutil.WriteFile(filename, imgBytes, 0644)这个imgBytes是图片字节流,下面的代表r w x分别是4 2 1,所以这个0644代表拥有文件的用户可读可写,同一组的用户可读,其他用户可读。
owner group other 0 - rwx - rwx - rwx
另外,在处理strconv.Itoa(int(time.Now().UnixNano()))时,需要将时间戳改为int类型因为itoa时将int类型转为字符串类型,而时间戳是int64类型的。
三、实现异步下载功能
有人说用Go实现异步下载很容易啊~一行代码就能实现,嘿嘿嘿。没错,我们先看一看怎么实现的。
func DownloadImgAsync(url string) { go DownloadImg(url) }
但是这样,多少张图片就需要开辟多少条协程。
我们应该怎么办呢?
chSem = make(chan int,5)
先建立一个管道,容量为5,这样就可以同时下载张图片,也就是并发量为5.
func DownloadImgAsync(url string) { downloadWG.Add(1) go func() { chSem <- 1 DownloadImg(url) <-chSem downloadWG.Done() }() downloadWG.Wait() }
然后每次下载前往管道里面写入一个数,下载完就从管道读出一个数,这样就保证每次最多同时只下载5张照片。
然后你想到了运行会出现什么问题吗?
对的,我们保存文件是以时间戳命名的,如果异步下载的话,可能多个文件时间戳一致,所以我们得生成随机文件名。
四、生成随机文件名
上面我们说到了要生成随机文件名,下面我们就来写吧~
首先先要生成随机数,我打算在时间戳后面添加一个随机数来避免文件名重复。
先来展示一下生成随机数的代码:
func GetRandomInt(start,end int) int { randomMT.Lock() <- time.After(1 * time.Nanosecond) r := rand.New(rand.NewSource(time.Now().UnixNano())) ret := start + r.Intn(end - start) randomMT.Unlock() return ret }
先建立一个互斥锁,然后阻塞一纳秒,然后计算范围内的随机数,然后解开互斥锁,最后返回这个字符串。
接下来的生成随机文件名的函数就比较简单了:
func GetRandomName() string { timestamp := strconv.Itoa(int(time.Now().UnixNano())) randomNum := strconv.Itoa(GetRandomInt(100, 10000)) return timestamp + "-" + randomNum }
就是生成时间戳和随机数,然后拼接。
五、使用Title属性作为文件名
我们是利用正则表达式获取图片链接和图片名Title的,刚开始我想是一个正则表达式爬取链接,一个爬取名称,但是有没有可能有图片没有Title属性,所以我选择爬取所有的不管是否有Title属性的信息。就像这样:
我们先来看看有关的第一段代码:
func GetPageImginfos(url string) []map[string] string { html := GetHtml(url) re := regexp.MustCompile(ReImgName) rets := re.FindAllStringSubmatch(html, -1) imgInfos := make([]map[string] string,0) for _,ret := range rets { imgInfo := make(map[string] string) imgUrl := "https://www.yuebing.com/"+ret[1] imgInfo["url"] = imgUrl[0:78] imgInfo["filename"]=GetImgNameTag(ret[1]) //fmt.Println(imgInfo["filename"]) imgInfos = append(imgInfos, imgInfo) } return imgInfos }
这段代码是利用正则表达式
ReImgName = `
爬取带有图片链接和Title属性的字符串,然后将url和filename保存到Map中,因为图片链接都是一样长的,所以比较省事这里利用截取字符串就行了,但是Title标签就没这么轻松,它的长度是不固定的。那么怎么办呢?
下面展示一下怎么获取Title标签内的值吧:
func GetImgNameTag(imgTag string) string { re := regexp.MustCompile(ReTitle) rets := re.FindAllStringSubmatch(imgTag, -1) //fmt.Println(rets) if len(rets) > 0{ return rets[0][1] }else { return GetRandomName() } }
我们是再次使用正则表达式来获取Title内的值的。
正则表达式内容如下:
ReTitle = `title="(.+)`
这个爬虫就初步完成了。赶快爬点送岳母娘吧~
然后我就发现了一个大问题。
就是我发现这个异步下载只能异步下载没一页,并不能并发下载多页的图片。于是要对程序进行修改.......
我们把异步下载函数加上参数wg *sync.WaitGroup
func DownloadImgAsync(url ,filename string,wg *sync.WaitGroup) { wg.Add(1) go func() { chSem <- 1 DownloadImg(url,filename) <-chSem downloadWG.Done() }() }
然后不在这里wait,而在主函数里面wait。
这里展示一下主函数。
func main() { for i:=1;i<=15;i++{ j := strconv.Itoa(i) url := "https://www.yuebing.com/category-0-b0-min0-max0-attr0-" + j + "-sort_order-ASC.html" imginfos := GetPageImginfos(url) for _,imgInfoMap := range imginfos{ DownloadImgAsync(imgInfoMap["url"],imgInfoMap["filename"],&downloadWG) time.Sleep(500 * time.Millisecond) } } downloadWG.Wait() }
这样就明显速度快多了。
六、最后
代码我已经上传到Github了,请自取。
https://github.com/ReganYue/Crawling_yuebing_pics
Go 正则表达式
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。