【问题标题】:Download files from url parallely in python在python中从url并行下载文件
【发布时间】:2014-01-22 07:25:30
【问题描述】:

我在数据库中有一些我想并行下载的链接。我试着连续做,但花了太多时间。我有大约 1877 个链接。

我尝试使用此代码并行运行下载,但它引发错误:失败:'tuple' object has no attribute 'read'

#!/usr/bin/env python

import urllib
from stream import ThreadPool

URLs = [
  'http://www.cnn.com/',
  'http://www.bbc.co.uk/',
  'http://www.economist.com/',
  'http://nonexistant.website.at.baddomain/',
  'http://slashdot.org/',
  'http://reddit.com/',
  'http://news.ycombinator.com/'
 ]

def retrieve(urls):
    for url in urls:
    print url,' '
    res = urllib.urlretrieve(url).read()
    yield url, res

if __name__ == '__main__':
    retrieved = URLs >> ThreadPool(retrieve, poolsize=7)
    for url, content in retrieved:
        print '%r is %d bytes' % (url, len(content))
    for url, exception in retrieved.failure:
        print '%r failed: %s' % (url, exception)

我也试过了:

import urllib
import tldextract
from multiprocessing.pool import ThreadPool

URLs = [
  'http://www.cnn.com/',
  'http://www.bbc.co.uk/',
  'http://www.economist.com/',
  'http://nonexistant.website.at.baddomain/',
   'http://slashdot.org/',
  'http://reddit.com/',
  'http://news.ycombinator.com/'
 ]


def dwld(url):
  print url
  res = urllib.urlopen(url).read() 
  filename = tldextract.extract(url)
  with open(filename.domain, 'wb') as fh:
     fh.write(res)
  return url 

pool = ThreadPool(processes = 4)
pool.map(dwld, URLs)

给我 回溯(最近一次通话最后): 文件“dwld_thread.py”,第 26 行,在 pool.map(dwld,网址) 文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/multiprocessing/pool.py”,第 148 行,在地图中 return self.map_async(func, iterable, chunksize).get() 文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/multiprocessing/pool.py”,第 422 行,在 get 提高自我价值 IOError: [Errno socket error] [Errno 8] nodename or servname provided, or not known

【问题讨论】:

  • 您使用的“流”库是什么?
  • 我认为您的第二个版本的问题是您想调用pool.close()pool.join()(或者,更好的是,只需使用with 语句,如果这适用于您的版本......我忘记多处理 Pools 何时成为上下文管理器)。

标签: python


【解决方案1】:

我不知道您使用的 stream.ThreadPool 是什么,或者它的 API 是什么……但问题很明显:

res = urllib.urlretrieve(url).read()

如果您查看urlretrieve 的文档:

返回一个元组(文件名,标题),其中文件名是可以在其中找到对象的本地文件名……

你显然不能打电话给read。如果您想使用此旧版 API 下载到本地文件,然后读取该文件,您可以

filename, headers = urllib.urlretrieve(url)
with open(filename) as f:
    res = f.read()

但是为什么呢?只需使用urllib2.urlopen,它“返回一个带有两个附加方法的类文件对象”,因此您可以在其上调用read,您不会创建临时文件,也不会使用旧文件多年来没有人维护的设计不正确的功能。


但是 Python 在标准库中内置了一个不错的 ThreadPoolExecutor。如果你看看他们展示给你的第一个例子,这正是你想要做的。

很遗憾,您使用的是 Python 2.x,它没有 concurrent.futures 模块。幸运的是,PyPI 上有一个 backport 可用于 2.5+。

Python 也有multiprocessing.dummy.Pool(也可以在未记录但可能更易读的名称multiprocessing.ThreadPool 下找到)。但是,如果您愿意为某些您显然不确定如何使用并且我从未听说过的模块离开标准库,我猜您使用futures 不会有任何问题。所以:

import futures
import urllib2

URLs = [
  'http://www.cnn.com/',
  'http://www.bbc.co.uk/',
  'http://www.economist.com/',
  'http://nonexistant.website.at.baddomain/',
  'http://slashdot.org/',
  'http://reddit.com/',
  'http://news.ycombinator.com/'
 ]

def load_url(url):
    return urllib2.urlopen(url).read()

if __name__ == '__main__':
    with futures.ThreadPoolExecutor(max_workers=7) as executor:
        fmap = dict((executor.submit(load_url, url), url) for url in URLs)
        for f in futures.as_completed(fmap):
            url = fmap[f]
            try:
                content = f.result()
            except Exception as exception:
                print '%r failed: %s' % (url, exception)
            else:
                print '%r is %d bytes' % (url, len(content))

【讨论】:

  • 在这一行抛出错误:fmap = {executor.submit(load_url, url): url for url in URLS}
  • fmap = {executor.submit(load_url, url): url 中的 url} ^ SyntaxError: invalid syntax
  • @wannaC:啊,Python 2.6 我敢打赌,对吧?所以你没有dict理解。已编辑(但未经测试;请告诉我)。真的,futures 的全部内容:URL 的东西过于复杂;我只是这样做以反映文档中的示例……
  • 是的,它现在是 2.6 代码。它还要求我使用 concurrent.futures 而不是期货。如果我想检查链接是否是 pdf 的,只有一个问题我应该在哪里检查?
  • @wannaC:您想根据 URL 上的扩展名进行检查,通过查看返回内容的文件魔法,尝试使用 PDF 库打开它,还是什么?您可以在read 数据后立即在后台线程上执行任何这些操作,或者在您正在执行len(content) 的主线程上执行任何操作。唯一的限制是,如果需要大量 CPU 工作,请不要在后台线程中执行。
【解决方案2】:

urllib.urlretrieve(url).read() 应该是urllib.urlopen(url).read()

【讨论】:

    【解决方案3】:
    from threading import *
    from time import sleep
    # if Python2:
    import urllib
    # if Python3:
    # import urllib.request
    
    URLs = [
      'http://www.cnn.com/',
      'http://www.bbc.co.uk/',
      'http://www.economist.com/',
      'http://nonexistant.website.at.baddomain/',
      'http://slashdot.org/',
      'http://reddit.com/',
      'http://news.ycombinator.com/'
     ]
    
    class worker(Thread):
        def __init__(self, link):
            Thread.__init__(self)
            self.link = link
            self.start()
        def run(self):
            # if Python2:
            res = urllib.urlopen(url).read() # as mentioned by @DhruvPathak
            # if Python3:
            # res = urllib.request.urlopen(url).read()
            with open(url, 'rb') as fh:
                fh.write(res) # store fetched data in a file called <link>
    
    for url in urls:
        while len(enumerate()) > 500:
            sleep(0.25)
        worker(url)
    
    while len(enumerate()) > 1:
        sleep(0.25) # wait for all threads to finish
    

    【讨论】:

    • 不应该打开包含 wb 而不是 rb?它也只下载最后一个网址而不是所有网址。
    • 另外,如果您想将其扩展到 5000 个 URL,您不会想要生成 5000 个线程。 OP 正在尝试使用可以解决该问题的线程池;为什么要让他的代码变得更糟?
    • 另外,如果你想等待所有线程完成,一遍又一遍地枚举线程是一种非常糟糕的方法,除非你试图确保你烧掉 100 % CPU 并导致可怕的 GIL 冲突。这就是join 的用途。只需执行workers = [worker(url) for url in urls],然后执行for worker in workers: worker.join()
    • 它会尝试使用http://www.cnn.com/ 之类的文件名保存文件,这在大多数平台上都无法正常工作。 (你可能会得到一个关于没有像http: 这样的相对路径的错误。)
    • @abarnert Haters 会讨厌,是的,这段代码并不完整。我很少去 Stack Overflow 为人们做所有的工作,有些事情应该留给用户自己去解决。但确实应该有一个线程池,因为有很多链接,所以我添加了一个。 enumerate() 不征税,可用于此目的。是的,文件名会很糟糕,谁在他们的头脑中会称他们为http:/google.com/,我知道我不会..但是用户有他自己的偏好,所以我会让用户决定他的命名约定,因为这不是 OP 问题的一部分。
    【解决方案4】:

    使用multiprocessing 怎么样?

    示例代码:

    #! /usr/bin/env python
    
    # -*- coding: utf-8 -*-
    
    
    import sys
    import urllib
    from multiprocessing import Pool
    
    import os
    
    POOL = 8
    PDFS_DOWNLOAD_DIR = 'pdfs'
    PDF_LINKS = sys.argv[1]
    
    
    class DownloadFiles(object):
        def __init__(self):
            self.pdf_links = self.read_links_from_file()
            self.create_download_dir()
    
        def create_download_dir(self):
            try:
                if not os.path.exists(PDFS_DOWNLOAD_DIR):
                    os.makedirs(PDFS_DOWNLOAD_DIR)
            except IOError as e:
                exit()
    
        def read_links_from_file(self):
            try:
                with open(PDF_LINKS, 'r') as f:
                    return list(set([x.strip() for x in f]))
            except (IndexError, IOError) as e:
                exit()
    
        def get_file(self, link):
    
            filename = link.split('/')[-2]
    
            print('Downloading file --> "{filename}"'.format(
                filename=filename
            ))
    
            urllib.urlretrieve(link, filename='{pdfs_data}/{filename}'.format(
                pdfs_data=PDFS_DOWNLOAD_DIR,
                filename=filename
            ))
    
        def download(self):
    
            pool = Pool(POOL)
            pool.map(self.get_file, self.pdf_links)
    
            pool.close()
            pool.join()
    
            print('\nSuccessfully downloaded files from given source!\n')
    
    
    d = DownloadFiles()
    d.download()
    

    【讨论】:

      猜你喜欢
      • 2017-12-25
      • 2019-11-18
      • 1970-01-01
      • 2014-02-11
      • 2017-01-23
      • 2021-10-27
      • 1970-01-01
      • 1970-01-01
      • 2023-03-03
      相关资源
      最近更新 更多