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
有问题或者可以一起探讨的,请联系我,或者留言.

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

查看所有标签

猜你喜欢:

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

The Little MLer

The Little MLer

Matthias Felleisen、Daniel P. Friedman、Duane Bibby、Robin Milner / The MIT Press / 1998-2-19 / USD 34.00

The book, written in the style of The Little Schemer, introduces instructors, students, and practicioners to type-directed functional programming. It covers basic types, quickly moves into datatypes, ......一起来看看 《The Little MLer》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

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

RGB CMYK 互转工具