中秋节快到了,确定不爬点月饼送岳母娘?
最近在学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 = `<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="(.+)`
这个爬虫就初步完成了。赶快爬点送岳母娘吧~
然后我就发现了一个大问题。
就是我发现这个异步下载只能异步下载没一页,并不能并发下载多页的图片。于是要对程序进行修改.......
我们把异步下载函数加上参数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
- 点赞
- 收藏
- 关注作者
评论(0)