Собираем базу аудиокниг для удобной фильтрации
Мой телеграм канал: https://t.me/winc0de. Всем привет! Наверняка многим из вас знакома проблема уставших глаз из-за длительной работой за компьютером. К сожалению, из-за этого приходится ограничивать себя в других занятиях. Одним из них есть чтение книг. В связи с этим, я уже более 5 лет почти каждый день слушаю аудиокниги. За это время научился параллельно заниматься чем-то и вникать в суть озвучки. Сейчас я даже в спортзале слушаю книги! Представьте как это удобно: час дороги пешком туда и обратно + полтора часа упражнений. Средняя книга в районе 10-15 часов записи.
Со временем все чаще и чаще появлялась проблема выбора материала. Ведь довольно большую роль играет чтец, жанр книги. Часто возникает ситуация, когда кто-то советует книгу (или в той же статье на хабре в читальном зале), а аудио-версии банально нет еще. Все эти проблемы я попытался решить отдельным сайтом. Сейчас есть парочка довольно больших и раскрученных по аудиокнигам, где вы можете прямо онлайн слушать их. Такие сайты обладают достаточно слабым фильтром по книгам. И, по сути, являются чисто каталогом.
Источник информации
За все время я заметил, что рутрекер является одним из самых масштабных хранилищ аудиокниг. Если книга существует в таком формате, то почти наверняка она есть в раздачах. Многие чтецы даже вручную делают релизы торрентов. Первым заданием было полной синхронизацией всех доступных аудиокниг с рутрекера.
Выбор книги
- Семантический глобальный поиск по всей базе по всем текстовым полям
- Сортировка (asc/desc) по дате создания торрента, количеству просмотров (на сайте), рейтингу (из внешних источников), количество загрузок (по данным рутрекера), ну и наугад
- Фильтр по автору произведения, автору озвучки, жанрам, и возможность исключить книги, которые вы отметили как «прочитанное»
- Возможность подписки на авторов книг или озвучки. Да-да! Вы можете выбрать понравившегося исполнителя и подписаться на все его обновления. Я, например, мониторю все книги Игоря Князева
База рутрекера
Итак, первый пункт это анализ публикаций рутрекера и формирование базы. Для хранилища выбрал MongoDB. Во-первых, идеально для кучи не особо связанных данных, во-вторых, идеально показала себя в плане производительности. Да и вообще разрабатывать сайт с простым «пробрасыванием» json с UI на базу очень просто и занимает минимальное время. Кстати, в MongoDB 3.2 добавили left outer join. Основной сложностью было унифицирование информации. Рутрекер хоть и заставляет оформлять раздачи (за что им спасибо), но все равно за 10 лет (именно столько времени прошло с момента публикации первой аудиокниги) оформление отличается. Пришлось открывать наугад разные разделы и собирать возможные варианты.
Скрипт парсера написан на питоне, для эмуляции браузера библиотека mechanize, для работы с DOM — BeautifulSoup. Метод, который возвращает объект максимально эмулирующий поведение обычного браузера. Второй метод получает объект браузера, авторизируется на рутрекере и возвращает этот самый объект, внутри которого уже хранятся cookies авторизации.
Сам по себе сбор данных выглядит как набор регулярных выражений в разных вариациях:
Очень важно использовать BULK-запросы в mongo, чтобы парсер не нагружал единичными вставками базу. К счастью, все это делается очень просто:
Поле slug генерируется пакетом slugify (pip install slugify).
Вот список всех полей для каждой из книг, которые я в итоге собрал:
Сразу же не забываем создать индекс для всех полей, по которым будет идти сортировка или фильтрация:
Это замедлит время вставки, но очень ускорит выборку. Синхронизация базы происходит раз в день, поэтому второй вариант для сайта предпочтительней.
Загрузка данных происходит по всем подфорумам аудиокниг:
Нормализация базы
Данные мы скачали, но есть проблема: нет точности в указанных данных. Кто-то напишет «В. Герасимов», кто-то «Вячеслав Герасимов». В одном месте укажут полное или альтернативное название произведение. Также появился вопрос в получении независимой оценки произведения. Погуглил пару заголовков книг и посмотрел на выдачу первых сайтов. Одним из них оказался fantlab.ru, который строит оценку по голосам пользователей, имеет довольно внушительную базу книг, содержит полное описание жанра и поджанров книг, точное имя автора и произведения.
Имя автора, название книгиАбсолютно вся информация из скриншота парсится и вносится в базу. Все поля вручную проверяются членами сообщества fantlab. Все идеально, но есть одна проблема: как связать раздачу с рутрекера и определенную запись с fantlab? В раздачах не указывают отдельно названия произведения. Иногда даже автора неверно пишут (или не указывают). По сути, полным источником информации есть заголовок. Всю боль можно увидеть в следующем скриншоте раздач:
Стоит ли говорить, что даже исключив весь текст в угловых скобках встроенный поиск на fantlab не справляется и не находит ничего. Выход я нашел, хоть и не совсем изящный: phantomjs(selenium) + google.
У меня довольно много проектов используют эту связку, поэтому настроенный headless-браузер и базовые скрипты для selenium готовы были для использования. По сути, я брал заголовок с рутрекера, добавлял к нему приставку " fantlab" и гуглил. Первый результат, который по шаблону подходил по адресу произведения парсился. Оставлю пару замечаний по поводу phantomjs: очень сильно течет память. Я давно уже сделал для себя пару «костылей», которые позволяют процессу жить месяцами на сервере и не падать по причине нехватки памяти:
Эта функция выполняется в момент запроса какого-то ресурса и проверяет его по маске медиа-файлов. Все картинки и видео исключаются. Т.е. они даже не загружаются в память. Вторая функция принудительно сбрасывает кеш. Вызывать нужно по таймеру раз в
Открываем гугл и вбиваем ему в поле поиска любой текст, чтобы сменить UI (получить результат выдачи). Все дальнейшие запросы будут происходить на этой же странице.
Так как запросы все аяксовые, нам нужно вручную проверять факт загрузки. В selenium для этого есть некоторые методы, которые ожидают пока определенный элемент не появится на странице.
ЖанрыСледующий шаг: приведение к одному виду всех имен авторов и всех жанров. В некоторых раздачах писали «ужас», в других «ужасы». Здесь на помощь пришла библиотека pymorphy2: позволяет получить начальную форму слова.
Имена авторовС авторами можно было бы тоже что-то придумать с библиотекой pymorphy2: разбивать на слова, проверять вхождения слов и их совпадение. Но тут я вспомнил пункт про глобальный поиск всего по всем полям. Это и будет решением. Для полнотекстового поиска взял sphinx. Он напрямую не дружит с mongodb, поэтому нужно написать скрипт, который будет выбрасывать xml с данными по указанной схеме.
Параметры в sphinx.conf:
И команда: indexer bookaudio --rotate
Как же использовать поиск для унификации полей? Берем список всех авторов книг, и складываем одинаковые вхождения. Получится что-то типа:
То, что используется максимально часто наверняка есть наиболее правильной формой. Берем топовые вхождения и делаем глобальный поиск по всем авторам.
Все похожие вхождения (в том числе «В. Герасимов», например) будут заменены на наиболее используемые формы.
ИнтерфейсНаписание веб-интерфейса для всего этого не несет никакой технической сложности. По сути, это надстройка для доступа к базе. Вот что у меня получилось. Список самых скачиваемых аудиокниг за всю историю трекера:
И работа с фильтрами:
Как видите, я захотел посмотреть все книги, озвученные Игорем Князевым по жанру «российская фантастика», отсортированные по количеству загрузок на рутрекере (вверху самые скачиваемые).
Пробелом или нажатием на карточки внизу раскрывается информация о книге. Благодаря mongodb все фильтры отрабатывают мгновенно по базе в 30к книг.