使用“request”下载数百个文件会“暂停在中间”



我有一个问题,我使用requests从url下载文件的代码没有明显的原因而暂停。当我启动脚本时,它会下载几百个文件,但后来它就停在了某个地方。如果我在浏览器中手动尝试url,则图像加载没有问题。我也尝试过使用urllib.retrieve,但遇到了同样的问题。我在OSX上使用Python 2.7.5。

跟随你找到

  • 我使用的代码
  • 堆栈跟踪(dtruss),而程序正在停滞并且
  • 当我ctrl-c在10分钟内没有发生任何事情后的过程时,打印的回溯

代码:

def download_from_url(url, download_path):
    with open(download_path, 'wb') as handle:
        response = requests.get(url, stream=True)
        for block in response.iter_content(1024):
            if not block:
                break
            handle.write(block)
def download_photos_from_urls(urls, concept):
    ensure_path_exists(concept)
    bad_results = list()
    for i, url in enumerate(urls):
        print i, url,
        download_path = concept+'/'+url.split('/')[-1]
        try:
            download_from_url(url, download_path)
            print
        except IOError as e:
            print str(e)
    return bad_result

堆栈跟踪:

My-desk:~ Me$ sudo dtruss -p 708 SYSCALL(args) = return

追溯:

318 http://farm1.static.flickr.com/32/47394454_10e6d7fd6d.jpg
Traceback (most recent call last):
  File "slow_download.py", line 71, in <module>
    if final_path == '':
  File "slow_download.py", line 34, in download_photos_from_urls
    download_path = concept+'/'+url.split('/')[-1]
  File "slow_download.py", line 21, in download_from_url
    with open(download_path, 'wb') as handle:
  File "/Library/Python/2.7/site-packages/requests/models.py", line 638, in generate
    for chunk in self.raw.stream(chunk_size, decode_content=True):
  File "/Library/Python/2.7/site-packages/requests/packages/urllib3/response.py", line 256, in stream
    data = self.read(amt=amt, decode_content=decode_content)
  File "/Library/Python/2.7/site-packages/requests/packages/urllib3/response.py", line 186, in read
    data = self._fp.read(amt)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 567, in read
    s = self.fp.read(amt)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 380, in read
    data = self._sock.recv(left)
KeyboardInterrupt

因此,为了统一所有评论,并提出一个潜在的解决方案:几百次下载后失败的原因有几个——可能是Python内部的原因,例如达到了打开文件句柄的最大数量,也可能是服务器阻止你成为机器人的问题。

您没有共享所有代码,所以说起来有点困难,但至少根据您所展示的内容,在打开要写入的文件时,您正在使用with上下文管理器,所以您不应该在那里遇到问题。在退出循环后,请求对象可能无法正确关闭,但我将在下面向您展示如何处理这一问题。

默认的requests用户代理是(在我的机器上):

python-requests/2.4.1 CPython/3.4.1 Windows/8

因此,想象一下你请求的服务器正在筛选各种类似的UA并限制它们的连接数量并不太不可思议。您之所以能够让代码使用urllib.retrieve,是因为它的UA与请求不同,所以服务器允许它继续处理大约相同数量的请求,然后也将其关闭。

为了解决这些问题,我建议将download_from_url()函数更改为以下内容:

import requests
from time import sleep
def download_from_url(url, download_path, delay=5):
    headers = {'Accept-Encoding': 'identity, deflate, compress, gzip', 
               'Accept': '*/*',
               'Connection': 'keep-alive',
               'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0'}
    with open(download_path, 'wb') as handle:
        response = requests.get(url, headers=headers) # no stream=True, that could be an issue
        handle.write(response.content)
        response.close()
        sleep(delay)

我们不使用stream=True,而是使用默认值False来立即下载请求的全部内容。headers dict包含一些默认值,以及非常重要的'User-Agent'值,在本例中,它恰好是我的UA,通过使用What's MyUserAgent确定。请随意将其更改为您首选浏览器返回的内容。在这里,我不用麻烦地按1KB的块迭代内容,而是一次将整个内容写入磁盘,消除了多余的代码和一些潜在的错误源——例如,如果网络连接出现问题,你可能会暂时有空块,然后出错。我还明确关闭了请求,以防万一。最后,我为函数添加了一个额外的参数delay,使函数在返回之前休眠一定的秒数。我给它一个默认值5,你可以随心所欲(它也接受小数秒的浮点值)。

我没有碰巧有一个大的图像URL列表来测试这一点,但它应该如预期的那样工作。祝你好运

也许缺少池可能会导致连接过多。尝试这样的方法(使用会话):

import requests
session = requests.Session()
def download_from_url(url, download_path):
    with open(download_path, 'wb') as handle:
        response = session.get(url, stream=True)
        for block in response.iter_content(1024):
            if not block:
                break
            handle.write(block)
def download_photos_from_urls(urls, concept):
    ensure_path_exists(concept)
    bad_results = list()
    for i, url in enumerate(urls):
        print i, url,
        download_path = concept+'/'+url.split('/')[-1]
        try:
            download_from_url(url, download_path)
            print
        except IOError as e:
            print str(e)
    return bad_result

最新更新