GO语言的进阶之路-爬虫进阶之路

                         GO语言的进阶之路-爬虫进阶之路

                                                  作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

  网络爬虫是一种自动获取网页内容的程序,是搜索引擎的重要组成部分。我们今天要介绍的就是一个简单的网络爬虫,可以爬取img,script文件,当然你也可以修改一下你的脚本程序,进行爬去avi,mp4,rmvb等等。将趴取到的内容下载下来,然后打包成一个压缩文件,最终实现的效果就是用户访问一个网站就能将内容download下来。是不是很带劲?那就跟着我的脚本一起探索其中的乐趣吧。

  本来是想把代码贴在这里就OK了,但是为了让一些小白知道这个爬虫的实现原理,我打算把爬虫的函数一个一个进行分解的给大家讲解出来,深入分析一下爬虫,因为我们不能一口吃个大胖子嘛。还有就是我直接把代码贴出来效果就不咋好了,主要是我要写点内容嘛~哈哈,接下来就跟我一起体验一下其中的乐趣吧,如果本篇博客你能全部看懂的话,那么恭喜你,你也可以写一个爬虫了,当然你可以适当修改源代码,进行爬虫你想要的内容。对了,有Golang基础的童鞋可以直接将进度条移动到结尾,直接去撸源代码了,我都做了相应的注释。

  在使用爬虫的时候,我要声明三点:

      1>.你的电脑是可以联网的,换句话说,你在哪台电脑上执行程序,如果可以用那台电脑通过网页访问到数据的话,那么爬虫只要写的没问题,你就可以拿到你想要拿到的数据;

    2>.在你的带宽充足的情况下,你可以开启多个协程帮你爬去你想要的内容,但是要注意开启的个数,不然的话可能会被封掉IP;

    3>.爬虫可以爬去公有资源和私有资源,如果您用VIP登录到爱奇艺,乐视,或是腾讯视频爬去到的资源请不要在用作商业用途,爬虫是一个很好的工具要取之有节用只有度哟。

  好了,话不多数,我们一起开始爬虫进阶之旅吧。

 

一.path与filepath的区别;

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:[email protected]
 6 */
 7 
 8 /*
 9 filepath适合处理操作系统的路径;
10 path适合处理http或者是ftp这种协议带有""分割线的路径;
11 */

  path的官方手册:https://golang.org/pkg/path/

  filepath的官方手册:https://golang.org/pkg/path/filepath/

 1 package main
 2 
 3 import (
 4     "path/filepath"
 5     "fmt"
 6     "path"
 7 )
 8 
 9 func main() {
10     yinzhengjie_blog := "http://www.cnblogs.com/yinzhengjie/p/7201980.html" //这里定义的路径是浏览器上的。
11 
12     //golang_windows_path := "D:\\Golang环境\\Go环境安装\\go\\bin\\go.exe" //这里定义的路径是windows操作系统的。
13 
14     golang_linux_path := "/yinzhengjie/golang/local/go/bin/go"  //这里定义的路径是linux操作系统的。
15 
16     filepath_dir := filepath.Dir(yinzhengjie_blog)  //找到文件存放的绝对路径,我们发现效果是不一样的呢。
17     //filepath_dir := filepath.Dir(golang_windows_path)
18     path_dir := path.Dir(golang_linux_path) //只能得到当前的路径。
19 
20     filepath_name := filepath.Base(yinzhengjie_blog) //拿到文件名称。
21     path_name := path.Base(golang_linux_path)
22 
23     fullname := filepath.Join(filepath_dir,filepath_name)  //拿到完整路径。
24     fullname1 := filepath.Join(path_dir,path_name)
25 
26     fmt.Printf("filepath得到的目录的结果是:\033[31;1m%s\033[0m\n",filepath_dir)
27     fmt.Printf("path得到的目录的结果是:\033[31;1m%s\033[0m\n",path_dir)
28 
29     fmt.Printf("filepath得到的文件名的结果是:\033[31;1m%s\033[0m\n",filepath_name)
30     fmt.Printf("path得到的文件名的结果是:\033[31;1m%s\033[0m\n",path_name)
31 
32     fmt.Printf("filepath得到的完整路径的结果是:\033[31;1m%s\033[0m\n",fullname)
33     fmt.Printf("filepath得到的完整路径的结果是:\033[31;1m%s\033[0m\n",fullname1)
34 }
35 
36 
37 
38 
39 #以上代码输出结果如下:
40 filepath得到的目录的结果是:http:\www.cnblogs.com\yinzhengjie\p
41 path得到的目录的结果是:/yinzhengjie/golang/local/go/bin
42 filepath得到的文件名的结果是:7201980.html
43 path得到的文件名的结果是:go
44 filepath得到的完整路径的结果是:http:\www.cnblogs.com\yinzhengjie\p\7201980.html
45 filepath得到的完整路径的结果是:\yinzhengjie\golang\local\go\bin\go

   对了,关于获取目录还有一个模块(os/exec)也挺有意思的,分享给大家:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:[email protected]
 6 */
 7 
 8 package main
 9 
10 import (
11     "os/exec"
12     //"os"
13     "fmt"
14     "strings"
15 )
16 
17 func main() {
18     //path,err := exec.LookPath(os.Args[0])
19     path,err := exec.LookPath("D:\\Golang环境\\Golang_Program\\Golang_lesson\\Day9\\6.io读取http请求.go")
20     if err != nil {
21         panic("报错了!")
22     }
23     fmt.Println("文件的绝对路径是:",path)
24 
25     index := strings.LastIndex(path,"\\")
26     path_name := string(path[0:index+1])
27     fmt.Println("该文件所在目录是:",path_name)
28 }
29 
30 
31 
32 #以上代码执行结果如下:
33 文件的绝对路径是: D:\Golang环境\Golang_Program\Golang_lesson\Day9\6.io读取http请求.go
34 该文件所在目录是: D:\Golang环境\Golang_Program\Golang_lesson\Day9\

  上篇关于Golang的博客我们一起分享了关于过滤img标签的src,但是我们获取到的路径可能存在不完整路径,有的是绝对路径有的是相对路径。好了,这个时候我们想要拿到完整的路径,我们就可以用到这个filepath模块了,我们可以对源代码进行修改。源代码请参考:http://www.cnblogs.com/yinzhengjie/p/7201980.html。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:[email protected]
 6 */
 7 
 8 package main
 9 
10 import (
11     "errors"
12     "fmt"
13     "github.com/PuerkitoBio/goquery"
14     "log"
15     "net/http"
16     "net/url"
17     "os"
18     "strings"
19     "path/filepath"
20 )
21 
22 func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
23     var urls []string //定义一个空切片数组
24     resp, err := http.Get(url)
25     if err != nil {
26         log.Fatal(err)
27     }
28     defer resp.Body.Close()
29     if resp.StatusCode != http.StatusOK {
30         return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
31     }
32     doc, err := goquery.NewDocumentFromResponse(resp)
33     if err != nil {
34         log.Fatal(err)
35     }
36     doc.Find("img").Each(func(i int, s *goquery.Selection) {
37         link, ok := s.Attr("src")
38         if ok {
39             urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
40         } else {
41             fmt.Println("抱歉,没有发现该路径。")
42         }
43 
44     })
45     return urls, nil
46 }
47 
48 func Clean_urls(root_path string, picture_path []string) []string {
49     var Absolute_path []string //定义一个绝对路径数组。
50     url_info, err := url.Parse(root_path)
51     if err != nil {
52         log.Fatal(err)
53     }
54     Scheme := url_info.Scheme //获取到链接的协议
55     //fmt.Println("使用的协议是:",Scheme)
56     Host := url_info.Host //获取链接的主机名
57     for _, souce_path := range picture_path {
58         if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。
59 
60         } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
61             souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
62         } else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
63             souce_path = Scheme + "://" + Host + souce_path
64         } else {
65             souce_path = filepath.Dir(root_path) + souce_path  //文件名称和用户输入的目录相拼接。
66         }
67         Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
68     }
69     return Absolute_path //最终返回处理后的每个链接的绝对路基。
70 }
71 
72 func main() {
73     root_path := os.Args[1]               //定义一个URl,也就是我们要爬的网站。
74     picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
75     if err != nil {
76         log.Fatal(err)
77     }
78 
79     Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
80 
81     for _, Picture_absolute_path := range Absolute_path {
82         fmt.Println(Picture_absolute_path) //最终我们会得到一个图片的完整路径,我们可以对这个路径进行下载,压缩,加密等等操作。
83     }
84 }

 

二.下载图片;

  既然我们能得到文件的完整路径,那么我们可以对这个路径搞点事情,比如下载这个路径地址,如果是图片就会得到一个图片到本地,如果是个视频,嘿嘿,那就会有视频被下载,这样不用你手动去点,自己就可以把这些事情搞定。代码及注释如下:

  1 /*
  2 #!/usr/bin/env gorun
  3 @author :yinzhengjie
  4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 
  5 
  6 EMAIL:[email protected]
  7 */
  8 
  9 package main
 10 
 11 import (
 12     "errors"
 13     "fmt"
 14     "github.com/PuerkitoBio/goquery"
 15     "log"
 16     "net/http"
 17     "net/url"
 18     "strings"
 19     "io/ioutil"
 20     "path/filepath"
 21     "os"
 22     "io"
 23 )
 24 
 25 func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
 26     var urls []string //定义一个空切片数组
 27     resp, err := http.Get(url)
 28     if err != nil {
 29         log.Fatal(err)
 30     }
 31     defer resp.Body.Close()
 32     if resp.StatusCode != http.StatusOK {
 33         return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
 34     }
 35     doc, err := goquery.NewDocumentFromResponse(resp)
 36     if err != nil {
 37         log.Fatal(err)
 38     }
 39     doc.Find("img").Each(func(i int, s *goquery.Selection) {
 40         link, ok := s.Attr("src")
 41         if ok {
 42             urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
 43         } else {
 44             fmt.Println("抱歉,没有发现该路径。")
 45         }
 46 
 47     })
 48     return urls, nil
 49 }
 50 
 51 func Clean_urls(root_path string, picture_path []string) []string {
 52     var Absolute_path []string //定义一个绝对路径数组。
 53     url_info, err := url.Parse(root_path)
 54     if err != nil {
 55         log.Fatal(err)
 56     }
 57     Scheme := url_info.Scheme //获取到链接的协议
 58     Host := url_info.Host //获取链接的主机名
 59     for _, souce_path := range picture_path {
 60         if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。
 61 
 62         } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
 63             souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
 64         } else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
 65             souce_path = Scheme + "://" + Host + souce_path
 66         } else {
 67             souce_path = filepath.Dir(root_path) + souce_path  //文件名称和用户输入的目录相拼接。
 68         }
 69         Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
 70     }
 71     return Absolute_path //最终返回处理后的每个链接的绝对路基。
 72 }
 73 
 74 func downloadImgs(urls []string, dir string) error {
 75     for _,link := range urls{
 76         resp,err := http.Get(link)
 77         if err != nil {
 78             log.Fatal(err)
 79         }
 80         defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
 81         if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
 82             log.Fatal(resp.Status)
 83         }
 84         file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
 85         full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
 86         f,err := os.Create(full_name) //创建我们定义到文件。
 87         if err != nil {
 88             log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
 89         }
 90         io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
 91         fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
 92         //defer os.RemoveAll(file_name) //删除文件。
 93     }
 94     return nil
 95 }
 96 
 97 
 98 
 99 func main() {
100     root_path := "http://daily.zhihu.com/"               //定义一个URl,也就是我们要爬的网站。
101     picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
102     if err != nil {
103         log.Fatal(err)
104     }
105 
106     Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
107     tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
108     //defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
109     fmt.Println(tmpdir)
110     err = downloadImgs(Absolute_path,tmpdir)
111     if err != nil {
112         log.Panic(err)
113     }
114 }

 

以上代码用法展示如下:

 1 '''
 2 [root@yinzhengjie tmp]# go run clean_url.go  //运行脚本
 3 /tmp/yinzhengjie008703569
 4 已下载文件至:/tmp/yinzhengjie008703569/phone_sample.png
 5 已下载文件至:/tmp/yinzhengjie008703569/qr_top2.png
 6 已下载文件至:/tmp/yinzhengjie008703569/v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
 7 已下载文件至:/tmp/yinzhengjie008703569/v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
 8 已下载文件至:/tmp/yinzhengjie008703569/v2-1df263e7225586d7b96524cde3e03377.jpg
 9 已下载文件至:/tmp/yinzhengjie008703569/v2-1ab013750fc8e99592f1575dd624d349.jpg
10 已下载文件至:/tmp/yinzhengjie008703569/v2-a637e7ed8cc70f682217095d85bb760f.jpg
11 已下载文件至:/tmp/yinzhengjie008703569/v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
12 已下载文件至:/tmp/yinzhengjie008703569/v2-606c8d97459eea560a982d38ad675635.jpg
13 已下载文件至:/tmp/yinzhengjie008703569/v2-a34fc25ebfc5236148935e7e38e22421.jpg
14 已下载文件至:/tmp/yinzhengjie008703569/v2-3ed913c7731f3fc8931da4a9d652511c.jpg
15 已下载文件至:/tmp/yinzhengjie008703569/v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
16 已下载文件至:/tmp/yinzhengjie008703569/v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
17 已下载文件至:/tmp/yinzhengjie008703569/v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
18 已下载文件至:/tmp/yinzhengjie008703569/v2-ce17057210e31bba97da37c5583a57c4.jpg
19 已下载文件至:/tmp/yinzhengjie008703569/v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
20 已下载文件至:/tmp/yinzhengjie008703569/v2-4b74a8fb442ee483498f71f678568c46.jpg
21 已下载文件至:/tmp/yinzhengjie008703569/v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
22 已下载文件至:/tmp/yinzhengjie008703569/v2-872e11950d98c437bbebc193a0763b91.jpg
23 已下载文件至:/tmp/yinzhengjie008703569/v2-92842c98334327e21bd102ad6971f5f5.jpg
24 已下载文件至:/tmp/yinzhengjie008703569/v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
25 已下载文件至:/tmp/yinzhengjie008703569/v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
26 已下载文件至:/tmp/yinzhengjie008703569/v2-34290a2c50bfe672890efce34e6fb69f.jpg
27 已下载文件至:/tmp/yinzhengjie008703569/v2-1785cb51899750c8b2ac2229ce9a6402.jpg
28 已下载文件至:/tmp/yinzhengjie008703569/v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
29 已下载文件至:/tmp/yinzhengjie008703569/v2-f93fc24b0ebfe2575369259dedbafd63.jpg
30 已下载文件至:/tmp/yinzhengjie008703569/v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
31 已下载文件至:/tmp/yinzhengjie008703569/v2-7212853bb56d350bd412e75b40dbfad4.jpg
32 已下载文件至:/tmp/yinzhengjie008703569/v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
33 已下载文件至:/tmp/yinzhengjie008703569/v2-99c631a661dc923e1b29d01d7cf4010c.jpg
34 已下载文件至:/tmp/yinzhengjie008703569/v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
35 已下载文件至:/tmp/yinzhengjie008703569/v2-82515795f5f9bbbfecab04bb8c095043.jpg
36 已下载文件至:/tmp/yinzhengjie008703569/qr_bottom.png
37 [root@yinzhengjie tmp]# ll /tmp/yinzhengjie008703569  //查看是否下载成功。当你可以下载下来可以打开看看,里面的内容我就不给大家分享了,哈哈~
38 total 1692
39 -rw-r--r-- 1 root root 171741 Jul 24 12:10 phone_sample.png
40 -rw-r--r-- 1 root root   2462 Jul 24 12:10 qr_bottom.png
41 -rw-r--r-- 1 root root    829 Jul 24 12:10 qr_top2.png
42 -rw-r--r-- 1 root root  57857 Jul 24 12:10 v2-1785cb51899750c8b2ac2229ce9a6402.jpg
43 -rw-r--r-- 1 root root  57490 Jul 24 12:10 v2-1ab013750fc8e99592f1575dd624d349.jpg
44 -rw-r--r-- 1 root root  45726 Jul 24 12:10 v2-1df263e7225586d7b96524cde3e03377.jpg
45 -rw-r--r-- 1 root root  57550 Jul 24 12:10 v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
46 -rw-r--r-- 1 root root  50676 Jul 24 12:10 v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
47 -rw-r--r-- 1 root root  26011 Jul 24 12:10 v2-34290a2c50bfe672890efce34e6fb69f.jpg
48 -rw-r--r-- 1 root root  19310 Jul 24 12:10 v2-3ed913c7731f3fc8931da4a9d652511c.jpg
49 -rw-r--r-- 1 root root  60436 Jul 24 12:10 v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
50 -rw-r--r-- 1 root root  33389 Jul 24 12:10 v2-4b74a8fb442ee483498f71f678568c46.jpg
51 -rw-r--r-- 1 root root  57105 Jul 24 12:10 v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
52 -rw-r--r-- 1 root root  59942 Jul 24 12:10 v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
53 -rw-r--r-- 1 root root  59402 Jul 24 12:10 v2-606c8d97459eea560a982d38ad675635.jpg
54 -rw-r--r-- 1 root root  57641 Jul 24 12:10 v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
55 -rw-r--r-- 1 root root  26449 Jul 24 12:10 v2-7212853bb56d350bd412e75b40dbfad4.jpg
56 -rw-r--r-- 1 root root  58009 Jul 24 12:10 v2-82515795f5f9bbbfecab04bb8c095043.jpg
57 -rw-r--r-- 1 root root  57380 Jul 24 12:10 v2-872e11950d98c437bbebc193a0763b91.jpg
58 -rw-r--r-- 1 root root  35464 Jul 24 12:10 v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
59 -rw-r--r-- 1 root root  57299 Jul 24 12:10 v2-92842c98334327e21bd102ad6971f5f5.jpg
60 -rw-r--r-- 1 root root  37474 Jul 24 12:10 v2-99c631a661dc923e1b29d01d7cf4010c.jpg
61 -rw-r--r-- 1 root root  61607 Jul 24 12:10 v2-a34fc25ebfc5236148935e7e38e22421.jpg
62 -rw-r--r-- 1 root root  59202 Jul 24 12:10 v2-a637e7ed8cc70f682217095d85bb760f.jpg
63 -rw-r--r-- 1 root root  58204 Jul 24 12:10 v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
64 -rw-r--r-- 1 root root  43556 Jul 24 12:10 v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
65 -rw-r--r-- 1 root root  25607 Jul 24 12:10 v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
66 -rw-r--r-- 1 root root  55630 Jul 24 12:10 v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
67 -rw-r--r-- 1 root root  31262 Jul 24 12:10 v2-ce17057210e31bba97da37c5583a57c4.jpg
68 -rw-r--r-- 1 root root  59030 Jul 24 12:10 v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
69 -rw-r--r-- 1 root root  58919 Jul 24 12:10 v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
70 -rw-r--r-- 1 root root  56729 Jul 24 12:10 v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
71 -rw-r--r-- 1 root root  54278 Jul 24 12:10 v2-f93fc24b0ebfe2575369259dedbafd63.jpg
72 [root@yinzhengjie tmp]# 
73 '''

 

三.生产tar包;

  完成第二部下载之后,你也得到很多文件,一动起来不太方便,要是能生产一个tar.gz的文件就好了,于是我们又可以增加打包压缩功能。我们要支持党的教育,爬一些健康的网站,比如知乎网站。代码如下:

  1 /*
  2 #!/usr/bin/env gorun
  3 @author :yinzhengjie
  4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
  5 EMAIL:[email protected]
  6 */
  7 
  8 package main
  9 
 10 import (
 11     "errors"
 12     "fmt"
 13     "github.com/PuerkitoBio/goquery"
 14     "log"
 15     "net/http"
 16     "net/url"
 17     "strings"
 18     "io/ioutil"
 19     "path/filepath"
 20     "os"
 21     "io"
 22     "compress/gzip"
 23     "archive/tar"
 24 )
 25 
 26 func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
 27     var urls []string //定义一个空切片数组
 28     resp, err := http.Get(url)
 29     if err != nil {
 30         log.Fatal(err)
 31     }
 32     defer resp.Body.Close()
 33     if resp.StatusCode != http.StatusOK {
 34         return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
 35     }
 36     doc, err := goquery.NewDocumentFromResponse(resp)
 37     if err != nil {
 38         log.Fatal(err)
 39     }
 40     doc.Find("img").Each(func(i int, s *goquery.Selection) {
 41         link, ok := s.Attr("src")
 42         if ok {
 43             urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
 44         } else {
 45             fmt.Println("抱歉,没有发现该路径。")
 46         }
 47 
 48     })
 49     return urls, nil
 50 }
 51 
 52 func Clean_urls(root_path string, picture_path []string) []string {
 53     var Absolute_path []string //定义一个绝对路径数组。
 54     url_info, err := url.Parse(root_path)
 55     if err != nil {
 56         log.Fatal(err)
 57     }
 58     Scheme := url_info.Scheme //获取到链接的协议
 59     Host := url_info.Host //获取链接的主机名
 60     for _, souce_path := range picture_path {
 61         if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。
 62 
 63         } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
 64             souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
 65         } else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
 66             souce_path = Scheme + "://" + Host + souce_path
 67         } else {
 68             souce_path = filepath.Dir(root_path) + souce_path  //文件名称和用户输入的目录相拼接。
 69         }
 70         Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
 71     }
 72     return Absolute_path //最终返回处理后的每个链接的绝对路基。
 73 }
 74 
 75 func downloadImgs(urls []string, dir string) error {
 76     for _,link := range urls{
 77         resp,err := http.Get(link)
 78         if err != nil {
 79             log.Fatal(err)
 80         }
 81         defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
 82         if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
 83             log.Fatal(resp.Status)
 84         }
 85         file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
 86         full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
 87         f,err := os.Create(full_name) //创建我们定义到文件。
 88         if err != nil {
 89             log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
 90         }
 91         io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
 92         //fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
 93         //defer os.RemoveAll(file_name) //删除文件。
 94     }
 95     return nil
 96 }
 97 
 98 
 99 func make_tar(dir string, w io.Writer) error {
100     basedir := filepath.Base(dir) //取出文件的目录
101     compress := gzip.NewWriter(w) //实现压缩功能
102     defer compress.Close()
103     tw := tar.NewWriter(w)  //表示我们会把数据都写入w中去。而这个w就是我们在主函数中创建的文件。
104     defer tw.Close()
105     filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { //用"filepath.Walk"函数去递归"dir"目录下的文件。
106         header,err := tar.FileInfoHeader(info,"") //将文件信息读取出来传给"header"变量,注意"info"后面参数是不传值的,除非你的目录是该软连接。
107         if err != nil {
108             return err
109         }
110         p,_ := filepath.Rel(dir,path)  //取出文件的上级目录。
111         header.Name= filepath.Join(basedir,p) //header.Name = path  //这是将path的相对路径传给"header.Name ",然后在写入到tw中去。不然的话只能拿到"info.Name ()"的名字,也就是如果不来这个操作的话它只会保存文件名,而不会记得路径。
112         //fmt.Printf("path=%s,header.name=%s,info.name= %s\n",path,header.Name,info.Name())
113         tw.WriteHeader(header) //将文件的信息写入到文件w中去。
114         if info.IsDir() {
115             return nil
116         }
117         f1,err := os.Open(path)
118         if err != nil {
119             log.Panic("创建文件出错!")
120         }
121         defer f1.Close()
122         io.Copy(tw,f1) //再将文件的内容写到tw中去。
123         return nil
124     })
125     return nil
126 }
127 
128 
129 func main() {
130     root_path := "http://daily.zhihu.com/"               //定义一个URl,也就是我们要爬的网站。
131     picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
132     if err != nil {
133         log.Fatal(err)
134     }
135     Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
136     //for _, Picture_absolute_path := range Absolute_path {
137     //    fmt.Println(Picture_absolute_path) //最终我们会得到一个图片的完整路径,我们可以对这个路径进行下载,压缩,加密等等操作。
138     //}
139     tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
140     defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
141     //fmt.Println(tmpdir)
142     err = downloadImgs(Absolute_path,tmpdir)
143     if err != nil {
144         log.Panic(err)
145     }
146     //make_tar("..",os.Stdout) //将结果输出到屏幕上。
147     f,err := os.Create("img.tar.gz")  //创建一个"io.Writer",即可写对象。
148     if err != nil {
149         fmt.Println(err)
150         return
151     }
152     defer f.Close()
153     make_tar(tmpdir,f) //将下载到文件放到一个临时目录中,然后把这个临时目录生成一个我们自定义的文件f。
154 }

 

以上代码执行效果如下:

 1 [root@yinzhengjie tmp]# ll
 2 total 8
 3 -rw-r--r-- 1 root root 6517 Jul 24 12:21 download.go
 4 [root@yinzhengjie tmp]# 
 5 [root@yinzhengjie tmp]# go run download.go 
 6 [root@yinzhengjie tmp]# 
 7 [root@yinzhengjie tmp]# ll
 8 total 1652
 9 -rw-r--r-- 1 root root    6517 Jul 24 12:21 download.go
10 -rw-r--r-- 1 root root 1682455 Jul 24 12:23 img.tar.gz
11 [root@yinzhengjie tmp]# 
12 [root@yinzhengjie tmp]# tar xf img.tar.gz 
13 [root@yinzhengjie tmp]# ll
14 total 1656
15 -rw-r--r-- 1 root root    6517 Jul 24 12:21 download.go
16 -rw-r--r-- 1 root root 1682455 Jul 24 12:23 img.tar.gz
17 drwx------ 2 root root    4096 Jul 24 12:23 yinzhengjie477065368
18 [root@yinzhengjie tmp]# 
19 [root@yinzhengjie tmp]# ll yinzhengjie477065368/
20 total 1692
21 -rw-r--r-- 1 root root 171741 Jul 24 12:23 phone_sample.png
22 -rw-r--r-- 1 root root   2462 Jul 24 12:23 qr_bottom.png
23 -rw-r--r-- 1 root root    829 Jul 24 12:23 qr_top2.png
24 -rw-r--r-- 1 root root  57857 Jul 24 12:23 v2-1785cb51899750c8b2ac2229ce9a6402.jpg
25 -rw-r--r-- 1 root root  57490 Jul 24 12:23 v2-1ab013750fc8e99592f1575dd624d349.jpg
26 -rw-r--r-- 1 root root  45726 Jul 24 12:23 v2-1df263e7225586d7b96524cde3e03377.jpg
27 -rw-r--r-- 1 root root  57550 Jul 24 12:23 v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
28 -rw-r--r-- 1 root root  50676 Jul 24 12:23 v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
29 -rw-r--r-- 1 root root  26011 Jul 24 12:23 v2-34290a2c50bfe672890efce34e6fb69f.jpg
30 -rw-r--r-- 1 root root  19310 Jul 24 12:23 v2-3ed913c7731f3fc8931da4a9d652511c.jpg
31 -rw-r--r-- 1 root root  60436 Jul 24 12:23 v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
32 -rw-r--r-- 1 root root  33389 Jul 24 12:23 v2-4b74a8fb442ee483498f71f678568c46.jpg
33 -rw-r--r-- 1 root root  57105 Jul 24 12:23 v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
34 -rw-r--r-- 1 root root  59942 Jul 24 12:23 v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
35 -rw-r--r-- 1 root root  59402 Jul 24 12:23 v2-606c8d97459eea560a982d38ad675635.jpg
36 -rw-r--r-- 1 root root  57641 Jul 24 12:23 v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
37 -rw-r--r-- 1 root root  26449 Jul 24 12:23 v2-7212853bb56d350bd412e75b40dbfad4.jpg
38 -rw-r--r-- 1 root root  58009 Jul 24 12:23 v2-82515795f5f9bbbfecab04bb8c095043.jpg
39 -rw-r--r-- 1 root root  57380 Jul 24 12:23 v2-872e11950d98c437bbebc193a0763b91.jpg
40 -rw-r--r-- 1 root root  35464 Jul 24 12:23 v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
41 -rw-r--r-- 1 root root  57299 Jul 24 12:23 v2-92842c98334327e21bd102ad6971f5f5.jpg
42 -rw-r--r-- 1 root root  37474 Jul 24 12:23 v2-99c631a661dc923e1b29d01d7cf4010c.jpg
43 -rw-r--r-- 1 root root  61607 Jul 24 12:23 v2-a34fc25ebfc5236148935e7e38e22421.jpg
44 -rw-r--r-- 1 root root  59202 Jul 24 12:23 v2-a637e7ed8cc70f682217095d85bb760f.jpg
45 -rw-r--r-- 1 root root  58204 Jul 24 12:23 v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
46 -rw-r--r-- 1 root root  43556 Jul 24 12:23 v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
47 -rw-r--r-- 1 root root  25607 Jul 24 12:23 v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
48 -rw-r--r-- 1 root root  55630 Jul 24 12:23 v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
49 -rw-r--r-- 1 root root  31262 Jul 24 12:23 v2-ce17057210e31bba97da37c5583a57c4.jpg
50 -rw-r--r-- 1 root root  59030 Jul 24 12:23 v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
51 -rw-r--r-- 1 root root  58919 Jul 24 12:23 v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
52 -rw-r--r-- 1 root root  56729 Jul 24 12:23 v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
53 -rw-r--r-- 1 root root  54278 Jul 24 12:23 v2-f93fc24b0ebfe2575369259dedbafd63.jpg
54 [root@yinzhengjie tmp]# 

 

四.启用web功能;

  当你觉得在命令行中输入传入参数去下载一个文件觉得没什么新奇的,那么我们可以用web的形式,实现相同的效果,好啦,其实很简单,只需要修改一部分代码就可以搞定这些事情,知识在传惨的过程中要在浏览器的地址栏进行传惨;

  代码及注释如下:

  1 /*
  2 #!/usr/bin/env gorun
  3 @author :yinzhengjie
  4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
  5 EMAIL:[email protected]
  6 */
  7 
  8 package main
  9 
 10 import (
 11     "errors"
 12     "fmt"
 13     "github.com/PuerkitoBio/goquery"
 14     "log"
 15     "net/http"
 16     "net/url"
 17     "strings"
 18     "io/ioutil"
 19     "path/filepath"
 20     "os"
 21     "io"
 22     "compress/gzip"
 23     "archive/tar"
 24 )
 25 
 26 func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
 27     var urls []string //定义一个空切片数组
 28     resp, err := http.Get(url)
 29     if err != nil {
 30         log.Fatal(err)
 31     }
 32     defer resp.Body.Close()
 33     if resp.StatusCode != http.StatusOK {
 34         return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
 35     }
 36     doc, err := goquery.NewDocumentFromResponse(resp)
 37     if err != nil {
 38         log.Fatal(err)
 39     }
 40     doc.Find("img").Each(func(i int, s *goquery.Selection) {
 41         link, ok := s.Attr("src")
 42         if ok {
 43             urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
 44         } else {
 45             fmt.Println("抱歉,没有发现该路径。")
 46         }
 47 
 48     })
 49     return urls, nil
 50 }
 51 
 52 func Clean_urls(root_path string, picture_path []string) []string {
 53     var Absolute_path []string //定义一个绝对路径数组。
 54     url_info, err := url.Parse(root_path)
 55     if err != nil {
 56         log.Fatal(err)
 57     }
 58     Scheme := url_info.Scheme //获取到链接的协议
 59     Host := url_info.Host //获取链接的主机名
 60     for _, souce_path := range picture_path {
 61         if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。
 62 
 63         } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
 64             souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
 65         } else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
 66             souce_path = Scheme + "://" + Host + souce_path
 67         } else {
 68             souce_path = filepath.Dir(root_path) + souce_path  //文件名称和用户输入的目录相拼接。
 69         }
 70         Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
 71     }
 72     return Absolute_path //最终返回处理后的每个链接的绝对路基。
 73 }
 74 
 75 func downloadImgs(urls []string, dir string) error {
 76     for _,link := range urls{
 77         resp,err := http.Get(link)
 78         if err != nil {
 79             log.Fatal(err)
 80         }
 81         defer resp.Body.Close()  //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
 82         if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
 83             log.Fatal(resp.Status)
 84         }
 85         file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
 86         full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
 87         f,err := os.Create(full_name) //创建我们定义到文件。
 88         if err != nil {
 89             log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
 90         }
 91         io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
 92         //fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
 93         //defer os.RemoveAll(file_name) //删除文件。
 94     }
 95     return nil
 96 }
 97 
 98 
 99 func make_tar(dir string, w io.Writer) error {
100     basedir := filepath.Base(dir) //取出文件的目录
101     compress := gzip.NewWriter(w) //实现压缩功能
102     defer compress.Close()
103     tw := tar.NewWriter(w)  //表示我们会把数据都写入w中去。而这个w就是我们在主函数中创建的文件。
104     defer tw.Close()
105     filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { //用"filepath.Walk"函数去递归"dir"目录下的文件。
106         header,err := tar.FileInfoHeader(info,"") //将文件信息读取出来传给"header"变量,注意"info"后面参数是不传值的,除非你的目录是该软连接。
107         if err != nil {
108             return err
109         }
110         p,_ := filepath.Rel(dir,path)  //取出文件的上级目录。
111         header.Name= filepath.Join(basedir,p) //header.Name = path  //这是将path的相对路径传给"header.Name ",然后在写入到tw中去。不然的话只能拿到"info.Name ()"的名字,也就是如果不来这个操作的话它只会保存文件名,而不会记得路径。
112         //fmt.Printf("path=%s,header.name=%s,info.name= %s\n",path,header.Name,info.Name())
113         tw.WriteHeader(header) //将文件的信息写入到文件w中去。
114         if info.IsDir() {
115             return nil
116         }
117         f1,err := os.Open(path)
118         if err != nil {
119             log.Panic("创建文件出错!")
120         }
121         defer f1.Close()
122         io.Copy(tw,f1) //再将文件的内容写到tw中去。
123         return nil
124     })
125     return nil
126 }
127 
128 func fech_Images(w io.Writer, root_path string) {
129     //root_path := "http://daily.zhihu.com/ "               //定义一个URl,也就是我们要爬的网站。
130     picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
131     if err != nil {
132         log.Fatal(err)
133     }
134     Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
135 
136     tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
137     defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
138     err = downloadImgs(Absolute_path,tmpdir)
139     if err != nil {
140         log.Panic(err)
141     }
142     make_tar(tmpdir,w) //将结果返回给客户端。
143 }
144 
145 func handle_http( w http.ResponseWriter, r *http.Request)  { //w是服务器将要返回的内容,r是用户的请求量。
146     r.ParseForm() //解析用户的请求.
147     fech_Images(w,r.FormValue("yinzhengjie_url")) //此处的"yinzhengjie_url"就是自定义一个路径提示符。浏览器访问方式:http://172.16.3.211:8080/?yinzhengjie_url=http://daily.zhihu.com/
148 
149 }
150 
151 func main() {
152     http.HandleFunc("/",handle_http)
153     http.ListenAndServe(":8080",nil)  //表示监听服务器的所有网卡的"8080端口。"
154 }

 

 下面我们只需要在服务端运行该代码即可。如果感兴趣的小伙伴还可以加上日志功能,用户下载了什么我们以日志的方式打印出来,有时间整理出来笔记会给大家分享的。

服务端在命令行中执行:

 1 [root@yinzhengjie tmp]# ifconfig 
 2 eth0 Link encap:Ethernet HWaddr 00:0C:29:52:11:50 
 3 inet addr:172.16.3.211 Bcast:172.16.3.255 Mask:255.255.255.0
 4 inet6 addr: fe80::20c:29ff:fe52:1150/64 Scope:Link
 5 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
 6 RX packets:123146 errors:0 dropped:0 overruns:0 frame:0
 7 TX packets:46777 errors:0 dropped:0 overruns:0 carrier:0
 8 collisions:0 txqueuelen:1000 
 9 RX bytes:43970986 (41.9 MiB) TX bytes:11497847 (10.9 MiB)
10 
11 lo Link encap:Local Loopback 
12 inet addr:127.0.0.1 Mask:255.0.0.0
13 inet6 addr: ::1/128 Scope:Host
14 UP LOOPBACK RUNNING MTU:16436 Metric:1
15 RX packets:184 errors:0 dropped:0 overruns:0 frame:0
16 TX packets:184 errors:0 dropped:0 overruns:0 carrier:0
17 collisions:0 txqueuelen:0 
18 RX bytes:10120 (9.8 KiB) TX bytes:10120 (9.8 KiB)
19 
20 [root@yinzhengjie tmp]#
21 
22 [root@yinzhengjie tmp]# go run web_server.go

客户端在浏览器中输入:

http://172.16.3.211:8080/?yinzhengjie_url=http://daily.zhihu.com/

打开下载后的内容:

GO语言的进阶之路-爬虫进阶之路_第1张图片

 

你可能感兴趣的:(GO语言的进阶之路-爬虫进阶之路)