Яндекс.Метрика

    Реклама

    Рейтинг фантастических романов, или Я сделаю свой «IMDB для книг», с преферансом и библиотекаршами

    Долго выбирал между «Алгоритмами», «Читальным залом» и «Я пиарюсь», в итоге остановился на 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