Python社区  »  Python

Python 实现多线程下载器

Python网络爬虫与数据挖掘 • 8 月前 • 155 次点击  

前言

我为什么会想到要写一个下载器呢,实在是被百度云给逼的没招了,之前用 Axel 配合直链在百度云下载视频能达到满速,结果最近两天 Axel 忽然不能用了,于是我就想着要不干脆自己写一个吧,就开始四处查询资料,这就有了这篇博客。

我假设阅读这篇博客的你已经对以下知识有所了解:

  • Python 的文件操作

  • Python 的多线程

  • Python 的线程池

  • Python 的 requests 库

  • HTTP 报文的首部信息

下载

获取文件采用的是 requests 库,该已经封装好了许多 http 请求,我们只需要发送 get 请求,然后将请求的内容写入文件即可:

import requests

r = requests.get('http://files.smashingmagazine.com/wallpapers/july-17/summer-cannonball/cal/july-17-summer-cannonball-cal-1920x1080.png')
with open('wallpaper.png', 'wb') as f:
   f.write(r.content)

随后看看文件夹,那张名为 wallpaper.png 的图片就是我们刚刚下载的。

但是这个功能太简单了,甚至简陋,我们需要多线程并发执行下载各自的部分,然后再汇总。

拆分

为了拆分,首先得知道数据块的大小,HTTP 报文首部提供了这样的信息:

用 head 方法去获取 http 首部信息,再从获取的信息提取出 Content-Length 字段(上文图片大小为 261258 bytes)

import requests

headers = {'Range': 'bytes={}-{}'.format(0, 100000)}
r = requests.get('http://files.smashingmagazine.com/wallpapers/july-17/summer-cannonball/cal/july-17-summer-cannonball-cal-1920x1080.png', headers = headers)
with open('wallpaper.png', 'wb') as f:
   f.write(r.content)

我们得到了图片的前 100001 个字节(Range 的范围是包括起始和终止的),打开 wallpaper.png 你应该能看到一幅“半残”的图。

这样我们里目标更近了一步,继续:

确认线程数(比如 8 个),261258//8 = 32657,前 7 个线程都取 32657 个 bytes,第八个取剩余的

part = size // nums

for i in range(nums):
       start = part * i
       if i == num_thread - 1:   # 最后一块
           end = file_size
       else:
           end = start + part

每个线程获取到的内容按顺序写入文件(file.seek() 调节文件指针)

def down(start, end):
   headers = {'Range': 'bytes={}-{}'.format(start, end)}
   # 这里最好加上 stream=True,避免下载大文件出现问题
   r = requests.get(self.url, headers=headers, stream=True)
   with open(filename, "wb+") as fp:
       fp.seek(start)
       fp.write(r.content)

嘛,线程多了起来就扔到线程池让它来帮我们调度。

封装

功能复杂了,用对象来封装整理一下:

class Downloader(): 
   def __init__(self, url, num, name):
       self.url = url
       self.num = num
       self.name = name
       r = requests.head(self.url)
       self.size = int(r.headers['Content-Length'])

   def down(self, start, end):

       headers = {'Range': 'bytes={}-{}'.format(start, end)}
       r = requests.get(self.url, headers=headers, stream=True)

       # 写入文件对应位置
       with open(self.name, "rb+") as f:
           f.seek(start)
           f.write(r.content)


   def run(self):
       f = open(self.name, "wb")
       f.truncate(self.size)
       f.close()

       futures = []
       part = self.size // self.num
       pool = ThreadPoolExecutor(max_workers = self.num)
       for i in range(self.num):
           start = part * i
           if i == self.num - 1:  
               end = self.size
           else:
               end = start + part - 1
           # 扔进线程池
           futures.append(pool.submit(self.down, start, end))
       wait(futures)

至此,核心功能都完成了,剩下的就是实际体验的优化了。

完整的代码已托管至 GitHub,地址见这里 https://github.com/WincerChan/Py-Downloader

结语

很可惜,我写的这个下载器还是不能下载百度云直链,不过嘛,好多人都说结果不重要,都说重要的是过程,不是么?写这个下载器我也确实学到了许多,至于一开始我是出于什么样的目的?管他呢

作者:Wincer
原文地址:https://blog.itswincer.com/posts/80689c8d/

学习Python和网络爬虫,关注公众号:datanami

近期文章:

  1. Python天坑:while 1比while True更快?

  2. 最想拥有的编程语言排行第一,它到底有什么魔力?

  3. 小白都懂的Python爬虫之网易云音乐下载

  4. 史上最全的 Python 3 类型转换指南


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/qSFULlRcEm
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/25160
 
155 次点击  
分享到微博