Свои Яндекс-Новости с преферансом и куртизанками

Я, как и миллионы сограждан, узнаю новости из топ-5 ленты Яндекс-Новостей на главной поисковика. Одним прекрасным днем я задумался о том, как именно работает их алгоритм.

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

Цель максимум:

Научиться предсказывать темы новостей из топ-5 яндекс-ленты.

Цель минимум:

Научится просто грамотно выделять темы новостей.

Основные шаги такие:

  1. Сбор ссылок на все СМИ, которые участвуют в Яндекс-новостях
  2. Сбор ссылок на RSS-ленты этих ресурсов
  3. Настройка парсера RSS-лент и запись сырой информации о статьях в базу данных (сбор Raw Data)
  4. Составление алгоритма определения тем новостей за определенный период (задача кластеризации) и записи информации в БД (processed data)
  5. Разработка веб-интерфейса с топом всех новостей и различными параметрами


Сбор ссылок на СМИ

Сбор ссылок я осуществлял с помощью программы Content Downloader (http://sbfactory.ru/). Крайне рекомендую! Программа платная, но не дорогая (от 1000 до 2000 рублей в зависимости от кол-ва потоков) Научиться пользоваться не сложно – есть много обучающих материалов и автор активно выходит на связь. Я пользуюсь не самой новой версией, просто потому что привык к старому интерфейсу.

Алгоритм сбора ссылок выглядел следующим образом:

а) сбор ссылок на страницы сайта http://news.yandex.ru/ методом массового обхода всего сайта (есть такая функция в Content Downloader). Весь сайт само собой обойти не удастся да и не нужно. Я собрал несколько тысяч страниц, чего вполне хватило.

б) сбор ссылок на новости СМИ со страниц, собранных выше. Чтобы указать программе, что именно нужно парсить, нужно просмотреть html-код искомого элемента. Нужная ссылка выглядит как-то вот так:

<a class=”b-link” …здесь всякий мусор… href=”http://russian.rt.com/article/68852″ target=”_blank”>Россия в 2015 году укрепит войска в Крыму, Калининграде и Арктике</a>

Указываем программе  границы парсинга:

начало:

<a class=”b-link”{skip}”href=”

конец:

” target=”_blank”>

({skip} – макрос позволяющий включить в границу все, что угодно до тех  пор пока не встретится последовательность символов, которая стоит после него)

Указываем “не включать границы” в парсинг (нам ведь нужна только ссылка а не html код)

Указываем что это повторяющиеся границы (то есть, мы говорим программе, что если найдется много таких границ, собирать нужно последовательно все, что найдется)

Запускаем парсинг. В результате получаем огромный список ссылок на новости.

Но новости сами по себе нам не интересны, нам нужны ссылки на ресурсы.

в) Получение списка СМИ. Воспользуемся старым добрым Excel. Выгружаем список в таблицу. Удаляем все вхождения “http://”  и “www.” через ctrl+h. Далее пользуемся функцией “разделить по столбцам”. Разделителем будет слеш “/”. Удаляем все, кроме первого столбца. Мы почти у цели – осталось удалить дубли (новости то разные, но ресурсы могут быть одинаковыми). Выделяем столбец, жмем “удалить дубли” – готово!

У меня получилось более 2000 самых разных ресурсов.

 

Сбор ссылок на RSS-ленты этих ресурсов

Для меня этот этап оказался самым унылым. Но начнем с начала. Прежде чем собирать список RSS желательно отсортировать список СМИ по популярности. Ведь для теста сойдет и сотня источников, но лучше сразу отобрать самые рейтинговые из них. Выход очень простой – собрать ТИЦ (тематический индекс цитирования Яндекса) для всего списка.

По запросу “массовое определение ТИЦ” на первом же месте видим http://www.raskruty.ru/tools/cy/

То что надо! Делаем три подхода (больше 1000 url за раз нельзя) – копируем результат в новую табличку Excel, сортируем по убыванию значения ТИЦ. Есть!

А теперь, собственно, вопрос знатокам. Как, зная ссылку на ресурс, получить ссылку на его rss-ленту?

Казалось бы, что может быть проще? Ан, нет. Нет никакой закономерности в формате ссылок на rss у ресурсов. Более того страницы rss-лент не индексируются. Поэтому что-то вроде “inurl: site.ru rss” в поисковиках почти никогда не работает.

Оказалось, существует специальный поисковик RSS – http://ctrlq.org/rss/

Однако верную ссылку на RSS при указании запроса в виде site:thenextweb.com  показывает слишком редко.

Потом я вспомнил, что существует Яндекс Лента – она же rss-читалка от Яндекса. Проверив несколько топовых сайтов, я был в полном восторге – вбиваешь ссылку на сайт, получаешь ссылку на rss. Но покопавшись внимательней, обнаружил такие досаднейшие недочеты:

  1. Ссылки на rss часто устаревшие. При клике по такой ссылке, как правило, попадаешь на 404 страницу. А новости, которые показываются Яндексом с такого ресурса, могут датироваться даже 2006 годом.
  2. Для Яндекса сайт c www и без – два разных ресурса.
  3. Бывает не находит ссылку вовсе.

Смирившись с тяжкой судьбой, решил все же автоматизировать процесс поиска rss через яндекс-подписки. Ничего не получилось: проблема авторизации, https соединение и подгрузка rss-ссылки “на лету”. Признаюсь, пробовал даже автокликер, но все впустую.

Пришлось собирать ручками. Если не находил через Яндекс – искал вручную на сайте. В итоге собрал с приятелем около 500 ссылок. Потом решу, как добить остальное.

 

Настройка парсера RSS-лент и запись информации о статьях в базу данных

Я использовал php + mysql для решения задачи.

  1. “Верхний” цикл – обход rss-лент всех имеющихся ресурсов
  2. “Нижний” цикл – обход всех новостей с конкретного ресурса и выявление “свежих”.

Здесь стоит задача определения “свежести” новости. В базе данных заводится таблица “rssurls”, которая состоит из двух полей: rssurl и lastdate.

Для каждого ресурса в базе нужно при каждом обходе записывать дату последней замеченной на ресурсе статьи. Так при следующем обходе скрипт сравнивает дату конкретной статьи из RSS ленты с датой из базы и если время статьи “новее” – записывает статью в базу и обновляет дату последней публикации.

Для статей заводится отдельная таблица “articles”, куда в отдельные поля сохраняется следующая информация: id записи, rssurl, title, date, link, category, record (время записи в базу) и tags(об этом поле – в следующей части).

Title, date, link, category – парсятся из rss. RSS, кто не знает, представляет и себя обыкновенный XML-файл, а значит с ними наверняка легко работать встроенными функциями php. Так и оказалось – есть замечательная функция simplexml_load_file, которая позволяет легко получать необходимые элементы. Но у этой функции есть недостаток – она не предназначена для работы с внешними источниками. По хорошему нужно работать через cURL, но для начала и так сойдет. Около четверти ресурсов отказывались подгружаться через  simplexml_load_file, хотя ссылка в браузере открывалась.

Не смотря на то, что RSS довольно “строг”, формат данных очень часто отличался. Особенно все плохо с датами: кто указывает timezone, а кто нет, кто указывает дни недели, а кто нет  и т.д. Пришлось долго повозиться с регулярными выражениями, чтобы привести все нормальный вид.

 

Составление алгоритма определения тем новостей

Самый интересный и творческий этап. Наверняка можно было бы применить модные и сложные статистические алгоритмы кластеризации. Но мне хотелось попробовать провести анализ “вручную” и в целом – получилось неплохо.

Я решил, что “темы” нужно вычленять из заголовков исходя из частоты упоминаний слов в них. Это довольно очевидно, но считать частоту слов в необработанном виде совершенно бессмысленно. Нужно как-то приводить к начальной форме все слова из конкретных заголовков.

Покопавшись в интернете я наткнулся на потрясающую библиотеку phpmorphy – http://phpmorphy.sourceforge.net/dokuwiki/

Библиотека среди прочего позволяет получать из любого слова на русском языке его базовую форму (по-научному процесс называется “лемматизация”). Это как раз то, что нам нужно!

Используя библиотеку, я написал функцию, которой на вход подается заголовок новости, а на выходе отдается строка из “нормированных” слов. Пример:

“Родители пропавших в Мексике студентов ворвались в казармы в Игуале” превращается в

“СТУДЕНТ РОДИТЕЛЬ ПРОПАСТЬ МЕКСИКА КАЗАРМА ВОРВАТЬСЯ ИГУАЛ”

(все вспомогательные знаки, кроме тире, игнорируются)

Дополняем функцию записи новости в таблицу articles, добавляя в поле tags строку с “нормированным” заголовком.

При первом же подсчете частот слов и их сортировке стало очевидно, что выбран верный путь. Стало ясно, что нужно добавить список стоп-слов, которые сами по себе не могут формировать новостную “тему”, но их частота упоминаний велика:

“НА”, “ЗА”, “ПО”, “ГОД”, “НЕ”, “БЫТЬ”, “ДО”, “ИЗ”, “ИЗА”, “ДЛЯ”, “ИЗ-ЗА”, “СТАТЬ”, “ЧТО”, “БОЛЕЕ”, “МОЧЬ”, “ПРИ”, и ряд других. Удаляем стоп-слова из нормированных заголовков.

Казалось бы после этого – все должно быть хорошо. Но часто попадаются “нормальные” слова, которые нельзя включить в стоп-лист, но которые точно не формируют “тему”.  Это объясняется тем, что одно и тоже слово встречается в заголовках, посвященных разным темам. Особенно часто это касается географических названий.

Встает вопрос как определять “ненастоящие” слова и убирать их из выборки? Интуиция подсказывает, что следует обратить внимание на следующие по популярности слова из заголовков, которые содержат проверяемое слово.

Пример распределения частот для “ненастоящих” слов “новый” и “СМИ”: 

  • 645 – НОВЫЙRplot
  • 44 – РОССИЯ
  • 29 – СТАРЫЙ
  • 28 – УКРАИНА
  • 24 – ДОНБАСС
  • 23 – НОМЕР

 

  • 387 – СМИ
  • 54 – РОССИЯ
  • 43 – РОССИЙСКИЙ
  • 35 – СИЛА
  • 29 – ВОЗДУШНО-КОСМИЧЕСКИЙ
  • 26 – РОСКОМНАДЗОР

 

А теперь “настоящие” слова “Нефть” и “Обстрел” 

  •  359 – НЕФТЬRplot02
  • 152 – ЦЕНА
  • 87 – БАРРЕЛЬ
  • 63 – НИЖЕ
  • 57 – ДОЛЛАР
  • 52 – УПАСТЬ

 

  • 279 – ОБСТРЕЛRplot03
  • 145 – АВТОБУС
  • 79 – ВОЛНОВАХА
  • 50 – ЧЕЛОВЕК
  • 46 – ОБСТРЕЛЯТЬ
  • 43 – ДОНБАСС

 

По форме “хвоста” можно определить “настоящесть” найденной темы.  О формулах – чуть ниже.

Еще была проблема дублирования одних и тех же “тем”. Я решил, что это из-за повторного учета слов. Будем считать, что новость не может быть посвящена разным темам одновременно. Если мы отнесли заголовок к определенной “настоящей” теме, то мы должны удалить ее из выборки, используемой для нахождения последующих тем.

Применив данный подход я практически полностью избавился от проблемы дублирования.

Вернемся к “настоящести”. Я не стал особо изощряться с формулами. Считал среднее геометрическое для значений частот пяти слов из “хвоста” и делил на частоту исходного слова. Эмпирическим путем пришел к коэффициенту 0,17. Если коэффициент “настоящести” равен или выше этого значения, считаем тему “настоящей”.

При нахождении каждой отдельной “настоящей” новости в новую таблицу top записывается “измерение”: название темы, три ключевых слова, частота основного слова, дата начала периода измерения, дата конца периода измерения,  html-код трех ссылок на примеры статей по теме.

  1. Веб-интерфейс с топом всех новостей

Я настроил cron так, чтобы обход всех rss-лент и сбор новостей происходил раз в десять минут.

Сами “измерения” происходят за 6-часовой период раз в 6 часов (пока что). Позже, когда соберем больше ссылок на rss, сделаю периоды меньше а сами измерения чаще. (оптимальные параметры еще предстоит выяснить. Сам Яндекс обновляется раз в 20 минут)

В самом веб-интерфейсе вывожу данные за последний период, где “частоты” сравниваются со значением из предыдущего измерения:

6SXJiDY

 

Задача минимум выполнена. Надеюсь, работа была проделана не зря )

2 thoughts on “Свои Яндекс-Новости с преферансом и куртизанками

  1. Игорь Шевченко

    Очень интересно. Обязательно пишите, если будете что-то продолжать что-то делать на эту тему.

    У Яндекса есть «база СМИ» со списком обрабатываемых сайтов: http://news.yandex.ru/smi/index.html. От нее осознанно отказались или просто не знали?

    Не пытались каким-то образом оценивать качество выделения тем? Было бы интересно сравнить качество работы вашего алгоритма выделения важных слов со стандартным TF-IDF.

    Reply
    1. Firepush Post author

      Продолжать буду, есть новый интересный материал по сбору ссылок на rss-ленты.
      По алгоритму были некоторый доработки (например, игнорирование однокоренных слов в “хвостах”), но они не стоят большого внимания.
      Про базу, честно говоря, не знал. Странно, что не обнаружил сам. Спасибо!
      Качество пока не оценивал, но я подумаю об этом, спасибо.

      Reply

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *