中秋节快到了,确定不爬点月饼送岳母娘?

举报
Regan Yue 发表于 2021/09/18 13:02:24 2021/09/18
【摘要】 中秋节快到了,确定不爬点月饼送岳母娘?最近在学Go时,发现Go语言写爬虫好像也不错,恰逢中秋节,于是想爬点月饼的图片玩玩,各位也可以爬点送岳母娘啊~温馨提示:本文是Go爬虫的教学博文,不会讨论过多有关Go语言写爬虫的重难点,不要担心看不懂,我也会介绍本文中用到的所有知识....如果是大佬,就此止步吧~ 也可以给本菜鸟点个赞再走~一、获取页面图片链接我们这里先介绍如何获取一个页面里面的图片链接...

中秋节快到了,确定不爬点月饼送岳母娘?

最近在学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属性的信息。就像这样:

image-20210910183458049

我们先来看看有关的第一段代码:

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 = `<a.+?path="(.+?)">`

爬取带有图片链接和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="(.+)`

这个爬虫就初步完成了。赶快爬点送岳母娘吧~


image-20210912192020486

然后我就发现了一个大问题。

就是我发现这个异步下载只能异步下载没一页,并不能并发下载多页的图片。于是要对程序进行修改.......

我们把异步下载函数加上参数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()
}

image-20210912193308787

这样就明显速度快多了。


六、最后

代码我已经上传到Github了,请自取。

https://github.com/ReganYue/Crawling_yuebing_pics


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。