Открыть список
Как стать автором
Обновить

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

Если принимаются сторонние модули, то я бы ещё посоветовал посмотреть на pycurl.
В нём, например, поддерживается keep-alive, который при скачивании нескольких картинок с одного сервера может положительно способствовать в плане скорости. Например, urllib.urlopen это не поддерживает когда я последний раз это проверял.
А если будет скачиваться текстовая информация, то pycurl так же может прозрачно это сжимать (если сервер поддерживает, конечно).
Весь пост — это один из ответов на SO, который я недавно встречал.
И что? Значит он абсолютно бесполезен? Я вот думаю над одним проектом, где надо парсить и сохранять странички. Мне этот пост попался вовремя.
Спасибо автору!
Я ничего против автора не имею, но хабр тем и ценен, что статьи в нем отличаются от вопросов на stackoverflow. Формат хабра скорее: «Как я написал парсер», или «Используем requests + lxml для парсинга».
Хабр ценен своими комментариями.
Не хватает sock.connect(...) ....write('GET /img.jpg HTTP/1.0\r\nHost: ...') ...read()
Я около полугода назад начал использовать 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-запросов, то лучше все оформить в виде какой-нибудь своей обертки над сокетами.
На самом деле curl достаточно эффективная библиотека, просто вы не используйте multicurl, который на больших объёмах отлично себя показывает.
Однопоточные парсеры прошлый век, количество данных с каждым годом только растёт.
Ну если уже говорить о действительно больших масштабах, то pyCurl в многопоточных приложениях себя плохо ведет, так как использует блокирующие функции, тот же getaddrinfo для резолвинга домена в IP. Поэтому лучший вариант — это gevent и подобные асинхронные решения.
Нет такой проблемы, если libcurl собрана с поддержкой c-ares. По умолчанию, ни в одном популярном дистре линукса (кроме Archlinux, кажется) этого нет, но можно самому собрать нужные пакеты.
Ну а статья эта — ну совсем для самых маленьких.
Только что запустил этот тест у себя локально на libcurl 7.34.0 и получил, что testPycurl примерно на 40% быстрее, чем testUrllib.
Видимо, действительно, зависит от сборки как выше уже заметили.

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


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


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


Сперва не заметил «с» перед скобочкой… И подумал, что с кешированием медленнее.
Для 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")
Может я чего-то не знаю, но я действительно не понимаю, почему эта новость находится на главной странице. Скоро, наверное, будут выкладывать на главную способы вывести «хеллоу ворлд», и пост длиной в абзац.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.