【问题标题】:urllib2 urlopen read timeout/blockurllib2 urlopen 读取超时/阻塞
【发布时间】:2012-11-24 17:43:44
【问题描述】:

最近我正在开发一个小型爬虫,用于在 url 上下载图像。

我在 urllib2 中使用 openurl() 和 f.open()/f.write():

这里是sn-p的代码:

# the list for the images' urls
imglist = re.findall(regImg,pageHtml)

# iterate to download images
for index in xrange(1,len(imglist)+1):
    img = urllib2.urlopen(imglist[index-1])
    f = open(r'E:\OK\%s.jpg' % str(index), 'wb')
    print('To Read...')

    # potential timeout, may block for a long time
    # so I wonder whether there is any mechanism to enable retry when time exceeds a certain threshold
    f.write(img.read())
    f.close()
    print('Image %d is ready !' % index)

在上面的代码中,img.read()可能会阻塞很长时间,希望在这个问题下做一些重试/重新打开图片url的操作。

我还关心上面代码的效率方面,如果要下载的图像数量有点大,使用线程池下载它们似乎更好。

有什么建议吗?提前致谢。

附言我发现 img 对象上的 read() 方法可能会导致阻塞,所以单独在 urlopen() 中添加超时参数似乎没有用。但我发现文件对象没有 read() 的超时版本。对此有什么建议吗?非常感谢。

【问题讨论】:

    标签: python multithreading web-crawler urllib2


    【解决方案1】:

    urllib2.urlopen 有一个 timeout 参数,用于所有阻塞操作(连接建立等)

    这个 sn-p 取自我的一个项目。我使用线程池一次下载多个文件。它使用urllib.urlretrieve,但逻辑是一样的。 url_and_path_list(url, path) 元组的列表,num_concurrent 是要生成的线程数,如果文件系统中已经存在文件,skip_existing 会跳过文件的下载。

    def download_urls(url_and_path_list, num_concurrent, skip_existing):
        # prepare the queue
        queue = Queue.Queue()
        for url_and_path in url_and_path_list:
            queue.put(url_and_path)
    
        # start the requested number of download threads to download the files
        threads = []
        for _ in range(num_concurrent):
            t = DownloadThread(queue, skip_existing)
            t.daemon = True
            t.start()
    
        queue.join()
    
    class DownloadThread(threading.Thread):
        def __init__(self, queue, skip_existing):
            super(DownloadThread, self).__init__()
            self.queue = queue
            self.skip_existing = skip_existing
    
        def run(self):
            while True:
                #grabs url from queue
                url, path = self.queue.get()
    
                if self.skip_existing and exists(path):
                    # skip if requested
                    self.queue.task_done()
                    continue
    
                try:
                    urllib.urlretrieve(url, path)
                except IOError:
                    print "Error downloading url '%s'." % url
    
                #signals to queue job is done
                self.queue.task_done()
    

    【讨论】:

      【解决方案2】:

      当你用urllib2.urlopen()创建tje连接时,你可以给一个超时参数。

      如文档中所述:

      可选的 timeout 参数指定超时时间(以秒为单位) 阻塞操作,如连接尝试(如果未指定,则 将使用全局默认超时设置)。这实际上只有效 用于 HTTP、HTTPS 和 FTP 连接。

      这样,您将能够管理最长等待时间并捕获引发的异常。

      【讨论】:

      • 非常感谢,我会试试这个。
      • 附注我发现 img 对象上的 read() 方法可能会导致阻塞,所以单独在 urlopen() 中添加超时参数似乎没有用。但我发现文件对象没有 read() 的超时版本。对此有什么建议吗?非常感谢。
      • @destiny1020 这个问题有什么好的解决方案吗?我在 read() 上遇到了导致我的脚本挂起的块。
      • 如果您在找到“读取被阻止”问题的答案之前找到此评论。见:stackoverflow.com/questions/811446/…
      【解决方案3】:

      我抓取大量文档的方法是让批处理器抓取和转储恒定大小的块。

      假设您要抓取一组已知的文档,例如 100K 文档。您可以有一些逻辑来生成固定大小的 1000 个文档块,这些文档将由线程池下载。抓取整个块后,您可以在数据库中进行批量插入。然后继续处理另外 1000 个文档等等。

      采用这种方法可以获得的好处:

      • 您可以利用线程池加快抓取速度。

      • 它在某种意义上是容错的,你可以从上次失败的块继续。

      • 您可以根据优先级生成块,即首先抓取重要文档。因此,如果您无法完成整个批次。重要的文件得到处理,不太重要的文件可以在下次运行时提取。

      【讨论】:

        【解决方案4】:

        一个看起来有效的丑陋黑客。

        import os, socket, threading, errno
        
        def timeout_http_body_read(response, timeout = 60):
            def murha(resp):
                os.close(resp.fileno())
                resp.close()
        
            # set a timer to yank the carpet underneath the blocking read() by closing the os file descriptor
            t = threading.Timer(timeout, murha, (response,))
            try:
                t.start()
                body = response.read()
                t.cancel()
            except socket.error as se:
                if se.errno == errno.EBADF: # murha happened
                    return (False, None)
                raise
            return (True, body)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-11
          • 2012-03-07
          • 2023-03-15
          • 1970-01-01
          • 2013-04-07
          相关资源
          最近更新 更多