Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #122, ФЕВРАЛЬ 2009 г.

Интерактивный контент для Джеймс Бонда. Сворачиваем горы интернет-контента в режиме онлайн

Валентин Голев (fromxa@va1en0k.net)




Нехилый объем интерактивной информации появляется в интернете каждое мгновение. Твиты (микросообщения по 140 символов, которые пишут пользователи twitter.com), фотки, подкасты, посты в блогах и комментарии к ним, видео на всевозможных ютубах... Попробуй обработай это все в «прямом эфире»!

Как мгновенно вычленять из этого потока именно то, что нужно тебе? На это дело не хватит никаких серверов и никакого трафика. Хорошо, что все уже придумали за нас: на помощь спешит Gnip.

Давай, для начала, разберемся, зачем вообще кому-то может понадобиться обрабатывать такие массивы информации. Рассмотрим типичнейшую жизненную ситуацию: пусть мы - суровые агенты, частные детективы или просто злые хакеры, поставившие перед собой задачу следить за кем-нибудь: опасным преступником или не очень опасной одноклассницей. Или более реальный пример: владельцам сайтов или каких-нибудь брендов нужно как можно раньше узнавать о любых упоминаниях о них в интернете. Яндекс с Гуглом тут не помогут: обновляют они свои базы нечасто, а ведь нам хочется узнавать обо всем здесь и сейчас.

Самый простой способ, первым приходящий в голову: просто запускаем бесконечный цикл, который постоянно бродит по всевозможным сайтам и оглядывает их на предмет нужного нам контента. Беспомощность подхода видна невооруженным глазом. Во-первых, нового сейчас появляется столько, что между двумя нашими запросами мы можем пропустить очень много интересного. Во-вторых, среди этого изобилия доля нужного нам крайне низка, так что куча процессорного времени, памяти, трафика и терпения администраторов сервисов (представь себе, им придется наблюдать за сотнями-тысячами запросов в минуту от одной и той же программы) уйдет вхолостую. Выходит, метод, с одной стороны, не работает, а с другой - сопряжен с огромными затратами. Забудем о нем, как о ночном кошмаре, и вспомним про спешащий нам на помощь Gnip.

Deus ex machine

Что же это за зверь - Гнип? Слоганов у него сразу два: «we got $h*t to pop» и «making data portability suck less», которые, в общем-то, все проясняют, но почти ничего не объясняют. Помимо звучных фраз, на главной странице (http://gnipcentral.com) присутствует проясняющая картинка: слева мы наблюдаем известные сервисы вроде Digg, Twitter и del.icio.us, называющиеся здесь Producers (продюсеры), а справа значатся относительно малоизвестные Consumers (потребители). Gnip получает все новенькое от продюсеров и отсылает потребителям, согласно их запросам: любой клиент может создать различные фильтры и получать только то, что нужно именно ему. Фильтр работает двумя способами: если мы хотим сами обращаться за свежими данными, например, собирая автоматические списки «последние 10 постов про...», Gnip предоставит нам специальный URL, по которому мы сможем обращаться и получать в ответ последние отфильтрованные «активности» (activities) в формате xml; если же нам нужно сразу узнавать о любом новом срабатывании фильтра, достаточно указать в его настройках адрес скрипта (postUrl), и любая новая «активность» будет отсылаться, как POST-данные в формате XML на этот скрипт - останется только написать обработчик.

Помимо postUrl, у каждого фильтра есть:

  • Название, служащее уникальным идентификатором;
  • Галочка fullData, уточняющая, хватит ли нам id поста/твита/видео/какой-то другой сущности или Гнипу стоит присылать полную информацию;
  • Набор правил (rules), по которым будет проходить фильтрация.

Правила бывают свои для каждого сервиса. Обычно можно делать ограничение по авторам (Actor), тегам (Tag), ну и другим, специфичным для продюсера параметрам.

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

Сервисов изначально не так много, включены лишь самые популярные, те, которые могут понадобиться всем: Twitter, del.icio.us, youtube, digg и еще несколько. Присутствует возможность с легкостью создавать своих продюсеров на основе, например, RSS-лент – на офсайте по этому поводу информации достаточно, и рассматривать фичу в статье я не буду.

Чтобы получить доступ к API и «админке» - специальной панели управления фильтрами и продюсерами, возможности которой, впрочем, аналогичны доступным через API, необходимо зарегистрироваться, заполнив несколько обязательных полей. Никакой специфичной информации вроде номера кредитки спрашивать не будут, «премодерации» и «проверки администратором» тоже нет: регистрация проста, доступ ко всем возможностям предоставляется сразу. Пароль запомни или запиши на рукав смирительной рубашки – он понадобится не только для доступа в панель управления, но и при использовании API. После регистрации посмотри на список продюсеров и выбери парочку для теста. Я выбрал youtube и del.icio.us - первый из-за ужасной его популярности, а второй - за простоту добавления контента (закладок на интернет-страницы).

Как я уже сказал, для слежки за любым количеством людей хватит одного фильтра на сервис. Давай же их создадим: назовем, например, MyYFilter и MyDFilter. Галочка fullData должна быть установлена, а в поле Actor введем свой логин (или логины, если у тебя они разные). Чтобы облегчить тестирование, на этих сервисах следить мы пока будем за собой, а не кем-нибудь. Заполним postUrl соответствующей ссылкой (например, http://example.tld/xak/watch/ping.php), по которой будет находиться скрипт-обработчик ping.php, – сейчас мы его напишем. Не забудь создать этот скрипт, хотя бы пустой: если Gnip при проверке словит ошибку, он может отказаться сохранять новый фильтр.

Пишем «связного»

Итак, Гнип свою работу уже выполняет, а нам надо делать свою. Чтобы знать, с чем придется работать, можно прислать себе приходящие POST-данные на почту. Они идут, к сожалению, не обычной строкой вида var1=val1&var2=val2, а в виде xml. Читать их придется из stdin:

$stdin = fopen("php://stdin", "r");
$pst = fread($stdin, getenv("CONTENT_LENGTH"));
mail("your@ma.il", "Ух ты, что нам пришло!", $pst);

Теперь можно и нужно:

  • залить этот нехитрый скрипт на сервер (не забыв сохранить его в UTF-8, дабы кодировка темы письма совпадала с кодировкой содержимого, присылаемого Гнипом в юникоде);
  • «пнуть» его добавлением какой-нибудь ссылки на del.icio.us (стоит заполнить все доступные поля в описании закладки, и тогда можно увидеть полную структуру данных, присылаемых Гнипом);
  • подождать несколько секунд, пока письмо дойдет до почтового ящика.

Содержимое письма будет примерно следующим:

Такую пургу присылает нам Гнип

<?xml version="1.0" encoding="UTF-8"
standalone="yes"?><activities publisher="delicious"><activity tags="ляляля,тралялял" source=
"http://feeds.delicious.com/v2/rss/va1en0k
" regarding="http://twitter.com/" url=
"http://delicious.com/url/
1cc089548931c4fe0463e7a98ec6078e#va1en0k" action=
"bookmark" actor="va1en0k" at=
"2009-01-11T12:13:23.000Z"><payload>
<body>Твиттер какой-то</body><raw>H4sIAGbiaUkAA52S3UrDMBiGz3cVoYIn6tKfubWz65FH4oHoFWTptxn6k5Kk
imdjN+A9eAUiCDph15DekWnXbroDwQUCIfnyPC9fEvJCRT2EwqKcXhIF0V2Z
nyLHQVckR65tB8hxx443dj10YpsR4q6wvkTNYs7FE4p5Rlg+se6VKsYYx5Ay
yngp+5Rn+IE4kNsJtiL9VT13M8Td7YNQ1bJa6NcdcB/HswxydStl1IJmALHs
7+FcLKTEpUixQ6ntB+cDP/AcOpiBPRh6MCKBD3Roj3ww/B2yMQggiouojWTO
240f+q38t/Zfvo1tXrIYMXkDIiPXLE8m1oykEqzDBUfb4DW7kSimUoj0i37T
H6a/S/1eLZBemTav9Fp/npmddYg3VXV9aoJ0AdQjUwpEow9xc1KXSF4KCsgE
2r7oHw/RRrK6ph6TgssLiaacJxkRiQzxBhj1Qtz83G8pwUzRwAIAAA==
</raw></payload></activity></activities>

Замечательно: самый обычный XML, который легко распарсить встроенными в PHP функциями и классами! Я выбрал SimpleXMLElement, поскольку он легкий и воздушный, как йогурт. Информация о закладке нам доступна почти вся: название (в теге body) и теги, ссылка на закладку, сохраненная ссылка, автор закладки и дата ее сохранения как атрибуты тега activity: tags, url, regarding, actor и at, соответственно. Стоит обратить внимание на то, что url-закладки и сохраненный в ней url - разные вещи, причем сохраненный url хранится в свойстве regarding. По regarding тоже можно фильтровать: указав в этом правиле, например, «http://xakep.ru/", мы бы узнавали обо всех новых закладках на портал.

Что ж, надо расшифровать пришедшие данные и отправить в «генштаб» письмо с информацией. Сначала создадим объект $parsedxml класса SimpleXMLElement конструктором с единственным аргументом: строкой, содержащей в себе XML, которую мы взяли из stdin. Все теги, вложенные в <activities>, будут свойствами этого объекта; а чтобы получить атрибуты какого-нибудь из них, нужно обратиться к методу attributes(), который вернет их в виде другого объекта. Например, $parsedxml->activity->attributes()->regarding содержит в себе сохраненный в закладке URL. Вначале проверим, что данные пришли именно с del.icio.us (так как они могли прийти и с youtube), и напишем, наконец, письмо:

$parsedxml = new SimpleXMLElement($pst);
if ($parsedxml->attributes()->publisher == "delicious")
{
mail("your@ma.il", "Ссылка в del.icio.us от
".$parsedxml->activity->attributes()->actor,
"Юстас - Алексу
Только что подопытный ".$parsedxml->activity->
attributes()->actor." сохранил на del.icio.us
ссылку на ".$parsedxml->activity->attributes()->
regarding.", назвав ее ".$parsedxml->activity->
payload->body." и обозначив тегами ".$parsedxml->
activity->attributes()->tags."."
);
}

Заливаем на сервер и снова сохраняем ссылку в делишсе - на сей раз какую-нибудь другую. Если все спрограммировано хорошо и Гнип не подкачает, то вскоре в ящик упадет письмо вроде:

Юстас - Алексу
Только что подопытный va1en0k сохранил на del.icio.us ссылку на http://gnipcentral.com/, назвав ее Gnip: We got $h*t to pop и обозначив тегами gnip,tool.

Если письмо не придет – сочувствую. Тебе предстоит долгая и упорная отладка через логи или почту. Ну а если все прошло нормально, можно радоваться, а потом дописывать аналогичный код для youtube, памятуя, что атрибуты и теги у видеохостинга могут оказаться другими (например, «regarding» не будет). Советую точно также послать к себе на почту пришедший после заливки какого-нибудь видео запрос или просто взять код с нашего диска.

Больше и больше

Порадовавшись работающей слежке за ютубом, пора задуматься и о «масшабируемости». Например, совсем не дело - наблюдать за одним-единственным человеком. Людей много, но каждый конкретный нечасто заливает видео и сохраняет ссылки, так что наш скрипт должен справляться с более значительным объемом работы. Как я уже упомянул, чтобы следить сразу за несколькими людьми, можно дописать их логины через запятую в правиле Actor-фильтров. Самый простой способ это сделать - открыть админку, нажать [view my filters], там щелкнуть по «edit», изменить фильтр и сохранить его.

Но мы же писали программу, которая должна автоматизировать наши действия - а тут приходится выполнять столько ненужного, лишнего и неудобного! Gnip не зря предоставляет пользователям очень даже неплохой и мощный API для работы с фильтрами. Наша прямая обязанность – им воспользоваться.

Интерфейс устроен не так уж сложно. Полное его описание (почему-то хранящееся в Google Docs) можно найти по ссылкам прямо с главной страницы http://gnipcentral.com - кликать надо по картинке «Data Consumers click here». Выглядит оно как GET- и POST-запросы через HTTPS к https://prod.gnipcentral.com, причем POST-данные должны быть в виде XML. Однако разбираться в этом интерфейсе вовсе не обязательно. Зачем, если есть фреймворки для кучи языков программирования?

Заботливые ребята из Gnip написали их сразу для .NET, Java, Python, Perl, PHP и Ruby. Эти библиотеки можно скачать с http://github.com/gnip (или взять с диска) и почаще обновлять там же (пишутся они чуть ли не в режиме «онлайн», равно как и серверная часть Гнипа). Где-то внутри архива для PHP таится папка Services: ее содержимое – папку Gnip и файл Gnip.php – нам придется распаковать куда-нибудь на сервер, а рядом создать скрипт add.php, ответственный за добавление логинов в правила фильтров.

Вся библиотека сосредоточена в классе Services_Gnip. Нам нужно создать его экземпляр, сообщив конструктору e-mail и пароль от Гнипа, после чего можно начинать работу с ним. Изменение фильтра проходит просто: получаем его в виде объекта класса Services_Gnip_Filter при помощи свойства getFilter($publisher, $name) – название сервиса, например, «delicious», и название фильтра: "MyDFilter". После чего меняем в нем все, что хотим, кроме названия - характеристики фильтра хранятся в виде свойств: например, rules - массив правил; впоследствии можно сохранять его при помощи updateFilter($publisher, $filter). Рассмотрим пример:

$f = $gnip->getFilter("delicious", "MyDFilter");
$f->rules[0]->value .= ", ivanov";
echo $gnip->updateFilter("delicious", $f);

Метод updateFilter возвращает строку ответа от сервера, в случае удачи это будет «Success». В приведенном примере я вывожу ее на экран. Для проверки можно зайти в админку Гнипа и посмотреть, действительно ли добавился логин в правило Actor - жмем [view my filters] в строке с каким-нибудь сервисом и рядом с нашим фильтром щелкаем на edit. Добавляется? Вот и отлично!

Полный код add.php аккуратненько лежит на диске вместе с фреймворком Gnip’а для PHP. Версия, которая там, позволяет выбрать, за кем и на каком сервисе следить. Весь код прост и занимает 24 строчки вместе с HTML-формочкой. Не думаю, что у тебя возникнут проблемы с разбором и адаптацией данного сочинения под свои нужды. Например, можно реализовать возможность удаления пользователей из фильтра. Тут все почти так же, как и с добавлением. Разве что не получится просто дописать логин к строке - придется разбивать ее при помощи explode, находить логин пользователя, а потом собирать обратно implode’ом. Единственное, что не стоит забывать - если вдруг тебе приспичит удалить всех пользователей, на почтовый ящик письма пойдут лавинами (фильтрации не будет вообще).
А вот задачка посложнее: сделать возможность выборок еще и по тегам, по url-ам, по чему угодно; для этого придется разобраться с созданием новых фильтров. Чтобы создать фильтр, нужен объект типа Services_Gnip_Filter:

$newfilter = new Services_Gnip_Filter($name, $fulldata, $postUrl, $rules);

Первый параметр - строка, второй - булевый тип. Затем снова идет строка (пустая, если пинговать не нужно) и массив правил. Каждое правило - объект типа Services_Gnip_Rule, который создается конструктором с двумя параметрами:

  • Тип (например, Actor, Regarding, To; список есть на страничке создания фильтра, для каждого из паблишеров список свой).
  • Значение (с ним все, как обычно; логины, например, или теги через запятую).

После создания фильтра в виде объекта, просим Гнип сохранить его у себя:

$gnip->createFilter($publisher, $newfilter)

Первый параметр здесь - название сервиса. Этот метод вернет строку с результатом (если все хорошо, то success). За подробностями можно обратиться в исходные коды библиотеки: их не так много, они хорошо структурированы и достаточно легко читаются.

Что-нибудь еще?

Разумеется, одним пингованием Gnip не ограничивается. Его авторы обещают нам немало хороших фич, но даже и среди сравнительно скудного количества готовых есть где развернуться. Скажем, нам необязательно создавать фильтр, который будет запрашивать твой скрипт; можно просто попросить его предоставить поток и обращаться к нему уже как хотим и когда хотим. Это полезно, если нет хостинга. Невероятно крут и тот факт, что нам можно создавать своих Publishers, которые могут базироваться, допустим, на RSS-фиде: останется лишь немного дописать скрипты, чтобы узнавать еще и о новых постах в блогах наших подследственных.
Короче, возможностей масса, да и применений не меньше.

Продвинутый API Гнипа позволяет писать приложения, которым для работы вообще не понадобится «человеческая рука» - не то, что многие сервисы, для которых изменение любой настройки можно проводить лишь через web-интерфейс. В наш век высоких скоростей и тонн информации Gnip невероятно актуален. Как разруливать такие потоки без него - непонятно. Среди обещанных фич пророчат возможность отдать Гнипу заботу об API и RSS, что позволит разработчикам не отвлекаться по мелочам, а пользователям - получить высоконастраиваемые фиды, а также намечается Gnip Identification – нечто, занимающееся связью и управлением профилями пользователей на разных сайтах. Еще нам обещают возможность авторизации на сервисе с использованием аккаунтов на других ресурсах - что-то типа продвинутого OpenID. В общем, будем ждать подробного описания и реализации. Удачного управления потоками данных!

CD

На диске к журналу, если постараться, можно отыскать всевозможные исходные коды к статье.

WWW

Все-все доки и библиотеки, упомянутые в статье, можно скачать, поискав на http://gnipcentral.com.

INFO

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

WARNING

Трудно сказать, насколько такая слежка законна, но, поскольку все данные публичны, вряд ли будут проблемы. Разве что по лицу получишь :).

Содержание