适合新手看的资源下载小程序

golang_291 · 2017-11-18 13:48:47 · 3169 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2017-11-18 13:48:47 的文章,其中的信息可能已经有所发展或是发生改变。

大部分新手(比如我)看完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行的资源下载小程序就完成啦


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3169 次点击  
加入收藏 微博
5 回复  |  直到 2017-11-23 15:30:25
Corwien
Corwien · #1 · 7年之前

不错,可以练练手!

gqwfusd
gqwfusd · #2 · 7年之前

用到了挺多的基础库,挺好的,可以试一下

ango
ango · #3 · 7年之前

请问爬的是那个网站?

lepig
lepig · #4 · 7年之前

这个可以 结合实践

golang_291
golang_291 · #5 · 7年之前

@ango 这个不方便透露,一个*片网站

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传