问题
我想从ftp服务器并行下载> 100.000文件(使用线程)。我之前使用urlretrieve尝试了解答here,但这给了我以下错误:URLError(OSError(24, 'Too many open files'))
。显然这个问题是一个错误(找不到引用了),所以我尝试将urlopen
与shutil
结合使用,然后将其写入我可以关闭自己的文件,如上所述{{3} }。这似乎工作正常,但后来我又得到了同样的错误:URLError(OSError(24, 'Too many open files'))
。我认为只要写入文件不完整或失败,with
语句就会导致文件关闭,但是看到文件仍然保持打开状态,最终会导致脚本暂停。
问题
如何防止此错误,即确保每个文件都关闭?
代码
import csv
import urllib.request
import shutil
from multiprocessing.dummy import Pool
def url_to_filename(url):
filename = 'patric_genomes/' + url.split('/')[-1]
return filename
def download(url):
url = url.strip()
try:
with urllib.request.urlopen(url) as response, open(url_to_filename(url), 'wb') as out_file:
shutil.copyfileobj(response, out_file)
except Exception as e:
return None, e
def build_urls(id_list):
base_url = 'ftp://some_ftp_server/'
urls = []
for some_id in id_list:
url = base_url + some_id + '/' + some_id + '.fna'
print(url)
urls.append(url)
return urls
if __name__ == "__main__":
with open('full_data/genome_ids.txt') as inFile:
reader = csv.DictReader(inFile, delimiter = '\t')
ids = {row['some_id'] for row in reader}
urls = build_urls(ids)
p = Pool(100)
print(p.map(download, urls))
答案 0 :(得分:2)
您可以尝试使用contextlib
关闭文件:
import contextlib
[ ... ]
with contextlib.closing(urllib.request.urlopen(url)) as response, open(url_to_filename(url), 'wb') as out_file:
shutil.copyfileobj(response, out_file)
[ ... ]
根据docs:
contextlib.closing(thing)
Return a context manager that closes thing upon completion of the block. [ ... ] without needing to explicitly close page. Even if an error occurs, page.close() will be called when the with block is exited.
***解决方法是提高Linux操作系统的开放文件限制。检查您当前的打开文件限制:
ulimit -Hn
在/etc/sysctl.conf
文件中添加以下行:
fs.file-max = <number>
其中<number>
是您要设置的打开文件的新上限。
关闭并保存文件。
sysctl -p
以便更改生效。
答案 1 :(得分:1)
我相信您创建的文件处理程序不会被系统及时处理,因为关闭连接需要一些时间。所以你最终会很快使用所有免费文件处理程序(包括网络套接字)。
您要为每个文件设置FTP连接。这是一种不好的做法。更好的方法是打开5-15个连接并重用它们,通过现有套接字下载文件,而不会为每个文件进行初始FTP握手。请参阅this post以供参考。
P.S。另外,正如@Tarun_Lalwani所提到的,创建一个包含超过〜1000个文件的文件夹并不是一个好主意,因为它会降低文件系统的速度。
答案 2 :(得分:0)
如何防止此错误,即确保每个文件都关闭?
要防止错误,您需要increase open file limit,或者更合理的是,减少线程池中的并发性。连接和文件关闭由上下文管理器正确完成。
您的线程池有100个线程,并打开至少200个句柄(一个用于FTP连接,另一个用于文件)。合理的并发性大约是10到30个线程。
这里的简化复制表明代码没问题。将一些内容放在当前目录的somefile
中。
test.py
#!/usr/bin/env python3
import sys
import shutil
import logging
from pathlib import Path
from urllib.request import urlopen
from multiprocessing.dummy import Pool as ThreadPool
def download(id):
ftp_url = sys.argv[1]
filename = Path(__name__).parent / 'files'
try:
with urlopen(ftp_url) as src, (filename / id).open('wb') as dst:
shutil.copyfileobj(src, dst)
except Exception as e:
logging.exception('Download error')
if __name__ == '__main__':
with ThreadPool(10) as p:
p.map(download, (str(i).zfill(4) for i in range(1000)))
然后在同一目录中:
$ docker run --name=ftp-test -d -e FTP_USER=user -e FTP_PASSWORD=pass \
-v `pwd`/somefile:/srv/dir/somefile panubo/vsftpd vsftpd /etc/vsftpd.conf
$ IP=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' ftp-test`
$ curl ftp://user:pass@$IP/dir/somefile
$ python3 client.py ftp://user:pass@$IP/dir/somefile
$ docker stop ftp-test && docker rm -v ftp-test