Pomp — метафреймворк для парсинга сайтов

С поддержкой asyncio и вдохновленный Scrapy.

Зачем еще один?


В первую очередь как инструмент для сбора данных, применяемый в моем хобби проекте, который не давил бы своей мощью, сложностью и наследием. И да, кто же будет сознательно начинать что-то новое на python2.x?

В итоге появилось идея сделать простой фреймворк для современной экосистемы python3.x, но такой же элегантный как Scrapy.

Под катом обзорная статья о Pomp в стиле FAQ.

Зачем вообще фреймворки для парсинга сайтов нужны?


И действительно, ведь на простой связке requests + lxml можно сделать очень многое. В действительности же фреймворки задают правила и нужные абстракции, и берут много рутины на себя.

Почему Pomp позиционируется как "метафреймворк"?


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

В этом и слабость и одновременно сила Pomp. Данный фреймворк позиционируется как "фреймворк для фреймворков", другими словами дает все что нужно для того что бы сделать свой фреймворк и начать продуктивно "клепать" веб пауков.

Pomp дает разработчику:

  • нужные абстракции(интерфейсы) и архитектуру схожую со Scrapy;
  • не навязывает выбор методов для работы с сетью и разбора добытого контента;
  • может работать как синхронно так и асинхронно;
  • конкурентная добыча и разбор контента (concurrent.futures);
  • не требует "проекта", настроек и прочих ограничений.

Выигрыш:

  • запуск на python2.x, python3.x и pypy (можно даже на google app engine запуститься)
  • можно использовать любимые библиотеки для работы c сетью и для разбора контента;
  • ввести свою очередь задач;
  • разработать cвой кластер пауков;
  • более простая прозрачная интеграция с headless браузерами (см пример интеграции с phatnomjs).

Другими словами из Pomp можно сделать Scrapy, если работать с сетью на Twisted и разбирать контент с помощью lxml и т.д.

Когда следует применять Pomp, а когда нет?


В случае когда вам требуется обработать N источников, с общей моделью данных и с периодическим обновлением данных — это и есть идеальный случай применения Pomp.

Если вам необходимо обработать 1-2 источника и забыть, то быстрее и понятнее все сделать на requests+lxml, и вовсе не использовать специальные фреймворки.

Pomp vs Scrapy/Grab/etc?


Попытаться сравнить можно только в разрезе конкретной задачи.

И что лучше сказать сложно, но для меня это вопрос решенный, так как я используя Pomp могу собрать любой сложности систему. С другими же фреймворками зачастую придется бороться с их "рамками" и даже забивать гвозди микроскопом, к примеру использовать Scrapy для работы с headless browsers, оставляя неудел всю мощь Twisted.

Архитектура




Основные блоки:
 - очередь запросов (задач);
 - "транспорт" (на диаграмме как BaseDownloader);
 - middlewares для пре- и пост-обработки запросов;
 - pipelines для последовательной обработки/фильтрации/сохранения добытых данных;
 - crawler для разбора контента и генерации следующих запросов;
 - engine, который связывает все части.

Простейший пример


Поиск на странице http://python.org/news предложений со словом python простейшим regexp.

import re
from pomp.core.base import BaseCrawler
from pomp.contrib.item import Item, Field
from pomp.contrib.urllibtools import UrllibHttpRequest

python_sentence_re = re.compile('[\w\s]{0,}python[\s\w]{0,}', re.I | re.M)

class MyItem(Item):
    sentence = Field()

class MyCrawler(BaseCrawler):
    """Extract all sentences with `python` word"""
    ENTRY_REQUESTS = UrllibHttpRequest('http://python.org/news')  # entry point

    def extract_items(self, response):
        for i in python_sentence_re.findall(response.body.decode('utf-8')):
            item = MyItem(sentence=i.strip())
            print(item)
            yield item

if __name__ == '__main__':
    from pomp.core.engine import Pomp
    from pomp.contrib.urllibtools import UrllibDownloader

    pomp = Pomp(
        downloader=UrllibDownloader(),
    )

    pomp.pump(MyCrawler())

Пример создания "кластера" для разбора craigslist.org


В примере используется:
 - Redis для организации централизованной очереди задач;
 - Apache Kafka для агрегации добытых данных;
 - Django на postgres для хранения и отображения данных;
 - grafana c kamon dashboards для отображения метрик работы кластера kamon-io/docker-grafana-graphite
 - docker-compose что бы запустить весь этот зоопарк на одной машине.

Исходный код и инструкции по запуску смотрите здесь — estin/pomp-craigslist-example.

А так же видео без звука, где большая часть времени ушла на развертывание окружения. На видео можно найти некоторые ошибки в сборе метрик о размере очереди задач.

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

Дальнейшие планы


Pomp по большей части уже сформировался и достиг поставленных целей.
Дальнейшее развитие скорее всего будут заключаться в более плотной интеграции с asyncio.

Ссылки


 - проект на bitbucket https://bitbucket.org/estin/pomp
 - зеркало проекта на github https://github.com/estin/pomp
 - документация http://pomp.readthedocs.org/en/latest/
Поделиться публикацией

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

    0
    Очень интересная вещь, надо опробовать ее в бою и сообщить Вам, насколько все это удобно.
      0
      Мне как автору будет интересна конструктивная критика или даже предложения по улучшению.
      Фреймворк всего-то несколько раз обкатывался на парочке не коммерческих хобби проектах, удовольствия ради.
        0
        Интересная альтернатива Scrapy, спасибо.
        Пару вопросов:
        1. У вас есть поддержка закачки больших файлов ~ 1-2Gb с сохранением сразу на диск с правильным именем из Content-Disposition? В Scrapy, к сожалению нет, все качает в память перед сохранением.
        2. Есть ли обнаружение застрявших соединений и их рестарт? К примеру, было передано 0 байт в течении последних 60 секунд, в Scrapy тоже не реализовано.
          0
          У вас есть поддержка закачки больших файлов ~ 1-2Gb с сохранением сразу на диск с правильным именем из Content-Disposition? В Scrapy, к сожалению нет, все качает в память перед сохранением.

          Нет, поддержки нет. Делать это придется самому — работать через "поток" и направлять его в файл.


          А если есть возможность, то лучше добыть ссылку на файл используя тот же Scrapy или Pomp, а далее фоном качать содержимое через curl/wget/etc отдельно с возможностью "дозакачки".


          Есть ли обнаружение застрявших соединений и их рестарт? К примеру, было передано 0 байт в течении последних 60 секунд, в Scrapy тоже не реализовано.

          И этого то же нет. Так же это делать нужно самому — ввести таймауты и реализовать очередь задач с логикой рестарта если был таймаут.

      0
      А какие-то тесты производительности делали?
        0
        Нет. Не с чем сравнить, так как работа с сетью может быть любая и разбор контента может быть любой, а как раз эти два компонента и отъедают больший кусок. Как вариант реализовать на Pomp подобие какого нибудь мейнстримного фреймворка и сравнить с ним, но идея сомнительная.
        0
        Достойный фреймворк, проникся трудом.
        Есть вопрос — как прикрутить socks-proxy?
          0
          • использовать libcurl для работы с сетью или сразу асинхронную обвязку tornado.httpclient


          • использовать обертку типа delegate и переводить любую socks в http и работать уже только с http проксей
            0
            О, гран мерси за наводку на tornado с libcurl!
            До этого из более-менее нормальных решений встречал (и использовал) https://github.com/polymorphm/lib-socks-proxy. Но curl_httpclient в tornado выглядит гораздо более стройным вариантом.
              0
              Пока я собирался, родился замечательный пакет: aiosocks — https://pypi.python.org/pypi/aiosocks | https://github.com/nibrag/aiosocks
              К aiohttp прикручивается на ура.
                0

                Это просто прекрасно! Спасибо за новость!
                У вас случаем нативной реализации http/2 для asyncio не завалялось?

                  0
                  Не, пока не попадалось.
                  Быстрое гугление дает только aioh2 — https://aioh2.readthedocs.io/en/latest/readme.html Но думаю оно уже явно известно…
            0
            Классная штука, мне очень понравилось.
            От начинающего программиста отдельное спасибо за рабочие примеры использования в разных вариантах. Очень помогло въехать что к чему и как пользоваться.
              0

              Спасибо. Работоспособность примеров и не только под контролем drone ci.

              0
              Спасибо огромное за статью! Очень интересная методология кластеризации сервисов.

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

              Самое читаемое