前段时间写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容,效率比较低,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效率.
多线程下载必须服务端支持
1.判断服务端是否支持多线下载:
使用 HEAD 方法请求资源,然后查看服务端返回数据
image.png
2.查看返回数据头部是否存在 `Accept-Ranges →bytes`
如果有,那么就支持多线程下载,没有的话基本上可以洗洗睡了.
Golang 实现环节
`
最近写了个小爬虫,从国外某网站上下载视频,初期使用的是单线下载,后面发现访问服务端的资源数过多,会被反爬机制限制,还有一个问题就是单线下载境外网站内容性能比较差,下载速度很慢,后面修修补补改了改,改为多线访问同个资源,顺利解决反爬机制,也提升了下载效率.
多线程下载必须服务端支持
1.判断服务端是否支持多线下载:
使用 HEAD 方法请求资源,然后查看服务端返回数据
image.png
2.查看返回数据头部是否存在 `Accept-Ranges →bytes`
如果有,那么就支持多线程下载,没有的话基本上可以洗洗睡了.
Golang 实现环节
判断是否支持多线下载
image.png
多线下载任务分配
image.png
执行下载
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的时候,就结束了爬取.
image.png
个人联系方式:
作者QQ:853151446
作者邮箱:853151446@qq.com
有问题或者可以一起探讨的,请联系我,或者留言.