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

时间:2021-09-12 17:10:36

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

                                                  作者:尹正杰

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

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

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

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

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

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

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

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

一.path与filepath的区别;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ /*
filepath适合处理操作系统的路径;
path适合处理http或者是ftp这种协议带有"/"分割线的路径;
*/

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

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

 package main

 import (
"path/filepath"
"fmt"
"path"
) func main() {
yinzhengjie_blog := "http://www.cnblogs.com/yinzhengjie/p/7201980.html" //这里定义的路径是浏览器上的。 //golang_windows_path := "D:\\Golang环境\\Go环境安装\\go\\bin\\go.exe" //这里定义的路径是windows操作系统的。 golang_linux_path := "/yinzhengjie/golang/local/go/bin/go" //这里定义的路径是linux操作系统的。 filepath_dir := filepath.Dir(yinzhengjie_blog) //找到文件存放的绝对路径,我们发现效果是不一样的呢。
//filepath_dir := filepath.Dir(golang_windows_path)
path_dir := path.Dir(golang_linux_path) //只能得到当前的路径。 filepath_name := filepath.Base(yinzhengjie_blog) //拿到文件名称。
path_name := path.Base(golang_linux_path) fullname := filepath.Join(filepath_dir,filepath_name) //拿到完整路径。
fullname1 := filepath.Join(path_dir,path_name) fmt.Printf("filepath得到的目录的结果是:\033[31;1m%s\033[0m\n",filepath_dir)
fmt.Printf("path得到的目录的结果是:\033[31;1m%s\033[0m\n",path_dir) fmt.Printf("filepath得到的文件名的结果是:\033[31;1m%s\033[0m\n",filepath_name)
fmt.Printf("path得到的文件名的结果是:\033[31;1m%s\033[0m\n",path_name) fmt.Printf("filepath得到的完整路径的结果是:\033[31;1m%s\033[0m\n",fullname)
fmt.Printf("filepath得到的完整路径的结果是:\033[31;1m%s\033[0m\n",fullname1)
} #以上代码输出结果如下:
filepath得到的目录的结果是:http:\www.cnblogs.com\yinzhengjie\p
path得到的目录的结果是:/yinzhengjie/golang/local/go/bin
filepath得到的文件名的结果是:7201980.html
path得到的文件名的结果是:go
filepath得到的完整路径的结果是:http:\www.cnblogs.com\yinzhengjie\p\7201980.html
filepath得到的完整路径的结果是:\yinzhengjie\golang\local\go\bin\go

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

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"os/exec"
//"os"
"fmt"
"strings"
) func main() {
//path,err := exec.LookPath(os.Args[0])
path,err := exec.LookPath("D:\\Golang环境\\Golang_Program\\Golang_lesson\\Day9\\6.io读取http请求.go")
if err != nil {
panic("报错了!")
}
fmt.Println("文件的绝对路径是:",path) index := strings.LastIndex(path,"\\")
path_name := string(path[0:index+1])
fmt.Println("该文件所在目录是:",path_name)
} #以上代码执行结果如下:
文件的绝对路径是: D:\Golang环境\Golang_Program\Golang_lesson\Day9\6.io读取http请求.go
该文件所在目录是: D:\Golang环境\Golang_Program\Golang_lesson\Day9\

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

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"net/url"
"os"
"strings"
"path/filepath"
) func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
var urls []string //定义一个空切片数组
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
log.Fatal(err)
}
doc.Find("img").Each(func(i int, s *goquery.Selection) {
link, ok := s.Attr("src")
if ok {
urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
} else {
fmt.Println("抱歉,没有发现该路径。")
} })
return urls, nil
} func Clean_urls(root_path string, picture_path []string) []string {
var Absolute_path []string //定义一个绝对路径数组。
url_info, err := url.Parse(root_path)
if err != nil {
log.Fatal(err)
}
Scheme := url_info.Scheme //获取到链接的协议
//fmt.Println("使用的协议是:",Scheme)
Host := url_info.Host //获取链接的主机名
for _, souce_path := range picture_path {
if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。 } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
} else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
souce_path = Scheme + "://" + Host + souce_path
} else {
souce_path = filepath.Dir(root_path) + souce_path //文件名称和用户输入的目录相拼接。
}
Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
}
return Absolute_path //最终返回处理后的每个链接的绝对路基。
} func main() {
root_path := os.Args[1] //定义一个URl,也就是我们要爬的网站。
picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
if err != nil {
log.Fatal(err)
} Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。 for _, Picture_absolute_path := range Absolute_path {
fmt.Println(Picture_absolute_path) //最终我们会得到一个图片的完整路径,我们可以对这个路径进行下载,压缩,加密等等操作。
}
}

二.下载图片;

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

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/ EMAIL:y1053419035@qq.com
*/ package main import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"net/url"
"strings"
"io/ioutil"
"path/filepath"
"os"
"io"
) func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
var urls []string //定义一个空切片数组
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
log.Fatal(err)
}
doc.Find("img").Each(func(i int, s *goquery.Selection) {
link, ok := s.Attr("src")
if ok {
urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
} else {
fmt.Println("抱歉,没有发现该路径。")
} })
return urls, nil
} func Clean_urls(root_path string, picture_path []string) []string {
var Absolute_path []string //定义一个绝对路径数组。
url_info, err := url.Parse(root_path)
if err != nil {
log.Fatal(err)
}
Scheme := url_info.Scheme //获取到链接的协议
Host := url_info.Host //获取链接的主机名
for _, souce_path := range picture_path {
if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。 } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
} else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
souce_path = Scheme + "://" + Host + souce_path
} else {
souce_path = filepath.Dir(root_path) + souce_path //文件名称和用户输入的目录相拼接。
}
Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
}
return Absolute_path //最终返回处理后的每个链接的绝对路基。
} func downloadImgs(urls []string, dir string) error {
for _,link := range urls{
resp,err := http.Get(link)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
log.Fatal(resp.Status)
}
file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
f,err := os.Create(full_name) //创建我们定义到文件。
if err != nil {
log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
}
io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
//defer os.RemoveAll(file_name) //删除文件。
}
return nil
} func main() {
root_path := "http://daily.zhihu.com/" //定义一个URl,也就是我们要爬的网站。
picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
if err != nil {
log.Fatal(err)
} Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
//defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
fmt.Println(tmpdir)
err = downloadImgs(Absolute_path,tmpdir)
if err != nil {
log.Panic(err)
}
}

以上代码用法展示如下:

 '''
[root@yinzhengjie tmp]# go run clean_url.go //运行脚本
/tmp/yinzhengjie008703569
已下载文件至:/tmp/yinzhengjie008703569/phone_sample.png
已下载文件至:/tmp/yinzhengjie008703569/qr_top2.png
已下载文件至:/tmp/yinzhengjie008703569/v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-1df263e7225586d7b96524cde3e03377.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-1ab013750fc8e99592f1575dd624d349.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-a637e7ed8cc70f682217095d85bb760f.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-606c8d97459eea560a982d38ad675635.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-a34fc25ebfc5236148935e7e38e22421.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-3ed913c7731f3fc8931da4a9d652511c.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-ce17057210e31bba97da37c5583a57c4.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-4b74a8fb442ee483498f71f678568c46.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-872e11950d98c437bbebc193a0763b91.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-92842c98334327e21bd102ad6971f5f5.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-34290a2c50bfe672890efce34e6fb69f.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-1785cb51899750c8b2ac2229ce9a6402.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-f93fc24b0ebfe2575369259dedbafd63.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-7212853bb56d350bd412e75b40dbfad4.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-99c631a661dc923e1b29d01d7cf4010c.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
已下载文件至:/tmp/yinzhengjie008703569/v2-82515795f5f9bbbfecab04bb8c095043.jpg
已下载文件至:/tmp/yinzhengjie008703569/qr_bottom.png
[root@yinzhengjie tmp]# ll /tmp/yinzhengjie008703569 //查看是否下载成功。当你可以下载下来可以打开看看,里面的内容我就不给大家分享了,哈哈~
total 1692
-rw-r--r-- 1 root root 171741 Jul 24 12:10 phone_sample.png
-rw-r--r-- 1 root root 2462 Jul 24 12:10 qr_bottom.png
-rw-r--r-- 1 root root 829 Jul 24 12:10 qr_top2.png
-rw-r--r-- 1 root root 57857 Jul 24 12:10 v2-1785cb51899750c8b2ac2229ce9a6402.jpg
-rw-r--r-- 1 root root 57490 Jul 24 12:10 v2-1ab013750fc8e99592f1575dd624d349.jpg
-rw-r--r-- 1 root root 45726 Jul 24 12:10 v2-1df263e7225586d7b96524cde3e03377.jpg
-rw-r--r-- 1 root root 57550 Jul 24 12:10 v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
-rw-r--r-- 1 root root 50676 Jul 24 12:10 v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
-rw-r--r-- 1 root root 26011 Jul 24 12:10 v2-34290a2c50bfe672890efce34e6fb69f.jpg
-rw-r--r-- 1 root root 19310 Jul 24 12:10 v2-3ed913c7731f3fc8931da4a9d652511c.jpg
-rw-r--r-- 1 root root 60436 Jul 24 12:10 v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
-rw-r--r-- 1 root root 33389 Jul 24 12:10 v2-4b74a8fb442ee483498f71f678568c46.jpg
-rw-r--r-- 1 root root 57105 Jul 24 12:10 v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
-rw-r--r-- 1 root root 59942 Jul 24 12:10 v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
-rw-r--r-- 1 root root 59402 Jul 24 12:10 v2-606c8d97459eea560a982d38ad675635.jpg
-rw-r--r-- 1 root root 57641 Jul 24 12:10 v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
-rw-r--r-- 1 root root 26449 Jul 24 12:10 v2-7212853bb56d350bd412e75b40dbfad4.jpg
-rw-r--r-- 1 root root 58009 Jul 24 12:10 v2-82515795f5f9bbbfecab04bb8c095043.jpg
-rw-r--r-- 1 root root 57380 Jul 24 12:10 v2-872e11950d98c437bbebc193a0763b91.jpg
-rw-r--r-- 1 root root 35464 Jul 24 12:10 v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
-rw-r--r-- 1 root root 57299 Jul 24 12:10 v2-92842c98334327e21bd102ad6971f5f5.jpg
-rw-r--r-- 1 root root 37474 Jul 24 12:10 v2-99c631a661dc923e1b29d01d7cf4010c.jpg
-rw-r--r-- 1 root root 61607 Jul 24 12:10 v2-a34fc25ebfc5236148935e7e38e22421.jpg
-rw-r--r-- 1 root root 59202 Jul 24 12:10 v2-a637e7ed8cc70f682217095d85bb760f.jpg
-rw-r--r-- 1 root root 58204 Jul 24 12:10 v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
-rw-r--r-- 1 root root 43556 Jul 24 12:10 v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
-rw-r--r-- 1 root root 25607 Jul 24 12:10 v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
-rw-r--r-- 1 root root 55630 Jul 24 12:10 v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
-rw-r--r-- 1 root root 31262 Jul 24 12:10 v2-ce17057210e31bba97da37c5583a57c4.jpg
-rw-r--r-- 1 root root 59030 Jul 24 12:10 v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
-rw-r--r-- 1 root root 58919 Jul 24 12:10 v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
-rw-r--r-- 1 root root 56729 Jul 24 12:10 v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
-rw-r--r-- 1 root root 54278 Jul 24 12:10 v2-f93fc24b0ebfe2575369259dedbafd63.jpg
[root@yinzhengjie tmp]#
'''

三.生产tar包;

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

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"net/url"
"strings"
"io/ioutil"
"path/filepath"
"os"
"io"
"compress/gzip"
"archive/tar"
) func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
var urls []string //定义一个空切片数组
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
log.Fatal(err)
}
doc.Find("img").Each(func(i int, s *goquery.Selection) {
link, ok := s.Attr("src")
if ok {
urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
} else {
fmt.Println("抱歉,没有发现该路径。")
} })
return urls, nil
} func Clean_urls(root_path string, picture_path []string) []string {
var Absolute_path []string //定义一个绝对路径数组。
url_info, err := url.Parse(root_path)
if err != nil {
log.Fatal(err)
}
Scheme := url_info.Scheme //获取到链接的协议
Host := url_info.Host //获取链接的主机名
for _, souce_path := range picture_path {
if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。 } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
} else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
souce_path = Scheme + "://" + Host + souce_path
} else {
souce_path = filepath.Dir(root_path) + souce_path //文件名称和用户输入的目录相拼接。
}
Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
}
return Absolute_path //最终返回处理后的每个链接的绝对路基。
} func downloadImgs(urls []string, dir string) error {
for _,link := range urls{
resp,err := http.Get(link)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
log.Fatal(resp.Status)
}
file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
f,err := os.Create(full_name) //创建我们定义到文件。
if err != nil {
log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
}
io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
//fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
//defer os.RemoveAll(file_name) //删除文件。
}
return nil
} func make_tar(dir string, w io.Writer) error {
basedir := filepath.Base(dir) //取出文件的目录
compress := gzip.NewWriter(w) //实现压缩功能
defer compress.Close()
tw := tar.NewWriter(w) //表示我们会把数据都写入w中去。而这个w就是我们在主函数中创建的文件。
defer tw.Close()
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { //用"filepath.Walk"函数去递归"dir"目录下的文件。
header,err := tar.FileInfoHeader(info,"") //将文件信息读取出来传给"header"变量,注意"info"后面参数是不传值的,除非你的目录是该软连接。
if err != nil {
return err
}
p,_ := filepath.Rel(dir,path) //取出文件的上级目录。
header.Name= filepath.Join(basedir,p) //header.Name = path //这是将path的相对路径传给"header.Name ",然后在写入到tw中去。不然的话只能拿到"info.Name ()"的名字,也就是如果不来这个操作的话它只会保存文件名,而不会记得路径。
//fmt.Printf("path=%s,header.name=%s,info.name= %s\n",path,header.Name,info.Name())
tw.WriteHeader(header) //将文件的信息写入到文件w中去。
if info.IsDir() {
return nil
}
f1,err := os.Open(path)
if err != nil {
log.Panic("创建文件出错!")
}
defer f1.Close()
io.Copy(tw,f1) //再将文件的内容写到tw中去。
return nil
})
return nil
} func main() {
root_path := "http://daily.zhihu.com/" //定义一个URl,也就是我们要爬的网站。
picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
if err != nil {
log.Fatal(err)
}
Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。
//for _, Picture_absolute_path := range Absolute_path {
// fmt.Println(Picture_absolute_path) //最终我们会得到一个图片的完整路径,我们可以对这个路径进行下载,压缩,加密等等操作。
//}
tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
//fmt.Println(tmpdir)
err = downloadImgs(Absolute_path,tmpdir)
if err != nil {
log.Panic(err)
}
//make_tar("..",os.Stdout) //将结果输出到屏幕上。
f,err := os.Create("img.tar.gz") //创建一个"io.Writer",即可写对象。
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
make_tar(tmpdir,f) //将下载到文件放到一个临时目录中,然后把这个临时目录生成一个我们自定义的文件f。
}

以上代码执行效果如下:

 [root@yinzhengjie tmp]# ll
total 8
-rw-r--r-- 1 root root 6517 Jul 24 12:21 download.go
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run download.go
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# ll
total 1652
-rw-r--r-- 1 root root 6517 Jul 24 12:21 download.go
-rw-r--r-- 1 root root 1682455 Jul 24 12:23 img.tar.gz
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# tar xf img.tar.gz
[root@yinzhengjie tmp]# ll
total 1656
-rw-r--r-- 1 root root 6517 Jul 24 12:21 download.go
-rw-r--r-- 1 root root 1682455 Jul 24 12:23 img.tar.gz
drwx------ 2 root root 4096 Jul 24 12:23 yinzhengjie477065368
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# ll yinzhengjie477065368/
total 1692
-rw-r--r-- 1 root root 171741 Jul 24 12:23 phone_sample.png
-rw-r--r-- 1 root root 2462 Jul 24 12:23 qr_bottom.png
-rw-r--r-- 1 root root 829 Jul 24 12:23 qr_top2.png
-rw-r--r-- 1 root root 57857 Jul 24 12:23 v2-1785cb51899750c8b2ac2229ce9a6402.jpg
-rw-r--r-- 1 root root 57490 Jul 24 12:23 v2-1ab013750fc8e99592f1575dd624d349.jpg
-rw-r--r-- 1 root root 45726 Jul 24 12:23 v2-1df263e7225586d7b96524cde3e03377.jpg
-rw-r--r-- 1 root root 57550 Jul 24 12:23 v2-307267fa9e33c51f3d2acc4d0cae09b0.jpg
-rw-r--r-- 1 root root 50676 Jul 24 12:23 v2-30a6ab7ee9be760d2d12ed354bf18ac5.jpg
-rw-r--r-- 1 root root 26011 Jul 24 12:23 v2-34290a2c50bfe672890efce34e6fb69f.jpg
-rw-r--r-- 1 root root 19310 Jul 24 12:23 v2-3ed913c7731f3fc8931da4a9d652511c.jpg
-rw-r--r-- 1 root root 60436 Jul 24 12:23 v2-4a37c869e5a4a7c31434b8337e20ff4f.jpg
-rw-r--r-- 1 root root 33389 Jul 24 12:23 v2-4b74a8fb442ee483498f71f678568c46.jpg
-rw-r--r-- 1 root root 57105 Jul 24 12:23 v2-56bc12c3fe26e6fa62aa6f874ba3c4a6.jpg
-rw-r--r-- 1 root root 59942 Jul 24 12:23 v2-58887fb8fdeb00872f9c11ebe41a51ef.jpg
-rw-r--r-- 1 root root 59402 Jul 24 12:23 v2-606c8d97459eea560a982d38ad675635.jpg
-rw-r--r-- 1 root root 57641 Jul 24 12:23 v2-6bf90abdddd72ecd2a35099c957b22dc.jpg
-rw-r--r-- 1 root root 26449 Jul 24 12:23 v2-7212853bb56d350bd412e75b40dbfad4.jpg
-rw-r--r-- 1 root root 58009 Jul 24 12:23 v2-82515795f5f9bbbfecab04bb8c095043.jpg
-rw-r--r-- 1 root root 57380 Jul 24 12:23 v2-872e11950d98c437bbebc193a0763b91.jpg
-rw-r--r-- 1 root root 35464 Jul 24 12:23 v2-88e98bc9cc22cd18a355c3e39bb49fb9.jpg
-rw-r--r-- 1 root root 57299 Jul 24 12:23 v2-92842c98334327e21bd102ad6971f5f5.jpg
-rw-r--r-- 1 root root 37474 Jul 24 12:23 v2-99c631a661dc923e1b29d01d7cf4010c.jpg
-rw-r--r-- 1 root root 61607 Jul 24 12:23 v2-a34fc25ebfc5236148935e7e38e22421.jpg
-rw-r--r-- 1 root root 59202 Jul 24 12:23 v2-a637e7ed8cc70f682217095d85bb760f.jpg
-rw-r--r-- 1 root root 58204 Jul 24 12:23 v2-ae95b0f879c23a8f86e4d0be9ea3cb1e.jpg
-rw-r--r-- 1 root root 43556 Jul 24 12:23 v2-b0d49b4709dbf4cbc90838ca383edd2e.jpg
-rw-r--r-- 1 root root 25607 Jul 24 12:23 v2-c6eafd4657c6b3d3d315a10976fc9327.jpg
-rw-r--r-- 1 root root 55630 Jul 24 12:23 v2-cb8b432cfa8aaac47f346f6effd33d63.jpg
-rw-r--r-- 1 root root 31262 Jul 24 12:23 v2-ce17057210e31bba97da37c5583a57c4.jpg
-rw-r--r-- 1 root root 59030 Jul 24 12:23 v2-d4257cc9d1d0ef8a12d13f960c55a216.jpg
-rw-r--r-- 1 root root 58919 Jul 24 12:23 v2-dd16e057c7888affa4fe8d2d94eba7aa.jpg
-rw-r--r-- 1 root root 56729 Jul 24 12:23 v2-ef679c0ed3f236e8d598a4855af0ffa8.jpg
-rw-r--r-- 1 root root 54278 Jul 24 12:23 v2-f93fc24b0ebfe2575369259dedbafd63.jpg
[root@yinzhengjie tmp]#

四.启用web功能;

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

  代码及注释如下:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"net/url"
"strings"
"io/ioutil"
"path/filepath"
"os"
"io"
"compress/gzip"
"archive/tar"
) func fetch(url string) ([]string, error) { //改函数会拿到我们想要的图片的路径。
var urls []string //定义一个空切片数组
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status) //表示当出现错误是,返回空列表,并将错误状态返回。
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
log.Fatal(err)
}
doc.Find("img").Each(func(i int, s *goquery.Selection) {
link, ok := s.Attr("src")
if ok {
urls = append(urls, link) //将过滤出来的图片路径都追加到urls的数组中去,最终返回给用户。
} else {
fmt.Println("抱歉,没有发现该路径。")
} })
return urls, nil
} func Clean_urls(root_path string, picture_path []string) []string {
var Absolute_path []string //定义一个绝对路径数组。
url_info, err := url.Parse(root_path)
if err != nil {
log.Fatal(err)
}
Scheme := url_info.Scheme //获取到链接的协议
Host := url_info.Host //获取链接的主机名
for _, souce_path := range picture_path {
if strings.HasPrefix(souce_path, "https") { //如果当前当前路径是以“https”开头说明是绝对路径,因此我们给一行空代码,表示不执行任何操作,千万别写:“continue”,空着就好。 } else if strings.HasPrefix(souce_path, "//") { //判断当前路径是否以“//”开头(说明包含主机名)
souce_path = Scheme + ":" + souce_path //如果是就对其进行拼接操作。以下逻辑相同。
} else if strings.HasPrefix(souce_path, "/") { //说明不包含主机名和协议,我们进行拼接即可。
souce_path = Scheme + "://" + Host + souce_path
} else {
souce_path = filepath.Dir(root_path) + souce_path //文件名称和用户输入的目录相拼接。
}
Absolute_path = append(Absolute_path, souce_path) //不管是否满足上面的条件,最终都会被追加到该数组中来。
}
return Absolute_path //最终返回处理后的每个链接的绝对路基。
} func downloadImgs(urls []string, dir string) error {
for _,link := range urls{
resp,err := http.Get(link)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() //千万要关闭链接,不然会造成资源泄露(就是为了防止死循环)。
if resp.StatusCode != http.StatusOK{ //如果返回状态出现错误,就抛出错误。也就是你要过滤出来你下载的图片是ok的!
log.Fatal(resp.Status)
}
file_name := filepath.Base(link) //创建一个文件名也就是,这里我们捕捉网站到原文件名称。
full_name := filepath.Join(dir,file_name) //这是将下载到文件存放在我们指定到目录中去。
f,err := os.Create(full_name) //创建我们定义到文件。
if err != nil {
log.Panic("创建文件失败啦!") //创建失败的话,我们就给出自定义的报错。
}
io.Copy(f,resp.Body) //将文件到内容拷贝到我们创建的文件中。
//fmt.Printf("已下载文件至:\033[31;1m%s\033[0m\n",full_name)
//defer os.RemoveAll(file_name) //删除文件。
}
return nil
} func make_tar(dir string, w io.Writer) error {
basedir := filepath.Base(dir) //取出文件的目录
compress := gzip.NewWriter(w) //实现压缩功能
defer compress.Close()
tw := tar.NewWriter(w) //表示我们会把数据都写入w中去。而这个w就是我们在主函数中创建的文件。
defer tw.Close()
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { //用"filepath.Walk"函数去递归"dir"目录下的文件。
header,err := tar.FileInfoHeader(info,"") //将文件信息读取出来传给"header"变量,注意"info"后面参数是不传值的,除非你的目录是该软连接。
if err != nil {
return err
}
p,_ := filepath.Rel(dir,path) //取出文件的上级目录。
header.Name= filepath.Join(basedir,p) //header.Name = path //这是将path的相对路径传给"header.Name ",然后在写入到tw中去。不然的话只能拿到"info.Name ()"的名字,也就是如果不来这个操作的话它只会保存文件名,而不会记得路径。
//fmt.Printf("path=%s,header.name=%s,info.name= %s\n",path,header.Name,info.Name())
tw.WriteHeader(header) //将文件的信息写入到文件w中去。
if info.IsDir() {
return nil
}
f1,err := os.Open(path)
if err != nil {
log.Panic("创建文件出错!")
}
defer f1.Close()
io.Copy(tw,f1) //再将文件的内容写到tw中去。
return nil
})
return nil
} func fech_Images(w io.Writer, root_path string) {
//root_path := "http://daily.zhihu.com/ " //定义一个URl,也就是我们要爬的网站。
picture_path, err := fetch(root_path) //“fetch”函数会帮我们拿到picture_path的路径,但是路径可能是相对路径或是绝对路径。不同意。
if err != nil {
log.Fatal(err)
}
Absolute_path := Clean_urls(root_path, picture_path) //“Clean_urls”函数会帮我们把picture_path的路径做一个统一,最终都拿到了绝对路径Absolute_path数组。 tmpdir,err := ioutil.TempDir("","yinzhengjie") //创建一个临时目录,注意第一个参数最好设置为空(如果设置的话指定目录必须为空。),因为系统会随机给你在"yinzhengjie"随机加一串数字。
defer os.RemoveAll(tmpdir) //将临时目录删除掉,但是未了能看到效果我们不要删除,方便我们取验证。
err = downloadImgs(Absolute_path,tmpdir)
if err != nil {
log.Panic(err)
}
make_tar(tmpdir,w) //将结果返回给客户端。
} func handle_http( w http.ResponseWriter, r *http.Request) { //w是服务器将要返回的内容,r是用户的请求量。
r.ParseForm() //解析用户的请求.
fech_Images(w,r.FormValue("yinzhengjie_url")) //此处的"yinzhengjie_url"就是自定义一个路径提示符。浏览器访问方式:http://172.16.3.211:8080/?yinzhengjie_url=http://daily.zhihu.com/ } func main() {
http.HandleFunc("/",handle_http)
http.ListenAndServe(":8080",nil) //表示监听服务器的所有网卡的"8080端口。"
}

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

服务端在命令行中执行:

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

客户端在浏览器中输入:

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

打开下载后的内容:

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