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

За питања попут "зашто?" постоји старији чланак - Натурал Геектимес - чинећи простор чистијим.

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

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

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

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

У почетку сам желео да генеришем РСС феед (или чак неколико), остављајући тамо само занимљиве ствари. Али на крају се испоставило да читање РСС-а није изгледало баш згодно: у сваком случају, да бисте коментарисали / гласали за чланак / додали га у своје фаворите, морате проћи кроз претраживач. Зато сам написао телеграм бот који ми шаље занимљиве чланке у личној поруци. Сам Телеграм од њих прави прелепе прегледе, који у комбинацији са информацијама о аутору/оцени/прегледима изгледа прилично информативно.

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

Испод реза су детаљи као што су карактеристике рада, процес писања и техничка решења.

Укратко о боту

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

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

Корисник поставља додатну оцену за ознаке и ауторе. Након тога се на чланке примењује филтер - сабирају се оцена чланка на Хабре, ауторова корисничка оцена и просек оцена корисника по таговима. Ако је износ већи од прага који је одредио корисник, онда чланак пролази филтер.

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

Напољу је било лето

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

Упркос чињеници да сам и сам с времена на време последњих неколико година писао код на стени, овај код обично нико не види нити гледа: пројекти кућних љубимаца, тестирање неких идеја, претходна обрада података, савладавање неких концепата из ФП-а. Заиста ме је занимало како изгледа писање кода у тиму, јер се код на стени може написати на веома различите начине.

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

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

Јули КСНУМКС

Укратко: Написао сам рашчлањивање Хабровог рсс феед-а.

  • com.github.pureconfig за читање типесафе конфигурација директно у класе случаја (испоставило се да је веома згодно)
  • scala-xml за читање кмл-а: пошто сам у почетку желео да напишем сопствену имплементацију за рсс феед, а рсс феед је у кмл формату, користио сам ову библиотеку за рашчлањивање. У ствари, појавио се и РСС парсинг.
  • scalatest за тестове. Чак и за мале пројекте, писање тестова штеди време - на пример, када се отклањају грешке кмл рашчлањивања, много је лакше преузети га у датотеку, написати тестове и исправити грешке. Када се касније појавила грешка са рашчлањивањем неког чудног хтмл-а са неважећим утф-8 знаковима, испоставило се да је згодније ставити га у датотеку и додати тест.
  • глумци из Аке. Објективно, уопште нису били потребни, али пројекат је написан из забаве, желео сам да их испробам. Као резултат тога, спреман сам да кажем да ми се допало. Идеја ООП-а се може посматрати и са друге стране - постоје глумци који размењују поруке. Оно што је интересантније је да можете (и треба да) пишете код на такав начин да порука можда неће стићи или можда неће бити обрађена (уопштено говорећи, када је налог покренут на једном рачунару, поруке не би требало да се изгубе). У почетку сам се чешао по глави и било је смећа у коду са глумцима који су се претплатили једни на друге, али сам на крају успео да смислим прилично једноставну и елегантну архитектуру. Код унутар сваког актера се може сматрати једнонитним; када се актер сруши, ацца га поново покреће - резултат је прилично толерантан систем.

КСНУМКС август

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

И мачке. Оне у стени.

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

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

У ствари, идеја је врло једноставна - имамо бројаче који се монотоно мењају. Број промоција постепено расте, као и број плусева (као и број минуса). Ако имам две верзије информација о чланку, онда могу да их „спојим у једну“ – стање бројача које је веће сматра се релевантнијим.

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

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

Уопштено говорећи, као у акки, није било потребе за овим, могли сте једноставно да сачувате упдатеДате за чланак и узмете новији без икаквих спајања, али пут авантуре ме је водио.

КСНУМКС август

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

Један актер је био мост између телеграм сервера и система порука у Акки. Једноставно је примао поруке и слао их жељеном глумцу за ћаскање. Глумац ћаскања могао би нешто да пошаље назад као одговор - и то би било послато назад у телеграм. Оно што је било веома згодно је то што се овај глумац показао максимално једноставним и садржао само логику за одговарање на поруке. Иначе, информације о новим чланцима стизале су у сваки разговор, али опет не видим никакве проблеме у томе.

Углавном, бот је већ радио, одговарао на поруке, чувао листу чланака послатих кориснику, а ја сам већ мислио да је бот скоро спреман. Полако сам додавао мале функције попут нормализације имена аутора и ознака (замена „сд ф“ са „с_д_ф“).

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

Све је пошло наопако

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

  • Чини се да МонгоДБ чува стање. У исто време, логови у пројекту су покварени, јер је Монга из неког разлога почела да их шаље спамом и неки људи су их једноставно искључили глобално.
  • Глумац бриџа у Телеграму се трансформисао до непрепознатљивости и сам је почео да анализира поруке.
  • Глумци за ћаскање су немилосрдно исечени, а уместо њих је замењен глумац који је сакрио све информације о свим четовима одједном. За сваку кијаву овај глумац је упадао у невоље. Па, да, као када ажурирате информације о чланку, тешко је послати их свим актерима ћаскања (ми смо као Гугл, милиони корисника чекају милион чланака у ћаскању за сваког), али сваки пут када се ћаскање ажурира, нормално је ући у Монга. Као што сам схватио много касније, радна логика ћаскања је такође била потпуно исечена и на њеном месту се појавило нешто што није функционисало.
  • Од класа типова није остало ни трага.
  • Нека нездрава логика се појавила у глумцима са њиховим претплатама једни на друге, што је довело до стања трке.
  • Структуре података са пољима типа Option[Int] претворио у Инт са магичним подразумеваним вредностима као што је -1. Касније сам схватио да монгоДБ складишти јсон и нема ништа лоше у томе да га тамо чува Option па, или бар рашчлани -1 као Ништа, али у то време то нисам знао и веровао сам на реч да „тако треба да буде“. Нисам написао тај код и нисам се трудио да га мењам за сада.
  • Сазнао сам да моја јавна ИП адреса има тенденцију да се мења, и сваки пут сам морао да је додам на белу листу Монга. Покренуо сам бота локално, Монга је била негде на серверима Монге као компаније.
  • Одједном је нестала нормализација ознака и форматирања порука за телеграме. (Хмм, зашто би то било?)
  • Свидело ми се што се стање бота чува у екстерној бази података, а када се поново покрене наставља да ради као да се ништа није догодило. Међутим, ово је био једини плус.

Другој особи се није журило, а све ове промене су се појавиле у једној великој гомили већ почетком септембра. Нисам одмах схватио размере насталог уништења и почео сам да разумем рад базе података, јер... Никада раније нисам имао посла са њима. Тек касније сам схватио колико је радног кода исечено и колико је грешака додато уместо њега.

Септембар

У почетку сам мислио да би било корисно савладати Монга и то добро урадити. Тада сам полако почео да схватам да је организовање комуникације са базом података такође уметност у којој се може много тркати и само грешити. На пример, ако корисник прими две поруке попут /subscribe - и као одговор на сваку направићемо унос у табели, јер у тренутку обраде тих порука корисник није претплаћен. Сумњам да комуникација са Монгом у садашњем облику није написана на најбољи начин. На пример, подешавања корисника су креирана у тренутку када се пријавио. Ако је покушао да их промени пре чињенице претплате... бот није ништа одговорио, јер је код у глумцу отишао у базу података за подешавања, није га нашао и срушио се. На питање зашто не бих направио подешавања по потреби, сазнао сам да нема потребе да их мењам ако се корисник није претплатио... Систем филтрирања порука је направљен некако неочигледно, па чак и након детаљног погледа на код могао сам да не разумем да ли је првобитно било замишљено на овај начин или постоји грешка.

Није постојао списак чланака достављених на ћаскање; уместо тога, предложено је да их сам напишем. Ово ме је изненадило – генерално, нисам био против увлачења свашта у пројекат, али би било логично за онога ко је те ствари унео и зезнуо. Али не, чинило се да је други учесник одустао од свега, али је рекао да је листа унутар ћаскања наводно лоше решење и да је потребно направити знак са догађајима попут „чланак и је послат кориснику к“. Затим, ако је корисник тражио да пошаље нове чланке, било је потребно послати захтев бази, која би из догађаја изабрала догађаје везане за корисника, такође добила листу нових чланака, филтрирала их, послала кориснику и врати догађаје о овоме у базу података.

Други учесник је однесен негде ка апстракцијама, када ће бот примати не само чланке са Хабра и бити послат не само у телеграм.

Некако сам имплементирао догађаје у виду посебног знака за другу половину септембра. Није оптимално, али је барем бот почео да ради и поново ми шаље чланке, а ја сам полако схватио шта се дешава у коду.

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

Узнемирио сам се и погледао историју урезивања и количину написаног кода. Погледао сам тренутке који су првобитно били добро написани, а онда су покварени...

Јеби га

Сетио сам се чланка Ви нисте Гоогле.

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

Зашто ми треба Доцкер, монгоДБ и други царго култ „озбиљног“ софтвера ако код једноставно не ради или ради криво?

Раздвојио сам пројекат и урадио све како сам хтео.

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

Отприлике у исто време променио сам посао и слободног времена ми је јако недостајало. Ујутро сам се пробудио право у возу, увече сам се вратио касно и нисам више хтео ништа да радим. Једно време нисам ништа радио, онда ме је савладала жеља да завршим бот и почео сам полако да преписујем код док сам се ујутро возио на посао. Нећу рећи да је било продуктивно: седење у возу који се тресло са лаптопом у крилу и гледање преливања стека са телефона није баш згодно. Међутим, време проведено у писању кода је пролетело потпуно неприметно, а пројекат је почео полако да се креће ка радном стању.

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

  • База података постаје још једна тачка неуспеха.
  • Код постаје сложенији и биће ми потребно више времена да га напишем.
  • Код постаје спор и неефикасан; уместо промене објекта у меморији, промене се шаљу у базу података и, ако је потребно, повлаче се назад.
  • Постоје ограничења у погледу врсте складиштења догађаја у посебној табели, која су повезана са посебностима базе података.
  • Пробна верзија Монге има нека ограничења и ако наиђете на њих, мораћете да покренете и конфигуришете Монга на нечему.

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

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

Исправљена гомила ситница: на пример, за чланке је сада назначен број прегледа, лајкова, несвиђања и коментара у тренутку проласка филтера корисника. Генерално, изненађујуће је колико је ситница морало да се исправи. Водио сам списак, забележио све „неправилности“ и исправио их колико је то било могуће.

На пример, додао сам могућност подешавања свих подешавања директно у једној поруци:

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

И још један тим /settings приказује их тачно у овом облику, можете узети текст из њега и послати сва подешавања пријатељу.
Чини се као мала ствар, али постоји на десетине сличних нијанси.

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

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

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

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

  1. Глумац који чува стање свих ћаскања и чланака. Учитава своје стање из датотеке на диску и с времена на време га чува, сваки пут у нову датотеку.
  2. Глумац који с времена на време посећује РСС фид, учи о новим чланцима, гледа линкове, анализира и шаље ове чланке првом актеру. Осим тога, понекад тражи списак чланака од првог актера, бира оне који нису старији од три дана, али нису дуго ажурирани, и ажурира их.
  3. Глумац који комуницира телеграмом. Још увек сам донео рашчлањивање поруке у потпуности овде. На пријатељски начин, желео бих да га поделим на два – тако да једна анализира долазне поруке, а друга се бави проблемима транспорта као што је поновно слање непослатих порука. Сада нема поновног слања, а порука која није стигла због грешке једноставно ће бити изгубљена (осим ако није забележена у логовима), али до сада то није стварало проблеме. Можда ће настати проблеми ако се гомила људи претплати на бота и ја достигнем ограничење за слање порука).

Оно што ми се допало је што захваљујући акки падови глумаца 2 и 3 углавном не утичу на перформансе бота. Можда се неки чланци не ажурирају на време или неке поруке не стигну до телеграма, али налог поново покреће глумца и све наставља да ради. Чувам информацију да се чланак приказује кориснику само када актер телеграма одговори да је успешно испоручио поруку. Најгоре што ми прети је да пошаљем поруку неколико пута (ако је испоручена, али се потврда некако изгуби). У принципу, ако први актер није складиштио стање у себи, већ је комуницирао са неком базом података, онда би и он могао неприметно да падне и врати се у живот. Могао бих да пробам и акка упорност да вратим стање актера, али садашња имплементација ми одговара својом једноставношћу. Није да ми се код често руши – напротив, уложио сам доста труда да то учиним немогућим. Али срања се дешавају, а могућност да се програм разбије на изоловане делове-глумци ми се чинила заиста згодном и практичном.

Додао сам круг-ци тако да ако се код поквари, одмах ћете сазнати за то. У најмању руку, то значи да је код престао да се компајлира. У почетку сам желео да додам Трависа, али је приказао само моје пројекте без виљушки. Генерално, обе ове ствари се могу слободно користити у отвореним репозиторијумима.

Резултати

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

Бот линк: https://t.me/HabraFilterBot
Гитхуб: https://github.com/Kright/habrahabr_reader

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

  • Чак и мали пројекат може одузети много времена.
  • Ви нисте Гоогле. Нема смисла гађати врапце из топа. Једноставно решење може да функционише једнако добро.
  • Пројекти за кућне љубимце су веома добри за експериментисање са новим технологијама.
  • Телеграм ботови су написани прилично једноставно. Да није било „тимског рада“ и експеримената са технологијом, бот би био написан за недељу или две.
  • Модел глумца је занимљива ствар која се добро слаже са вишенитним и кодом отпорним на грешке.
  • Мислим да сам схватио зашто заједница отвореног кода воли виљушке.
  • Базе података су добре јер стање апликације више не зависи од пада/поновног покретања апликације, али рад са базом података компликује код и намеће ограничења структури података.

Извор: ввв.хабр.цом

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