Навеяно статьей «Google продолжает уничтожать RSS».
Некоторое время назад возникла у меня надобность читать несколько rss-лент. Вопрос для меня был относительно новым, ранее с rss я дело имел весьма эпизодически, так что начал изучать эту тему и выбирать ридер с чистого листа.
Результаты были… скажем так — не радующими.
На облачные сервисы с веб-интерфейсами, у меня хроническая аллергия пополам с паранойей, и последовавшие похороны гугльридера лишний раз подтвердили, что паранойя плохого не посоветует. Да и регулярные изменения привычных интерфейсов у веб-сервисов тоже не радуют.
Среди локально ставящихся ридеров нашлось несколько штук, которыми я попытался пользоваться, но ни один из них так и не был выбран в качестве рабочего. Тут и раздражающие мелочи, вроде плохо настраиваемого интерфейса, убогих инструментов по фильтрации или странная логика сохранения; и более глобальная проблема с тем, что одной из лент, которые хотелось смотреть, были флибустянские отзывы о книгах, где за сутки запросто может обновиться вся лента, и часть постов в итоге будет пропущена; постоянно же держать ридер запущенным — совершенно не вдохновляющий вариант. А кроме того зоопарк из разнородных читалок на машине разводить не хотелось совершенно.
И тогда, после некоторого времени размышлений о несовершенстве этого мира, и формулирования хотелок — в руки привычно был взят напильник и пара дней была посвящена велосипедостроению.
Результат успешно работает уже год с лишним. Возможно, готовый рецепт кому-то сэкономит время и силы, хотя конечно, рецепт может подойти далеко не всем, поскольку основан он был на том, что уже имелся домашний сервер под Centos 6, круглосуточно подключенный к интернету.
Итак, для построения собственного агрегатора из спичек и желудей, кроме собственно сервера, мне понадобились дополнительно пакеты:
rsstool
ssmtp
fdupes
… а также некоторое время и желание повозиться с perl.
В итоге был написан довольно примитивный скрипт, приводить который полностью нет особого смысла, достаточно будет общей логики работы:
После запуска скрипт считывает список лент из текстового конфига в виде:
habrahabr#http://habrahabr.ru/rss — то есть заголовок, который будет использоваться дальше для работы с лентой, разделитель "#" и сам адрес ленты. Все это, плюс сгенерированные по заголовку имена файлов для сохранения промежуточных данных, заносится в несколько массивов. Затем по этим массивам проходимся foreach(), в котором выполняются следующие действия:
1. Через rsstool скрипт скачивает саму ленту в виде csv с разделителями @, iconv конвертирует скачанное в кодировку koi-8, grep отрезает заголовок, и все это сохраняется в файл для свежескачанного.
2 Натравливаем diff на файл с архивом ленты и файл со свежескачанным. Разница между ними, помеченная ">" и будет требующимися нам новостями, которые появились с момента последнего скачивания. Эта разница записывается в отдельный файл, а также в архив ленты — чтобы при следующем скачивании эти сообщения были отмечены как уже полученные.
3. Теперь можно начинать радоваться — мы получили файл habrahabr.diff с последними сообщениями ленты, и теперь с этим файлом мы можем сделать вообще всё. Что и делаем, построчно считывая файл, а затем, разбирая строчки вида:
«Хабрахабр / Захабренные / Тематические / Посты»@«1399799340»@«habrahabr.ru/post/222391»@«Google продолжает уничтожать RSS»@«На этой неделе, а именно 8 мая, Google отключил RSS-ленту <...cut...>. Читать дальше →»
… создаем в отдельном каталоге файлы согласно формату RFC2822. После этого скрипт продолжает свою работу, обрабатывая следующую ленту в списке, или же засыпает минут на -дцать; а сформированные письма, тем временем, обрабатывает запускающийся по cron'у второй скрипт. Он при помощи fdupes удаляет в каталоге с файлами RFC2822 все одинаковые файлы, кроме одного… (Да, знаю, это костыль. Да, стыдно. Но проблема с тем, что отдельные дублирующиеся строки, несмотря ни на что, всё же не вычищаются diff'ом, всплыла несколько позже написания скрипта, а времени разбираться не нашлось.)… а затем, используя ssmtp, отправляет их на специально заведенный почтовый ящик.
И теперь, получив ленту на почту, мы сможем спокойно, не страдая от очередного приступа креативности дизайнеров и судьбоносных решений менеджеров, читать её везде, в одном и том же привычном и любимом почтовом клиенте (том же самом TheBat!, например), пользуясь при этом всем богатым инструментарием в виде фильтров, сортировок, поиска и прочих полезностей, которые сделают чтение ленты удобнее и эффективнее.
UPD: Немножко причесав и откомментировав выкладываю собственно скрипты. Первый делает собственно скачивание лент и создает файлы с постами для отправки на почту, второй — отправляет их. Оба запускаются по cron. Ну и плюс кусочек конфига для примера.
Некоторое время назад возникла у меня надобность читать несколько rss-лент. Вопрос для меня был относительно новым, ранее с rss я дело имел весьма эпизодически, так что начал изучать эту тему и выбирать ридер с чистого листа.
Результаты были… скажем так — не радующими.
На облачные сервисы с веб-интерфейсами, у меня хроническая аллергия пополам с паранойей, и последовавшие похороны гугльридера лишний раз подтвердили, что паранойя плохого не посоветует. Да и регулярные изменения привычных интерфейсов у веб-сервисов тоже не радуют.
Среди локально ставящихся ридеров нашлось несколько штук, которыми я попытался пользоваться, но ни один из них так и не был выбран в качестве рабочего. Тут и раздражающие мелочи, вроде плохо настраиваемого интерфейса, убогих инструментов по фильтрации или странная логика сохранения; и более глобальная проблема с тем, что одной из лент, которые хотелось смотреть, были флибустянские отзывы о книгах, где за сутки запросто может обновиться вся лента, и часть постов в итоге будет пропущена; постоянно же держать ридер запущенным — совершенно не вдохновляющий вариант. А кроме того зоопарк из разнородных читалок на машине разводить не хотелось совершенно.
И тогда, после некоторого времени размышлений о несовершенстве этого мира, и формулирования хотелок — в руки привычно был взят напильник и пара дней была посвящена велосипедостроению.
Результат успешно работает уже год с лишним. Возможно, готовый рецепт кому-то сэкономит время и силы, хотя конечно, рецепт может подойти далеко не всем, поскольку основан он был на том, что уже имелся домашний сервер под Centos 6, круглосуточно подключенный к интернету.
Итак, для построения собственного агрегатора из спичек и желудей, кроме собственно сервера, мне понадобились дополнительно пакеты:
rsstool
ssmtp
fdupes
… а также некоторое время и желание повозиться с perl.
В итоге был написан довольно примитивный скрипт, приводить который полностью нет особого смысла, достаточно будет общей логики работы:
После запуска скрипт считывает список лент из текстового конфига в виде:
habrahabr#http://habrahabr.ru/rss — то есть заголовок, который будет использоваться дальше для работы с лентой, разделитель "#" и сам адрес ленты. Все это, плюс сгенерированные по заголовку имена файлов для сохранения промежуточных данных, заносится в несколько массивов. Затем по этим массивам проходимся foreach(), в котором выполняются следующие действия:
1. Через rsstool скрипт скачивает саму ленту в виде csv с разделителями @, iconv конвертирует скачанное в кодировку koi-8, grep отрезает заголовок, и все это сохраняется в файл для свежескачанного.
rsstool --wget --csv=@ @url[$i](то есть http://habrahabr.ru/rss) | iconv -c -f UTF-8 -t KOI8-R| grep SITE\@DATE\@URL\@TITLE\@DESC -v >/path_to_rss2email/feed_new_@rssname[$i].rss (то есть /path_to_rss2email/feed_new_habrahabr.rss)
2 Натравливаем diff на файл с архивом ленты и файл со свежескачанным. Разница между ними, помеченная ">" и будет требующимися нам новостями, которые появились с момента последнего скачивания. Эта разница записывается в отдельный файл, а также в архив ленты — чтобы при следующем скачивании эти сообщения были отмечены как уже полученные.
diff -iaEbwB --strip-trailing-cr $path_temp/@rssarcfile[$i] (т.е. /path_to_rss2email/habrahabr.rss) $path_temp/feed_new_@rssname[$i].rss (т.е. /path_to_rss2email/feed_new_habrahabr.rss) | grep ^\\>\\ >$path_temp/@rssdiffer[$i] (т.е. /path_to_rss2email/habrahabr.diff)
3. Теперь можно начинать радоваться — мы получили файл habrahabr.diff с последними сообщениями ленты, и теперь с этим файлом мы можем сделать вообще всё. Что и делаем, построчно считывая файл, а затем, разбирая строчки вида:
«Хабрахабр / Захабренные / Тематические / Посты»@«1399799340»@«habrahabr.ru/post/222391»@«Google продолжает уничтожать RSS»@«На этой неделе, а именно 8 мая, Google отключил RSS-ленту <...cut...>. Читать дальше →»
… создаем в отдельном каталоге файлы согласно формату RFC2822. После этого скрипт продолжает свою работу, обрабатывая следующую ленту в списке, или же засыпает минут на -дцать; а сформированные письма, тем временем, обрабатывает запускающийся по cron'у второй скрипт. Он при помощи fdupes удаляет в каталоге с файлами RFC2822 все одинаковые файлы, кроме одного… (Да, знаю, это костыль. Да, стыдно. Но проблема с тем, что отдельные дублирующиеся строки, несмотря ни на что, всё же не вычищаются diff'ом, всплыла несколько позже написания скрипта, а времени разбираться не нашлось.)… а затем, используя ssmtp, отправляет их на специально заведенный почтовый ящик.
И теперь, получив ленту на почту, мы сможем спокойно, не страдая от очередного приступа креативности дизайнеров и судьбоносных решений менеджеров, читать её везде, в одном и том же привычном и любимом почтовом клиенте (том же самом TheBat!, например), пользуясь при этом всем богатым инструментарием в виде фильтров, сортировок, поиска и прочих полезностей, которые сделают чтение ленты удобнее и эффективнее.
UPD: Немножко причесав и откомментировав выкладываю собственно скрипты. Первый делает собственно скачивание лент и создает файлы с постами для отправки на почту, второй — отправляет их. Оба запускаются по cron. Ну и плюс кусочек конфига для примера.
rsstoemail.pl
#! /usr/bin/perl use MIME::Base64; $path_temp="/home/media/rsstool"; # каталог для временных файлов/архивов # считываем файл конфигурации и заносим разобранную строчку в массивы - заголовок ленты, url ленты, файл с архивом ленты open(config,"</etc/rss/rsstoemail.conf"); $numb=0; while (!eof(config)) { $strcnf=<config>; $strcnf=~tr/\n//d; (@rss[$numb],@rssname[$numb],@rssfeeds[$numb])=split "#", $strcnf; @rssarcfile[$numb]="@rssname[$numb].rss"; @rssdiffer[$numb]="@rssname[$numb].diff"; $numb=$numb+1; } close config; # считываем файл конфигурации и заносим разобранную строчку в массивы - заголовок ленты, url ленты, файл с архивом ленты # если файла для архива ленты нет - создаем его foreach $srtname1(@rssarcfile) { if (! -e "$path_temp/$srtname1") { `echo >$path_temp/$srtname1`; } } # если файла для архива ленты нет - создаем его # проверим нет ли уже запущенной копии если есть - выходим $run=`ps -AH|grep -c rsstoemail.pl`; $run=~tr/\n//d; if ($run ne "1") { exit; } # проверим нет ли уже запущенной копии если есть - выходим # обрабатываем список лент foreach $i(@rss) { # если в строчке из списка что-то есть if (@rss[$i] ne "") { $chkdiff=""; # обнуляем дифф-файл с новыми постами `echo >$path_temp/@rssdiffer[$i]`; printf "run rsstool\n"; # скачиваем ленту, конвертируем текст в koi-8, вырезаем заголовок и сбрасываем во временный файл `/sbin/myscript/rsstool --wget --csv=@ @rssfeeds[$i] | iconv -c -f UTF-8 -t KOI8-R| grep SITE\@DATE\@URL\@TITLE\@DESC -v >$path_temp/feed_new_@rssname[$i].rss`; # сравниваем временный файл с архивом ленты, результаты записываем в дифф `diff -iaEbwB --strip-trailing-cr $path_temp/@rssarcfile[$i] $path_temp/feed_new_@rssname[$i].rss | grep ^\\>\\ >$path_temp/@rssdiffer[$i]`; # проверяем сколько в получившемся диффе строчек $chkdiff=`grep \@ $path_temp/@rssdiffer[$i] -c`; $chkdiff=~tr/\n//d; # если в нем что-то есть if ($chkdiff ne "0") { my @toemail = (); my @original = (); # открываем дифф, считываем построчно, из строчек вырезаем > оставшийся после diff # результат заносим в массив для обработки и массив с исходником open(diffopen,"<$path_temp/@rssdiffer[$i]"); while (!eof(diffopen)) { $string = <diffopen>; $string=~s/^(\>\ )//; $string=~tr/\n//d; push (@toemail, $string); push (@original, $string); } close diffopen; # открываем дифф, считываем построчно, из строчек вырезаем > оставшийся после diff # результат заносим в массив для обработки и массив с исходником # отправка на почту # создаем на основе даты и времени уникальную строку и сообщаем когда и сколько было новых постов - для отладки $dirr=`date '+%Y-%m-%d-%H_%M_%S'`; $newm=$#toemail+1; printf "$dirr new messages $newm\n"; # создаем на основе даты и времени уникальную строку и сообщаем когда и сколько было новых постов - для отладки # проходим по массиву с новыми постами foreach $difs(@toemail) { # обрабатываем строчку перед созданием письма # убираем символы " $difs=~tr/\"//d; # убираем конечный пробел $difs=~s/\ $//; $nmf=""; $date=""; $url=""; $bookname=""; $body=""; $resser=""; $ssurl=""; $sndusr=""; # разбираем строку в переменные ($nmf,$date,$url,$bookname,$body)=split '@', $difs; # дополнительная обработка текста, отправляемого на почту # в тексте для ленты отзывов флибусты и либрусека вырезаем и заносим в переменную ник оставившего отзыв if (@rssname[$i] eq "flibusta" or @rssname[$i] eq "librusec") { $body=~m/(.+?)(\ про\ )/; $sndusr=$1; $sndusr=~tr/\n//d; } # в тексте для ленты отзывов флибусты и либрусека вырезаем и заносим в переменную ник оставившего отзыв # в ленте отзывов и новых поступлений флибусты url меняем на единообразный (поскольку там может быть # несколько вариантов proxy.flibusta.net, flibusta.net, www.flibusta.net ) и делаем сразу url для скачивания if (@rssname[$i] eq "flibusta" or @rssname[$i] eq "flibustanewbooks") { ($resser,$ssurl)=split "\/b\/",$url; $resser=~m/(flibusta.net)/; if ($1 eq "flibusta.net") { $surl="http://flibusta.net/b/$ssurl/download\n"; } else { $surl=$url; } } else { $surl=$url; } # в ленте отзывов и новых поступлений флибусты url меняем на единообразный (поскольку там может быть # несколько вариантов proxy.flibusta.net, flibusta.net, www.flibusta.net ) и делаем сразу url для скачивания # в ленте новых поступлений флибусты изменяем строку в формат название книги - автор - жанр if (@rssname[$i] eq "flibustanewbooks") { ($book_auth,$book_name,$book_genre)=split "- ",$bookname; $bookname="$book_name - $book_auth - $book_genre"; } # в ленте новых поступлений флибусты изменяем строку в формат название книги - автор - жанр # дополнительная обработка текста, отправляемого на почту # строку заголовка кодируем в base64 и добавляем строку с кодировкой для поля SUBJ: $booknameenc=encode_base64("$bookname my_@rssname[$i]_rss"); $booknameenc=~tr/\n//d; $booknameenc="\=\?KOI8\-R\?B\?$booknameenc\?\="; # строку заголовка кодируем в base64 и добавляем строку с кодировкой для поля SUBJ: # спим секунду и генерируем уникальную строку на основе времени sleep 1; $dirr=`date '+%Y-%m-%d-%H_%M_%S'`; # спим секунду и генерируем уникальную строку на основе времени # создаем файл с письмом в каталоге для отправки почты с именем вида msgrss_2014-05-20-19_26_09 и начинаем записывать туда текст письма open(fopen,">>$path_temp/mail/msgrss_$dirr"); print fopen "From\:\ fromemail\@domen\.ru\n"; print fopen "To\:\ toemail\@gmail\.com\n"; print fopen "Subject\: $booknameenc\n"; print fopen "MIME-Version: 1.0\n"; print fopen "Content-Type: multipart/mixed;\n"; print fopen " boundary=\"----------12012917B16D15D68\"\n"; print fopen "------------12012917B16D15D68\n"; print fopen "Content-Type: text/plain; charset=koi8-r\n"; print fopen "Content-Transfer-Encoding: 8bit\n"; print fopen "\n"; print fopen "$nmf\n"; print fopen "$surl\n"; print fopen "$bookname\n"; print fopen "\n"; if ($sndusr ne "") { print fopen "sendbyuser:$sndusr\n"; } print fopen "\n"; print fopen "$body\n"; print fopen "\n"; close fopen; # создаем файл с письмом в каталоге для отправки почты с именем вида msgrss_2014-05-20-09_26_09 и начинаем записывать туда текст письма } # после завершения обработки одной ленты из списка # копируем файл с новыми постами в каталог архива с уникальным именем - включено для отладки `cp $path_temp/@rssdiffer[$i] $path_temp/arhiv/diffnew\_$dirr`; # добавляем пустую строку в файл с архивом лент - включено для отладки `echo >>$path_temp/@rssarcfile[$i]`; # в файл архива ленты загоняем исходные новые посты open(arcrss,">>$path_temp/@rssarcfile[$i]"); foreach $difffs(@original) { $difffs=~s/^(\>\ )//; $difffs=~tr/\n//d; print arcrss "$difffs\n"; } close(arcrss); # в файл архива ленты загоняем исходные новые посты } } } printf "start send messages\n"; # ночью производим убивание возможных дубликатов в архиве ленты if ($hour eq "04" and $min<"30") { foreach $srtname(@rssarcfile) { `uniq -u $path_temp/$srtname >$path_temp/$srtname.tmp`; `mv -f $path_temp/$srtname.tmp $path_temp/$srtname`; } sleep 1000; } # ночью производим убивание возможных дубликатов в архиве ленты
rsssendemail.pl
#! /usr/bin/perl # общий каталог временных файлов $path_all="/home/media/rsstool"; # подкаталоги $path_mail="$path_all/mail"; $path_mail_arhiv="$path_all/mail_arhiv"; $path_arhiv="$path_all/arhiv"; # получаем список дублицирующихся файлов без _send в конце @list_dupes=`/sbin/myscript/fdupes -f $path_mail|grep -v send|grep -v \^\$`; # проходим по списку и удаляем дубликаты foreach $fl(@list_dupes) { $fl=~tr/\n//d; `rm -f $fl`; printf "rm dupe $fl\n"; } # проходим по списку и удаляем дубликаты # получаем список оставшихся файлов без _send на конце @list_files=`ls $path_mail/ |grep -v send`; sleep 5; # проходим по списку отправляя файлы через ssmtp а затем переименовываем отправленные в *_send foreach $i(@list_files) { $i=~tr/\n//d; printf "sended $path_mail/$i\n"; `/sbin/myscript/ssmtp toemail\@gmail.com \<$path_mail/$i`; `mv $path_mail/$i $path_mail/$i\_send`; sleep 1; } # проходим по списку отправляя файлы через ssmtp а затем переименовываем отправленные в *_send # перекидываем в архив все файлы кроме последней тысячи @all_files=`ls $path_mail/`; $cont=$#all_files-1000; if ($cont > 0) { printf "bigger 1000 to $cont\n"; for ($y=0; $y<$cont;$y++) { @all_files[$y]=~tr/\n//d; `mv $path_mail/@all_files[$y] $path_mail_arhiv`; printf "@all_files[$y]\n"; } } # перекидываем в архив все файлы кроме последней тысячи
rsstoemail.conf
1#librusec#http://lib.rus.ec/polka/show/all/rss 2#flibusta#http://flibusta.net/polka/show/all/rss 3#nnm#http://nnm.me/rss/ 4#habrahabr#http://habrahabr.ru/rss 5#3dnewssoft#http://www.3dnews.ru/software-news/rss/ 6#3dnewshard#http://www.3dnews.ru/news/rss/ 7#flibustanewbooks#http://flibusta.net/new/rss
