Экстрактор контента из веб-документов



    Здравствуй, Хабр!

    Это мой первый пост, в котором я хочу поделиться своей наработкой в решении такой задачки, как выделение контента на странице. Собственно, задачка давно висела в голове в фоновом режиме. Но так сложилось, что именно сейчас мне самому понадобился инструмент, кроме того наткнулся на статейку на хабре: habrahabr.ru/company/mailru/blog/200394 и решил — пора. Ладно, поехали.


    Ход мыслей


    Собственно, к чему такая картинка в начале статьи? Дело в том, что задачу решать можно совершенно по-разному. Не буду ударяться в долгие рассуждения о возможных способах решения, их плюсах и минусах. Главное то, что в этом посте к задаче я подхожу как к проблеме классификации. Итак, вот ход мысли:
    • Придумываем набор факторов, чтобы любой элемент в DOM можно было векторизовать.
    • Каким то образом собираем пачку документов.
    • В каждом документе векторизуем все элементы в DOM ниже BODY в дереве. Опять же как-то.
    • Для каждого из векторизованных элементов назначаем класс 1 или 0. 0 — не целевой, 1 — целевой.
    • Бьём выборку на две части в пропорции 50/50 или около того.
    • На одном куске обучаем наш классификатор, на другом его тестируем, получаем результат в виде полноты, точности. Ну или любой метрики типа F-score тысячи их.

    Проницательный читатель наверняка скажет, что вместо последних двух пунктов лучше сделать, например, кросс-валидацию и будет прав. В целом это не важно в данном случае, т.к. статья в первую очередь посвящена инструменту, а не сопряжённым математико/алгоритмическим деталям.

    Про идейную сторону дела вроде бы всё ясно. Посмотрим на технологическую сторону.
    • В качестве языка выбрал python. В основном потому что он мне нравится (:
    • В качестве математической библиотеки для обучения сразу же был выбран sklearn.
    • Поскольку я почему-то решил что javascript-страницы должны также успешно обрабатываться, в качестве движка для парсинга был выбран PyQt4. Как окажется далее — это очень правильный выбор.


    Решение


    Как обычно, оказалось что идея не учитывает всякие неприятные «мелочи». А дело в том, что звучит всё здорово в предыдущем пункте, но совершенно не ясно как размечать выборку? Т.е. как выбирать целевые элементы в DOM для дальнейшего обучения? И вот тогда в голову пришла правильная мысль: а давайте пусть это будет интерактивный браузер. Выбирать целевые блоки будем при помощи мыши и клавиатуры. Эдакий визуализированный процесс разметки не выходя из браузера.

    Задумывалось следущее: есть браузер, в котором можно водить мышкой, и элемент под мышкой «подсвечивается». Когда выбран нужный элемент, пользователь нажимает определённый хоткей. В результате страница парсится, DOM векторизуется, а выделенный элемент получает класс 1, в то время как остальные — класс 0.

    Результаты


    Я не хочу копипасить сюда портянки из кода — всё в открытом виде и доступно в репозитории. Кому надо — почитаете там. Да, кому лень, можно ставить с помощью pip, но учтите, писал и тестировал только на Ubuntu >=12.04.

    В итоге получилась библиотечка с треми основными возможностями:
    • Интерактивное обучение распознаванию контента в браузере. Полученная модель классификатора сериализуется в файлик.
    • Интерактивное тестирование распознавания контента в браузере. Элементы, которые на странице были проклассифицированы как целевые — «подсвечиваются».
    • Консольная тулза, умеющая выдрать html целевого элемента DOM по заданному URL и файлику с моделькой.


    Кстати, после установки пакетика constractor — станут доступны для запуска два скриптика:
    • constractor_train.py — это интерактивная обучалка/тестилка. Тулза умеет подсвечивать элемент под указателем мыши, векторизовать страницу по нажатию хоткея, обучиться на основе данных полученных с разных страниц, сохранить факторы и модельку в файлики, загрузить их из файликов, подсветить элемент на основании текущей модельки.
    • constractor_predict.py — это консольная выдиралка html целевых элементов. В целом это всё что тулза умеет (:


    Картинки


    Для совсем уж ленивых привожу примеры с картинками. Например, хотим научить тулзу определять шапку Хабра.

    1) Наводим мышку на шапку. Когда нужная область выделена (чёрным бэкграундом), нажимаем Ctrl+S. Тем самым добавили векторизованные элементы в выборку.


    Повторяем процедуру несколько раз.


    2) Далее нажимаем Ctrl+T для обучения. Заходим на произвольную страницу с нашей шапкой. Нажимаем Ctrl+P для прогноза.


    Заключение


    Библиотечка пока очень сырая и требует много доработок, прошу не гнать тапком критиковать строго — всё писалось в действительно сжатые сроки.

    Из планов по доработкам: расширение множества дефолтных факторов, добавление встроенных моделек для распознавания разных типов блоков и многое другое. Конечно же я всё это буду постепенно пилить в свободное время. Однако буду очень признателен, если найдутся хабровольцы, готовые также в свободное время поконтрибьютить в библиотечку.

    Спасибо за внимание!
    • +31
    • 23.4k
    • 9
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 9

      +8
      Какая-то куцая статья, непонятно даже, какой алгоритм(ы?) классификации использовались. Судя по первой картинке, идет намек на логистическую регрессию, ведь так?
        0
        По-умолчанию используется градиентный бустинг над деревьями решений. В целом, если глянуть код, становится понятно, что передать можно любой классификатор, т.к. функция которая производит обучение — это параметр в конструкторе. Если конкретнее, то текущая ф-ция обучения такая:

        def train(classes, features):
            from sklearn.ensemble import GradientBoostingClassifier
            learner = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0, max_depth=1, random_state=0)
            clf = learner.fit(features, classes)
            result = clf.score(features, classes)
            return result, clf
        


        И передаётся она в инстанс браузера при создании вот так:
        trainer = GuiTrainer(train_function=train)
        


        В целом, статья может и куцая, но смысл её не в освещении математических/алгоритмических аспектов, как я уже писал. Смысл — рассказать что есть вот такая библиотека и приветствуются все желающие принять участие в её допиливании. Если это не ясно из текста — прошу прощения за сумбурность изложения. Буду учиться лучше излагать свои мысли О_о
          0
          В репозитории есть 2 части: GUI для аннотации и собственно модель. Мне кажется, что их хорошо бы разделить. Т.к. GUI для аннотации — штука интересная, а с моделью все довольно непроработано. Например, нет скриптов для оценки качества. Добавлять какие-то дополнительные feature-функции, смотреть, как работают нынешние, настраивать параметры модели и т.д. толку поэтому мало сейчас.

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

          Еще было бы хорошо использовать соглашения scikit-learn. Например, обычный порядок «X, y» в функции тренировки, FeatureUnion и DictVectorizer для вычисления фич — вместо своих классов.

          Картинка, кстати, неправильная — границы, по которым разделяются данные в random forest, не так выглядят :)
            0
            В целом со всеми замечаниями согласен и почти про все из них и так был в курсе. Тем не менее, в силу ограниченности свободного времени, выбор у меня был из 2-ух вариантов: выложить как есть и постепенно допиливать, или не выкладывать вообще, и не ясно когда ещё руки дойдут. Решил что первый вариант вполне приемлемый. Так что за замечания спасибо — учту. Но уже в следующих версиях.
        +1
        раз уж Вы начали размечать страницы, то почему бы просто не брать xpath выбранных элементов и не приводить их к ближайшему общему предку, которого и извлекать?
        Ну и статья, на которую Вы ссылаетесь в начале, о том как находить нужный контент на странице БЕЗ учителя и в этом ее смысл.
          0
          Да, так было бы правильно делать, если задача стоит выделять элемент на разных страницах одного ресурса. И то, в последнее время нельзя рассчитывать на фиксированную вёрстку и имена классов/айдишников.

          В любом случае, основная мысль всего происходящего — это на основании обучения по страницам из разных ресурсов сделать алгоритм, умеющий распознавать целевой блок на всех ресурсах подобного класса.
            0
            Про замечание по поводу статьи в начале — полностью согласен. Однако, прошу заметить, что я и не писал что сделал что-то подобное. Упомянул её исключительно в сюжетном контексте (:
          • UFO just landed and posted this here
              0
              По-моему, AlexeyTokar буквально в предыдущем комментарии предложил эту мысль, а я ответил почему это не то что нужно. А про phantomjs — штука хорошая, но не вполне понимаю для чего он тут нужен? Как он поможет в интерактивной разметке веб-страничек? Да и чем фантом удобнее PyQt4?

            Only users with full accounts can post comments. Log in, please.