大部分新手(比如我)看完golang圣经之后不知道该做些什么好,那不如就来做一个资源下载小程序吧。这里用一个视频网站作为示范,来编写一个小小的资源下载小程序
因为是个小程序,所以所有函数和方法都放在一个 main 包里。
首先导入我有用到的标准库。
```
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
"strings"
"sync"
"time"
)
```
一些全局变量
```
var index_url string = "https://www.example.com"//这里就用example来替换掉它的域名,免得被人找麻烦
/*
ptnIndexItem是一个个播放视频网页的链接
ptnVideoItem是为了匹配视频播放网页里的视频链接
dir 是你要下载的路径
*/
var ptnIndexItem = regexp.MustCompile(`<a[^<>]+href *\= *[\"']?(\/[\d]+)\"[^<>]*title\=\"([^\"]*)\".*name.*>`)
var dir string = "./example_video"
var ptnVideoItem = regexp.MustCompile(`<a[^<>]+href *\= *[\"']?(https\:\/\/[^\"]+)\"[^<>]*download[^<>]*>`)
//增加一个等待组
var wg sync.WaitGroup
```
写一个结构体,为了存放每一个视频的下载进度,为什么用int64 是因为后面用到的文件大小和ContentLength都是int64
```
/*
用于记录下载进度的结构体
*/
type DownList struct {
Data map[string][]int64
Lock sync.Mutex
}
```
懒得对每一个error进行判断,写一个检查用的函数
```
/*
检查点
*/
func check(e error) {
if e != nil {
panic(e)
}
}
```
获取网页的方法
```
/*
url: 需要获取的网页
return:
content 抓取到的网页源码
statusCode 返回的状态码
*/
func Get(url string) (content string, statusCode int) {
resp, err1 := http.Get(url)
if err1 != nil {
statusCode = -100
return
}
defer resp.Body.Close()
data, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
statusCode = -200
return
}
statusCode = resp.StatusCode
content = string(data)
return
}
```
用文件来记录这个视频的链接,每次下载之前先和这个文件里面的链接比较,有就不下载了,免得重复下载。
得先创建一个你钟意的文件
```
/*
param:
filename: 文件名
text 需要比较的字符串
return:
ture 没
false 有
*/
func readOnLine(filename string, text string) bool {
fi, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND, os.ModeAppend)
check(err)
defer fi.Close()
text = text + "\n"
br := bufio.NewReader(fi)
for {
a, c := br.ReadString('\n')
if c == io.EOF {
fmt.Println(text, "不存在,现在写入")
fi.WriteString(text)
return true
}
if string(a) == text {
fmt.Println("存在", text)
break
}
}
return false
}
```
lock 为了防止后面goroutines 并发读写map时候报错
\033[2J 是用来清空屏幕的,做到类似top的效果,虽然远不如top,但是能看到下载进度还是很爽的
```
/*
输出下载进度的方法
*/
func (downList *DownList) process() {
for {
downList.Lock.Lock()
for key, arr := range downList.Data {
fmt.Printf("%s progress: [%-50s] %d%% Done\n", key, strings.Repeat("#", int(arr[0]*50/arr[1])), arr[0]*100/arr[1])
}
//fmt.Println(downList)
downList.Lock.Unlock()
time.Sleep(time.Second * 3)
fmt.Printf("\033[2J")
}
}
```
下载视频的函数,
```
/*
url: 视频链接
filename: 本地文件名
downList: 用来记录下载进度的一个结构体的指针
*/
func Down(url string, filename string, downList *DownList) bool {
b := make([]byte, 1024)
f, err := os.Create(filename)
if err != nil {
fmt.Println("创建文件失败")
return false
}
defer f.Close()
repo, err := http.Get(url)
if err != nil {
fmt.Println("获取资源失败")
return false
}
defer repo.Body.Close()
bufRead := bufio.NewReader(repo.Body)
for {
n, err := bufRead.Read(b)
if err == io.EOF {
break
}
f.Write(b[:n])
fileInfo, err := os.Stat(filename)
fileSize := fileInfo.Size()
//fmt.Println(fileSize, "--", repo.ContentLength)
downList.Lock.Lock()
downList.Data[filename] = []int64{fileSize, repo.ContentLength}
downList.Lock.Unlock()
}
wg.Done()
return true
}
```
下面就是main 函数了
```
func main() {
//初始化downList 与 map
var downListF DownList
downListF.Data = make(map[string][]int64)
downList := &downListF
//首先获取index网页的内容
context, statusCode := Get(index_url)
if statusCode != 200 {
fmt.Println("error")
return
}
/* 提取并复制到二维数组
html_result [][]string
html_result[n] []string 匹配到的链接
html_result[n][0] string 全匹配数据
html_result[n][1] url string
html_result[n][2] title string
*/
html_result := ptnIndexItem.FindAllStringSubmatch(context, -1)
length := len(html_result)
go downList.process()
for i := 0; i < length; i++ {
v := html_result[i]
video_html, video_status := Get(index_url + v[1])
if video_status != 200 {
fmt.Println("error")
continue
}
video_result := ptnVideoItem.FindAllStringSubmatch(video_html, -1)
ok := readOnLine("test", v[1])
if len(video_result) > 0 && len(video_result[0]) > 0 && ok {
//fmt.Println(video_result[0][1])
wg.Add(1)
dirFile := path.Join(dir, html_result[i][2])
go Down(video_result[0][1], dirFile, downList)
}
//fmt.Println(i, video_html)
}
wg.Wait()
return
}
```
这样,不到200行的资源下载小程序就完成啦
有疑问加站长微信联系(非本文作者))