Python中多线程文件下载与Shell中更新下载进度

1 投票
2 回答
986 浏览
提问于 2025-04-18 09:45

为了学习多线程文件下载,我写了这段简单的代码:

import urllib2
import os
import sys
import time
import threading

urls = ["http://broadcast.lds.org/churchmusic/MP3/1/2/nowords/271.mp3",
"http://s1.fans.ge/mp3/201109/08/John_Legend_So_High_Remix(fans_ge).mp3",
"http://megaboon.com/common/preview/track/786203.mp3"]

url = urls[1]

def downloadFile(url, saveTo=None):
    file_name = url.split('/')[-1]
    if not saveTo:
        saveTo = '/Users/userName/Desktop'
    try:
        u = urllib2.urlopen(url)
    except urllib2.URLError , er:
        print("%s" % er.reason)
    else:

        f = open(os.path.join(saveTo, file_name), 'wb')
        meta = u.info()
        file_size = int(meta.getheaders("Content-Length")[0])
        print "Downloading: %s Bytes: %s" % (file_name, file_size)
        file_size_dl = 0
        block_sz = 8192
        while True:
            buffer = u.read(block_sz)
            if not buffer:
                break

            file_size_dl += len(buffer)
            f.write(buffer)
            status = r"%10d  [%3.2f%%]" % (file_size_dl, file_size_dl * 100. / file_size)
            status = status + chr(8)*(len(status)+1)
            sys.stdout.write('%s\r' % status)
            time.sleep(.2)
            sys.stdout.flush()
            if file_size_dl == file_size:
                print r"Download Completed %s%% for file %s, saved to %s" % (file_size_dl * 100. / file_size, file_name, saveTo,)
        f.close()
        return


def synchronusDownload():
    urls_saveTo = {urls[0]: None, urls[1]: None, urls[2]: None}
    for url, saveTo in urls_saveTo.iteritems():
        th = threading.Thread(target=downloadFile, args=(url, saveTo), name="%s_Download_Thread" % os.path.basename(url))
        th.start()

synchronusDownload()

但是看起来在开始第二个下载时,它会等第一个线程完成后才开始下载下一个文件,这在终端输出中也能看到。

我原本的计划是同时开始所有下载,并打印出正在下载的文件的进度。

任何帮助都非常感谢。谢谢。

2 个回答

1

你的函数实际上是同时运行的。你可以通过在每个函数开始时打印一些内容来验证这一点——当你的程序启动时,会立即打印出3条输出。

发生的情况是,你的前两个文件非常小,以至于在调度程序切换线程之前,它们就已经完全下载好了。试着在你的列表中设置一些更大的文件:

urls = [
"http://www.wswd.net/testdownloadfiles/50MB.zip",
"http://www.wswd.net/testdownloadfiles/20MB.zip",
"http://www.wswd.net/testdownloadfiles/100MB.zip",
]

程序输出:

Downloading: 100MB.zip Bytes: 104857600
Downloading: 20MB.zip Bytes: 20971520
Downloading: 50MB.zip Bytes: 52428800
Download Completed 100.0% for file 20MB.zip, saved to .
Download Completed 100.0% for file 50MB.zip, saved to .
Download Completed 100.0% for file 100MB.zip, saved to .
2

这是一个常见的问题,通常会按照以下步骤来解决:

1.) 使用 Queue.Queue 来创建一个包含你想要访问的所有网址的队列。

2.) 创建一个类,让它继承自 threading.Thread。这个类应该有一个运行的方法,这个方法会从队列中取出一个网址并获取数据。

3.) 根据你的类创建一个线程池,让这些线程充当“工作者”。

4.) 在队列处理完之前,不要退出程序,直到 queue.join() 完成。

撰写回答