Долго выбирал между «Алгоритмами», «Читальным залом» и «Я пиарюсь», в итоге остановился на Data Mining.

Эта история началась в конце октября, когда я очередной раз пытался выбрать, что бы мне почитать. Лично я с собой в отпуск/в дорогу беру что-нибудь из фантастики (как, думаю, и большинство присутствующих), причем категорически не люблю всякий модный новодел.

И вот, терзаясь муками выбора, я забил в поиск «IMDB for books» и… не нашел ничего пристойного. Весь интернет забит рекомендательными сервисами для книг, и все они выдают полную чушь. Вот, например, топ имхонета для раздела «Самая лучшая фантастика и фэнтези»:

1. Мастер и Маргарита. Михаил Булгаков, 1940 год
2. ��веты для Элджернона (рассказ). Дэниел Киз, 1959 год
3. Цветы для Элджернона. Дэниел Киз, 1966 год
4. Битва Королей. Джордж Мартин, 1998 год
5. Рыцарь Ордена: Клинки у трона. Сергей Садов, 2000 год
6. Голубятня в Орехове. Владислав Крапивин, 1983 год


Эээ… Это совсем не то, что я ожидал увидеть на первых местах в рейтинге фантастики. «Мы пойдём другим путём», — подумал я. Отказавшись от идеи найти нормальный читательский рейтинг, я просто пошёл в Вики, нашёл список лауреатов премий Хьюго и Небьюла и выбрал пару-тройку книг — как, собственно, я всегда раньше и делал.

«А не замутить ли мне свой рейтинг книг, взяв за основу престижные премии?» — внезапно подумал я. И замутил. Знакомьтесь: top-books.info



Итак, мне потребовалось сделать следующее:

1. Найти и распарсить логи номинантов и победителей премий;
2. Сформировать из них списки книг и авторов;
3. Присвоить каждой книге рейтинг;
4. Найти и приклеить к каждой книге картинку и описание;
5. Найти и приклеить к каждому автору краткую биографическую справку;
6. Сделать по всему этому поиск;
7. Прикрутить голосовалку.

А теперь подробнее…

Логи премий



Я решил ограничиться тремя премиями: Hugo, Nebula и Locus. Все остальные либо узкоспециализированные, либо даются недавно.

Списки победителей и номинантов на Hugo и Nebula я взял из Вики:
en.wikipedia.org/wiki/Hugo_Award_for_Best_Novel
en.wikipedia.org/wiki/Nebula_Award_for_Best_Novel

С Locus оказалос�� сложнее. Списки номинантов пришлось собирать по годам:
www.locusmag.com/SFAwards/Db/Locus.html

К тому же в этих списках огромное количество номинантов, штук по 20, большая часть из которых мне абсолютно ничего не говорила. Так что я ограничился первой пятеркой номинантов из категории «Best Novel» (выдавались в 1971-1981 гг) и категорий «SF Novel», «Fantasy Novel» (с 1982 по 2011).

Книги и авторы



Разбирал я всё это дело скриптами, написанными на лучшем в мире языке — JavaSсript-е :). Hugo и Nebula разобрались легко (в Википедии всё-таки единого стиля оформления придерживаются), с Locus пришлось немного помучиться. Вот так примерно выглядел разбор логов Locus-а:

    parseBook = function (s) {
        var alternates = /\([^\)]+title ([^\)]+)\)/.exec(s);
        
        if (alternates) {
            var alternateTitle = trim(alternates[1]).replace(/^"/, '').replace(/"$/, '');
            s = s.replace(alternates[0], '');
        }
        
        s = s.replace(/ \(.+\)$/, '');
        
        var parts = s.split(', '),
            delimeter = parts.length - 1;
            
        if (delimeter > 1 && parts[delimeter].indexOf('Jr') == 0) {
            delimeter--;
        }
        
        var title = trim(parts.slice(0, delimeter).join(', ')).replace(/^"/, '').replace(/"$/, ''),
            author = trim(parts.slice(delimeter).join(', '));
        
        if (author.indexOf(' & ') != -1) {
            author = author.split(' & ');
        }
            
        return {
            title: alternateTitle ? [title, alternateTitle] : title,
            author: author
        }
    }


В итоге я получил примерно вот такой список авторов:

    "a-e-van-vogt": {
        "fullName": "Vogt, A. E. van",
        "alias": "a-e-van-vogt",
        "firstName": "A.",
        "middleName": "E.",
        "lastName": "Vogt",
        "preposition": "van"
    },
    "kurt-vonnegut": {
        "fullName": "Vonnegut, Kurt",
        "alias": "kurt-vonnegut",
        "firstName": "Kurt",
        "middleName": "",
        "lastName": "Vonnegut",
        "preposition": ""
    },


И вот такой список книг:

    "the-boy-who-bought-old-earth": {
        "see": "the-planet-buyer"
    },
    "dune": {
        "alias": "dune",
        "title": "Dune",
        "awards": {
            "1965": [
                {
                    "award": "nebula",
                    "won": true
                }
            ],
            "1966": [
                {
                    "award": "hugo",
                    "won": true
                }
            ]
        },
        "authorAlias": "frank-herbert"
    },
    "and-call-me-conrad": {
        "alias": "and-call-me-conrad",
        "title": [
            "...And Call Me Conrad",
            "This Immortal"
        ],
        "awards": {
            "1966": [
                {
                    "award": "hugo",
                    "won": true
                }
            ]
        },
        "authorAlias": "roger-zelazny"
    },
    "this-immortal": {
        "see": "and-call-me-conrad"
    },


У номинантов на Locus ещё есть поле place — занятое место. Hugo и Nebula ранжирования для номинантов не дают.

Рейтинги



Я перепробовал несколько вариантов, и в итоге остановился на вот такой формуле:

rating = 6 + 3 * (sum(s[i])) / possibleAwards + yearTotal / 100

Здесь possibleAwards — число наград, которые могла теоретически получить книга (= число премий, выдававшихся в год публикации книги), yearTotal — общее количество номинантов премий в год публикации книги, s[i] — набранный книгой балл по каждой премии.

s[i] считалось так: 1, если книга выигрывала премию; 1/число номинантов, если книга была номинирована на Hugo или Nebula, но не получала премию; (число номинантов — занятое место + 1)/число номинантов для претендентов на Locus.

Итого, каждая книга получала 6 баллов просто так, по факту попадания в шорт-лист какой-нибудь премии; от 0 до 3 баллов в зависимости от полученных премий (итого от 6 до 9); плюс небольшую поправку в виде общего числа номинантов в тот год / 100, для того, чтобы (а) немного пессимизировать книги, получавшие премии в самом начале, когда списков номинантов ещё не было; (б) из того соображения, что, если в какой-то год было много номинантов, то год в целом был удачнее предыдущих.

Например, возьмём «Проклятье Шалиона»:

    "the-curse-of-chalion": {
        "alias": "the-curse-of-chalion",
        "title": "The Curse of Chalion",
        "awards": {
            "2002": [
                {
                    "award": "hugo",
                    "won": false
                },
                {
                    "award": "locus",
                    "won": false,
                    "category": "fantasy novel",
                    "place": 3
                }
            ]
        },
        "authorAlias": "lois-mcmaster-bujold"
    }


Книга набирает 0.16(6) балла за номинацию на Хьюго (1 из 6 номинантов) + 0.6 балла за Локус (3-е место из 5) + 0.11 за общее число претендентов (6 + 5) на премии, в которых книга участвовала. Итого: 6.9.

В итоге, топ-10 приобрел следующий вид:

9.2 American Gods / Gaiman, Neil
9.2 Paladin of Souls / Bujold, Lois McMaster
9.1 The Forever War / Haldeman, Joe
9.1 The Gods Themselves / Asimov, Isaac
9.1 Dune / Herbert, Frank
9.1 Ringworld / Niven, Larry
9.1 Startide Rising / Brin, David
9.1 Speaker for the Dead / Card, Orson Scott
9.1 Doomsday Book / Willis, Connie
9.1 The Yiddish Policemen's Union / Chabon, Michael


Из десятки лично я, правда, читал только «Паладин душ», «Дюну» и «Сами боги», но их нахождение в топ-10 представлялось мне вполне адекватным.

Рейтинг авторов



С рейтингом авторов пришлось помучиться. Мне хотелось, чтобы автор с большим количеством хороших книг был в топе выше автора с одной, но очень хорошей. Я перебрал много формул, и остановился на такой:

rating = (sum + 3)/(n + 1)

Здесь sum — сумма рейтингов книг автора, n — количество книг. Легко заметить, что эта формула фактически эквивалентна тому, что каждому автору засчитывается фиктивная книга с рейтингом 3, что и позволяет пессимизировать авторов с малым количеством книг. Топ-10 в итоге получился таким:

1 Heinlein, Robert A.
2 Le Guin, Ursula K.
3 Asimov, Isaac
4 Card, Orson Scott
5 Bujold, Lois McMaster
6 Willis, Connie
7 Brin, David
8 Haldeman, Joe
9 Clarke, Arthur C.
10 Pohl, Frederik

Вот этот топ меня полностью удовлетворил :)

Майним данные о книгах



Информацию о книгах я набрал из Amazon Product Advertising API — в рамках партнерской программы Амазон разрешает использовать информацию о продаваемых изданиях. Меня интересовали картинки и описания. В целом, схема работы была такая:

1. Выбираем книгу
2. Делаем запрос по заголовку книги с фильтром по одному из авторов
3. Ищем в ответе item-ы с тем же заголовком и автором
4. Записываем уникальный идентификатор (ASIN) и reviews.
5. Если чего-то не нашли, пробуем искать по другому заголовку (если у книги их несколько) либо в другом индексе.

Я искал сначала в индексе Kindle Store (я за прогресс и всё такое :)), а потом по бумажным книгам. В итоге, из 580 книг 378 удалось найти в Kindle Store.

Ищет Amazon PAAPI довольно адекватно, хотя на первые места могут проскакивать какие-то левые ответы. Единственное, что API полностью игнорирует диакритические знаки и не находит таких авторов, как Miéville и такие заголовки, как Tales of Nevèrÿon — их, в итоге, пришлось искать руками.

Майним данные об авторах



Авторов пришлось выцеплять из Википедии посредством Wikimedia API. Велосипед, честно говоря, тот ещё. В итоге, 90% запросов по авторам нормально отработало просто по имени и фамилии, но те 10%, у которых распространенные имена, пришлось потом перезабивать руками. Если же к поисковому запросу помимо имени дописать что-нибудь типа «author» или «fantasy writer» — то начнут нормально работать 10% неуникальных имен, но остальные 90% сломаются напрочь.

В итоге, для каждого автора я вытащил из википедии преамбулу к статье. Дорогие редакторы википедии, вам очень не помешает guideline для преамбул. Многие статьи сурово отдают капитанством (David Brin, например), в других в преамбуле целое сочинение написано (Isaac Asimov).

Поиск



Ну, тут особо выбора не было — Google Custom Search Engine. Пришлось немного пошаманить CSS-ом, чтобы расположить его там, где я хотел, но вроде работает.

Кстати, у Google CSE обратная к Амазону проблема — по Mieville отказывается искать, нужно забивать Miéville.

Голосовалка



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

Господа разработчики 2gis API и Leaflet API! Простите меня! Ваши API — сказка по сравнению с FB. Такого плохо организованного и отвратительно документированного API я ещё не встречал. Почти неделя мучений потребовалось мне, чтобы прикрутить эту байду.

Господа разработчики Фейсбука! Наведите порядок в документации! Невозможно работать совершенно.

Русская версия



В изначальных планах стояло так же создание и русскоязычной версии, но, как оказалось, никакого русскоязычного контента вытянуть я не могу. У Озона своего API нет, русская Вики и половины авторов не знает. Так что в этом месте полный фейл.

И что дальше?



Да ничего, особо. Господа любители фантастики — наслаждайтесь. Рейтинг, по моим ощущениям, более чем адекватен. (В порядке эксперимента я прочитал №1 в списке — "Американские боги" Нила Геймана. Очень крутая книжка, доложу я вам.) Если какие-то оценки кажутся вам неправильными — welcome, голосуйте. Только учтите, что у начальных оценок выставлен вес в 1000 голосов, так что перебить их, кхм, непросто. Лично я первым делом выставил десятки сильно недооценным, на мой взгляд, "Проклятью Шалиона", "Ночи в тоскливом октябре" и "Другому ветру".

Сразу предупреждаю, что у литературных премий в чести серьёзное чтиво, поэтому развлекательная фантастика представлена в рейтинге очень слабо. То же, к сожалению, относится и к пионерам фантастического жанра — в рейтинге широко представлена литература, начиная с 60-х годов, более ранняя — с перебоями. (Кстати, волевым волюнтаристским решением я добавил в рейтинг «Властелина колец» с рейтингом 9.0 и «Хоббита» с рейтингом 8.0, а то Толкиен с единственным «Сильмариллионом» выглядел странновато.)

Новых поступлений в рейтинг (включая отечественную фантастику) нет и не будет, пока не найдётся способ более-менее надёжно дать им начальный рейтинг. Если это кому-то интересно (и мне будет не лень) могу дополнительно вкрутить и рейтинг классических романов по тому же принципу.

В общем, enjoy!

UPD Хабрэффект, хабрэффект… 3% CPU, 8% Memory.