Телеграма бот за персонализиран избор на статии од Хабр

За прашања како „зошто?“ има постара статија - Природен Geektimes - правејќи го просторот почист.

Има многу написи, од субјективни причини некои не ми се допаѓаат, а некои, напротив, штета е да се прескокнат. Би сакал да го оптимизирам овој процес и да заштедам време.

Горенаведената статија предложи пристап за скриптирање во прелистувачот, но навистина не ми се допадна (иако го користев претходно) од следниве причини:

  • За различни прелистувачи на вашиот компјутер/телефон, мора повторно да го конфигурирате, доколку е можно.
  • Строгото филтрирање од авторите не е секогаш погодно.
  • Проблемот со авторите чии написи не сакате да ги пропуштите, дури и ако се објавуваат еднаш годишно, не е решен.

Филтрирањето вградено во страницата врз основа на рејтингот на написите не е секогаш погодно, бидејќи високо специјализираните статии, и покрај нивната вредност, можат да добијат прилично скромна оцена.

Првично, сакав да генерирам RSS фид (или дури и неколку), оставајќи само интересни работи таму. Но, на крајот, се покажа дека читањето RSS не изгледа многу погодно: во секој случај, за да коментирате/гласате за статија/да ја додадете во вашите омилени, треба да поминете низ прелистувачот. Затоа напишав телеграм бот кој ми праќа интересни написи во лична порака. Самата Телеграма прави прекрасни прегледи од нив, кои во комбинација со информации за авторот/оценката/прегледите изгледаат доста информативно.

Телеграма бот за персонализиран избор на статии од Хабр

Под резот има детали како што се карактеристиките на делото, процесот на пишување и техничките решенија.

Накратко за ботот

Репозиториум: https://github.com/Kright/habrahabr_reader

Бот во телеграма: https://t.me/HabraFilterBot

Корисникот поставува дополнителна оцена за ознаки и автори. После тоа, на написите се применува филтер - се собираат рејтингот на статијата на Habré, рејтингот на корисникот на авторот и просекот за оценки на корисниците по ознаки. Ако износот е поголем од прагот одреден од корисникот, тогаш написот го поминува филтерот.

Страна цел на пишување бот беше да се стекне забава и искуство. Покрај тоа, редовно се потсетував на тоа Јас не сум Гугл, и затоа многу работи се прават што е можно поедноставно, па дури и примитивно. Сепак, тоа не го спречи процесот на пишување на ботот да трае три месеци.

Надвор беше лето

Јули завршуваше, и решив да напишам бот. И тоа не сам, туку со другарка која ја совлада скалата и сакаше да напише нешто на неа. Почетокот изгледаше ветувачки - кодот ќе го пресече тим, задачата изгледаше лесна и мислев дека за неколку недели или еден месец ботот ќе биде готов.

И покрај фактот што јас самиот пишувам код на карпата од време на време во последните неколку години, никој обично не го гледа или гледа овој код: проекти за домашни миленици, тестирање на некои идеи, претходна обработка на податоци, совладување на некои концепти од FP. Навистина ме интересираше како изгледа пишувањето код во тим, бидејќи кодот на рок може да се напише на многу различни начини.

Што можеше да отиде така? Сепак, да не брзаме со работите.
Сè што се случува може да се следи со користење на историјата на извршување.

Еден познаник создаде складиште на 27 јули, но не направи ништо друго, па почнав да пишувам код.

јули 30

Накратко: Напишав парсирање на rss фидот на Хабр.

  • com.github.pureconfig за читање на конфигурации на typesafe директно во класи на случаи (се покажа дека е многу погодно)
  • scala-xml за читање xml: бидејќи првично сакав да напишам сопствена имплементација за rss feed, а rss feed е во xml формат, ја користев оваа библиотека за парсирање. Всушност, се појави и RSS парсирање.
  • scalatest за тестови. Дури и за мали проекти, пишувањето тестови заштедува време - на пример, кога се дебагира парсирањето на xml, многу е полесно да се преземе во датотека, да се пишуваат тестови и да се поправат грешките. Кога подоцна се појави грешка со парсирање на некој чуден html со неважечки utf-8 знаци, се покажа дека е попогодно да се стави во датотека и да се додаде тест.
  • актери од Ака. Објективно, тие воопшто не беа потребни, но проектот е напишан за забава, сакав да ги пробам. Како резултат на тоа, подготвен сум да кажам дека ми се допадна. Идејата за ООП може да се погледне од друга страна - има актери кои разменуваат пораки. Она што е поинтересно е што можете (и треба) да пишувате код на таков начин што пораката може да не пристигне или да не се обработи (општо кажано, кога сметката работи на еден единствен компјутер, пораките не треба да се губат). Отпрвин си ја чешав главата и имаше ѓубре во кодот со актери кои се претплатуваа еден на друг, но на крајот успеав да дојдам до прилично едноставна и елегантна архитектура. Кодот во секој актер може да се смета за еден нишки; кога актерот ќе се сруши, acca го рестартира - резултатот е систем доста толерантен на грешки.

9 август

Додадов во проектот scala-scrapper за парсирање на html страници од Habr (за извлекување информации како што се рејтинг на статијата, број на обележувачи итн.).

И мачки. Оние во карпата.

Телеграма бот за персонализиран избор на статии од Хабр

Потоа прочитав книга за дистрибуирани бази на податоци, ми се допадна идејата за CRDT (реплициран тип на податоци без конфликт, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, хабр), па објавив типска класа на комутативна полугрупа за информации за статијата на Хабре.

Всушност, идејата е многу едноставна - имаме бројачи кои монотоно се менуваат. Бројот на промоции постепено расте, како и бројот на плус (како и бројот на минуси). Ако имам две верзии на информации за статија, тогаш можам да ги „спојувам во една“ - состојбата на бројачот што е поголем се смета за порелевантна.

Полугрупа значи дека два објекти со информации за статија може да се спојат во еден. Комутативно значи дека можете да ги споите и A + B и B + A, резултатот не зависи од редоследот и на крајот ќе остане најновата верзија. Патем, тука има и асоцијативност.

На пример, како што беше планирано, rss по парсирањето обезбеди малку ослабени информации за статијата - без метрика како што е бројот на прегледи. Специјален актер потоа земал информации за написите и трчал до страниците на html за да ги ажурира и да ги спои со старата верзија.

Општо земено, како и во akka, немаше потреба за ова, едноставно можеше да го зачуваш updateDate за статијата и да земеш понова без никакво спојување, но ме доведе патот на авантурата.

12 август

Почнав да се чувствувам послободна и, само за забава, го направив секој разговор посебен актер. Теоретски, самиот актер тежи околу 300 бајти и тие можат да се создадат во милиони, така што ова е сосема нормален пристап. Ми се чини дека решението се покажа доста интересно:

Еден актер беше мост помеѓу серверот за телеграма и системот за пораки во Ака. Тој едноставно добивал пораки и ги испраќал до саканиот чет актер. Актерот за разговор би можел да испрати нешто како одговор - и тоа би било вратено на телеграмата. Она што беше многу погодно е што овој актер се покажа што е можно поедноставен и ја содржеше само логиката за одговарање на пораките. Патем, информациите за нови написи доаѓаа на секој разговор, но повторно не гледам никакви проблеми во ова.

Во принцип, ботот веќе работеше, одговараше на пораки, чуваше листа на статии испратени до корисникот, а јас веќе мислев дека ботот е речиси подготвен. Полека додавав мали функции како нормализирање на имињата и ознаките на авторите (заменувајќи го „s.d f“ со „s_d_f“).

Остана само едно мала но — никаде не се спаси државата.

Сè тргна наопаку

Можеби сте забележале дека ботот го напишав главно сам. Така, вториот учесник се вклучи во развојот, а во кодот се појавија следните промени:

  • MongoDB се чинеше дека ја складира состојбата. Во исто време, логовите во проектот беа скршени, бидејќи поради некоја причина Монга почна да ги спамува и некои луѓе едноставно ги исклучија глобално.
  • Актерот на мостот во Телеграм се трансформираше непрепознатливо и самиот почна да ги анализира пораките.
  • Актерите за разговори беа безмилосно исечени, а наместо нив беа заменети со актер кој ги криеше сите информации за сите разговори одеднаш. За секое кивање овој актер влегуваше во неволја. Па, да, како кога се ажурираат информации за статија, тешко е да се испраќаат до сите учесници во разговорот (ние сме како Google, милиони корисници чекаат по милион статии во разговорот за секој), но секогаш кога разговорот се ажурира, нормално е да се оди во Монга. Како што сфатив многу подоцна, и работната логика на разговорите беше целосно отсечена и на нејзино место се појави нешто што не функционираше.
  • Од класите на типови нема ни трага.
  • Се појави некоја нездрава логика кај актерите со нивните претплати еден на друг, што доведе до состојба на трка.
  • Структури на податоци со полиња од типот Option[Int] се претвори во Int со магични стандардни вредности како -1. Подоцна сфатив дека mongoDB го складира json и нема ништо лошо да го складираш таму Option добро, или барем анализирајте го -1 како Никој, но во тоа време не го знаев ова и го прифатив зборот дека „така треба да биде“. Јас не го напишав тој код и засега не се трудев да го променам.
  • Дознав дека мојата јавна IP адреса има тенденција да се менува и секој пат кога морав да ја додадам на белата листа на Mongo. Локално го лансирав ботот, Монга беше некаде на серверите на Монга како компанија.
  • Одеднаш исчезна нормализацијата на ознаките и форматирањето на пораките за телеграми. (Хмм, зошто би било тоа?)
  • Ми се допадна што состојбата на ботот е зачувана во надворешна база на податоци и кога се рестартира продолжува да работи како ништо да не се случило. Сепак, ова беше единствениот плус.

На второто лице не му се брзаше особено, а сите овие промени се појавија во еден голем куп веќе на почетокот на септември. Веднаш не го ценев размерот на произлезеното уништување и почнав да ја разбирам работата на базата на податоци, бидејќи ... Никогаш порано не сум се занимавал со нив. Дури подоцна сфатив колку работен код е исечен и колку грешки се додадени на негово место.

Септември

Отпрвин мислев дека би било корисно да го совладам Монга и да го направам тоа добро. Потоа полека почнав да разбирам дека организирањето комуникација со базата на податоци е исто така уметност во која можеш да направиш многу трки и само да грешиш. На пример, ако корисникот прими две пораки како /subscribe - и како одговор на секоја ќе креираме запис во табелата, бидејќи во моментот на обработка на тие пораки корисникот не е претплатен. Имам сомнеж дека комуникацијата со Монга во нејзината сегашна форма не е најдобро напишана. На пример, поставките на корисникот беа креирани во моментот кога тој се регистрираше. Ако се обидел да ги смени пред фактот на претплата... ботот не одговорил ништо, бидејќи кодот во актерот отишол во базата на податоци за поставките, не го најде и паднал. На прашањето зошто да не креирам поставки по потреба, дознав дека нема потреба да ги менувам доколку корисникот не се претплатил... Системот за филтрирање пораки е направен некако неочигледно, па дури и по внимателно разгледување на кодот можев не разбирам дали првично било наменето на овој начин или има грешка таму.

Немаше список на статии поднесени на разговорот, наместо тоа, ми беше предложено да ги напишам сам. Ова ме изненади - генерално, не бев против да се влечат секакви работи во проектот, но логично би било тој што ги внесе овие работи и ги зафркна. Но, не, вториот учесник се чинеше дека се откажа од сè, но рече дека списокот во разговорот е наводно лошо решение и дека е неопходно да се направи знак со настани како „статија y е испратена до корисникот x“. Потоа, доколку корисникот побарал да испрати нови статии, потребно било да се испрати барање до базата на податоци, со што би се одбрале настани поврзани со корисникот од настаните, ќе се добие и листа на нови статии, ќе се филтрира, ќе се испратат до корисникот. и фрлете ги настаните за ова назад во базата на податоци.

Вториот учесник беше занесен некаде кон апстракции, кога ботот ќе добива не само написи од Хабр и ќе биде испратен не само на телеграма.

Некако спроведов настани во форма на посебен знак за втората половина на септември. Не е оптимално, но барем ботот почна да работи и повторно почна да ми испраќа статии, а јас полека сфатив што се случува во кодот.

Сега можете да се вратите на почетокот и да запомните дека складиштето првично не е создадено од мене. Што можеше да оди вака? Моето барање за повлекување беше одбиено. Се испостави дека имам код за redneck, дека не знаев како да работам во тим и морав да поправам грешки во тековната крива на имплементација, а не да ја рафинирам во употреблива состојба.

Се вознемирив и ја погледнав историјата на commit и количината на напишаниот код. Гледав моменти кои првично беа добро напишани, а потоа беа скршени...

Фати го

Се сетив на статијата Вие не сте Google.

Мислев дека никому навистина не му треба идеја без имплементација. Мислев дека сакам да имам работен бот, кој ќе работи во една копија на еден компјутер како едноставна програма за Java. Знам дека мојот бот ќе работи со месеци без рестартирање, бидејќи веќе имам напишано такви ботови во минатото. Ако одеднаш падне и не му испрати на корисникот друга статија, небото нема да падне на земја и ништо катастрофално нема да се случи.

Зошто ми требаат Docker, mongoDB и друг товарен култ на „сериозен“ софтвер ако кодот едноставно не работи или работи криво?

Го откачив проектот и направив сè како што сакав.

Телеграма бот за персонализиран избор на статии од Хабр

Отприлика во исто време, ја сменив работата и слободното време многу ми недостасуваше. Утрото се разбудив точно во возот, навечер се вратив доцна и повеќе не сакав да правам ништо. Не правев ништо, а потоа ме совлада желбата да го завршам ботот и почнав полека да ја препишувам шифрата додека возев на работа наутро. Нема да кажам дека беше продуктивно: не е многу погодно да седите во воз што тресе со лаптоп во скутот и да гледате како се прелеваат купчиња од вашиот телефон. Меѓутоа, времето поминато за пишување код леташе сосема незабележано, а проектот полека почна да се движи кон работна состојба.

Некаде во задниот дел од мојот ум имаше црв на сомнеж кој сакаше да користи mongoDB, но мислев дека покрај предностите на „сигурното“ складирање на државата, има и забележителни недостатоци:

  • Базата на податоци станува уште една точка на неуспех.
  • Кодот станува покомплексен и ќе ми треба подолго време да го напишам.
  • Кодот станува бавен и неефикасен; наместо да се менува објектот во меморијата, промените се испраќаат до базата на податоци и, доколку е потребно, се повлекуваат назад.
  • Постојат ограничувања за видот на складирање на настани во посебна табела, кои се поврзани со особеностите на базата на податоци.
  • Пробната верзија на Monga има некои ограничувања, и ако наидете на нив, ќе мора да ја стартувате и конфигурирате Monga на нешто.

Ја отсеков монгата, сега состојбата на ботот едноставно се чува во меморијата на програмата и од време на време се зачувува во датотека во форма на json. Можеби во коментарите ќе напишат дека грешам, дека тука треба да се користи базата итн. Но, ова е мој проект, пристапот со датотеката е што е можно поедноставен и функционира на транспарентен начин.

Ги исфрли магичните вредности како -1 и ги врати нормалните Option, додаде складирање на хеш табела со испратени статии назад до објектот со информации за разговор. Додадено е бришење на информации за статии постари од пет дена, за да не се складира сè. Ја доведов евиденцијата во работна состојба - дневниците се пишуваат во разумни количини и во датотеката и во конзолата. Додадени се неколку администраторски команди како што се зачувување на состојбата или добивање статистика како што се бројот на корисници и статии.

Поправи еден куп ситници: на пример, за статии сега е означен бројот на прегледи, допаѓања, недопаѓања и коментари во моментот на поминување на филтерот на корисникот. Во принцип, изненадувачки е колку ситници требаше да се поправат. Водов список, ги забележав сите „неправилности“ таму и колку што можев ги исправив.

На пример, ја додадов можноста да ги поставам сите поставки директно во една порака:

/subscribe
/rating +20
/author a -30
/author s -20
/author p +9000
/tag scala 20
/tag akka 50

И уште еден тим /settings ги прикажува токму во оваа форма, можете да го земете текстот од него и да ги испратите сите поставки на пријател.
Се чини како мала работа, но има десетици слични нијанси.

Спроведено филтрирање на статии во форма на едноставен линеарен модел - корисникот може да постави дополнителна оцена за автори и ознаки, како и праг вредност. Ако збирот на оцената на авторот, просечната оцена за ознаките и вистинската оцена на статијата е поголема од прагот, тогаш написот му се прикажува на корисникот. Можете или да побарате од ботот статии со командата /new, или да се претплатите на ботот и тој ќе испраќа статии во лична порака во секое време од денот.

Општо земено, имав идеја секоја статија да извлече повеќе функции (главни центри, број на коментари, обележувачи, динамика на промени во рејтингот, количина на текст, слики и код во статијата, клучни зборови) и да му покаже на корисникот добро/ не е ок гласање под секоја статија и тренирај модел за секој корисник, но бев премногу мрзлив.

Покрај тоа, логиката на работата нема да биде толку очигледна. Сега можам рачно да поставам рејтинг од +9000 за пациентотЗеро и со рејтинг на праг од +20 ќе ми се гарантира дека ќе ги добијам сите негови написи (освен, се разбира, ако не поставам -100500 за некои ознаки).

Конечната архитектура се покажа како прилично едноставна:

  1. Актер што ја чува состојбата на сите разговори и написи. Ја вчитува неговата состојба од датотека на дискот и ја зачувува од време на време, секој пат во нова датотека.
  2. Актер кој го посетува RSS изворот од време на време, дознава за нови статии, ги гледа врските, ги анализира и ги испраќа овие написи до првиот актер. Покрај тоа, понекогаш бара список на написи од првиот актер, ги избира оние кои не се постари од три дена, но не се ажурирани долго време и ги ажурира.
  3. Актер кој комуницира со телеграма. Сè уште ја донесов комплетната парсирање на пораката овде. На пријателски начин, би сакал да го поделам на два - така што едниот ги анализира дојдовните пораки, а вториот се занимава со транспортни проблеми како што се повторно испраќање неиспратени пораки. Сега нема повторно испраќање, а пораката што не пристигнала поради грешка едноставно ќе се изгуби (освен ако не е забележана во дневниците), но досега тоа не предизвикало никакви проблеми. Можеби ќе се појават проблеми ако еден куп луѓе се претплатат на ботот и јас го достигнам лимитот за испраќање пораки).

Она што ми се допадна е што благодарение на akka, падовите на актерите 2 и 3 генерално не влијаат на изведбата на ботот. Можеби некои написи не се ажурираат навреме или некои пораки не стигнуваат до телеграмата, но сметката го рестартира актерот и сè продолжува да работи. Ги зачувувам информациите дека написот му се прикажува на корисникот само кога актерот на телеграмата ќе одговори дека успешно ја доставил пораката. Најлошото нешто што ми се заканува е да ја испратам пораката неколку пати (ако е доставена, но потврдата некако се губи). Во принцип, ако првиот актер не ја складирал државата во себе, туку комуницирал со некоја база на податоци, тогаш и тој можел незабележливо да падне и да се врати во живот. Би можел да пробам и akka persistance за да ја вратам состојбата на актерите, но сегашната имплементација ми одговара со својата едноставност. Не е дека мојот код често паѓаше - напротив, вложив многу труд за да го оневозможам. Но, срање се случува, и способноста да се раздели програмата на изолирани парчиња-актери ми изгледаше навистина згодна и практична за мене.

Додадов круг-ци, така што ако се скрши кодот, веднаш ќе дознаете за тоа. Во најмала рака, тоа значи дека кодот престана да се компајлира. Првично сакав да додадам тревис, но тоа ги прикажуваше моите проекти само без вилушки. Општо земено, и двете од овие работи можат слободно да се користат во отворени складишта.

Резултатите од

Веќе е ноември. Напишан е ботот, го користам последниве две недели и ми се допадна. Ако имате идеи за подобрување, пишете. Не ја гледам поентата во монетизацијата - нека работи и испраќа интересни написи.

Врска со бот: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Мали заклучоци:

  • Дури и мал проект може да потрае многу време.
  • Вие не сте Google. Нема смисла да се пукаат врапчиња од топ. Едноставно решение може да работи исто толку добро.
  • Проектите за домашни миленици се многу добри за експериментирање со нови технологии.
  • Телеграмските ботови се напишани прилично едноставно. Да не беше „тимска работа“ и експерименти со технологијата, ботот ќе беше напишан за една или две недели.
  • Актерскиот модел е интересна работа што одлично се вклопува со кодот со повеќе нишки и толерантен за грешки.
  • Мислам дека добив вкус зошто заедницата со отворен код сака вилушки.
  • Базите на податоци се добри затоа што состојбата на апликацијата повеќе не зависи од паѓањата/рестартирањата на апликацијата, туку работата со база на податоци го комплицира кодот и наметнува ограничувања на структурата на податоците.

Извор: www.habr.com

Додадете коментар