Свои Яндекс-Новости
By Firepush |
Я, как и миллионы сограждан, узнаю новости из топ-5 ленты Яндекс-Новостей на главной поисковика. Одним прекрасным днем я задумался о том, как именно работает их алгоритм.
Подумав, что не все так страшно, решил попробовать сделать свой собственный агрегатор. Но я, признаюсь, довольно меркантильный и не стал бы тратить свое время просто ради развлечения. Отлично! Осталось придумать как можно на этом заработать: если научится предсказывать горячие темы и создать собственный новостной портал, то можно довольно быстро набрать трафик с поисковых запросов.
Цель максимум:
Научиться предсказывать темы новостей из топ-5 яндекс-ленты.
Цель минимум:
Научится просто грамотно выделять темы новостей.
Основные шаги такие:
Сбор ссылок на все СМИ, которые участвуют в Яндекс-новостях
Сбор ссылок на RSS-ленты этих ресурсов
Настройка парсера RSS-лент и запись сырой информации о статьях в базу данных (сбор Raw Data)
Составление алгоритма определения тем новостей за определенный период (задача кластеризации) и записи информации в БД (processed data)
Разработка веб-интерфейса с топом всех новостей и различными параметрами
Сбор ссылок на СМИ
Сбор ссылок я осуществлял с помощью программы 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. Но покопавшись внимательней, обнаружил такие досаднейшие недочеты:
Ссылки на rss часто устаревшие. При клике по такой ссылке, как правило, попадаешь на 404 страницу. А новости, которые показываются Яндексом с такого ресурса, могут датироваться даже 2006 годом.
Для Яндекса сайт c www и без – два разных ресурса.
Бывает не находит ссылку вовсе.
Смирившись с тяжкой судьбой, решил все же автоматизировать процесс поиска rss через яндекс-подписки. Ничего не получилось: проблема авторизации, https соединение и подгрузка rss-ссылки “на лету”. Признаюсь, пробовал даже автокликер, но все впустую.
Пришлось собирать ручками. Если не находил через Яндекс – искал вручную на сайте. В итоге собрал с приятелем около 500 ссылок. Потом решу, как добить остальное.
Настройка парсера RSS-лент и запись информации о статьях в базу данных
Я использовал php + mysql для решения задачи.
“Верхний” цикл – обход rss-лент всех имеющихся ресурсов
“Нижний” цикл – обход всех новостей с конкретного ресурса и выявление “свежих”.
Здесь стоит задача определения “свежести” новости. В базе данных заводится таблица “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-код трех ссылок на примеры статей по теме.
Веб-интерфейс с топом всех новостей
Я настроил cron так, чтобы обход всех rss-лент и сбор новостей происходил раз в десять минут.
Сами “измерения” происходят за 6-часовой период раз в 6 часов (пока что). Позже, когда соберем больше ссылок на rss, сделаю периоды меньше а сами измерения чаще. (оптимальные параметры еще предстоит выяснить. Сам Яндекс обновляется раз в 20 минут)
В самом веб-интерфейсе вывожу данные за последний период, где “частоты” сравниваются со значением из предыдущего измерения:
6SXJiDY
Задача минимум выполнена. Надеюсь, работа была проделана не зря )