加速urllib.urlretrieve

3

我正在从互联网上下载图片,结果发现需要下载大量的图片。我使用以下代码片段的一个版本(实际上是循环遍历我打算下载的链接并下载图片):

import urllib
urllib.urlretrieve(link, filename)

我每15分钟下载大约1000张图片,根据我需要下载的数量来看,这速度非常慢。为了提高效率,我设置了每5秒一次的超时限制(但仍有很多下载需要更长时间)。
import socket
socket.setdefaulttimeout(5)

除了在计算机集群上运行作业以并行下载外,是否有更快/更高效的方法来下载图片?


我认为这可能会激发你的灵感:http://stackoverflow.com/questions/1628766/python-package-for-multi-threaded-spider-w-proxy-support - snahor
1
请查看像Scrapy或Twisted这样的软件包。Scrapy基于Twisted,但使用起来更容易一些。两者都提供非阻塞API。通过这种方式,您可以将多个文件作为一个下载。请友好地限制从同一服务器并行下载的数量 - Tammo Heeren
嗨Tammo,非常感谢。顺便说一下,我发现了一篇优秀的文章,比较了scrappy和urllib2:http://www.scrapinginsider.com/2016/01/scrapy-urllib2-requests-beautifulsoup-lxml.html - 另外,我相信多线程可以显著提高上述代码的性能。一旦我弄清楚如何做到这一点,我会发布一个答案。 - Alejandro Simkievich
2个回答

3

我之前的代码非常天真,因为我没有利用多线程。显然需要等待URL请求响应,但计算机在代理服务器响应时也可以进行其他请求。

通过以下调整,您可以将效率提高10倍 - 并且还有进一步提高效率的方法,使用像scrapy这样的软件包。

要添加多线程,请使用multiprocessing软件包执行以下操作:

1)将URL检索封装在一个函数中:

import import urllib.request

def geturl(link,i):
try:
    urllib.request.urlretrieve(link, str(i)+".jpg")
except:
    pass

2) 然后创建一个集合,包含您想要下载的图片的所有网址和名称:

urls = [url1,url2,url3,urln]
names = [i for i in range(0,len(urls))]

3) 从 multiprocessing 包导入 Pool 类并使用该类创建一个对象(显然,在实际程序中,您将在第一行代码中包含所有导入):

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(100)

然后使用pool.starmap()方法,并传递函数及其参数。

results = pool.starmap(geturl, zip(links, d))

注意: pool.starmap() 仅适用于 Python 3


如果您只想进行输入而不进行解包,请使用 pool.map - user8491363

0

当程序进入I/O等待状态时,执行会被暂停,以便内核可以执行与I/O请求相关的低级操作(这称为上下文切换),并且直到I/O操作完成后才恢复。

上下文切换是一项相当繁重的操作。它要求我们保存程序的状态(失去CPU级别的任何缓存),并放弃使用CPU。稍后,当我们被允许再次运行时,我们必须花时间在主板上重新初始化程序并准备恢复(当然,所有这些都发生在幕后)。

另一方面,通过并发,我们通常有一个称为“事件循环”的东西在运行,它管理我们的程序中什么时候运行以及何时运行。实质上,事件循环只是需要运行的函数列表。列表顶部的函数被运行,然后是下一个,以此类推。

以下是一个简单的事件循环示例:

from Queue import Queue
from functools import partial

eventloop = None

class EventLoop(Queue):
    def start(self):
        while True:
            function = self.get()
            function()

def do_hello():
    global eventloop
    print "Hello"
    eventloop.put(do_world)

def do_world():
    global eventloop
    print "world"
    eventloop.put(do_hello)

if __name__ == "__main__":
    eventloop = EventLoop()
    eventloop.put(do_hello)
    eventloop.start()

如果以上内容似乎是您可以使用的内容,并且您也想了解gevent, tornado,AsyncIO如何帮助您解决问题,那么请前往您的(大学)图书馆,借阅{{link1:Micha Gorelick和Ian Ozsvald的《高性能Python》}},并阅读pp. 181-202。

注意:上述代码和文本来自提到的书籍


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接