14.04.2009Фильтрация rss-потоков с помощью Sinatra и HTTParty
Задача
Для фильтрации rss-потоков сужествует множество инструментов. Для своей задачи мне захотелось написать простейшее решение и заодно попробовать пару новых инструментов.
Надо: собрать воедино несколько единообразных rss-потоков, отфильтровав только нужное, и выдать единый rss-поток.
Для удобства предположим, что потоки имеют одинаковый формат — atom. Адреса нужных нам потоков будут находиться в текстовом файле, разделенные переносом строки. Так же как и необходимые нам ключевые слова. Так же допустим, что наличие ключевых слов будем отслеживать в заголовках.
Ресурсы
Поскольку я собираюсь фильтровать на лету, мне не нужно ничего нигде хранить, я решил попробовать лёгкий руби-фреймворк под названием Sinatra.
А для работы с самими потоками, для получения их с их серверов используем простой и удобный инструмент HTTParty.
sudo gem i sinatra
sudo gem i httparty
Сбор и фильтрация
Создадим библиотечный файл feed_fetcher.rb:
require 'rubygems'
require 'httparty'
class FeedFetcher
include HTTParty
format :xml # позволяет получть результат сразу расфасованный
# в Hash
def self.get_items
urls = nil # будет массив адресов
titles = nil # будет массив нужных частей заголовков
items = [] # будет массив записей
File.open("path_to_feed_urls_file") do |f|
urls = f.readlines.each(&:strip!)
end
File.open("path_to_titles_file") do |f|
titles = f.readlines.each(&:strip!)
end
# составим единое регулярное выражение для фильтрации
retitles = Regexp.union(titles.reject(&:empty?).map { |t| %r{\b#{Regexp.escape(t)}\b}i })
# соберём записи со всех адресов в единый масив
urls.each do |u|
items += get(u)["rss"]["channel"]["item"] unless u.empty?
end
# отфильтруем по регулярному выражению и упорядочим по дате
items.select { |i| i["title"] =~ retitles }.sort do |x, y|
DateTime.parse(y["pubDate"]) <=> DateTime.parse(x["pubDate"])
end
end
end
Выдача результата
Результат будем так же выдавать в формате atom, поэтому нам понадобится builder, который, например, входит в состав active_support. Но можно установить его и отдельно.
Файл feed_filter.rb:
require 'rubygems'
require 'sinatra'
require 'active_support'
require 'feed_fetcher.rb'
get '/' do
content_type 'application/xml', :charset => 'utf-8'
@items = FeedFetcher.get_items
builder :index
end
По-умолчанию Sinatra хранит шаблоны в папке views. Файл views/index.builder:
xml.instruct!
xml.rss "version" => "2.0", "xmlns:atom" => "http://www.w3.org/2005/Atom" do
xml.channel do
xml.title "My Filtered Feed"
xml.link "http://lonelyelk.com"
xml.pubDate CGI::rfc1123_date Time.parse(@items.first["pubDate"]) if @items.any?
xml.description "Some description"
@items.each do |item|
xml.item do
item.each_pair do |key, value|
xml.tag!(key, value)
end
end
end
end
end
Запуск приложения с помощью passenger
Для запуска приложения мы будем использовать Passenger, который поддерживает не только rails, но и rack. Для этого нам понадобится создать папку public и указать к ней путь.
В установках виртуального сервера для apache:
<VirtualHost *:80>
ServerAdmin webmaster@mydomain.ru
ServerName feedfilter.mydomain.ru
DocumentRoot /path/to/feed_filter/public
...
</VirtualHost>
А в папке приложения нужно создать файл config.ru:
require 'rubygems'
require 'sinatra'
Sinatra::Application.set(:run, false)
Sinatra::Application.set(:environment, ENV['RACK_ENV'])
require 'feed_filter'
run Sinatra::Application
Вот и всё. Естественно, ещё следует написать тесты. Так же для публикации можно использовать capistrano. Но это, я думаю, всем под силу.