簡體   English   中英

在python中下載超過1000個文件

[英]Downloading over 1000 files in python

因此,也許從我的代碼開始:

def download(fn, filename, index):
    urllib.request.urlretrieve(fn,
                     os.path.join('music', re.sub('[%s]' % ''.join(CHAR_NOTALLOWED), '', filename) + '.mp3'))
    print(str(index) + '# DOWNLOADED: ' + filename)

for index, d in enumerate(found):
    worker = Thread(target=download, args=(found[d], d, index))
    worker.setDaemon(True)
    worker.start()
worker.join()

我的問題是,當我嘗試下載1000個以上的文件時,總是會收到此錯誤,但我不知道為什么:

Traceback (most recent call last):
  File "E:/PythonProject/1.1/mp3y.py", line 238, in <module>
    worker.start()
  File "E:\python34\lib\threading.py", line 851, in start
    _start_new_thread(self._bootstrap, ())
RuntimeError: can't start new thread

我嘗試使用隊列,但遇到了相同的錯誤....我想將此線程分為一部分,但我不知道如何:O

簡潔版本:

with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor:
    for index, d in enumerate(found):
        executor.submit(download, found[d], d, index)

而已; 一次微不足道的更改,並且比現有代碼少兩行,您就完成了。


那么,您現有的代碼有什么問題? 一次啟動1000個線程始終是一個壞主意。*一旦超過幾十個線程,您將增加的調度程序和上下文切換開銷要比並發節省更多。

如果您想知道為什么它在1000左右失敗,那可能是因為一個庫在舊版本的Windows上運行,**,或者是因為您的堆棧空間不足了,***。 但是無論哪種方式,都沒有關系。 正確的解決方案是不要使用太多線程。

通常的解決方案是使用線程池-啟動大約8-12個線程,****並讓它們提取URL以從隊列中下載。 你可以建立這個自己,或者你可以使用concurrent.futures.ThreadPoolExecutormultiprocessing.dummy.Pool附帶STDLIB。 如果您查看文檔中的主要ThreadPoolExecutor示例 ,它幾乎可以完全滿足您的要求。 實際上,您想要的甚至更簡單,因為您不在乎結果。


附帶說明一下,您的代碼中還有另一個嚴重的問題。 如果守護線程,則不允許join它們。 另外,您僅嘗試加入創建的最后一個,但絕不能保證最后一個完成。 同樣,首先要守護下載線程可能不是一個好主意,因為當您的主線程完成時(在等待一個任意選擇的下載完成之后),其他線程可能會被打斷並留下部分文件。

另外,如果想守護進程線程,最好的辦法是通過daemon=True給構造函數。 如果您需要在創建后執行此操作,只需執行t.daemon = True 僅在需要向后兼容Python 2.5時才調用不setDaemon使用的setDaemon函數。


*我想我不應該總是這樣說,因為在2025年,要利用您成千上萬個慢速內核,這將是日常工作。 但是在2014年,在普通筆記本電腦/台式機/服務器硬件上,情況總是很糟糕。

**較舊版本的Windows(至少NT 4)在接近1024個線程時會出現各種奇怪的錯誤,因此許多線程庫僅拒絕創建1000個以上的線程。 盡管這里似乎不是這種情況,因為Python只是調用了Microsoft自己的包裝函數_beginthreadex ,但沒有這樣做。

***默認情況下,每個線程獲得1MB的堆棧空間。 並且在32位應用程序中,有最大的總堆棧空間,我假定您的Windows版本默認為1GB。 您可以為每個線程自定義堆棧空間,也可以自定義總進程堆棧空間,但是Python既不自定義,也幾乎不自定義任何其他應用程序。

****除非您的下載全部來自同一服務器,否則這種情況下,您最多可能需要4個,如果不是您的服務器,則實際上多於2個通常被認為是不禮貌的。 為何還要8-12? 憑經驗證明,很久以前就進行了很好的測試。 它可能不再是最佳的,但對於大多數用途而言,它可能已經足夠接近了。 如果您確實需要提高性能,可以使用不同的數字進行測試。

通常,所允許的最大線程數是有限制的。 根據您的系統,這個數目可能在幾十到數千之間,但是考慮到您打算下載的文件數量,不要指望可以創建相同數量的線程。

通常不要同時啟動1000個以上的線程,每次嘗試下載一個文件時都不是一個好主意。 您的連接將立即阻塞,這比一次下載幾個文件的效率低得多,此外,它浪費了很多服務器資源,因此被認為不易社交。

在一個情況下使用這樣的模式來創建一個小的工作線程 ,每個投票方式的queue.Queue對於要下載的文件,然后下載一個文件,然后輪詢下一個文件的隊列。 現在,主程序可以從原始列表中饋入該隊列,調度文件下載,直到所有下載完成。


此規則的一個明顯例外是,如果您是從人為地限制下載速度的站點下載文件。 尤其是視頻門戶網站因此而聞名。 在這種情況下,使用大量線程可能是適當的。 在一種情況下,從dailymotion下載時,我發現許多20–30個線程最適合我。

使用隊列可以工作,但是您必須限制創建的輔助線程的數量。 這是使用100個工作人員和一個Queue來處理1000個工作項目的代碼:

import Queue
from threading import Thread

def main():
  nworkers = 100
  q = Queue.Queue(1000+nworkers)
  # add the work
  for i in range(1000):
    q.put(i)
  # add the stop signals
  for i in range(nworkers):
    q.put(-1)
  # create and start up the threads
  workers = []
  for wid in range(nworkers):
    w = Thread(target = dowork, args = (q, wid))
    w.start()
    workers.append(w)
  # join all of the workers
  for w in workers: w.join()
  print "All done!"

def dowork(q, wid):
  while True:
    j = q.get()
    if j < 0:
      break
    else:
      print "Worker", wid, "processing item", j
  print "Worker", wid, "exiting"

if __name__ == "__main__":
  main()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM