Telegram bot za personalizirani odabir članaka s Habra

Za pitanja poput "zašto?" postoji stariji članak - Natural Geektimes - činimo prostor čišćim.

Ima puno članaka, iz subjektivnih razloga neki od njih mi se ne sviđaju, a neke je, naprotiv, šteta preskočiti. Želio bih optimizirati ovaj proces i uštedjeti vrijeme.

Gornji članak predložio je pristup skriptiranju unutar preglednika, ali mi se nije baš svidio (iako sam ga prije koristio) iz sljedećih razloga:

  • Za različite preglednike na vašem računalu/telefonu, morate ga ponovno konfigurirati, ako je ikako moguće.
  • Strogo filtriranje prema autorima nije uvijek zgodno.
  • Nije riješen problem s autorima čije članke ne želite propustiti, čak i ako izlaze jednom godišnje.

Filtriranje ugrađeno u web mjesto na temelju ocjena članaka nije uvijek zgodno, jer visoko specijalizirani članci, unatoč svojoj vrijednosti, mogu dobiti prilično skromnu ocjenu.

U početku sam želio generirati RSS feed (ili čak nekoliko), ostavljajući tamo samo zanimljive stvari. Ali na kraju se pokazalo da čitanje RSS-a nije baš zgodno: u svakom slučaju, da biste komentirali/glasovali za članak/dodali ga u svoje favorite, morate proći kroz preglednik. Zato sam napisao telegram bot koji mi šalje zanimljive članke u osobne poruke. Sam Telegram od njih radi prekrasne preglede koji u kombinaciji s podacima o autoru/ocjeni/pregledima izgledaju dosta informativno.

Telegram bot za personalizirani odabir članaka s Habra

Ispod reza nalaze se detalji kao što su značajke rada, postupak pisanja i tehnička rješenja.

Ukratko o botu

Spremište: https://github.com/Kright/habrahabr_reader

Bot u telegramu: https://t.me/HabraFilterBot

Korisnik postavlja dodatnu ocjenu za oznake i autore. Nakon toga se na članke primjenjuje filtar - zbrajaju se ocjena članka na Habréu, korisnička ocjena autora i prosjek korisničkih ocjena po tagu. Ako je iznos veći od praga koji je odredio korisnik, tada članak prolazi filtar.

Sporedni cilj pisanja bota bio je stjecanje zabave i iskustva. Uz to sam se redovito podsjećao na to Ja nisam Google, i stoga se mnoge stvari rade što jednostavnije, pa čak i primitivno. Međutim, to nije spriječilo da proces pisanja bota traje tri mjeseca.

Vani je bilo ljeto

Srpanj je bio na izmaku, a ja sam odlučio napisati bota. I to ne sam, nego s prijateljem koji je savladao scalu i htio je nešto napisati na njoj. Početak je izgledao obećavajuće - kod će rezati tim, zadatak se činio laganim i mislio sam da će za nekoliko tjedana ili mjesec dana bot biti spreman.

Unatoč činjenici da sam zadnjih nekoliko godina s vremena na vrijeme pisao kod na kamenu, nitko obično ne vidi niti pogleda ovaj kod: kućni projekti, testiranje nekih ideja, pretprocesiranje podataka, svladavanje nekih koncepata iz FP-a. Baš me zanimalo kako izgleda pisanje koda u timu, jer se kod na rocku može pisati na vrlo različite načine.

Što je moglo nestati tako? Ipak, nemojmo požurivati ​​stvari.
Sve što se događa može se pratiti pomoću povijesti predaje.

Poznanik je napravio repozitorij 27. srpnja, ali ništa više nije radio, pa sam počeo pisati kod.

srpanj 30

Ukratko: Napisao sam raščlanjivanje Habrovog rss feeda.

  • com.github.pureconfig za čitanje tipski sigurnih konfiguracija izravno u klase slučajeva (pokazalo se da je vrlo zgodno)
  • scala-xml za čitanje xml: budući da sam u početku želio napisati vlastitu implementaciju za rss feed, a rss feed je u xml formatu, koristio sam ovu biblioteku za raščlanjivanje. Zapravo, pojavilo se i RSS parsiranje.
  • scalatest za testove. Čak i za male projekte, pisanje testova štedi vrijeme - na primjer, kada otklanjate pogreške xml parsiranja, mnogo je lakše preuzeti ga u datoteku, napisati testove i ispraviti pogreške. Kada se kasnije pojavio bug s parsiranjem nekog čudnog html-a s nevažećim utf-8 znakovima, pokazalo se da je zgodnije staviti to u datoteku i dodati test.
  • glumci iz Akke. Objektivno, uopće nisu bili potrebni, ali projekt je napisan iz zabave, htio sam ih isprobati. Kao rezultat toga, spreman sam reći da mi se svidjelo. Ideja OOP-a može se promatrati i s druge strane – postoje akteri koji razmjenjuju poruke. Ono što je još zanimljivije je da možete (i trebate) napisati kod na takav način da poruka možda neće stići ili se možda neće obraditi (općenito govoreći, kada račun radi na jednom računalu, poruke se ne bi trebale izgubiti). Isprva sam se češao po glavi i bilo je smeća u kodu s glumcima koji su se pretplaćivali jedni na druge, ali na kraju sam uspio smisliti prilično jednostavnu i elegantnu arhitekturu. Kod unutar svakog aktera može se smatrati jednonitnim; kada se akter sruši, acca ga ponovno pokreće - rezultat je sustav prilično tolerantan na pogreške.

9 kolovoz

Dodao sam projektu scala-scrapper za raščlanjivanje html stranica iz Habra (za izvlačenje informacija poput ocjene članka, broja knjižnih oznaka itd.).

I Mačke. One u stijeni.

Telegram bot za personalizirani odabir članaka s Habra

Zatim sam pročitao knjigu o distribuiranim bazama podataka, svidjela mi se ideja o CRDT (Replicirani tip podataka bez sukoba, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), pa sam objavio klasu tipa komutativne polugrupe za informacije o članku na Habréu.

Zapravo, ideja je vrlo jednostavna – imamo brojače koji se monotono mijenjaju. Broj promaknuća postupno raste, kao i broj pluseva (kao i broj minusa). Ako imam dvije verzije informacija o članku, tada ih mogu "spojiti u jednu" - stanje brojača koje je veće smatra se relevantnijim.

Polugrupa znači da se dva objekta s informacijama o artiklu mogu spojiti u jedan. Komutativno znači da možete spojiti i A + B i B + A, rezultat ne ovisi o redoslijedu, a na kraju će ostati najnovija verzija. Inače, tu ima i asocijativnosti.

Na primjer, kao što je i planirano, rss je nakon parsiranja dao blago oslabljene informacije o članku – bez metrike kao što je broj pregleda. Poseban glumac je zatim uzeo informacije o člancima i otrčao do html stranica kako bi ih ažurirao i spojio sa starom verzijom.

Općenito govoreći, kao u akki, nije bilo potrebe za tim, mogao si jednostavno spremiti updateDate za članak i uzeti noviji bez ikakvih spajanja, ali mene je put avanture vodio.

12 kolovoz

Počeo sam se osjećati slobodnije i, samo zabave radi, svakom sam razgovoru napravio zasebnog glumca. Teoretski, sam glumac ima oko 300 bajtova i može ih se kreirati u milijunima, tako da je to sasvim normalan pristup. Čini mi se da se rješenje pokazalo prilično zanimljivim:

Jedan akter bio je most između poslužitelja telegrama i sustava poruka u Akki. Jednostavno je primao poruke i slao ih željenom chat akteru. Glumac chata mogao bi poslati nešto kao odgovor - i to bi bilo poslano natrag u telegram. Ono što je bilo vrlo zgodno je da se ovaj glumac pokazao što je moguće jednostavnijim i sadržavao je samo logiku za odgovaranje na poruke. Usput, informacije o novim člancima dolazile su na svaki chat, ali opet ne vidim nikakav problem u tome.

Općenito, bot je već radio, odgovarao na poruke, spremao popis članaka poslanih korisniku i već sam mislio da je bot gotovo spreman. Polako sam dodavao male značajke poput normaliziranja imena autora i oznaka (zamjena "sd f" sa "s_d_f").

Ostala je samo jedna stvar mali ali — država se nigdje nije spasila.

Sve je pošlo po zlu

Možda ste primijetili da sam bota napisao uglavnom sam. Dakle, drugi sudionik se uključio u razvoj, au kodu su se pojavile sljedeće promjene:

  • Čini se da MongoDB pohranjuje stanje. Istodobno, polomljeni su i logi u projektu jer ih je Monga iz nekog razloga počela spamati te su ih neki ljudi jednostavno globalno isključili.
  • Glumac mosta u Telegramu transformirao se do neprepoznatljivosti i počeo sam analizirati poruke.
  • Glumci za chatove nemilosrdno su izrezani, a umjesto njih je zamijenjen glumcem koji je sakrio sve informacije o svim chatovima odjednom. Na svaki kih, ovaj glumac je imao problema. Pa da, kao kod ažuriranja informacija o članku, njihovo slanje svim sudionicima chata je teško (mi smo poput Googlea, milijuni korisnika čekaju milijun članaka u chatu za svakog), ali svaki put kad se chat ažurira, normalno je ići u Mongu. Kao što sam shvatio mnogo kasnije, radna logika chatova također je potpuno izrezana i na njenom mjestu pojavilo se nešto što nije funkcioniralo.
  • Od tipskih klasa nije ostalo ni traga.
  • Pojavila se neka nezdrava logika u glumcima s međusobnim pretplatama, što dovodi do stanja utrke.
  • Strukture podataka s poljima tipa Option[Int] pretvorio u Int s magičnim zadanim vrijednostima poput -1. Kasnije sam shvatio da mongoDB pohranjuje json i nema ništa loše u pohranjivanju tamo Option dobro, ili barem analizirati -1 kao Ništa, ali u to vrijeme to nisam znao i vjerovao sam sebi na riječ da "tako treba biti." Nisam napisao taj kod i nisam se trudio mijenjati ga za sada.
  • Saznao sam da se moja javna IP adresa često mijenja i svaki put sam je morao dodati na popis dopuštenih Mongoa. Bot sam pokrenuo lokalno, Monga je bila negdje na serverima Monge kao tvrtke.
  • Odjednom je nestala normalizacija oznaka i formatiranja poruka za telegrame. (Hmm, zašto bi to bilo?)
  • Svidjelo mi se što se stanje bota pohranjuje u vanjsku bazu podataka, a nakon ponovnog pokretanja nastavlja raditi kao da se ništa nije dogodilo. Međutim, ovo je bio jedini plus.

Drugoj osobi nije se posebno žurilo, a sve te promjene pojavile su se u jednoj velikoj hrpi već početkom rujna. Nisam odmah shvatio razmjere rezultirajućeg razaranja i počeo sam shvaćati rad baze podataka, jer... Nikad prije nisam imao posla s njima. Tek kasnije sam shvatio koliko je radnog koda izrezano i koliko je grešaka dodano na njegovo mjesto.

Rujan

Isprva sam mislio da bi bilo korisno savladati Mongu i raditi je dobro. Tada sam polako počeo shvaćati da je organiziranje komunikacije s bazom također umjetnost u kojoj se možete puno utrkivati ​​i samo griješiti. Na primjer, ako korisnik primi dvije poruke poput /subscribe - i kao odgovor na svaku ćemo napraviti unos u tablici, jer u trenutku obrade tih poruka korisnik nije pretplaćen. Sumnjam da komunikacija s Mongom u sadašnjem obliku nije napisana na najbolji način. Na primjer, korisnikove postavke su stvorene u trenutku kada se prijavio. Ako ih je pokušao promijeniti prije činjenice pretplate... bot nije ništa odgovorio, jer je kod u glumcu otišao u bazu podataka za postavke, nije ga pronašao i srušio se. Na pitanje zašto ne izraditi postavke po potrebi, naučio sam da ih nema potrebe mijenjati ako korisnik nije pretplaćen... Sustav filtriranja poruka napravljen je nekako neočigledno, pa čak i nakon detaljnog pregleda koda mogao sam ne razumijem je li to tako zamišljeno u početku ili postoji greška.

Nije bilo popisa članaka poslanih na chat; umjesto toga, predloženo je da ih sam napišem. To me iznenadilo - općenito, nisam bio protiv uvlačenja svakakvih stvari u projekt, ali bilo bi logično da onaj tko je te stvari donio i zajebe. Ali ne, drugi sudionik kao da je odustao od svega, ali je rekao da je lista unutar chata navodno loše rješenje, te da je potrebno napraviti znak s događajima poput “članak y je poslan korisniku x”. Zatim, ako je korisnik tražio slanje novih članaka, bilo je potrebno poslati zahtjev bazi podataka koja bi iz događaja izabrala događaje vezane uz korisnika, također dobila popis novih članaka, filtrirala ih, poslala korisniku. i baciti događaje o tome natrag u bazu podataka.

Drugi sudionik je odveden negdje prema apstrakcijama, kada će bot primati ne samo članke s Habra i biti poslan ne samo na telegram.

Nekako sam implementirao događaje u obliku zasebnog znaka za drugu polovicu rujna. Nije optimalno, ali barem je bot proradio i ponovno mi počeo slati članke, a ja sam polako shvaćao što se događa u kodu.

Sada se možete vratiti na početak i zapamtiti da spremište nisam izvorno stvorio ja. Što je moglo ovako proći? Moj zahtjev za povlačenje je odbijen. Ispostavilo se da imam redneck kod, da ne znam raditi u timu i da sam morao popraviti greške u trenutnoj krivulji implementacije, a ne doraditi je do upotrebljivog stanja.

Uzrujao sam se i pogledao povijest predaje i količinu napisanog koda. Pogledao sam trenutke koji su prvotno bili dobro napisani, a onda su ponovno prekinuti...

Jebi ga

Sjetio sam se članka Vi niste Google.

Mislio sam da nitko zapravo ne treba ideju bez implementacije. Mislio sam da želim imati radnog bota, koji će raditi u jednoj jedinoj kopiji na jednom računalu kao jednostavan java program. Znam da će moj bot raditi mjesecima bez ponovnog pokretanja, jer sam već napisao takve botove u prošlosti. Ako iznenada padne i ne pošalje korisniku još jedan članak, nebo se neće srušiti na zemlju i neće se dogoditi ništa katastrofalno.

Zašto mi trebaju Docker, mongoDB i drugi kargo kult "ozbiljnog" softvera ako kod jednostavno ne radi ili radi naopako?

Forkovao sam projekt i napravio sve kako sam htio.

Telegram bot za personalizirani odabir članaka s Habra

Otprilike u isto vrijeme promijenio sam posao i slobodnog vremena je počelo jako nedostajati. Ujutro sam se probudio odmah u vlaku, navečer sam se kasno vratio i više nisam htio ništa raditi. Neko vrijeme nisam radio ništa, a onda me želja da dovršim bota svladala i počeo sam polako prepisivati ​​kod dok sam se ujutro vozio na posao. Neću reći da je bilo produktivno: sjediti u drhtavom vlaku s laptopom u krilu i gledati prekoračenje stogova s ​​telefona nije baš zgodno. Međutim, vrijeme utrošeno na pisanje koda proletjelo je potpuno nezapaženo, a projekt se polako počeo kretati prema radnom stanju.

Negdje u pozadini mog uma postojao je crv sumnje koji je želio koristiti mongoDB, ali mislio sam da pored prednosti "pouzdane" pohrane stanja postoje i vidljivi nedostaci:

  • Baza podataka postaje još jedna točka neuspjeha.
  • Kod postaje sve složeniji i trebat će mi više vremena da ga napišem.
  • Kod postaje spor i neučinkovit; umjesto promjene objekta u memoriji, promjene se šalju u bazu podataka i, ako je potrebno, povlače se natrag.
  • Postoje ograničenja u pogledu vrste pohrane događaja u zasebnoj tablici, koja su povezana s osobitostima baze podataka.
  • Probna verzija Monge ima neka ograničenja, a ako naiđete na njih, morat ćete pokrenuti i konfigurirati Mongu na nečemu.

Izrezao sam mongu, sada se stanje bota jednostavno pohranjuje u memoriju programa i s vremena na vrijeme sprema u datoteku u obliku json. Možda će u komentarima napisati da sam u krivu, da tu treba koristiti bazu itd. Ali ovo je moj projekt, pristup s datotekom je maksimalno jednostavan i radi na transparentan način.

Izbacio magične vrijednosti poput -1 i vratio normalne Option, dodano pohranjivanje hash tablice s poslanim člancima natrag u objekt s informacijama o chatu. Dodano brisanje informacija o člancima starijim od pet dana, da se ne pohranjuje sve. Zapisivanje sam doveo u radno stanje - zapisi se zapisuju u razumnim količinama i u datoteku i u konzolu. Dodano je nekoliko administratorskih naredbi kao što je spremanje stanja ili dobivanje statistike kao što je broj korisnika i članaka.

Ispravljena je hrpa sitnica: na primjer, za članke je sada naznačen broj pregleda, lajkova, nesviđanja i komentara u trenutku prolaska filtra korisnika. Općenito, iznenađujuće je koliko se sitnica moralo ispraviti. Vodio sam popis, tamo bilježio sve “nepravilnosti” i ispravljao ih koliko je to bilo moguće.

Na primjer, dodao sam mogućnost postavljanja svih postavki izravno u jednoj poruci:

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

I druga ekipa /settings prikazuje ih točno u ovom obliku, možete preuzeti tekst iz njega i poslati sve postavke prijatelju.
Čini se kao mala stvar, ali postoje deseci sličnih nijansi.

Implementirano filtriranje članaka u obliku jednostavnog linearnog modela - korisnik može postaviti dodatnu ocjenu za autore i oznake, kao i graničnu vrijednost. Ako je zbroj autorove ocjene, prosječne ocjene za oznake i stvarne ocjene članka veći od granične vrijednosti, tada se članak prikazuje korisniku. Od bota možete tražiti članke s naredbom /new ili se pretplatiti na bota i on će slati članke u osobnoj poruci u bilo koje doba dana.

Općenito govoreći, imao sam ideju za svaki članak izvući više značajki (hubovi, broj komentara, bookmarkovi, dinamika promjena ocjena, količina teksta, slika i koda u članku, ključne riječi), te pokazati korisniku ok/ nije u redu glasati ispod svakog članka i trenirati model za svakog korisnika, ali bio sam previše lijen.

Osim toga, logika rada neće biti tako očita. Sada mogu ručno postaviti ocjenu od +9000 za пациентаZero i uz ocjenu praga od +20 ja ću zajamčeno dobiti sve njegove članke (osim, naravno, ako ne postavim -100500 za neke oznake).

Konačna arhitektura se pokazala vrlo jednostavnom:

  1. Akter koji pohranjuje stanje svih razgovora i članaka. Učitava svoje stanje iz datoteke na disku i povremeno ga sprema, svaki put u novu datoteku.
  2. Glumac koji s vremena na vrijeme posjećuje RSS feed, uči o novim člancima, gleda poveznice, analizira i šalje te članke prvom glumcu. Osim toga, ponekad traži popis članaka od prvog aktera, odabire one koji nisu stariji od tri dana, ali nisu dugo ažurirani, te ih ažurira.
  3. Glumac koji komunicira telegramom. Ipak sam donio kompletnu analizu poruke ovdje. Na prijateljski način, želio bih ga podijeliti na dva - tako da jedan analizira dolazne poruke, a drugi se bavi transportnim problemima kao što je ponovno slanje neposlanih poruka. Sada nema ponovnog slanja, a poruka koja nije stigla zbog greške će se jednostavno izgubiti (osim ako nije zabilježeno u logovima), ali to do sada nije stvaralo probleme. Možda će nastati problemi ako se hrpa ljudi pretplati na bot, a ja dosegnem ograničenje za slanje poruka).

Ono što mi se svidjelo je da zahvaljujući akki, padovi glumaca 2 i 3 općenito ne utječu na performanse bota. Možda se neki članci ne ažuriraju na vrijeme ili neke poruke ne stignu do telegrama, ali račun ponovno pokreće glumca i sve nastavlja raditi. Čuvam informaciju da se članak prikazuje korisniku tek kada akter telegrama odgovori da je uspješno dostavio poruku. Najgora stvar koja mi prijeti je slanje poruke nekoliko puta (ako je isporučena, ali se potvrda nekako izgubi). U principu, ako prvi akter ne pohranjuje stanje u sebi, nego komunicira s nekom bazom podataka, onda bi i on mogao neprimjetno pasti i vratiti se u život. Mogao bih također pokušati s akka perzistencijom da vratim stanje aktera, ali trenutna implementacija mi odgovara svojom jednostavnošću. Nije da mi se kod često rušio - naprotiv, uložio sam dosta truda da to učinim nemogućim. Ali sranja se događaju, a mogućnost razbijanja programa na izolirane dijelove-glumce činila mi se stvarno zgodnom i praktičnom.

Dodao sam circle-ci tako da ako se kod pokvari, odmah ćete saznati za to. U najmanju ruku, to znači da se kod prestao kompilirati. U početku sam htio dodati travisa, ali prikazao je samo moje projekte bez forkova. Općenito, obje ove stvari mogu se slobodno koristiti u otvorenim spremištima.

Rezultati

Već je studeni. Bot je napisan, koristio sam ga zadnja dva tjedna i svidio mi se. Ako imate ideje za poboljšanje, pišite. Ne vidim smisao unovčavanja - neka samo radi i šalje zanimljive članke.

Veza za bot: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Mali zaključci:

  • Čak i mali projekt može oduzeti puno vremena.
  • Vi niste Google. Nema smisla gađati vrapce iz topa. Jednostavno rješenje može djelovati jednako dobro.
  • Projekti kućnih ljubimaca vrlo su dobri za eksperimentiranje s novim tehnologijama.
  • Telegram botovi su napisani vrlo jednostavno. Da nije bilo "timskog rada" i eksperimenata s tehnologijom, bot bi bio napisan za tjedan-dva.
  • Model glumca je zanimljiva stvar koja se dobro slaže s višenitnim i tolerantnim kodom.
  • Mislim da sam shvatio zašto zajednica otvorenog koda voli forkove.
  • Baze podataka su dobre jer stanje aplikacije više ne ovisi o padu/ponovnom pokretanju aplikacije, ali rad s bazom podataka komplicira kod i nameće ograničenja na strukturu podataka.

Izvor: www.habr.com

Dodajte komentar