python断点续传原理_python编写断点续传下载软件-程序员宅基地

技术标签: python断点续传原理  

一年一度的python小程序编写系列之——断点续传下载软件。

其实HTTP断点续传原理比较简单,在HTTP数据包中,可以增加Range头,这个头以字节为单位指定请求的范围,来下载范围内的字节流。如:

如上图勾下来的地方,我们发送数据包时选定请求的内容的范围,返回包即获得相应长度的内容。所以,我们在下载的时候,可以将目标文件分成很多“小块”,每次下载一小块(用Range标明小块的范围),直到把所有小块下载完。

当网络中断,或出错导致下载终止时,我们只需要记录下已经下载了哪些“小块”,还没有下载哪些。下次下载的时候在Range处填写未下载的小块的范围即可,这样就能构成一个断点续传。

其实像迅雷这种多线程下载器也是同样的原理。将目标文件分成一些小块,再分配给不同线程去下载,最后整合再检查完整性即可。

我们仍然使用之前介绍过的requests库作为HTTP请求库。

先看看这段文档:http://docs.python-requests.org/en/latest/user/advanced/#body-content-workflow,当请求时设置steam=True的时候就不会立即关闭连接,而我们以流的形式读取body,直到所有信息读取完全或者调用Response.close关闭连接。

所以,如果要下载大文件的话,就将steam设置为True,慢慢下载,而不是等整个文件下载完才返回。

stackoverflow上有同学给出了一个简单的下载demo:

def download_file(url):

local_filename = url.split('/')[-1]

# NOTE the stream=True parameter

r = requests.get(url, stream=True)

with open(local_filename, 'wb') as f:

for chunk in r.iter_content(chunk_size=1024):

if chunk: # filter out keep-alive new chunks

f.write(chunk)

f.flush()

return local_filename

这基本上就是我们核心的下载代码了。

好,我们结合这两个知识点写个小程序:支持断点续传的下载器。

我们可以先考虑一下需要注意的有哪些点,或者可以添加的功能有哪些:

用户自定义性:可以定义cookie、referer、user-agent。如某些下载站检查用户登录才允许下载等情况。

很多服务端不支持断点续传,如何判断?

怎么去表达进度条?

如何得知文件的总大小?使用HEAD请求?那么服务器不支持HEAD请求怎么办?

下载后的文件名(header中可能有filename,url中也有filename,用户还可以自己指定filename),怎么处理?还要考虑windows不允许哪些字符做文件名。

如何去分块,是否加入多线程。

其实想一下还是有很多疑虑,而且有些地方可能一时还解决不了。先大概想一下各个问题的答案:

headers可以由用户自定义

正式下载之前先HEAD请求,得到服务器status code是否是206,header中是否有Range-content等标志,判断是否支持断点续传。

可以先不使用进度条,只显示当前下载大小和总大小

在HEAD请求中匹配出Range-content中的文件总大小,或获得content-length大小(当不支持断点续传的时候会返回总content-length)。如果不支持HEAD请求或没有content-type就设置总大小为0.(总之不会妨碍下载即可)

文件名优先级:用户自定义 > header中content-disposition > url中的定义,为了避免麻烦,我这里和linux下的wget一样,忽略content-disposition的定义。如果用户不指定保存的用户名的话,就以url中最后一个“/”后的内容作为用户名。

为了稳定和简单,不做多线程了。如果不做多线程的话,我们分块就可以按照很小来分,如1KB,然后从头开始下载,一K一K这样往后填充。这样避免了很多麻烦。当下载中断的时候,我们只需要简单查看当前已经下载了多少字节,就可以从这个字节后一个开始继续下载。

解决了这些疑问,我们就开始动笔了。实际上,疑问并不是在未动笔的时候干想出来的,基本都是我写了一半突然发现的问题。

def download(self, url, filename, headers = {}):

finished = False

block = self.config['block']

local_filename = self.remove_nonchars(filename)

tmp_filename = local_filename + '.downtmp'

if self.support_continue(url): # 支持断点续传

try:

with open(tmp_filename, 'rb') as fin:

self.size = int(fin.read()) + 1

except:

self.touch(tmp_filename)

finally:

headers['Range'] = "bytes=%d-" % (self.size, )

else:

self.touch(tmp_filename)

self.touch(local_filename)

size = self.size

total = self.total

r = requests.get(url, stream = True, verify = False, headers = headers)

if total > 0:

print "[+] Size:%dKB" % (total / 1024)

else:

print "[+] Size: None"

start_t = time.time()

with open(local_filename, 'ab') as f:

try:

for chunk in r.iter_content(chunk_size = block):

if chunk:

f.write(chunk)

size += len(chunk)

f.flush()

sys.stdout.write('\b' * 64 + 'Now:%d, Total:%s' % (size, total))

sys.stdout.flush()

finished = True

os.remove(tmp_filename)

spend = int(time.time() - start_t)

speed = int(size / 1024 / spend)

sys.stdout.write('\nDownload Finished!\nTotal Time:%ss, Download Speed:%sk/s\n' % (spend, speed))

sys.stdout.flush()

except:

import traceback

print traceback.print_exc()

print "\nDownload pause.\n"

finally:

if not finished:

with open(tmp_filename, 'wb') as ftmp:

ftmp.write(str(size))

这是下载的方法。首先if语句调用self.support_continue(url)判断是否支持断点续传。如果支持则从一个临时文件中读取当前已经下载了多少字节,如果不存在这个文件则会抛出错误,那么size默认=0,说明一个字节都没有下载。

然后就请求url,获得下载连接,for循环下载。这个时候我们得抓住异常,一旦出现异常,不能让程序退出,而是正常将当前已下载字节size写入临时文件中。下次再次下载的时候读取这个文件,将Range设置成bytes=(size+1)-,也就是从当前字节的后一个字节开始到结束的范围。从这个范围开始下载,来实现一个断点续传。

判断是否支持断点续传的方法还兼顾了一个获得目标文件大小的功能:

def support_continue(self, url):

headers = {

'Range': 'bytes=0-4'

}

try:

r = requests.head(url, headers = headers)

crange = r.headers['content-range']

self.total = int(re.match(ur'^bytes 0-4/(\d+)$', crange).group(1))

return True

except:

pass

try:

self.total = int(r.headers['content-length'])

except:

self.total = 0

return False

用正则匹配出大小,获得直接获取headers['content-length'],获得将其设置为0.

运行来获取一下emlog最新的安装包:

中间我按Contrl + C人工打断了下载进程,但之后还是继续下载,实现了“断点续传”。但在我实际测试过程中,并不是那么多请求可以断点续传的,所以我对于不支持断点续传的文件这样处理:重新下载。

下载后的压缩包正常解压,也充分证明了下载的完整性:

做了个小动图:

如果你想把我的这个小脚本当一个工具来用的话,可以查看github下的说明:https://github.com/phith0n/py-wget。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39789206/article/details/110837466

智能推荐

js的三种接口(详细解释)_js接口类型-程序员宅基地

文章浏览阅读1.9w次,点赞6次,收藏20次。js接口实现三种方式_js接口类型

HDU6150&&2017CCPC网络赛Vertex Cover-程序员宅基地

文章浏览阅读402次。这一篇博客可能是最详细的了,如果帮助了你,请顶一下,谢谢支持。2017年CCPC网络选拔赛题目。现HDU6150题目的意思是求最小顶点覆盖是一个NP问题(NP问题可自行百度),没有一个好的算法去求。但是特殊图的时候有方法去求。现在说找到一种求最小顶点覆盖的算法。有点贪心的思想在里面。每次找图中度数最大的点(并且是编号最后一个点),把这个点加入解的集合,然后把和这个点所有关联的边也删

unity 下的资源加载内存管理-程序员宅基地

文章浏览阅读129次。学习两个方面的知识 看的这里http://www.nikest.com/web/jswd/2015/0316/145497_6.html① .NET 和 Mono 的垃圾回收中的内存管理,和内存泄漏的常见来源。② 进行内存泄漏发现党的UnityProfiler 和 .NET反汇编和公共中间语言CIL一、 大多数现在操作系统划分动态内存为栈和堆。许多CPU架构包括PC、M...

P2P专栏-程序员宅基地

文章浏览阅读532次。目前本人正在研究P2P技术,希望能与对该领域的其他人交流。_p2p专栏

半糖iOS版首页实现与基本原理揭秘_半糖直播app最新-程序员宅基地

文章浏览阅读1.4w次,点赞11次,收藏18次。很久以前,一个学弟的曾问过我如何实现半糖iOS版本首页效果,我当时一看觉得这个效果挺酷炫,然后去github上搜了一下,很多自称是仿半糖首页的,我下载之后发现其实很多代码都没有实现主要的代码。有些代码也做了一些简单的尝试,但是最后都放弃了,所以说这个效果还是没有很好的实现。我于是打算研究一下这个有趣的效果,经过工作之余一段时间的研究。终于研究好了这个问题。写下来与大家分享。_半糖直播app最新

limma包的使用技巧-程序员宅基地

文章浏览阅读1.1w次,点赞3次,收藏28次。limmar package是一个功能比较全的包,既含有cDNA芯片的RAW data输入、前处理(归一化)功能,同时也有差异化基因分析的“线性”算法(limma: Linear Models for Microarray Data),特别是对于“多因素实验(multifactor designed experiment)”。limmar包的可扩展性非常强,单通道(one channel)或者

随便推点

《合成孔径雷达成像——算法与实现》之【12】仿真图5.17_雷达成像方位向和距离向-程序员宅基地

文章浏览阅读3.9k次。零斜视角、正扫描情况下,单点目标的方位频谱:方位向的过采样体现在每个距离门的“间隙”上,其位于傅里叶变换后数据列的中间位置。经傅里叶变换后方位频率二次相位改变了符号,故图(b))中的等值线变成了椭圆。距离向和方位向的过采样可以从图(c)中的二维频谱看出。距离傅里叶变换同样会引起符号的改变,图(d)中的等值线又变成了双曲线。源代码下载地址:_雷达成像方位向和距离向

Guest OS, Qemu, KVM工作流程-程序员宅基地

文章浏览阅读1.6k次。Guest OS, Qemu, KVM工作流程 2013-06-10 00:08:11分类: LINUX 这里主要介绍基于x86平台的Guest Os, Qemu, Kvm工作流程,如图,通过KVM APIs可以将qemu的command传递到kvm:1.创建VMsystem_fd = open("/de

keytool工具生成jks证书_keytool-importkeypair 生成jks_小码张的博客-程序员宅基地

文章浏览阅读3.3k次。输入命令keytool -genkeypair -alias server_https -keypass oukele -keyalg RSA -keysize 1024 -validity 365 -keystore D:/server_https.keystore -storepass oukele-alias 别名-keypass 指定生成密钥的密码-keyalg 指定密钥使用的加密算法(如 RSA)-keysize 密钥大小-validity 过期时间,单位:天-keystore 指_keytool-importkeypair 生成jks

Android应用开发之(WebView中loadData与loadDataWithBaseURL的使用上的区别)_webview loaddatawithbaseurl loaddata区别-程序员宅基地

文章浏览阅读1.1k次。在开发Android平台的互联网应用时,经常会使用到WebView,好处主要有两个,一是可以更改要展现的内容(包括样式),二是可以实现部分功能的跨平台。 Android的WebView组件使用非常简单,可以使用loadUrl()加载一个Url地址,也可以使用loadData()或loadDataWithBaseURL()加载一段HTML代码片段。loadUrl()的使用大家应该都没_webview loaddatawithbaseurl loaddata区别

C# dataTable 单元格内插入 另一个DataTable_tb.columns add-程序员宅基地

文章浏览阅读1.5k次。在利用C#做服务端,然后利用datatable向前端返回时,希望在某行数据内,添加一个数组。主要是dataTable默认使用 datatable.columns.Add时,未指定列的类型 默认为string,需要指定为DataTable类型!DataTable tb= new DataTable();tb.Columns.Add("NewData",typeof(DataTab..._tb.columns add

SAP Explore hidden functions in MD04-程序员宅基地

文章浏览阅读451次。MD04 has many hidden powerful and useful functions. Knowing them could make our daily work easier to evalute MRP results.Set a manual firming date. You can set a manual firming date by menu ‘Edit’ > ‘Set Firming Date’.Receipts lying before the fi.