Readability своими руками
Поскольку побеждать Великий Китайский Роскомнадзор наша штука для обхода блокировок в интернете пока не особенно научилась, а рассказать что-нибудь странное про свою работу все равно хочется, расскажу про реимплементацию похожего на Readability алгоритма при помощи Node.js и Бэйцзинского технологического института.Что это вообще такое
Readability это радикальное продолжение идеи AdBlock убирать с веб-сайтов лишние элементы. Там, где AdBlock старается снести только самые бесполезные для пользователя вещи (в основном рекламу), Readability удаляет заодно скрипты, стили, навигацию и все остальное ненужное. Раньше такой вид страницы называли «версия для печати», хотя на самом-то деле текст предназначен для чтения (отсюда название Readability – «Удобочитаемость»).Лирическое отступление про парсеры
Основная характеристика парсера сайтов, или других слабо структурированных форматов – это количество знаний о частных случаях использования формата в дикой природе.Вырожденный случай обладания всеми знаниями – это парсер какого-то одного сайта. Т.е. если мы хотим воровать статьи с Хабрахабра, например, чтобы распечатывать их ночью на струйном принтере и приносить в жертву Сатане – мы можем посмотреть на существующую верстку и легко определить, что заголовок поста это h1.title.Программа, написанная таким способом, почти не будет ошибаться; для каждого отличного от Хабрахабра сайта придется писать новую программу.
Вырожденный идеальный случай: парсер вообще не знает, в каком формате он получил данные. Пример такой программы – strings (существует в большинстве неигровых операционных систем).
Если применить strings к какому-то не предназначенному для чтения файлу, можно получить список всего, что похоже на текст внутри этого файла. Например, команда
strings `which ls` напечатает ворох строк для форматирования внутри бинарника ls, и справку. %e %b %T %Y %e %b %R usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...] Чем меньше знаний, тем универсальнее парсер.Что уже есть Исходники первой версии Readability опубликованы и являют собой леденящий душу клубок регулярных выражений. Это само по себе неплохо, но частные случаи просто аховые. Мне бы хотелось алгоритм, который обладает гораздо меньшими знаниями о популярных сайтах в интернете (см. выше, «лирическое отступление»).Актуальная версия Readability закрыта и увешана плюшками разнообразной востребованности. Есть API.
Существует форк первой версии Readability компанией Apple (функция Reader в браузере Safari). Исходный код не очень-то открыт, но посмотреть на него можно, там еще больше регулярных выражений и частных случаев (например, есть такая переменная – isWordPressSite).
Проблемы оригинального скрипта – сложность модификации, аркадная эвристика. В основном работает, но требует нетривиальной доводки напильником. Версия Apple еще и лицензирована непонятно.
Что надо написать Парсер сайтов, обладающий минимальными знаниями о разметке. Входные данные – одна страница сайта, или фрагмент страницы. Результат – текстовое представление входных данных.Важный критерий – универсальность: программа будет работать и на клиенте, и на сервере. Поэтому мы не привязываемся к существующим реализациям DOM, а строим свою структуру данных (она еще и работает быстрее, чем полноценный DOM, т.к. данных нам нужно с гулькин, допустим, нос).
По этой же причине программа не будет уметь самостоятельно скачивать страницы из интернета, хранить результаты на диске, обладать пользовательским интерфейсом, вышивать крестиком.
Жизнь и приключения алгоритма В поисковике нашлось несколько статей на тему алгоритмизации описанного выше процесса. Больше всего мне приглянулись вот эти китайцы PDF.Формулы у меня получились немного другие, поэтому я расскажу кратенько про свой вариант китайского алгоритма.
Для каждого тега в документе:
Считаем оценкуЗдесь chars – количество текста (символов) внутри тега, hyperchars – количество текста в ссылках, tags – количество вложенных тегов (все метрики рекурсивные).
Считаем сумму оценокСумма оценок детей первого поколения (т.е. не рекурсивно).
Нашли тег с максимальной суммойЭто с высокой вероятностью контейнер основного текста. Или самый длинный комментарий. В любом случае там внутри буквы, это классно.
Простор для трудового подвига
Дальше оптимизация. Я опишу несколько случаев, но вообще это самая интересная тема, можно в комментариях поболтать.Мусор в основном тексте. Всякие горе-блогеры любят прямо в тело поста засунуть многочисленные кнопки своих социальных вконтактов, твиттеров и т.п. ненужные вещи. У таких кнопок оценка (score, см. выше) стремится к нулю, по этому принципу их можно сносить.
Я на всякий случай проверяю еще, что после удаления мусора оценка родителя выросла, если нет (или выросла несущественно) – то не удаляю, мало ли что там.
HTML. В алгоритме не используются знания о структуре документа, их можно теперь добавить, чтобы улучшить (или ускорить) работу программы. Т.е., допустим, пессимизировать заранее <footer> и <nav>, или добавлять к невидимым элементам (в браузере) аннотации и пропускать их совсем – тут реально простор для деятельности, я пока ничего не реализовал.
Текстовые сигналы. Если в тексте присутствуют запятые, точки и другие знаки препинания, это с большой вероятностью связный текст (в отличие от навигации, например). Такая эвристика была в Readability.
Тут надо обратить внимание на то, что знаки препинания в разных языках все-таки разные, и запятые в китайском ("," Unicode U+FF0C) отличаются от символа "," (ASCII 44).
Что получилось, как пользоваться Результат я назвал незатейливо readability2, выложил в npm.Кратенько про тесты Тестировать такую штуку надо обязательно, чтобы избежать регрессий (и вообще автоматически тестировать программы – это классно).Тут возникает некая проблема: тест readability – это сохраненная страница совершенно постороннего сайта, плюс выдранный с нее же руками «эталонный» текст. Я не очень понимаю, как распространять это таким способом, чтобы правоторговцы не стремились меня уничтожить за незаконное копирование сайтов и текстов.
Если кто-то знает правильный ответ, напишите, пожалуйста, в комментариях. Сейчас тесты живут в закрытом репозитории, но им очень хочется на свободу.
Исходники без тестов: GitHib
Пример использования Для иллюстративных целей я написал страничку demo.html, в которой две строки текста среди всякой навигации.Текст называется «Название». Содержательная часть:
Весь микрорайон тихонько чудо божье наблюдал:Поп Игнатий тилибонькал свой церковный причиндал.
(Public domain) К слову, я отказываюсь от имущественных авторских прав на это литературное произведение, передавая его таким образом в общественное достояние (public domain). Распространять и использовать полный текст теперь могут все без ограничений. Таким должен быть результат прогона программы. Если результат не такой, значит, все сломалось.А вот исходник программы demo.js с комментариями. Используется парсер sax авторства Isaac Z. Schlueter.
Документация, она же API Конструктор: var reader = new Readability Ничего не принимает.SAX интерфейс:
reader.onopentag(tagName) // <тег> reader.onattribute(name, value) // атрибут=значение reader.ontext(text) // текст reader.onclosetag(tagName) // </тег> Тут все аргументы – строки.Чтобы получить результат:
var res = reader.compute(), text = reader.clean(res) На выходе: res.heading – название статьи и text – основной текст без форматирования.Вместо reader.clean можно написать другой форматтер, тогда будет получаться не текст, а простая разметка, например.
Вывод
Программа работает. Ей пока немного страшно пользоваться, т.к. тестов всего около 20 штук, но я работаю над этим. Обновления будут. Патчи приветствуются, кроме всяких глупых. GitHub. Лицензия MIT, я забыл ее залить в репозиторий.Важное замечание: картинка слева не имеет никакого отношения к посту. Поэтому если она не загрузилась, и вы не видите слева никакой картинки – не расстраивайтесь.
Лучше напишите в комментариях, что вы обо всем этом думаете.