当然你也可以利用ftplib从ftp站点下载文件。此外python还提供了另外一种方法requests。
下面来看看三种方法是如何来下载zip文件的:
方法一:
1.urlopen()方法
urllib.urlopen(url[, data[, proxies]]) :创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。
参数url表示远程数据的路径,一般是网址;
参数data表示以post方式提交到url的数据(玩过web的人应该知道提交数据的两种方式:post与get。如果你不清楚,也不必太在意,一般情况下很少用到这个参数);
参数proxies用于设置代理。
urlopen返回 一个类文件对象,它提供了如下方法:
read() , readline() , readlines() , fileno() , close() :这些方法的使用方式与文件对象完全一样;
info():返回一个httplib.httpmessage 对象,表示远程服务器返回的头信息
getcode():返回http状态码。如果是http请求,200表示请求成功完成;404表示网址未找到;
geturl():返回请求的url;
urllib模块urlretrieve方法
下面我们再来看看 urllib 模块提供的 urlretrieve() 函数。urlretrieve() 方法直接将远程数据下载到本地。
urllib.urlretrieve(url[, filename[, reporthook[, data]]])
参数说明:
url:外部或者本地url
filename:指定了保存到本地的路径(如果未指定该参数,urllib会生成一个临时文件来保存数据);
reporthook:是一个回调函数,当连接上服务器、以及相应的数据块传输完毕的时候会触发该回调。我们可以利用这个回调函数来显示当前的下载进度。
data:指post到服务器的数据。该方法返回一个包含两个元素的元组(filename, headers),filename表示保存到本地的路径,header表示服务器的响应头。
方法二:
方法三:
看起来使用urllib最为简单,一句语句即可。当然你可以把urllib2缩写成:
请求关键参数:stream=true。默认情况下,当你进行网络请求后,响应体会立即被下载。你可以通过 stream 参数覆盖这个行为,推迟下载响应体直到访问 response.content 属性。
此时仅有响应头被下载下来了,连接保持打开状态,因此允许我们根据条件获取内容:
进一步使用 response.iter_content 和 response.iter_lines 方法来控制工作流,或者以 response.raw 从底层urllib3的 urllib3.httpresponse
保持活动状态(持久连接)
归功于urllib3,同一会话内的持久连接是完全自动处理的,同一会话内发出的任何请求都会自动复用恰当的连接!
注意:只有当响应体的所有数据被读取完毕时,连接才会被释放到连接池;所以确保将 stream 设置为 false 或读取 response 对象的 content 属性。
在python3中,print()方法的默认结束符(end=’\n’),当调用完之后,光标自动切换到下一行,此时就不能更新原有输出。
将结束符改为“\r”,输出完成之后,光标会回到行首,并不换行。此时再次调用print()方法,就会更新这一行输出了。
结束符也可以使用“\d”,为退格符,光标回退一格,可以使用多个,按需求回退。
在结束这一行输出时,将结束符改回“\n”或者不指定使用默认
下面是一个格式化的进度条显示模块。代码如下:
另一种方法是调用 curl 之类支持断点续传的下载工具。后续补充
一、http断点续传原理
其实http断点续传原理比较简单,在http数据包中,可以增加range头,这个头以字节为单位指定请求的范围,来下载范围内的字节流。如:
如上图勾下来的地方,我们发送数据包时选定请求的内容的范围,返回包即获得相应长度的内容。所以,我们在下载的时候,可以将目标文件分成很多“小块”,每次下载一小块(用range标明小块的范围),直到把所有小块下载完。
当网络中断,或出错导致下载终止时,我们只需要记录下已经下载了哪些“小块”,还没有下载哪些。下次下载的时候在range处填写未下载的小块的范围即可,这样就能构成一个断点续传。
其实像迅雷这种多线程下载器也是同样的原理。将目标文件分成一些小块,再分配给不同线程去下载,最后整合再检查完整性即可。
二、python下载文件实现方式
我们仍然使用之前介绍过的requests库作为http请求库。
所以,如果要下载大文件的话,就将steam设置为true,慢慢下载,而不是等整个文件下载完才返回。
stackoverflow上有同学给出了一个简单的下载demo:
这基本上就是我们核心的下载代码了。
当使用requests的get下载大文件/数据时,建议使用使用stream模式。
当把get函数的stream参数设置成false时,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足。
当把get函数的stream参数设置成true时,它不会立即开始下载,当你使用iter_content或iter_lines遍历内容或访问内容属性时才开始下载。需要注意一点:文件没有下载之前,它也需要保持连接。
iter_content:一块一块的遍历要下载的内容
iter_lines:一行一行的遍历要下载的内容
使用上面两个函数下载大文件可以防止占用过多的内存,因为每次只下载小部分数据。
三、断点续传结合大文件下载
好,我们结合这两个知识点写个小程序:支持断点续传的下载器。
我们可以先考虑一下需要注意的有哪些点,或者可以添加的功能有哪些:
其实想一下还是有很多疑虑,而且有些地方可能一时还解决不了。先大概想一下各个问题的答案:
解决了这些疑问,我们就开始动笔了。实际上,疑问并不是在未动笔的时候干想出来的,基本都是我写了一半突然发现的问题。
这是下载的方法。首先if语句调用self.support_continue(url)判断是否支持断点续传。如果支持则从一个临时文件中读取当前已经下载了多少字节,如果不存在这个文件则会抛出错误,那么size默认=0,说明一个字节都没有下载。
然后就请求url,获得下载连接,for循环下载。这个时候我们得抓住异常,一旦出现异常,不能让程序退出,而是正常将当前已下载字节size写入临时文件中。下次再次下载的时候读取这个文件,将range设置成bytes=(size+1)-,也就是从当前字节的后一个字节开始到结束的范围。从这个范围开始下载,来实现一个断点续传。
判断是否支持断点续传的方法还兼顾了一个获得目标文件大小的功能:
用正则匹配出大小,获得直接获取headers['content-length'],获得将其设置为0.
运行来获取一下emlog最新的安装包:
中间我按contrl + c人工打断了下载进程,但之后还是继续下载,实现了“断点续传”。但在我实际测试过程中,并不是那么多请求可以断点续传的,所以我对于不支持断点续传的文件这样处理:重新下载。
下载后的压缩包正常解压,也充分证明了下载的完整性:
动态图演示
下载视频的效果