Telegram bot za personalizirani izbor članaka iz Habra

Za pitanja poput "zašto?" postoji stariji članak - Natural Geektimes - čini prostor čistijim.

Članaka ima puno, iz subjektivnih razloga neke od njih ne volim, a neke je, naprotiv, šteta preskočiti. Želio bih optimizirati ovaj proces i uštedjeti vrijeme.

Gornji članak je predložio pristup skriptiranja u pretraživaču, ali mi se nije dopao (iako sam ga ranije koristio) iz sljedećih razloga:

  • Za različite pretraživače na vašem računaru/telefonu, morate ga ponovo konfigurisati, ako je ikako moguće.
  • Strogo filtriranje po autorima nije uvijek zgodno.
  • Problem sa autorima čije članke ne želite da propustite, čak i ako se objavljuju jednom godišnje, nije rešen.

Filtriranje ugrađeno u web stranicu na temelju ocjena članaka nije uvijek zgodno, jer visoko specijalizirani članci, unatoč njihovoj 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 ispostavilo da čitanje RSS-a nije izgledalo baš zgodno: u svakom slučaju, da biste komentirali/glasali za članak/dodali u svoje favorite, morate proći kroz pretraživač. Zato sam napisao telegram bot koji mi šalje zanimljive članke u ličnu poruku. Sam Telegram od njih pravi prelepe preglede, koji u kombinaciji sa informacijama o autoru/oceni/pregledima izgleda prilično informativno.

Telegram bot za personalizirani izbor članaka iz Habra

Ispod reza su detalji kao što su karakteristike rada, proces pisanja i tehnička rješenja.

Ukratko o botu

Repozitorijum: 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 filter - zbrajaju se ocjena članka na Habréu, autorova korisnička ocjena i prosjek korisničkih ocjena po tagovima. Ako je iznos veći od praga koji je odredio korisnik, onda članak prolazi filter.

Sporedni cilj pisanja bota bio je stjecanje zabave i iskustva. Pored toga, redovno sam se podsećao na to Ja nisam Google, pa se stoga mnoge stvari rade što jednostavnije i još primitivnije. Međutim, to nije spriječilo da proces pisanja bota potraje tri mjeseca.

Napolju je bilo leto

Juli se bližio kraju, a ja sam odlučio da napišem bota. I to ne sam, već sa prijateljem koji je savladao scalu i želio je nešto napisati na njoj. Početak je izgledao obećavajuće - kod će iseći tim, zadatak je izgledao lak i mislio sam da će za par sedmica ili mjesec bot biti spreman.

Uprkos činjenici da sam i ja sam s vremena na vrijeme pisao kod na stijeni posljednjih nekoliko godina, obično niko ne vidi niti pogleda u ovaj kod: kućni projekti, testiranje nekih ideja, prethodna obrada podataka, savladavanje nekih koncepata iz FP-a. Baš me je zanimalo kako izgleda pisanje koda u timu, jer se kod na stijeni može napisati na vrlo različite načine.

Šta je moglo nestati tako? Ipak, nemojmo požurivati ​​stvari.
Sve što se dešava može se pratiti korištenjem historije urezivanja.

Poznanik je napravio repozitorijum 27. jula, ali ništa drugo nije uradio, pa sam počeo da pišem kod.

30 Juli

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

  • com.github.pureconfig za čitanje typesafe konfiguracija direktno u klase slučaja (ispostavilo se da je vrlo zgodno)
  • scala-xml za čitanje xml-a: pošto sam u početku želeo da napišem sopstvenu implementaciju za rss feed, a rss feed je u xml formatu, koristio sam ovu biblioteku za raščlanjivanje. Zapravo, pojavio se i RSS parsing.
  • scalatest za testove. Čak i za male projekte pisanje testova štedi vrijeme - na primjer, kada se otklanjaju greške xml raščlanjivanja, mnogo je lakše preuzeti ga u datoteku, napisati testove i ispraviti greške. Kada se kasnije pojavila greška sa raščlanjivanjem nekog čudnog html-a sa nevažećim utf-8 znakovima, pokazalo se da je zgodnije staviti ga 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 dopao. Ideja OOP-a se može posmatrati i sa druge strane – postoje akteri koji razmenjuju poruke. Ono što je interesantnije je da možete (i trebali biste) napisati kod na način da poruka možda neće stići ili možda neće biti obrađena (općenito govoreći, kada je račun pokrenut na jednom računaru, poruke ne bi trebale biti izgubljene). U početku sam se češao po glavi i bilo je smeća u kodu sa glumcima koji su se pretplatili 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 ponovo pokreće - rezultat je prilično tolerantan sistem.

9 Aug

Dodao sam projekat scala-scrapper za raščlanjivanje html stranica sa Habra (za izvlačenje informacija kao što su ocjena članka, broj oznaka itd.).

I mačke. One u stijeni.

Telegram bot za personalizirani izbor članaka iz Habra

Zatim sam pročitao knjigu o distribuiranim bazama podataka, svidjela mi se ideja o CRDT-u (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 promocija postepeno raste, kao i broj pluseva (kao i minusa). Ako imam dvije verzije informacija o članku, onda ih mogu "spojiti u jednu" - stanje brojača koje je veće smatra se relevantnijim.

Polugrupa znači da se dva objekta sa informacijama o članku 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, ovdje postoji i asocijativnost.

Na primjer, kao što je planirano, rss nakon raščlanjivanja daje blago oslabljene informacije o članku - bez metrike kao što je broj pregleda. Poseban akter je zatim uzeo informacije o člancima i otrčao na html stranice da ih ažurira i spoji sa starom verzijom.

Uopšteno govoreći, kao u akki, nije bilo potrebe za ovim, mogli ste jednostavno pohraniti updateDate za članak i uzeti noviji bez ikakvih spajanja, ali put avanture me je vodio.

12 Aug

Počeo sam da se osećam slobodnije i, samo iz zabave, napravio sam od svakog razgovora posebnog glumca. Teoretski, sam glumac je težak oko 300 bajtova i mogu se kreirati u milionima, tako da je ovo sasvim normalan pristup. Čini mi se da se rješenje pokazalo prilično zanimljivim:

Jedan akter je bio most između telegram servera i sistema poruka u Akki. Jednostavno je primao poruke i slao ih željenom akteru razgovora. Glumac u ćaskanju mogao bi nešto vratiti kao odgovor - i to bi bilo poslano nazad u telegram. Ono što je bilo vrlo zgodno je to što se ovaj glumac pokazao što jednostavnijim i sadržavao je samo logiku za odgovaranje na poruke. Inače, informacije o novim člancima stizale su na svaki chat, ali opet ne vidim nikakve probleme u tome.

Uglavnom, bot je već radio, odgovarao na poruke, čuvao listu članaka poslanih korisniku, a ja sam već mislio da je bot skoro spreman. Polako sam dodavao male funkcije kao što je normalizacija imena autora i oznaka (zamjena “sd f” sa “s_d_f”).

Ostala je samo jedna stvar mali ali — država nije nigde sačuvana.

Sve je krenulo naopako

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

  • Čini se da MongoDB pohranjuje stanje. U isto vrijeme, logovi u projektu su pokvareni, jer je iz nekog razloga Monga počeo da ih šalje spamom i neki ljudi su ih jednostavno isključili globalno.
  • Glumac bridža u Telegramu se transformirao do neprepoznatljivosti i sam je počeo analizirati poruke.
  • Glumci za razgovore su nemilosrdno izrezani, a umjesto njih je zamijenjen glumac koji je sakrio sve informacije o svim razgovorima odjednom. Za svako kijanje, ovaj glumac je ulazio u nevolje. Pa da, kao kada ažurirate informacije o članku, teško je poslati ih svim akterima chata (mi smo kao Google, milioni korisnika čekaju milion članaka u chatu za svakog), ali svaki put kada se chat ažurira, normalno je ići u Monga. Kao što sam shvatio mnogo kasnije, i logika rada četovanja je potpuno izrezana i na njenom mjestu se pojavilo nešto što nije funkcionisalo.
  • Od klasa tipova nije ostao nikakav trag.
  • Neka nezdrava logika se pojavila u glumcima sa njihovim pretplatama jedni na druge, što je dovelo do stanja trke.
  • Strukture podataka sa poljima tipa Option[Int] pretvorio u Int sa magičnim zadanim vrijednostima poput -1. Kasnije sam shvatio da mongoDB pohranjuje json i nema ništa loše u tome da ga tamo pohrani Option pa, ili barem raščlaniti -1 kao Ništa, ali u to vrijeme to nisam znao i vjerovao sam na riječ da "tako treba biti." Nisam napisao taj kod i nisam se trudio da ga mijenjam za sada.
  • Saznao sam da moja javna IP adresa ima tendenciju da se menja, i svaki put sam morao da je dodam na belu listu Monga. Pokrenuo sam bota lokalno, Monga je bila negde na serverima Monge kao kompanije.
  • Odjednom je nestala normalizacija oznaka i formatiranja poruka za telegrame. (Hmm, zašto bi to bilo?)
  • Svidjelo mi se što je stanje bota pohranjeno u eksternoj bazi podataka, a nakon ponovnog pokretanja nastavlja raditi kao da se ništa nije dogodilo. Međutim, to je bio jedini plus.

Drugoj se nije posebno žurilo, a sve te promjene pojavile su se u jednoj velikoj gomili već početkom septembra. Nisam odmah shvatio razmjere nastalog uništenja i počeo sam razumijevati rad baze podataka, jer... Nikada ranije nisam imao posla sa njima. Tek kasnije sam shvatio koliko je radnog koda izrezano i koliko je grešaka dodato na njegovo mjesto.

Septembar

U početku sam mislio da bi bilo korisno savladati Monga i to dobro uraditi. Tada sam polako počeo shvaćati da je organiziranje komunikacije s bazom podataka također umjetnost u kojoj možete napraviti mnogo utrka i samo pogriješiti. Na primjer, ako korisnik primi dvije poruke poput /subscribe - i kao odgovor na svaku ćemo kreirati unos u tabeli, jer u trenutku obrade tih poruka korisnik nije pretplaćen. Sumnjam da komunikacija sa Mongom u sadašnjem obliku nije napisana na najbolji način. Na primjer, korisnička podešavanja su kreirana 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 našao i srušio se. Na pitanje zašto ne kreiram postavke po potrebi, saznao sam da ih nema potrebe mijenjati ako se korisnik nije pretplatio... Sistem filtriranja poruka je napravljen nekako neočigledno, a čak sam i nakon detaljnog pogleda na kod mogao ne razumijem da li je to prvobitno bilo zamišljeno ili postoji greška.

Nije bilo liste članaka dostavljenih na chat; umjesto toga, predloženo je da ih sam napišem. To me je iznenadilo – generalno, nisam bio protiv uvlačenja svašta u projekat, ali bi bilo logično za onoga ko je te stvari unio i zeznuo. Ali ne, činilo se da je drugi učesnik odustao od svega, ali je rekao da je lista unutar ćaskanja navodno loše rješenje, te je potrebno napraviti natpis sa 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 odabrala događaje vezane za korisnika, također dobila listu novih članaka, filtrirala ih, poslala ih korisniku i baciti događaje o ovome natrag u bazu podataka.

Drugi učesnik je odnesen negde ka apstrakcijama, kada će bot primati ne samo članke sa Habra, već će biti poslat ne samo u telegram.

Nekako sam implementirao događaje u vidu posebnog znaka za drugu polovinu septembra. Nije optimalno, ali barem je bot proradio i ponovo mi je počeo slati članke, a ja sam polako shvatio šta se dešava u kodu.

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

Uznemirio sam se i pogledao istoriju urezivanja i količinu napisanog koda. Pogledao sam trenutke koji su prvobitno bili dobro napisani, a onda su pokvareni...

Jebi ga

Sjetio sam se članka Vi niste Google.

Mislio sam da nikome nije potrebna ideja bez implementacije. Mislio sam da želim da imam funkcionalnog bota, koji će raditi u jednoj kopiji na jednom računaru kao jednostavan java program. Znam da će moj bot raditi mjesecima bez restartovanja, pošto sam takve botove već pisao u prošlosti. Ako iznenada padne i korisniku ne pošalje još jedan članak, nebo neće pasti na zemlju i neće se dogoditi ništa katastrofalno.

Zašto mi treba Docker, mongoDB i drugi cargo kult "ozbiljnog" softvera ako kod jednostavno ne radi ili radi krivo?

Odvojio sam projekat i uradio sve kako sam hteo.

Telegram bot za personalizirani izbor članaka iz Habra

Otprilike u isto vrijeme promijenio sam posao i slobodnog vremena mi je jako nedostajalo. Ujutro sam se probudio pravo u vozu, uveče sam se vratio kasno i nisam više hteo ništa da radim. Neko vreme nisam ništa radio, onda me je savladala želja da završim bot i počeo sam polako da prepisujem kod dok sam se ujutro vozio na posao. Neću reći da je bilo produktivno: sjediti u vozu koji se treslo s laptopom u krilu i gledati prelivanje steka s telefona nije baš zgodno. Međutim, vrijeme provedeno u pisanju koda proletjelo je potpuno neprimjetno, a projekt je polako krenuo ka radnom stanju.

Negdje u pozadini mog uma bio je crv sumnje koji je želio koristiti mongoDB, ali sam mislio da pored prednosti „pouzdanog“ pohranjivanja stanja, postoje i uočljivi nedostaci:

  • Baza podataka postaje još jedna tačka kvara.
  • Kod postaje složeniji i trebat će mi duže da ga napišem.
  • Kod postaje spor i neefikasan; umjesto promjene objekta u memoriji, promjene se šalju u bazu podataka i, ako je potrebno, povlače se.
  • Postoje ograničenja u pogledu vrste skladištenja događaja u posebnoj tabeli, koja su povezana sa posebnostima baze podataka.
  • Probna verzija Monge ima neka ograničenja, a ako naiđete na njih, morat ćete pokrenuti i konfigurirati Monga za nešto.

Izrezao sam monga, sada se stanje bota jednostavno pohranjuje u memoriju programa i s vremena na vrijeme sprema u datoteku u obliku json-a. Možda će u komentarima napisati da sam u krivu, da tu treba koristiti bazu podataka itd. Ali ovo je moj projekat, pristup fajlu je što jednostavniji i radi na transparentan način.

Izbacio magične vrijednosti poput -1 i vratio normalne Option, dodala je pohranu hash tablice s poslanim člancima natrag u objekt s informacijama o chatu. Dodano brisanje informacija o člancima starijim od pet dana, kako ne bi pohranili sve. Doveo sam evidenciju u radno stanje - evidencije se pišu u razumnim količinama i u fajl i u konzolu. Dodano nekoliko administratorskih naredbi kao što je spremanje stanja ili dobivanje statistike kao što je broj korisnika i članaka.

Ispravljena gomila sitnica: na primjer, za članke je sada naznačen broj pregleda, lajkova, nesviđanja i komentara u trenutku prolaska filtera korisnika. Općenito, iznenađujuće je koliko je sitnica trebalo ispraviti. Vodio sam listu, zabilježio sve “nepravilnosti” i ispravio ih koliko je to bilo moguće.

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

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

I još jedan tim /settings prikazuje ih upravo u ovom obliku, možete uzeti tekst iz njega i poslati sva podešavanja prijatelju.
Čini se kao mala stvar, ali postoji na desetine 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 zbir autorske ocjene, prosječne ocjene za oznake i stvarne ocjene članka veći od granične vrijednosti, članak se prikazuje korisniku. Možete ili zatražiti od bota članke komandom /new, ili se pretplatiti na bota i on će vam poslati članke u ličnoj poruci u bilo koje doba dana.

Uopšteno govoreći, imao sam ideju za svaki članak da izvučem više funkcija (hubova, broj komentara, bookmarka, dinamiku promjene ocjena, količinu teksta, slika i koda u članku, ključne riječi), i pokažem korisniku ok/ nije ok glasaj ispod svakog članka i treniraj model za svakog korisnika, ali sam bio previše lijen.

Osim toga, logika rada neće biti tako očigledna. Sada mogu ručno podesiti ocjenu od +9000 za pacijenta Zero, a s pragom ocjene od +20 garantovano ću primiti sve njegove članke (osim ako, naravno, ne postavim -100500 za neke oznake).

Konačna arhitektura se pokazala prilično jednostavnom:

  1. Glumac koji pohranjuje stanje svih razgovora i članaka. Učitava svoje stanje iz datoteke na disku i s vremena na vrijeme ga pohranjuje, svaki put u novu datoteku.
  2. Glumac koji s vremena na vrijeme posjećuje RSS feed, uči o novim člancima, gleda linkove, analizira i šalje te članke prvom akteru. Osim toga, ponekad traži spisak članaka od prvog aktera, bira one koji nisu stariji od tri dana, ali dugo nisu ažurirani i ažurira ih.
  3. Glumac koji komunicira telegramom. I dalje sam donio raščlanjivanje poruke u potpunosti ovdje. Na prijateljski način, htio bih to podijeliti na dva - tako da jedna analizira dolazne poruke, a druga se bavi problemima transporta kao što je ponovno slanje neposlanih poruka. Sada nema ponovnog slanja, a poruka koja nije stigla zbog greške jednostavno će biti izgubljena (osim ako nije zabilježena u logovima), ali do sada to nije stvaralo probleme. Možda će nastati problemi ako se gomila ljudi pretplati na bota i ja dostignem limit za slanje poruka).

Ono što mi se svidjelo je što zahvaljujući akki, padovi glumaca 2 i 3 uglavnom ne utiču na performanse bota. Možda se neki članci ne ažuriraju na vrijeme ili neke poruke ne stignu do telegrama, ali račun ponovo pokreće glumca i sve nastavlja raditi. Čuvam informaciju da se članak prikazuje korisniku samo kada akter telegrama odgovori da je uspješno isporučio poruku. Najgore što mi prijeti je da pošaljem poruku nekoliko puta (ako je isporučena, ali se potvrda nekako izgubi). U principu, ako prvi akter nije pohranio stanje u sebe, već komunicirao s nekom bazom podataka, onda bi i on mogao neprimjetno pasti i vratiti se u život. Mogao bih probati i akka upornost da vratim stanje aktera, ali sadašnja implementacija mi odgovara svojom jednostavnošću. Nije da mi se kod često ruši – naprotiv, uložio sam dosta truda da to učinim nemogućim. Ali sranja se dešavaju, a mogućnost da se program razbije na izolovane delove-glumci mi se činila zaista zgodnom i praktičnom.

Dodao sam krug-ci tako da ako se kod pokvari, odmah ćete saznati za to. U najmanju ruku, to znači da je kod prestao sa kompajliranjem. U početku sam htio dodati Travisa, ali je prikazao samo moje projekte bez viljuški. Općenito, obje ove stvari mogu se slobodno koristiti u otvorenim repozitorijumima.

Ishodi

Već je novembar. Bot je napisan, koristim ga zadnje dvije sedmice i dopao mi se. Ako imate ideje za poboljšanje, pišite. Ne vidim smisao u monetizaciji - neka samo radi i šalji zanimljive članke.

Bot link: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Mali zaključci:

  • Čak i mali projekat može oduzeti mnogo vremena.
  • Vi niste Google. Nema smisla gađati vrapce iz topa. Jednostavno rješenje može funkcionirati jednako dobro.
  • Projekti za kućne ljubimce su vrlo dobri za eksperimentiranje s novim tehnologijama.
  • Telegram botovi su napisani prilično jednostavno. Da nije bilo "timskog rada" i eksperimenata sa tehnologijom, bot bi bio napisan za nedelju ili dve.
  • Model glumca je zanimljiva stvar koja se dobro slaže sa višenitnim i kodom otpornim na greške.
  • Mislim da sam shvatio zašto zajednica otvorenog koda voli viljuške.
  • Baze podataka su dobre jer stanje aplikacije više ne zavisi od pada/ponovnog pokretanja aplikacije, ali rad sa bazom podataka komplikuje kod i nameće ograničenja na strukturu podataka.

izvor: www.habr.com

Dodajte komentar