Go 爬虫初体验

墨殇的技术博客 凡打不倒我的,必使我强大!!!—— 墨殇的技术博客 本文由博客端 HTTPS://47.95.141.144 主动推送
本贴最后更新于 195 天前,其中的信息可能已经渤澥桑田

前言

  闲来无事的时候,偶尔也会看看漫画,但是鹅厂的操作大家都懂,想看最新的你就得给钱,本着白嫖精神,我找到了扑飞漫画,但是这网页的阅读体验一言难尽,他家的 APP 也是,动不动就加载失败,一等一半天。思来想去,还是弄个爬虫把图片都爬下来,然后想法弄到 kindle 里面岂不美哉。因为不会 Python,所以只好用 GO 来写了,虽然没写过,但是可以现学嘛。

初识爬虫

  网上找了下资料,go 的写爬虫也太简单了吧,几行代码就搞定了,比如下面这样,几行代码就把整个页面拿到了。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	resp, err := http.Get("https://www.baidu.html")
	if err != nil {
		fmt.Println("http get error", err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("read error", err)
		return
	}
	fmt.Println(string(body))
}

  得知爬虫这么简单的我笑出了声,剩下的工作也就是获取网址源码,然后正则解析出我想要的东西,循环以上步骤拿到图片地址下载即可,整体可分为三步。

  1. 爬取漫画首页,获取目录标题极其链接
  2. 通过目录拿到的链接去访问图片所在页面,然后解析出图片地址
  3. 下载图片,并存储在指定页面

  有了如上结论的我开始奋笔疾书,然后当我进行到第二步获取图片地址的时候,发现爬下来的页面与网页实际的不一样。

image.png

image.png

  我获取到网页内容是一个 loading 页面,而不是这个页面最终的状态。起初的想法是因为网页一开始打开时这样,加载完后就正常了,所以我隔几秒在读取网页内容,事实证明这样不行。

意外之喜

  因为不知道啥原因导致的,所以也没找到解决办法,期间尝试了一下爬虫框架也依旧无果。但是也没有彻底放弃,没事就看看这页面的源码,希望能找出解决办法。

image.png

  乍一看是某种解密的东西,尝试运行下发现可以得到这一页的图片地址,还剩下几个变量又都是干嘛的呢,好奇之下运行了一下。

image.png

  如此一来,整个章节所有图片的地址也就都有了。问题也就是怎么在 go 里运行 js,然后就找到了一个叫 otto 的包,完美。

开始书写

  因为在寻找解决办法的过程中接触了一些爬虫框架,整体感觉比源码撸方便一点,主要是不用自己写正则,可以像 js 操作 DOM 节点那样找到自己想要的内容,所以就换了框架 colly 来完成整改爬虫。

  定义一个全局变量,用于存放章节与之对应的链接以及该章节下的所有图片

type Catalog struct {
	Title  string
	Url    string
	ImgArr []string
}

var catalog []*Catalog

1. 漫画首页

  第一步是爬取漫画的首页,通过获取目录内容去获取到章节的标题以及该章节的地址。比如一人之下,目录存放在 id="play_0" 下的无序列表中,获取对应 li 标签内 a 标签内容即可。

image.png

c := colly.NewCollector()
c.OnHTML("#play_0", func(e *colly.HTMLElement) {
	e.ForEach("ul li a", func(i int, element *colly.HTMLElement) {
		href := element.Attr("href")
		title := element.Text
		title = coverGBKToUTF8(title) // 页面编码转为UTF8
		catalog = append(catalog, &Catalog{Url: PF + href, Title: title})
	})
})

c.OnRequest(func(r *colly.Request) {
})

c.Visit(MANHUA + strconv.Itoa(Mid))

  扑飞漫画整站的编码是 gb2312,因为 go 只认 utf-8,所以对于爬取的内容需要处理下。

import "github.com/axgle/mahonia"

func coverGBKToUTF8(src string) string {
	return mahonia.NewDecoder("gbk").ConvertString(src)
}

2. 获取图片地址

  通过上面的代码,我们已经获取了所有目录对应地址,接下来需要做的只是去到对应页面,获取 JS 代码并执行,获得该章节下所有的图片地址

// 获取该章节所有图片地址
func GetImgArr(catalog *Catalog) {
	c := colly.NewCollector()
	c.OnHTML("head script", func(e *colly.HTMLElement) {
		if e.Text != "" {
			JavaScript := coverGBKToUTF8(e.Text)
			JavaScript += " function f() {return photosr;} f();"
			vm := otto.New()
			value, err := vm.Run(JavaScript)
			if err != nil {
				log.Println("解析图片地址失败:", err)
			}
			imgStr, err := value.ToString()
			if err != nil {
				log.Println("图片地址解析出错:", err)
			}
			imgArr := strings.Split(imgStr, ",")
			catalog.ImgArr = imgArr[1:]
		}
	})

	c.OnRequest(func(r *colly.Request) {
	})

	c.Visit(catalog.Url)
}

3. 下载图片

  这个网上一搜图片一大堆,所以直接 copy 一份就可以了

// 创建文件夹并存储图片
func CreateFileGetImg(catalog *Catalog, index int) {
   if catalog.Title == "通知" {
      return
   }
   dir := DIR + strconv.Itoa(index) + "--" + catalog.Title
   err := os.MkdirAll(dir, os.ModePerm)
   if err != nil {
      log.Println("创建文件夹失败")
   }
   
   for k, v := range catalog.ImgArr {
         resp, err := http.Get(ImgHeader + v)
         if err != nil {
            log.Println(err)
         }
         body, _ := ioutil.ReadAll(resp.Body)
         out, _ := os.Create(dir + "/" + strconv.Itoa(k+1) + ".jpg")
         io.Copy(out, bytes.NewReader(body))
         resp.Body.Close()
   
}

4. 细节处理

  在获取图片地址并下载图片的时候,可能会出现类似 Get http://res.img.fffmanhua.com/2017/08/22/20/28acd5236d.jpg: EOF 的错误,我处理的方式是再获取一次,知道它不报错为止。

GetImage:
resp, err := http.Get(ImgHeader + v)
if err != nil {
	log.Println(err)
	goto GetImage // 获取图片出错重新获取
}

5. 并发

  上述代码已经可以完成我的程序需求了,但是面对漫画量很大的情况下,执行的时间还是很吃力的,所以需要加上并发。一开始的考虑是在获取章节的时候直接并发,顺道把图片也下载了。

var wg sync.WaitGroup
for k, v := range catalog {
	wg.Add(1)
	go func(v *Catalog, k int) {
		//获取图片地址
		GetImgArr(v)
		if runtime.NumGoroutine() > MaxNum {
			MaxNum = runtime.NumGoroutine()
		}
		//创建文件夹并获取图片
		CreateFileGetImg(v, len(catalog)-k)
		wg.Done()
	}(v, k)
}
wg.Wait()

  我在编辑器 Goland 里面运行的时候是没有问题的,但是当我编译后拿去 Windows 和 Mac 里运行就会出一大堆的问题,最后只能限制一下并发量的问题,最终的解决办法是并发获取图片目录信息,然后做一个队列去下载图片,同时最多下载 10 张,这样一来,性能也能够吃得消,虽然慢一点,但是它稳。

源码

后记

  吐槽一下,苹果有时候真的很坑,我在通过队列下载的时候,出现了 catalog 数组中的 imgArr 长度为为空的情况,调试了很久都没用,扔给别人在 Windows 上就乱跑。

image.png

image.png

  最后的最后附上一张成功的截图
image.png

image.png

image.png

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    445 引用 • 1349 回帖 • 644 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    102 引用 • 265 回帖
2 操作
InkDP 在 2020-07-09 18:11:49 更新了该帖
InkDP 在 2020-07-09 18:10:10 更新了该帖

赞助商 我要投放

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • johnsonxiaoyu

    举报等着吃牢饭吧你

    1 回复
  • InkDP
    捐赠者 作者

    不会吧,不会吧,这就要吃牢饭了

  • wizardforcel

    漫画内容占空间太大,我暂时不考虑备份,啥时候存储技术又重大突破再说。

  • Raphael

    😳 这才几天,已经打不开网站了

  • someone56695

    666