Telegram bot pre personalizovaný výber článkov od Habr

Na otázky typu "prečo?" je tu starší článok - Natural Geektimes – robí priestor čistejším.

Článkov je veľa, niektoré sa mi zo subjektívnych dôvodov nepáčia a niektoré je naopak škoda preskočiť. Chcel by som optimalizovať tento proces a ušetriť čas.

Vyššie uvedený článok navrhol prístup skriptovania v prehliadači, ale veľmi sa mi nepáčil (aj keď som ho predtým používal) z nasledujúcich dôvodov:

  • Pre rôzne prehliadače na vašom počítači/telefóne ho musíte nakonfigurovať znova, ak je to možné.
  • Prísne filtrovanie podľa autorov nie je vždy vhodné.
  • Problém s autormi, ktorých články nechcete zmeškať, aj keď vychádzajú raz ročne, nie je vyriešený.

Filtrovanie zabudované do stránky na základe hodnotení článkov nie je vždy vhodné, pretože vysoko špecializované články môžu napriek svojej hodnote získať pomerne skromné ​​hodnotenie.

Pôvodne som chcel vygenerovať RSS feed (alebo aj niekoľko), pričom som tam nechal len zaujímavé veci. Nakoniec sa ale ukázalo, že čítanie RSS sa nezdalo príliš pohodlné: v každom prípade, ak chcete komentovať/hlasovať za článok/pridať si ho medzi obľúbené, musíte prejsť cez prehliadač. Preto som napísal telegramového robota, ktorý mi posiela zaujímavé články do osobnej správy. Samotný Telegram z nich robí krásne ukážky, čo v kombinácii s informáciami o autorovi/hodnotení/zhliadnutiach pôsobí celkom informatívne.

Telegram bot pre personalizovaný výber článkov od Habr

Pod strihom sú detaily, ako sú vlastnosti diela, proces písania a technické riešenia.

Stručne o robotovi

Úložisko: https://github.com/Kright/habrahabr_reader

Bot v telegrame: https://t.me/HabraFilterBot

Používateľ nastaví dodatočné hodnotenie pre značky a autorov. Potom sa na články aplikuje filter – sčíta sa hodnotenie článku na Habré, autorovo užívateľské hodnotenie a priemer užívateľských hodnotení podľa tagu. Ak je množstvo väčšie ako užívateľom špecifikovaný limit, článok prejde filtrom.

Vedľajším cieľom písania robota bolo získať zábavu a skúsenosti. Navyše som si to pravidelne pripomínal Nie som Google, a preto sa mnohé veci robia čo najjednoduchšie až primitívne. To však nezabránilo tomu, aby proces písania bota trval tri mesiace.

Vonku bolo leto

Júl sa končil a ja som sa rozhodol napísať robota. A nie sám, ale s kamarátom, ktorý ovládal scalu a chcel na ňu niečo napísať. Začiatok vyzeral sľubne - kód bude zostrihaný tímom, úloha sa zdala jednoduchá a myslel som si, že o pár týždňov či mesiacov bude bot hotový.

Napriek tomu, že ja sám posledné roky z času na čas píšem kód na skale, tento kód väčšinou nikto nevidí a nepozerá: pet projekty, testovanie nejakých nápadov, predspracovanie dát, zvládnutie niektorých konceptov z FP. Veľmi ma zaujímalo, ako vyzerá písanie kódu v tíme, pretože kód na skale sa dá písať veľmi rôznymi spôsobmi.

Čo mohlo ísť tak? Neponáhľajme však veci.
Všetko, čo sa stane, je možné sledovať pomocou histórie odovzdania.

Známy vytvoril úložisko 27. júla, ale nič iné nerobil, tak som začal písať kód.

júla 30

Stručne: Napísal som analýzu Habrovho rss feedu.

  • com.github.pureconfig na čítanie typovo bezpečných konfigurácií priamo do tried prípadov (ukázalo sa to ako veľmi pohodlné)
  • scala-xml na čítanie xml: keďže som pôvodne chcel napísať vlastnú implementáciu pre rss feed a rss feed je vo formáte xml, použil som túto knižnicu na analýzu. V skutočnosti sa objavila aj analýza RSS.
  • scalatest na testy. Aj pri maličkých projektoch písanie testov šetrí čas – napríklad pri ladení xml parsovania je oveľa jednoduchšie ho stiahnuť do súboru, napísať testy a opraviť chyby. Keď sa neskôr objavila chyba s parsovaním nejakého podivného html s neplatnými znakmi utf-8, ukázalo sa, že je pohodlnejšie to dať do súboru a pridať test.
  • herci z Akka. Objektívne neboli vôbec potrebné, ale projekt bol napísaný pre zábavu, chcel som ich vyskúšať. V dôsledku toho som pripravený povedať, že sa mi to páčilo. Na myšlienku OOP sa možno pozrieť z druhej strany – existujú herci, ktorí si vymieňajú správy. Zaujímavejšie je, že môžete (a mali by ste) napísať kód takým spôsobom, že správa nemusí prísť alebo nemusí byť spracovaná (všeobecne povedané, keď účet beží na jednom počítači, správy by sa nemali stratiť). Najprv som sa škrabal na hlave a v kóde bol odpad s hercami, ktorí sa navzájom upísali, ale nakoniec sa mi podarilo vymyslieť pomerne jednoduchú a elegantnú architektúru. Kód vo vnútri každého herca možno považovať za jednovláknový; keď herca zlyhá, acca ho reštartuje - výsledkom je systém pomerne odolný voči chybám.

9 augusta

Pridal som do projektu scala-scrapper na analýzu html stránok od Habra (na vytiahnutie informácií ako hodnotenie článku, počet záložiek atď.).

A Mačky. Tie v skale.

Telegram bot pre personalizovaný výber článkov od Habr

Potom som si prečítal knihu o distribuovaných databázach, páčila sa mi myšlienka CRDT (bezkonfliktný replikovaný dátový typ, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), tak som zverejnil typovú triedu komutatívnej pologrupy pre informáciu o článku o Habrém.

V skutočnosti je myšlienka veľmi jednoduchá – máme počítadlá, ktoré sa monotónne menia. Počet akcií postupne rastie, rovnako ako počet plusov (aj mínusov). Ak mám dve verzie informácií o článku, môžem ich „zlúčiť do jednej“ – stav počítadla, ktorý je väčší, sa považuje za relevantnejší.

Polskupina znamená, že dva objekty s informáciami o článku možno zlúčiť do jedného. Komutatívny znamená, že môžete zlúčiť A + B aj B + A, výsledok nezávisí od poradia a nakoniec zostane najnovšia verzia. Mimochodom, existuje tu aj asociativita.

Napríklad, ako bolo plánované, rss po analýze poskytlo mierne oslabené informácie o článku - bez metrík, ako je počet zobrazení. Špeciálny herec potom zobral informácie o článkoch a rozbehol sa na html stránky, aby ich aktualizoval a zlúčil so starou verziou.

Všeobecne povedané, ako v akka, nebolo to potrebné, jednoducho ste si mohli uložiť updateDate pre článok a vziať novší bez akýchkoľvek zlúčení, ale cesta dobrodružstva ma viedla.

12 augusta

Začal som sa cítiť slobodnejšie a len tak pre zábavu som z každého chatu urobil samostatného herca. Teoreticky samotný herec váži okolo 300 bajtov a dajú sa vytvoriť v miliónoch, takže je to úplne normálny prístup. Zdá sa mi, že riešenie sa ukázalo byť celkom zaujímavé:

Jeden aktér bol mostom medzi telegramovým serverom a systémom správ v Akka. Jednoducho prijímal správy a posielal ich želanému chatovaciemu hercovi. Aktér chatu mohol ako odpoveď poslať niečo späť - a to by sa poslalo späť do telegramu. Čo bolo veľmi výhodné, je, že tento herec sa ukázal byť čo najjednoduchší a obsahoval iba logiku pre odpovedanie na správy. Mimochodom, informácie o nových článkoch prišli do každého chatu, ale opäť v tom nevidím žiadne problémy.

Vo všeobecnosti robot už fungoval, odpovedal na správy, ukladal zoznam článkov odoslaných používateľovi a už som si myslel, že robot je takmer pripravený. Pomaly som pridal malé funkcie, ako je normalizácia mien autorov a značiek (nahradenie „sd f“ za „s_d_f“).

Ostávalo už len jediné malé ale — štát sa nikde nezachránil.

Všetko sa pokazilo

Možno ste si všimli, že som bota napísal väčšinou sám. Do vývoja sa teda zapojil druhý účastník a v kóde sa objavili tieto zmeny:

  • Zdá sa, že MongoDB je v stave uloženia. Zároveň boli prelomené logy v projekte, pretože Monga ich z nejakého dôvodu začal spamovať a niektorí ľudia ich jednoducho globálne vypli.
  • Herec mosta v Telegrame sa zmenil na nepoznanie a sám začal analyzovať správy.
  • Hercov na chaty nemilosrdne vystrihli a namiesto nich ich nahradil herec, ktorý zatajil všetky informácie o všetkých chatoch naraz. Za každé kýchnutie sa tento herec dostal do problémov. Áno, ako pri aktualizácii informácií o článku je ich odoslanie všetkým aktérom chatu zložité (sme ako Google, milióny používateľov čakajú na každého milión článkov v chate), ale vždy, keď sa chat aktualizuje, je normálne ísť do Mongy. Ako som si uvedomil oveľa neskôr, pracovná logika chatov bola tiež úplne vystrihnutá a na jej mieste sa objavilo niečo, čo nefungovalo.
  • Po typových triedach nezostala žiadna stopa.
  • V hercoch sa objavila nezdravá logika s ich vzájomnými úpismi, čo viedlo k rasovému stavu.
  • Dátové štruktúry s poliami typu Option[Int] zmenil na Int s magickými predvolenými hodnotami ako -1. Neskôr som si uvedomil, že mongoDB ukladá json a nie je nič zlé na tom, že ho tam uložím Option no, alebo aspoň analyzovať -1 ako None, ale vtedy som to nevedel a vzal som si za slovo, že „tak by to malo byť“. Ten kód som nenapísal a zatiaľ som sa neobťažoval ho meniť.
  • Zistil som, že moja verejná IP adresa má tendenciu sa meniť a zakaždým som ju musel pridať na bielu listinu Mongo. Spustil som bota lokálne, Monga bol niekde na serveroch Monga ako spoločnosť.
  • Zrazu zmizla normalizácia značiek a formátovanie správ pre telegramy. (Hmm, prečo by to bolo?)
  • Páčilo sa mi, že stav robota je uložený v externej databáze a po reštarte pokračuje v práci, akoby sa nič nestalo. To však bolo jediné plus.

Druhý sa nikam neponáhľal a všetky tieto zmeny sa na jednej veľkej kope objavili už začiatkom septembra. Okamžite som nedocenil rozsah výslednej deštrukcie a začal som chápať prácu databázy, pretože... Nikdy predtým som sa s nimi nezaoberal. Až neskôr som si uvedomil, koľko pracovného kódu bolo vyrezaných a koľko chýb bolo namiesto neho pridaných.

Septembra

Najprv som si myslel, že by bolo užitočné ovládať Mongu a robiť to dobre. Potom som pomaly začal chápať, že organizovať komunikáciu s databázou je tiež umenie, v ktorom sa dá veľa pretekať a len robiť chyby. Napríklad, ak používateľ dostane dve správy ako /subscribe - a ako odpoveď na každú vytvoríme záznam v tabuľke, pretože v čase spracovania týchto správ používateľ nie je prihlásený. Mám podozrenie, že komunikácia s Mongou v súčasnej podobe nie je napísaná práve najlepšie. Napríklad nastavenia používateľa boli vytvorené v momente, keď sa prihlásil. Ak sa ich pokúsil zmeniť pred faktom predplatného... robot nič nereagoval, pretože kód v hercovi prešiel do databázy nastavení, nenašiel ho a zrútil sa. Na otázku, prečo nevytvoriť nastavenia podľa potreby, som sa dozvedel, že nie je potrebné ich meniť, ak sa používateľ neprihlásil na odber... Systém filtrovania správ bol urobený akosi nezrozumiteľne a aj po bližšom pohľade na kód som mohol nerozumiem, či to bolo pôvodne zamýšľané týmto spôsobom, alebo je tam chyba.

Do chatu nebol odoslaný žiadny zoznam článkov, namiesto toho mi bolo navrhnuté, aby som ich napísal ja. To ma prekvapilo - vo všeobecnosti som nebol proti naťahovaniu všelijakých vecí do projektu, ale logické by to bolo pre toho, kto tieto veci doniesol a posral. Ale nie, zdá sa, že druhý účastník rezignoval na všetko, ale povedal, že zoznam v chate bol údajne zlým riešením a bolo potrebné urobiť znamenie s udalosťami ako „článok y bol odoslaný používateľovi x“. Potom, ak používateľ požiadal o zaslanie nových článkov, bolo potrebné odoslať požiadavku do databázy, ktorá by z udalostí vybrala udalosti súvisiace s používateľom, získala aj zoznam nových článkov, vyfiltrovala ich, odoslala používateľovi a hodí udalosti o tomto späť do databázy.

Druhý účastník sa nechal uniesť niekam smerom k abstrakciám, kedy bude bot dostávať nielen články od Habra a posielať ho nielen na telegram.

Akcie v podobe samostatného nápisu som nejako zrealizoval na druhú polovicu septembra. Nie je to optimálne, ale bot aspoň začal pracovať a znova mi začal posielať články a pomaly som zisťoval, čo sa v kóde deje.

Teraz sa môžete vrátiť na začiatok a pamätať si, že úložisko som pôvodne nevytvoril ja. Čo mohlo takto dopadnúť? Moja žiadosť o stiahnutie bola zamietnutá. Ukázalo sa, že som mal redneck kód, že som nevedel pracovať v tíme a musel som opraviť chyby v aktuálnej implementačnej krivke a nie doladiť ju do použiteľného stavu.

Naštval som sa a pozrel som sa na históriu odovzdania a množstvo napísaného kódu. Pozrel som sa na momenty, ktoré boli pôvodne dobre napísané a potom boli prerušené...

Do prdele

Spomenul som si na článok Nie ste Google.

Myslel som si, že nikto naozaj nepotrebuje nápad bez realizácie. Myslel som si, že chcem mať fungujúceho bota, ktorý bude fungovať v jednej kópii na jednom počítači ako jednoduchý java program. Viem, že môj bot bude fungovať mesiace bez reštartov, keďže som už v minulosti takýchto botov napísal. Ak náhle spadne a nepošle používateľovi ďalší článok, nebo sa nespadne k zemi a nič katastrofálne sa nestane.

Prečo potrebujem Docker, mongoDB a ďalší kult „seriózneho“ softvéru, ak kód jednoducho nefunguje alebo funguje nesprávne?

Projekt som rozdelil a urobil všetko tak, ako som chcel.

Telegram bot pre personalizovaný výber článkov od Habr

Približne v rovnakom čase som zmenil prácu a voľný čas mi začal veľmi chýbať. Ráno som sa zobudil priamo vo vlaku, večer som sa vrátil neskoro a už sa mi nechcelo nič robiť. Chvíľu som nič nerobil, potom ma premohla túžba dokončiť robota a počas rannej jazdy do práce som začal pomaly prepisovať kód. Nehovorím, že to bolo produktívne: sedieť v natriasajúcom sa vlaku s notebookom na kolenách a pozerať sa na pretečenie zásobníka z telefónu nie je príliš pohodlné. Čas strávený písaním kódu však preletel úplne bez povšimnutia a projekt sa začal pomaly posúvať k funkčnému stavu.

Niekde vzadu v mojej mysli bol červík pochybností, ktorý chcel použiť mongoDB, ale myslel som si, že okrem výhod „spoľahlivého“ stavového úložiska sú tu aj viditeľné nevýhody:

  • Databáza sa stáva ďalším bodom zlyhania.
  • Kód sa stáva zložitejším a jeho napísanie mi bude trvať dlhšie.
  • Kód sa stáva pomalým a neefektívnym; namiesto zmeny objektu v pamäti sa zmeny odošlú do databázy a v prípade potreby sa stiahnu späť.
  • Existujú obmedzenia týkajúce sa typu ukladania udalostí do samostatnej tabuľky, ktoré sú spojené so zvláštnosťami databázy.
  • Skúšobná verzia Monga má určité obmedzenia a ak na ne narazíte, budete musieť Mongu na niečom spustiť a nakonfigurovať.

Vystrihol som mongu, teraz sa stav robota jednoducho uloží do pamäte programu a z času na čas sa uloží do súboru vo forme json. Snáď v komentároch napíšu, že sa mýlim, že práve tu treba použiť databázu atď. Ale toto je môj projekt, prístup k súboru je čo najjednoduchší a funguje to transparentne.

Vyhodil magické hodnoty ako -1 a vrátil normálne Option, pridané ukladanie hash tabuľky s odoslanými článkami späť do objektu s informáciami o chate. Pridané vymazanie informácií o článkoch starších ako päť dní, aby sa neukladalo všetko. Logovanie som uviedol do funkčného stavu - logy sa zapisujú v primeranom množstve do súboru aj do konzoly. Pridaných niekoľko admin príkazov, ako je ukladanie stavu alebo získavanie štatistík, ako je počet používateľov a článkov.

Opravená kopa maličkostí: napríklad pri článkoch sa teraz uvádza počet zobrazení, hodnotení Páči sa mi, Nepáči sa mi a komentárov v čase prechodu filtrom používateľa. Vo všeobecnosti je prekvapujúce, koľko malých vecí bolo potrebné opraviť. Viedol som si zoznam, zaznamenal som tam všetky „nezrovnalosti“ a podľa možnosti som ich opravil.

Napríklad som pridal možnosť nastaviť všetky nastavenia priamo v jednej správe:

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

A ďalší tím /settings zobrazí ich presne v takejto podobe, môžete z nej prevziať text a poslať všetky nastavenia priateľovi.
Vyzerá to ako maličkosť, no podobných nuancií sú desiatky.

Implementované filtrovanie článkov vo forme jednoduchého lineárneho modelu – používateľ si môže nastaviť dodatočné hodnotenie pre autorov a značky, ako aj prahovú hodnotu. Ak je súčet hodnotenia autora, priemerného hodnotenia pre tagy a skutočného hodnotenia článku väčší ako prahová hodnota, potom sa článok zobrazí používateľovi. Môžete buď požiadať robota o články príkazom /new, alebo sa prihlásiť na odber bota a on vám bude posielať články v osobnej správe kedykoľvek počas dňa.

Vo všeobecnosti som mal pre každý článok nápad vytiahnuť viac funkcií (huby, počet komentárov, záložky, dynamiku zmien hodnotenia, množstvo textu, obrázky a kód v článku, kľúčové slová) a ukázať používateľovi ok/ nie ok hlasujte pod každým článkom a trénujte model pre každého užívateľa, ale bol som príliš lenivý.

Navyše logika práce nebude taká zrejmá. Teraz môžem manuálne nastaviť hodnotenie +9000 pre pacienta nula a s prahovým hodnotením +20 budem zaručene dostávať všetky jeho články (samozrejme, ak pre niektoré značky nenastavím -100500).

Konečná architektúra sa ukázala byť celkom jednoduchá:

  1. Herec, ktorý ukladá stav všetkých rozhovorov a článkov. Načíta svoj stav zo súboru na disku a z času na čas ho uloží späť, zakaždým do nového súboru.
  2. Herec, ktorý z času na čas navštívi informačný kanál RSS, dozvie sa o nových článkoch, pozrie si odkazy, analyzuje a odošle tieto články prvému aktérovi. Okrem toho si niekedy vyžiada zoznam článkov od prvého aktéra, vyberie tie, ktoré nie sú staršie ako tri dni, ale dlho neboli aktualizované a aktualizuje ich.
  3. Herec, ktorý komunikuje telegramom. Ešte som sem priniesol kompletnú analýzu správy. Priateľským spôsobom by som to chcel rozdeliť na dve časti – tak, že jedna analyzuje prichádzajúce správy a druhá rieši problémy s transportom, ako je opätovné odosielanie neodoslaných správ. Teraz nedochádza k opätovnému odosielaniu a správa, ktorá nedorazila kvôli chybe, sa jednoducho stratí (pokiaľ to nie je uvedené v protokoloch), ale zatiaľ to nespôsobuje žiadne problémy. Možno nastanú problémy, ak sa na odber robota prihlási veľa ľudí a ja dosiahnem limit na odosielanie správ).

Čo sa mi páčilo je, že vďaka akka pády hercov 2 a 3 vo všeobecnosti neovplyvňujú výkon bota. Možno sa niektoré články neaktualizujú včas alebo sa niektoré správy nedostanú do telegramu, ale účet reštartuje herca a všetko pokračuje v práci. Informáciu o tom, že článok sa používateľovi zobrazí, ukladám až vtedy, keď aktér telegramu odpovie, že správu úspešne doručil. Najhoršie, čo mi hrozí, je poslať správu viackrát (ak je doručená, ale potvrdenie sa akosi stratí). V princípe, ak by prvý aktér neukladal stav v sebe, ale komunikoval s nejakou databázou, tak mohol aj on nebadane padnúť a vrátiť sa do života. Mohol by som skúsiť aj akka persistenciu na obnovenie stavu hercov, ale súčasná realizácia mi vyhovuje svojou jednoduchosťou. Nie je to tak, že by mi často padal kód – práve naopak, vynaložil som dosť veľa úsilia na to, aby som to znemožnil. Ale niečo sa stáva a možnosť rozdeliť program na izolované kúsky – hercov sa mi zdala naozaj pohodlná a praktická.

Pridal som circle-ci tak, že ak sa kód pokazí, okamžite sa o tom dozviete. Minimálne to znamená, že sa kód prestal kompilovať. Pôvodne som chcel pridať travis, ale ukázal len moje projekty bez vidličiek. Vo všeobecnosti možno obe tieto veci voľne používať v otvorených úložiskách.

Výsledky

Už je november. Bot je napísaný, používam ho posledné dva týždne a páčil sa mi. Ak máte nápady na zlepšenie, napíšte. Nevidím zmysel v tom, aby som to speňažil – nechajte to fungovať a posielajte zaujímavé články.

Odkaz na robota: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Malé závery:

  • Aj malý projekt môže zabrať veľa času.
  • Nie ste Google. Strieľať vrabce z dela nemá zmysel. Jednoduché riešenie môže fungovať rovnako dobre.
  • Projekty domácich miláčikov sú veľmi dobré na experimentovanie s novými technológiami.
  • Telegramové roboty sú napísané celkom jednoducho. Ak by nebolo „tímovej práce“ a experimentov s technológiou, robot by bol napísaný za týždeň alebo dva.
  • Herecký model je zaujímavá vec, ktorá sa dobre hodí k viacvláknovému a chybám odolnému kódu.
  • Myslím, že som okúsil, prečo komunita open source miluje forky.
  • Databázy sú dobré, pretože stav aplikácie už nezávisí od pádov/reštartov aplikácie, ale práca s databázou komplikuje kód a ukladá obmedzenia na dátovú štruktúru.

Zdroj: hab.com

Pridať komentár