python百餘行代碼能做什麼?能實現一個類IDM下載程序!

1.前言

很久沒寫博客了。某天在b站上面看到的使用python的多線程文件IO操作機制,突發奇想來實現下

人們總有這樣的困擾,使用有名的工具下載大文件的時候,總會限速(比如某度雲),使用迅雷下載BT文件的時候又不得不被廣告困擾。使用IDM這種無限制下載軟件的時候 ,又會彈出各種註冊彈窗的問題。故筆者這裏使用python的多線程文件IO下載,百行代碼左右實現一個快速下載工具。

2.環境

  • win10
  • python3.6
  • Pycharm2017

3.效果

使用python實現的類IDM下載器,具有不限制網速(取決於網速帶寬),以及多線程下載的特點。

在這裏插入圖片描述

4.實現步驟

4.1 導包

實現這個功能主要用到的是python中的Request庫,以及線程處理庫,基於python對文件IO操作的友好性,使用較少的代碼實現了上述下載的功能。

import os
import time
import sys
from requests import get,head
from concurrent.futures import ThreadPoolExecutor,wait

如果運行出錯。可能需要安裝相關包

4.2 初始化

新定義一個下載類,對其進行初始化,聲明下載鏈接,線程數以及另存爲的文件名

    def __init__(self, url, nums, file):
        self.url = url      # url鏈接
        self.num = nums     # 線程數
        self.name = file    # 文件名字
        self.getSize = 0    # 大小
        self.info = {
            'main': {
                'progress': 0,						# 主線程狀態
                'speed': ''							# 下載速度
            },
            'sub': {
                'progress': [0 for i in range(nums)],    # 子線程狀態
                'stat': [1 for i in range(nums)]         # 下載狀態
            }
        }
        r = head(self.url)
        # 狀態碼顯示302則迭代尋找文件
        while r.status_code == 302:
            self.url = r.headers['Location']
            print("此url已重定向至{}".format(self.url))
            r = head(self.url)
        self.size = int(r.headers['Content-Length'])
        print('該文件大小爲: {} bytes'.format(self.size))
  • HTTP中返回302表示文件重定向,可能出現的原因是下載文件鏈接失效,這時候主需要重新選擇一個可以在遊覽器中就可以下載的文件連接即可

4.3 獲取下載

定義一個下載方法,主要使用Request庫,以及多線程的方式,由於python對文件I/O爲密集型操作,較爲友好,因此使用此種方式爲直接獲取的形式。

    def down(self, start, end, thread_id, chunk_size = 10240):
        raw_start = start
        for _ in range(10):
            try:
                headers = {'Range': 'bytes={}-{}'.format(start, end)}
                r = get(self.url, headers=headers, timeout=10, stream=True)	# 獲取
                print(f"線程{thread_id}連接成功")
                size = 0
                with open(self.name, "rb+") as fp:
                    fp.seek(start)
                    for chunk in r.iter_content(chunk_size=chunk_size):
                        if chunk:
                            self.getSize += chunk_size
                            fp.write(chunk)
                            start += chunk_size
                            size += chunk_size
                            progress = round(size / (end - raw_start) * 100, 2)
                            self.info['sub']['progress'][thread_id - 1] = progress
                            self.info['sub']['stat'][thread_id - 1] = 1
                return
            except Exception as error:
                print(error)
                self.down(start, end, thread_id)
        print(f"{start}-{end}, 下載失敗")
        self.info['sub']['start'][thread_id - 1] = 0

4.4 終端打印

定義一個顯示方法,在終端對其但打印下載信息,包括主線程下載速度以及子線程線程數和狀態

    def show(self):
        while True:
            speed = self.getSize
            time.sleep(0.5)
            speed = int((self.getSize - speed) * 2 / 1024)	
            if speed > 1024:
                speed = f"{round(speed / 1024, 2)} M/s"
            else:
                speed = f"{speed} KB/s"
            progress = round(self.getSize / self.size * 100, 2)
            self.info['main']['progress'] = progress
            self.info['main']['speed'] = speed
            print(self.info)
            if progress >= 100:
                break		# end

4.5 執行

定義運行方法,包括對文件IO的處理以及下載判斷

    def run(self):
        # 創建一個要下載的文件
        fp = open(self.name, 'wb')
        print(f"正在初始化下載文件: {self.name}")
        fp.truncate(self.size)
        print(f"文件初始化完成")
        start_time = time.time()
        fp.close()
        part = self.size // self.num		# 整除
        pool = ThreadPoolExecutor(max_workers=self.num + 1)
        futures = []
        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, i + 1))
        futures.append(pool.submit(self.show))
        print(f"正在使用{self.num}個線程進行下載...")
        wait(futures)
        end_time = time.time()
        speed = int(self.size / 1024 / (end_time - start_time))
        if speed > 1024:
            speed = f"{round(speed / 1024, 2)} M/s"
        else:
            speed = f"{speed} KB/s"
        print(f"{self.name}下載完成,平均速度: {speed}")

入口函數如下

if __name__ == '__main__':
    debug = 1           # 測試情況
    if debug:
        url = 'http://119.6.237.61:8899/w10.xitongxz.net/202007/DEEP_GHOST_WIN10_X64_V2020_07.iso'
        down = Dowmloader(url, 8, os.path.basename(url))
    else:
        # 命令行執行方式
        url = sys.argv[1]       # 下載鏈接
        file = sys.argv[2]      # 默認保存在項目路徑下,文件的名字以文件格式結尾
        thread_num = int(sys.argv[3])  # 使用的線程數量
        down = Dowmloader(url, thread_num, file)
    down.run()

5. 運行方法與效果

5.1 運行方法

運行程序的方式有兩種,

  • 直接運行
  • 使用命令行運行

直接運行就是直接在IDE中執行程序。輸出爲py文件,如上圖所示,另外一種就是使用命令行參數的方式,可自行選擇仙下載線程和文件名

比如我們的python文件名字爲test_demo.py 要下載一個.iso文件,在工程目錄下執行如下命令,我這裏要下載的文件是一個iso鏡像系統,

image-20200704000316143

將其鏈接拷貝出來

# python .py文件 鏈接 文件名 線程數
python test_demo.py '鏈接' test.iso 8

執行

5.2 效果

使用這種方式,將取決於自己網速,而不再受限制了,有點香啊

在這裏插入圖片描述
在這裏插入圖片描述

6.思考與不足

6. 1 思考

python中對任務的執行方法有兩種

  • CPU計算密集型:指CPU計算佔主要的任務,CPU一直處於滿負荷狀態。比如在一個很大的列表中查找元素,複雜的加減乘除等。
  • IO密集型:是指磁盤IO、網絡IO佔主要的任務,計算量很小。比如請求網頁、讀寫文件等。當然我們在Python中可以利用time中的sleep達到IO密集型任務的目的

因此,在使用爬蟲等網頁操作的時候,多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費,而開啓多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執行效率)。所以python的多線程對IO密集型代碼比較友好,而使用多線程時,考慮到計算機硬件結構的負擔,線程保持在8個左右最爲合適,設置較多的線程加大CPU的負擔。

6.2 不足

在執行這個程序的時候,有些功能暫時未能實現,如

  • 該程序暫時不能實現斷點續傳的功能,只能將程序一直運行下去,知道文件下載完畢
  • 暫時未能將其封裝爲一個下載界面,提供可視化的下載監控,可能後期會實現下

7. 附錄

7.1 程序下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章