0,0
рейтинг
25 января 2014 в 00:24

Разработка → Четыре метода загрузки изображений с веб-сайта с помощью Python

Недавно пришлось по работе написать простенький парсер на питоне, который бы скачивал с сайта изображения (по идее тот же самый парсер может качать не только изображения, но и файлы других форматов) и сохранял их на диске. Всего я нашел в интернете четыре метода. В этой статье я их решил собрать все вместе.

Вот эти методы:

1-ый метод

Первый метод использует модуль urllib (или же urllib2). Пусть имеется ссылка на некое изображение img. Метод выглядит следующим образом:

import urllib

resource = urllib.urlopen(img)
out = open("...\img.jpg", 'wb')
out.write(resource.read())
out.close()


Здесь нужно обратить внимание, что режим записи для изображений — 'wb' (бинарный), а не просто 'w'.

2-ой метод

Второй метод использует тот же самый urllib. В дальнейшем будет показано, что этот метод чуть медленнее первого (отрицательный оттенок фактора скорости парсинга неоднозначен), но достоин внимания из-за своей краткости:

import urllib
urllib.urlretrieve(img, "...\img.jpg")


Притом стоит заметить, что функция urlretrieve в библиотеке urllib2 по неизвестным мне причинам (может кто подскажет по каким) отсутствует.

3-ий метод

Третий метод использует модуль requests. Метод имеет одинаковый порядок скорости выгрузки картинок с первыми двумя методами:

import requests

p = requests.get(img)
out = open("...\img.jpg", "wb")
out.write(p.content)
out.close()

При этом при работе с веб в питоне рекомендуется использовать именно requests вместо семейств urllib и httplib из-за его краткости и удобства обращения с ним.

4-ый метод

Четвертый метод по скорости кардинально отличается от предыдущих методов (на целый порядок). Основан на использовании модуля httplib2. Выглядит следующим образом:

import httplib2

h = httplib2.Http('.cache')
response, content = h.request(img)
out = open('...\img.jpg', 'wb')
out.write(content)
out.close()


Здесь явно используется кэширование. Без кэширования (h = httplib2.Http()) метод работает в 6-9 раза медленнее предыдущих аналогов.

Тестирование скорости проводилось на примере скачивания картинок с расширением *.jpg c сайта новостной ленты lenta.ru. Выбор картинок, подпадающих под этот критерий и измерение времени выполнения программы производились следующим образом:

import re, time, urllib2

url = "http://lenta.ru/"
content = urllib2.urlopen(url).read()
imgUrls = re.findall('img .*?src="(.*?)"', сontent)

start = time.time()
for img in imgUrls:
    if img.endswith(".jpg"):
        """реализация метода по загрузке изображения из url"""

print time.time()-start


Постоянно меняющиеся картинки на сайте не повлияли на чистоту измерений, поскольку методы отрабатывали друг за другом. Полученные результаты таковы:

Таблица сравнения скоростей методов
Метод 1, с Метод 2, с Метод 3, с Метод 4, с (без кэширования, с)
0.823 0.908 0.874 0.089 (7.625)

Данные представлены как результат усреднения результатов семи измерений.
Просьба к тем, кто имел дело с библиотекой Grab (и с другими), написать в комментариях аналогичный метод по скачиванию изображений с помощью этой и других библиотек.
Марат Вильданов @maratvildan
карма
5,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (18)

  • 0
    Если принимаются сторонние модули, то я бы ещё посоветовал посмотреть на pycurl.
    В нём, например, поддерживается keep-alive, который при скачивании нескольких картинок с одного сервера может положительно способствовать в плане скорости. Например, urllib.urlopen это не поддерживает когда я последний раз это проверял.
    А если будет скачиваться текстовая информация, то pycurl так же может прозрачно это сжимать (если сервер поддерживает, конечно).
  • +10
    Весь пост — это один из ответов на SO, который я недавно встречал.
    • –6
      И что? Значит он абсолютно бесполезен? Я вот думаю над одним проектом, где надо парсить и сохранять странички. Мне этот пост попался вовремя.
      Спасибо автору!
    • +8
      Я ничего против автора не имею, но хабр тем и ценен, что статьи в нем отличаются от вопросов на stackoverflow. Формат хабра скорее: «Как я написал парсер», или «Используем requests + lxml для парсинга».
      • –7
        Хабр ценен своими комментариями.
  • +6
    Не хватает sock.connect(...) ....write('GET /img.jpg HTTP/1.0\r\nHost: ...') ...read()
  • +2
    Я около полугода назад начал использовать python для аналогичных целей — массового парсинга страниц, поэтому мне тоже было интересно, какой способ работает быстрее. Для этого я набросал небольшой тест: pastebin.com/mH2ASEGX. Скрипт в 100 итераций получает главную страницу vk.com и ищет на ней наличие паттерна — типичные действия при парсинге. Резульаты следующие:
    ('testUrllib()', 19.59859853472629)
    ('testUrllib2()', 22.586007300934412)
    ('testHttplib()', 16.670537860489773)
    ('testSocket()', 1.5129479809538537)
    ('testRequests()', 9.380710576092)
    ('testPycurl()', 17.76420596649031)
    

    Из выводов: видно, что urllib-функции и httplib работают приблизительно в два раза медленнее, чем популярная библиотека Requests. Это вызвано тем, что urllib* не поддерживают keep-alive и на каждый запрос открывают-закрывают новый сокет (в третьей версии питона это исправили). Нужно скзаать, что с httplib кипэлайвы использовать, в принципе, можно, но контролировать их нужно вручную, через хедеры, тогда скорость работы будет приблизительно в 2 раза выше. Pycurl по скорости тоже ничем не отличается от других высокоуровневых библиотек, не знаю, правда, поддерживает ли он keep-alive.
    Ну а сокеты, как самый низкоуровневый доступ к сети, рвут все библиотеки с огромным отрывом.

    Поэтому если стоит вопрос максимальной производительности и нет сложных http-запросов, то лучше все оформить в виде какой-нибудь своей обертки над сокетами.
    • 0
      На самом деле curl достаточно эффективная библиотека, просто вы не используйте multicurl, который на больших объёмах отлично себя показывает.
      Однопоточные парсеры прошлый век, количество данных с каждым годом только растёт.
      • 0
        Ну если уже говорить о действительно больших масштабах, то pyCurl в многопоточных приложениях себя плохо ведет, так как использует блокирующие функции, тот же getaddrinfo для резолвинга домена в IP. Поэтому лучший вариант — это gevent и подобные асинхронные решения.
        • 0
          Нет такой проблемы, если libcurl собрана с поддержкой c-ares. По умолчанию, ни в одном популярном дистре линукса (кроме Archlinux, кажется) этого нет, но можно самому собрать нужные пакеты.
          • +1
            Ну а статья эта — ну совсем для самых маленьких.
    • 0
      Только что запустил этот тест у себя локально на libcurl 7.34.0 и получил, что testPycurl примерно на 40% быстрее, чем testUrllib.
      Видимо, действительно, зависит от сборки как выше уже заметили.

      Кстати, если добавить сжатие, то testPycurl будет ещё вдобавок где-то в два раза быстрее (у меня в результате получилось примерно в три раза быстрее testUrllib'a):
      curlHandler.setopt(pycurl.ENCODING, 'gzip')
      


      • 0
        Я в urllib2 добавлял поддержку gzip так:
        class GZipProcessor(urllib2.BaseHandler):
            """A handler to add gzip capabilities to urllib2 requests
            http://techknack.net/python-urllib2-handlers/
            """
            def http_request(self, req):
                req.add_header("Accept-Encoding", "gzip")
                return req
            https_request = http_request
        
            def http_response(self, req, resp):
                if resp.headers.get("content-encoding") == "gzip":
                    gz = GzipFile(
                                fileobj=StringIO(resp.read()),
                                mode="r"
                              )
                    old_resp = resp
                    resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url,
                                              old_resp.code)
                    resp.msg = old_resp.msg
                return resp
            https_response = http_response
        
        
        opener = urllib2.build_opener()
        opener.add_handler(GZipProcessor())
        opener.open("http://example.com/")
        

        С keep-alive у urllib к сожалению всё печально. Рецепты есть, но старые и не поддерживаемые.
  • +1
    Не понятно на что тут смотреть, даже проверки валидности изображения нет. Отдаст сервер 404 и будет битое изображение где-то потом выдаваться.
  • +1
    Сравнивать Grab не имеет смысла, граб это удобный фрэймворк поверх pycurl, данная операция может быть выполнена как синхронным грабом from grab import Grab так и асинхронным Spider — from grab.spider import Spider. Смысла особого нет замерять время, в конечном итоге все упирается в ширину канала и нестабильный пинг до цели.
  • +1
    Здесь явно используется кэширование. Без кэширования (h = httplib2.Http()) метод работает в 6-9 раза медленнее предыдущих аналогов.


    Метод 4, с (без кэширования, с)
    0.089 (7.625)


    Сперва не заметил «с» перед скобочкой… И подумал, что с кешированием медленнее.
  • 0
    Для Python3 надо написать первые 2 способа так:

    Способ 1

    from urllib.request import urlopen
    
    resource = urlopen(img)
    out = open("...\img.jpg", 'wb')
    out.write(resource.read())
    out.close()
    


    Способ 2

    from urllib.request import urlretrieve
    urlretrieve(img, "...\img.jpg")
    
  • 0
    Может я чего-то не знаю, но я действительно не понимаю, почему эта новость находится на главной странице. Скоро, наверное, будут выкладывать на главную способы вывести «хеллоу ворлд», и пост длиной в абзац.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.