Golang实现多线程下载

栏目: Go · 发布时间: 5年前

内容简介:前段时间写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容,效率比较低,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效率.`最近写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容性能比较差,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效

前段时间写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容,效率比较低,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效率.

多线程下载必须服务端支持

1.判断服务端是否支持多线下载:
     使用 HEAD 方法请求资源,然后查看服务端返回数据
Golang实现多线程下载

image.png

2.查看返回数据头部是否存在 `Accept-Ranges →bytes`
    如果有,那么就支持多线程下载,没有的话基本上可以洗洗睡了.

Golang 实现环节

`

最近写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容性能比较差,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效率.

多线程下载必须服务端支持

1.判断服务端是否支持多线下载:
     使用 HEAD 方法请求资源,然后查看服务端返回数据
Golang实现多线程下载

image.png

2.查看返回数据头部是否存在 `Accept-Ranges →bytes`
    如果有,那么就支持多线程下载,没有的话基本上可以洗洗睡了.

Golang 实现环节

判断是否支持多线下载

Golang实现多线程下载

image.png

多线下载任务分配

Golang实现多线程下载

image.png

执行下载

Golang实现多线程下载

image.png

完整代码

package download

import (
    "github.com/labstack/gommon/log"
    "io/ioutil"
    "net/http"
    "os"
    "strconv"
    "strings"
    "sync"
    "sync/atomic"
    "time"
)

var client = http.Client{Timeout: time.Second * 180}

var threadGroup = sync.WaitGroup{}

var packageSize int64

func init() {
    //每个线程下载文件的大小
    packageSize = 1048576 * 4
}

func Download(url, cachePath string, scheduleCallback func(schedule float64)) string {

    var localFileSize int64
    var file *os.File
    if info, e := os.Stat(cachePath); e != nil {
        if os.IsNotExist(e) {
            if createFile, err := os.Create(cachePath); err == nil {
                file = createFile
            } else {
                panic(err)
            }
        } else {
            panic(e)
        }
    } else {
        localFileSize = info.Size()
    }
    //HEAD 方法请求服务端是否支持多线程下载,并获取文件大小
    if request, e := http.NewRequest("HEAD", url, nil); e == nil {
        if response, i := client.Do(request); i == nil {
            defer response.Body.Close()
            //得到文件大小
            ContentLength := response.ContentLength
            if localFileSize == ContentLength {
                log.Warn("file exist~")
                return cachePath
            } else {
                //判断是否支持多线下载
                if strings.Compare(response.Header.Get("Accept-Ranges"), "bytes") == 0 {
                    //支持 走下载流程
                    if dispSliceDownload(file, ContentLength, url, scheduleCallback) == 0 {
                        return cachePath
                    } else {
                        return ""
                    }
                } else {
                    panic("nonsupport ~")
                }
            }
        } else {
            panic(i)
        }
    } else {
        panic(e)
    }
    return ""
}

func dispSliceDownload(file *os.File, ContentLength int64, url string, scheduleCallback func(schedule float64)) int {
    defer file.Close()
    //文件总大小除以 每个线程下载的大小
    i := ContentLength / packageSize
    //保证文件下载完整
    if ContentLength%packageSize > 0 {
        i += 1
    }
    //下载总进度
    var schedule int64
    //分配下载线程
    for count := 0; count < int(i); count++ {
        //计算每个线程下载的区间,起始位置
        var start int64
        var end int64
        start = int64(int64(count) * packageSize)
        end = start + packageSize
        if int64(end) > ContentLength {
            end = end - (end - ContentLength)
        }
        //构建请求
        if req, e := http.NewRequest("GET", url, nil); e == nil {
            req.Header.Set(
                "Range",
                "bytes="+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10))
            //
            threadGroup.Add(1)
            go sliceDownload(req, file, &schedule, &ContentLength, scheduleCallback, start)
        } else {
            panic(e)
        }

    }
    //等待所有线程完成下载
    threadGroup.Wait()
    return 0
}

func sliceDownload(request *http.Request, file *os.File, schedule *int64, ContentLength *int64, scheduleCallback func(schedule float64),
    start int64) {
    defer threadGroup.Done()
    if response, e := client.Do(request); e == nil && response.StatusCode == 206 {
        defer response.Body.Close()
        if bytes, i := ioutil.ReadAll(response.Body); i == nil {
            i2 := len(bytes)
            //从我们计算好的起点写入文件
            file.WriteAt(bytes, start)
            atomic.AddInt64(schedule, int64(i2))
            val := atomic.LoadInt64(schedule)
            num := float64(val*1.0) / float64(*ContentLength) * 100
            scheduleCallback(float64(num))
        } else {
            panic(e)
        }
    } else {
        panic(e)
    }
}

因为硬盘空间有限,爬取到180GB的时候,就结束了爬取.

Golang实现多线程下载

image.png

个人联系方式:
作者QQ:853151446
作者邮箱:853151446@qq.com
有问题或者可以一起探讨的,请联系我,或者留言.

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图形程序开发人员指南

图形程序开发人员指南

Michael Abrash / 前导工作室 / 机械工业出版社 / 1998 / 128

Michael Abrash's classic Graphics Programming Black Book is a compilation of Michael's previous writings on assembly language and graphics programming (including from his "Graphics Programming" column......一起来看看 《图形程序开发人员指南》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具