在写爬虫的过程中常常遇到下载大文件的情况,比如说视频之类的。如果只是传统的直接下载保存,速度就比较慢,所有就想写个多线程同步下载大文件的模块。
使用到的库
模块中使用到的库都比较简单:requests(写爬虫的都知道吧),threading(多线程,必须的),os(文件操作需要用到)。
主要的难点
一个是多线程下载的同步问题,另一个是文件中断下载后,再次开始从上次中断的地方继续下载。
其实我觉得就这两个问题,迅雷之类的下载器早就已经给我们做了个解决方法事例,那就是在下载文件的路径添加一个管理下载进度的文件。
具体实现
模块中有两个类,一个负责处理管理文件的更新和创建等,还有一个是线程任务,发下载请求,写入文件等。
管理文件类
文件内的格式使用的是很简单的用“=”分割的配置文件的形式,包括四个配置信息,分别是:【已写入的字节】range,【未写入的字节】range,【写入中的字节】range和文件下载的url。之前所说的比较难处理的问题都是用这个管理文件解决的。
主要思路是,刚开始下载时创建这个文件,获取带下载文件的大小,并填入【未下载字节】中去。然后其他线程同步的不断从这个【未下载字节】的中提取一小部分写到【下载中字节】,发请求下载,并写入到文件中去,之后再把已经下载好的字节写入到【已下载字节】中去。因为考虑到下载的字节可能时一段一段分隔开的,所有写成图中所示的形式。
其实对range的分割,合并(也就是这个类的主要功能)还是需要一些小技巧的,如果想要自己先练练的话可以前往https://leetcode.com/problems/insert-interval/
之前也是写的时候感觉很熟悉,发现在leetcode上做过类似的题目。
多线程下载类
这个类就没有什么比较复杂的处理了,主要就是读取待下载字节的同步,获得文件大小,下载文件Range。(在http(s)中,文件下载是分了很多次请求的,每次请求的headers中带上Range可以指明需要下载文件的哪一部分,格式为: Range: bytes=1024-2048 )获取文件大小可以先发一个Range : bytes=0-0 这样的请求过去,响应中的header会带有content-Range的头部(如果他需要的话,一般都会有),这个值就是文件的总大小。
多线程同步,就用普通的锁就好了(threading.Lock类),文件内容的写入和配置的读取都需要。写入到文件的指定位置用的是file的seek函数,不清楚的可以百度一下,就跟c里面的移动文件位置指针一样。
对了,外面还有一个创建这些下载线程的守护线程。
总结
这样子,大文件的多线程下载和中断续传功能就得以实现了。有兴趣的话可以自己写一下,挺有意思的。需要参考的话-https://github.com/HBertram/python-grab