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 站。
解决方案
设置代理把文件下载到本地很简单,如下所示:
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)
可是这种办法下载小文件没问题,如果是大文件内存就告急了,那么该如何下载大文件呢?我们首先必然想到分块下载,那么怎么实现这样的功能呢?不要另起炉灶,只需稍作调整!如下所示:
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)
是不是很简单?在这里我把这个下载器做一个扩展,可以显示已经下载了多久,下载了多少等信息。
下面我就做一个大概的设计,如果下载之前可以得到文件的大小就用一个进度条表示下载进度,如果没有获取到文件大小我们就显示下载了多少,两个都要能够显示下载时间。
在这里我先设计进度条,再设计下载器。
进度条
进度条设计非常简单,我们设计成如下所示的样子。
下面来说一下具体的实现,首先把上面这个进度条中不变的东西抽出来,开始和结束的一对中括号,已经完成的部分('-'),即将完成的部分('>'),未完成的部分(空格),知道这些写出构造方法轻而易举,如下所示:
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。这个参数同时决定了进度条有多少个 '-' 和多少个空格,方法实现非常简单,如下所示。
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 方法,这个方法对时间做了一个格式化,实现起来很简单,如下所示:
@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
接下来就是设计下载的方法,这个方法有点繁琐,我就懒的细讲了,大概就是分成两种情况讨论——能获取文件大小与不能获取文件大小,如下所示:
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 方法,这个方法用于通知下载了多少,并且做了人性化,方法的实现很简单,如下所示:
@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
就是这样整个下载器已经完成了,下面我直接给出这个下载器的完整源代码。
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 界面,可以把它改成命令行参数的形式,让它更灵活,更好用!
本文分享自 Python机器学习算法说书人 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!