前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GitHub 下载东西很慢?不存在的~!

GitHub 下载东西很慢?不存在的~!

作者头像
不可言诉的深渊
发布2019-07-28 11:40:55
22.1K0
发布2019-07-28 11:40:55
举报

GitHub 是每一个程序员经常访问的网站之一,其实程序员的网站还有很多,比如 StackOverFlow。一提到 GitHub,大家第一个想到的一定是 clone 或者下载项目,可是大家在 clone 或者下载的时候会发现很慢,为什么?怎么解决?接下来我就着重解决这个问题。

为什么很慢?

首先来分析一下为什么很慢,我们先猜测有哪些原因导致下载很慢,我想到 3 个可能的原因:网络距离太远,访问的人太多,下载节点很少或者只有一个。

网络距离可以使用 tracert 命令(查看从你电脑访问服务器经过了多少个路由器)来查看,如果 tracert 追踪失败,并不能说明网络距离很远,因为有些服务器不让你追踪,即使你在它旁边。没关系,我们还可以通过打开 GitHub 首页的时间来间接的估计一下网络距离有多远,测试之前需要先把浏览数据啥的清除掉,不然会有缓存加速,如图所示。

从图中可以看出 28000 毫秒,也就是 28 秒,GitHub 首页才加载完成,这说明 GitHub 服务器距离我不是一般的远。下面我们就需要想办法解决这个问题,下载不一定要使用 C/S 或者 B/S 的架构,可以使用 P2P,从多个节点获取数据应该会快一点吧。我们可以尝试用迅雷或者其他 P2P 下载软件来下载,这里我以 tesserocr 的数据集为例作讲解,下载链接为:

https://codeload.github.com/tesseract-ocr/tessdata/zip/master

我们复制链接到迅雷进行下载,会发现一直都是只有一个节点,这个节点实际上就是 GitHub 服务器,如图所示。

GitHub 果然不简单,但是没有关系,我还是有办法加速下载,就是用代理绕一下,这里我推荐大家使用付费的 socks5 代理(阿布云上有卖,我是本地有代理软件),因为普通的 http 和 https 代理都被用烂了。设置代理很简单,打开 Internet 选项,点击连接,如图所示。

点击局域网设置,如图所示。

为 LAN 使用代理服务器前面打勾,然后填写地址和端口,确认修改即可。这么做确实可以,但是有缺点,毕竟下载的时候逛一下 B 站很正常,可是这个代理是系统全局的,如果这么设置了,所有网站都会走这个代理,这样的话逛 B 站就没有那么顺畅了,那么有没有办法只给我的下载设置代理呢?有的,我们不一定要下载压缩文件,使用 git 相关命令设置一下代理没问题(具体上网查一下,这里直接省略不讲),但是 git clone 到本地的是没有压缩的,直接原封不动的拿下来的,这就意味着在其他条件不变的情况下 clone 比下载压缩文件的时间要久,就以我现在讲解使用的 tesserocr 数据集为例,这个数据集压缩包是 634.97 MB,解压后 3 GB,这种情况一般人都是愿意下载压缩文件本地解压。我们可以通过写一个脚本来加速下载这个压缩文件,同时不影响逛 B 站。

解决方案

设置代理把文件下载到本地很简单,如下所示:

代码语言:javascript
复制
from requests import get
open('master.zip', 'wb').write(get('https://codeload.github.com/tesseract-ocr/tessdata/zip/master',
                                   proxies={'https': 'socks5:127.0.0.1:1080'}).content)

可是这种办法下载小文件没问题,如果是大文件内存就告急了,那么该如何下载大文件呢?我们首先必然想到分块下载,那么怎么实现这样的功能呢?不要另起炉灶,只需稍作调整!如下所示:

代码语言:javascript
复制
from requests import get
with get('https://codeload.github.com/tesseract-ocr/tessdata/zip/master', proxies={'https': 'socks5://127.0.0.1:1080'},
         stream=True)as response:
    with open('master.zip', 'wb')as file:
        for chunk in response.iter_content(chunk_size=1024):
            file.write(chunk)

是不是很简单?在这里我把这个下载器做一个扩展,可以显示已经下载了多久,下载了多少等信息。

下面我就做一个大概的设计,如果下载之前可以得到文件的大小就用一个进度条表示下载进度,如果没有获取到文件大小我们就显示下载了多少,两个都要能够显示下载时间。

在这里我先设计进度条,再设计下载器。

进度条

进度条设计非常简单,我们设计成如下所示的样子。

下面来说一下具体的实现,首先把上面这个进度条中不变的东西抽出来,开始和结束的一对中括号,已经完成的部分('-'),即将完成的部分('>'),未完成的部分(空格),知道这些写出构造方法轻而易举,如下所示:

代码语言:javascript
复制
class ProcessBar:
    def __init__(self, started_char='[', ended_char=']', finished_char='-', next_finished_char='>',
                 unfinished_char=' '):
        self.started_char = started_char
        self.ended_char = ended_char
        self.finished_char = finished_char
        self.next_finished_char = next_finished_char
        self.unfinished_char = unfinished_char

大家可以改成自己喜欢的样式,具体怎么改我就不讲了。接下来是两个变化的东西,完成度和时间。

先来说一下完成度的设计,完成度我们用一个方法来实现,这个方法的参数是 % 前面的那个数,比如完成度 25%,这个参数就是 25。这个参数同时决定了进度条有多少个 '-' 和多少个空格,方法实现非常简单,如下所示。

代码语言:javascript
复制
    def intermediate_state(self, finished):
        if finished < 100:
            return f'{self.started_char}{self.finished_char*finished}{self.next_finished_char}' \
                f'{self.unfinished_char*(99-finished)}{self.ended_char} {finished}% {self.time_format()}'
        return f'{self.started_char}{self.finished_char*finished}{self.ended_char} {finished}% {self.time_format()}'

在这里我们发现还有一个 time_format 方法,这个方法对时间做了一个格式化,实现起来很简单,如下所示:

代码语言:javascript
复制
    @staticmethod
    def time_format():
        second = int(perf_counter())
        minute = hour = day = 0
        if second >= 60:
            minute = second//60
            second = second % 60
        if minute >= 60:
            hour = minute//60
            minute = minute % 60
        if hour >= 24:
            day = hour//24
            hour = hour % 24
        if second < 10:
            second = f'0{second}'
        if minute < 10:
            minute = f'0{minute}'
        if hour < 10:
            hour = f'0{hour}'
        return f'{day}d {hour}:{minute}:{second}'

进度条就这么完成了,下面开始最核心的部分——下载器!

下载器

在设计下载器之前,我们想一下在用浏览器下载资源的过程中需要知道什么?下载地址,下载到本地的文件名,本地文件的保存位置。对,这三个信息是必不可少的,在这里我还加上了代理和请求头两个可选参数,同时我把下载位置设置成默认参数,默认当前位置。知道这些,实现构造下载器类的构造方法非常简单,如下所示。

代码语言:javascript
复制
class GithubDownloader:
    def __init__(self, url, file_name, headers=None, proxies=None, download_location=''):
        self.url = url
        self.headers = headers
        self.proxies = proxies
        self.file_name = file_name
        self.download_location = download_location

接下来就是设计下载的方法,这个方法有点繁琐,我就懒的细讲了,大概就是分成两种情况讨论——能获取文件大小与不能获取文件大小,如下所示:

代码语言:javascript
复制
    def download(self):
        process_bar = ProcessBar()
        with get(self.url, headers=self.headers, proxies=self.proxies, stream=True)as response:
            if response.headers.get('Content-Length'):
                file_size = int(response.headers['Content-Length'])
                print('文件大小:', self.format_unit(file_size))
                with open(self.download_location + self.file_name, 'wb')as file:
                    current_size = 0
                    print('开始下载……')
                    for chunk in response.iter_content(chunk_size=1024):
                        finished = int(current_size / file_size * 100)
                        print('下载进度:', process_bar.intermediate_state(finished), end='\r', flush=True)
                        current_size += len(chunk)
                        if chunk:
                            file.write(chunk)
                    print('下载完成!', process_bar.intermediate_state(100), end='\r', flush=True)
            else:
                with open(self.download_location + self.file_name, 'wb')as file:
                    current_size = 0
                    print('开始下载……')
                    for chunk in response.iter_content(chunk_size=1024):
                        print(f'已下载:{self.format_unit(current_size)} {ProcessBar.time_format()}', end='\r',
                              flush=True)
                        current_size += len(chunk)
                        if chunk:
                            file.write(chunk)
                    print('下载完成!', process_bar.intermediate_state(100), end='\r', flush=True)

在这里我们发现有一个 format_unit 方法,这个方法用于通知下载了多少,并且做了人性化,方法的实现很简单,如下所示:

代码语言:javascript
复制
    @staticmethod
    def format_unit(byte):
        if byte >= 1024:
            kb = byte/1024
        else:
            return'%7.2fB' % byte
        if kb >= 1024:
            mb = kb/1024
        else:
            return'%7.2fKB' % kb
        if mb >= 1024:
            gb = mb/1024
        else:
            return'%7.2fMB' % mb
        if gb >= 1024:
            tb = gb/1024
            return'%7.2fTB' % tb
        return'%7.2fGB' % gb

就是这样整个下载器已经完成了,下面我直接给出这个下载器的完整源代码。

代码语言:javascript
复制
from time import perf_counter
from requests import get
# https://github.com/tesseract-ocr/tessdata.git
# https://codeload.github.com/tesseract-ocr/tessdata/zip/master


class ProcessBar:
    def __init__(self, started_char='[', ended_char=']', finished_char='-', next_finished_char='>',
                 unfinished_char=' '):
        self.started_char = started_char
        self.ended_char = ended_char
        self.finished_char = finished_char
        self.next_finished_char = next_finished_char
        self.unfinished_char = unfinished_char

    def intermediate_state(self, finished):
        if finished < 100:
            return f'{self.started_char}{self.finished_char*finished}{self.next_finished_char}' \
                f'{self.unfinished_char*(99-finished)}{self.ended_char} {finished}% {self.time_format()}'
        return f'{self.started_char}{self.finished_char*finished}{self.ended_char} {finished}% {self.time_format()}'

    @staticmethod
    def time_format():
        second = int(perf_counter())
        minute = hour = day = 0
        if second >= 60:
            minute = second//60
            second = second % 60
        if minute >= 60:
            hour = minute//60
            minute = minute % 60
        if hour >= 24:
            day = hour//24
            hour = hour % 24
        if second < 10:
            second = f'0{second}'
        if minute < 10:
            minute = f'0{minute}'
        if hour < 10:
            hour = f'0{hour}'
        return f'{day}d {hour}:{minute}:{second}'


class GithubDownloader:
    def __init__(self, url, file_name, headers=None, proxies=None, download_location=''):
        self.url = url
        self.headers = headers
        self.proxies = proxies
        self.file_name = file_name
        self.download_location = download_location

    @staticmethod
    def format_unit(byte):
        if byte >= 1024:
            kb = byte/1024
        else:
            return'%7.2fB' % byte
        if kb >= 1024:
            mb = kb/1024
        else:
            return'%7.2fKB' % kb
        if mb >= 1024:
            gb = mb/1024
        else:
            return'%7.2fMB' % mb
        if gb >= 1024:
            tb = gb/1024
            return'%7.2fTB' % tb
        return'%7.2fGB' % gb

    def download(self):
        process_bar = ProcessBar()
        with get(self.url, headers=self.headers, proxies=self.proxies, stream=True)as response:
            if response.headers.get('Content-Length'):
                file_size = int(response.headers['Content-Length'])
                print('文件大小:', file_size)
                with open(self.download_location + self.file_name, 'wb')as file:
                    current_size = 0
                    print('开始下载……')
                    for chunk in response.iter_content(chunk_size=1024):
                        finished = int(current_size / file_size * 100)
                        print('下载进度:', process_bar.intermediate_state(finished), end='\r', flush=True)
                        current_size += len(chunk)
                        if chunk:
                            file.write(chunk)
                    print('下载完成!', process_bar.intermediate_state(100), end='\r', flush=True)
            else:
                with open(self.download_location + self.file_name, 'wb')as file:
                    current_size = 0
                    print('开始下载……')
                    for chunk in response.iter_content(chunk_size=1024):
                        print(f'已下载:{self.format_unit(current_size)} {ProcessBar.time_format()}', end='\r',
                              flush=True)
                        current_size += len(chunk)
                        if chunk:
                            file.write(chunk)
                    print('下载完成!', process_bar.intermediate_state(100), end='\r', flush=True)


if __name__ == '__main__':
    github_downloader = GithubDownloader('https://codeload.github.com/tesseract-ocr/tessdata/zip/master', 'master.zip',
                                         headers={
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,applicatio'
                      'n/signed-exchange;v=b3', 'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Host': 'codeload.github.com',
            'Upgrade-Insecure-Requests': '1',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0'
                          '.3770.100 Safari/537.36'},
                                         proxies={'https': 'socks5://localhost:1080'})
    github_downloader.download()

运行结果如图所示。

10 分钟不到庞大的 tesserocr 数据集就下载好了,这个程序大家可以自行扩展,扩展途径很多,比如可以做一个 GUI 界面,可以把它改成命令行参数的形式,让它更灵活,更好用!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
AI 应用产品
文字识别(Optical Character Recognition,OCR)基于腾讯优图实验室的深度学习技术,将图片上的文字内容,智能识别成为可编辑的文本。OCR 支持身份证、名片等卡证类和票据类的印刷体识别,也支持运单等手写体识别,支持提供定制化服务,可以有效地代替人工录入信息。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档