Bot de Telegram per a una selecció personalitzada d'articles d'Habr

Per a preguntes com "per què?" hi ha un article més antic - Natural Geektimes: netejar l'espai.

Hi ha molts articles, per motius subjectius alguns d'ells no m'agraden, i alguns, al contrari, és una llàstima saltar-los. M'agradaria optimitzar aquest procés i estalviar temps.

L'article anterior va suggerir un enfocament de seqüència de comandaments dins del navegador, però no em va agradar molt (tot i que l'he fet servir abans) pels motius següents:

  • Per a diferents navegadors del vostre ordinador/telèfon, heu de tornar a configurar-lo, si és possible.
  • El filtratge estricte per part dels autors no sempre és convenient.
  • El problema dels autors els articles dels quals no us voleu perdre, encara que es publiquin un cop l'any, no s'ha resolt.

El filtratge integrat al lloc en funció de les classificacions d'articles no sempre és convenient, ja que els articles altament especialitzats, malgrat el seu valor, poden rebre una qualificació més aviat modesta.

Inicialment, volia generar un feed RSS (o fins i tot diversos), deixant-hi només coses interessants. Però al final, va resultar que llegir RSS no em va semblar gaire convenient: en tot cas, per comentar/votar un article/afegir-lo als teus preferits, has de passar pel navegador. Per això vaig escriure un bot de telegram que m'envia articles interessants en un missatge personal. Telegram en fa boniques visualitzacions prèvies, que, combinades amb informació sobre l'autor/valoració/visualitzacions, semblen bastant informatives.

Bot de Telegram per a una selecció personalitzada d'articles d'Habr

A sota del tall hi ha detalls com ara les característiques de l'obra, el procés d'escriptura i les solucions tècniques.

Breument sobre el bot

Repositori: https://github.com/Kright/habrahabr_reader

Bot al telegrama: https://t.me/HabraFilterBot

L'usuari estableix una qualificació addicional per a les etiquetes i els autors. Després d'això, s'aplica un filtre als articles: se sumen la valoració de l'article a Habré, la valoració dels usuaris de l'autor i la mitjana de les valoracions dels usuaris per etiqueta. Si la quantitat és superior al llindar especificat per l'usuari, l'article passa el filtre.

Un objectiu secundari d'escriure un bot era guanyar diversió i experiència. A més, m'ho recordava regularment No sóc Google, i per tant moltes coses es fan de la manera més senzilla i fins i tot primitiva possible. Tanmateix, això no va impedir que el procés d'escriptura del bot trigués tres mesos.

A fora era estiu

El juliol s'acabava i vaig decidir escriure un bot. I no sol, sinó amb un amic que dominava scala i hi volia escriure alguna cosa. El començament semblava prometedor: el codi el retallaria un equip, la tasca semblava fàcil i vaig pensar que en un parell de setmanes o un mes el bot estaria llest.

Malgrat que jo mateix he estat escrivint codi on the rock de tant en tant durant els darrers anys, ningú sol veure o mirar aquest codi: projectes de mascota, provar algunes idees, preprocessar dades, dominar alguns conceptes de FP. M'interessava molt com era escriure codi en un equip, perquè el codi en rock es pot escriure de maneres molt diferents.

Què podria haver anat tan? Tanmateix, no apurem les coses.
Tot el que passa es pot fer un seguiment mitjançant l'historial de commits.

Un conegut va crear un dipòsit el 27 de juliol, però no va fer res més, així que vaig començar a escriure codi.

30 juliol

Breument: vaig escriure una anàlisi del canal rss d'Habr.

  • com.github.pureconfig per llegir les configuracions de typesafe directament a les classes de casos (va resultar molt convenient)
  • scala-xml per llegir xml: com que inicialment volia escriure la meva pròpia implementació per al feed rss, i el feed rss està en format xml, vaig utilitzar aquesta biblioteca per analitzar. De fet, també va aparèixer l'anàlisi RSS.
  • scalatest per a proves. Fins i tot per a projectes petits, escriure proves estalvia temps; per exemple, quan es depura l'anàlisi xml, és molt més fàcil descarregar-lo a un fitxer, escriure proves i corregir errors. Quan més tard va aparèixer un error amb l'anàlisi d'algun html estrany amb caràcters utf-8 no vàlids, va resultar més convenient posar-lo en un fitxer i afegir una prova.
  • actors d'Akka. Objectivament, no es necessitaven gens, però el projecte va ser escrit per diversió, volia provar-los. Com a resultat, estic disposat a dir que m'ha agradat. La idea de POO es pot veure des de l'altra banda: hi ha actors que intercanvien missatges. El que és més interessant és que podeu (i hauríeu) escriure codi de manera que el missatge no arribi o no es processi (en termes generals, quan el compte s'executa en un sol ordinador, els missatges no s'han de perdre). Al principi em rascava el cap i hi havia escombraries al codi amb actors subscrits entre ells, però al final vaig aconseguir una arquitectura força senzilla i elegant. El codi dins de cada actor es pot considerar d'un sol fil quan un actor falla, l'acca el reinicia; el resultat és un sistema bastant tolerant a errors.

9 agost

M'he afegit al projecte scala-scrapper per analitzar pàgines html d'Habr (per extreure informació com ara la qualificació de l'article, el nombre d'adreces d'interès, etc.).

I Gats. Els de la roca.

Bot de Telegram per a una selecció personalitzada d'articles d'Habr

Després vaig llegir un llibre sobre bases de dades distribuïdes, em va agradar la idea de CRDT (Tipus de dades replicades sense conflictes, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), així que vaig publicar una classe tipus d'un semigrup commutatiu per obtenir informació sobre l'article sobre Habré.

De fet, la idea és molt senzilla: tenim comptadors que canvien monòtonament. El nombre de promocions va creixent progressivament, així com el nombre d'avantatges (així com el nombre de menys). Si tinc dues versions d'informació sobre un article, puc "fusionar-les en una": l'estat del comptador que és més gran es considera més rellevant.

Un semigrup significa que dos objectes amb informació sobre un article es poden fusionar en un de sol. Commutatiu vol dir que podeu combinar tant A + B com B + A, el resultat no depèn de l'ordre i, al final, es mantindrà la versió més nova. Per cert, aquí també hi ha associativitat.

Per exemple, tal com estava previst, rss després de l'anàlisi va proporcionar informació lleugerament debilitat sobre l'article, sense mètriques com ara el nombre de visualitzacions. Aleshores, un actor especial va agafar informació sobre els articles i va anar a les pàgines html per actualitzar-lo i combinar-lo amb la versió antiga.

En termes generals, com a akka, no hi havia necessitat d'això, simplement podríeu emmagatzemar updateDate per a l'article i agafar-ne un de més nou sense cap fusió, però el camí de l'aventura em va portar.

12 agost

Vaig començar a sentir-me més lliure i, només per diversió, vaig fer de cada xerrada un actor independent. Teòricament, un actor en si pesa uns 300 bytes i es poden crear en milions, de manera que aquest és un enfocament completament normal. Em sembla que la solució va resultar força interessant:

Un actor era un pont entre el servidor de telegrames i el sistema de missatges a Akka. Simplement va rebre missatges i els va enviar a l'actor de xat desitjat. L'actor de xat podria enviar alguna cosa com a resposta, i es tornaria a enviar al telegrama. El que era molt convenient és que aquest actor va resultar ser el més senzill possible i només contenia la lògica per respondre als missatges. Per cert, la informació sobre articles nous va arribar a cada xat, però de nou no hi veig cap problema.

En general, el bot ja funcionava, responia missatges, emmagatzemava una llista d'articles enviats a l'usuari, i ja pensava que el bot estava gairebé a punt. A poc a poc vaig afegir petites funcions com la normalització dels noms i etiquetes dels autors (substituint "sd f" per "s_d_f").

Només quedava una cosa petit però — l'estat no es va salvar enlloc.

Tot va sortir malament

Potser us heu adonat que vaig escriure el bot principalment sol. Per tant, el segon participant es va implicar en el desenvolupament i van aparèixer els següents canvis al codi:

  • MongoDB semblava emmagatzemar l'estat. Al mateix temps, els registres del projecte es van trencar, perquè per alguna raó Monga va començar a enviar-los correu brossa i algunes persones simplement els van desactivar globalment.
  • L'actor de pont de Telegram es va transformar més enllà del reconeixement i va començar a analitzar missatges ell mateix.
  • Els actors dels xats van ser eliminats sense pietat i, en canvi, van ser substituïts per un actor que amagava tota la informació sobre tots els xats alhora. Per cada esternut, aquest actor entrava en problemes. Bé, sí, com quan actualitzeu informació sobre un article, enviar-la a tots els actors del xat és difícil (som com Google, milions d'usuaris esperen un milió d'articles al xat per a cadascun), però cada vegada que s'actualitza el xat, és normal entrar a Monga. Com em vaig adonar molt més tard, la lògica de treball dels xats també es va tallar completament i al seu lloc va aparèixer una cosa que no funcionava.
  • No queda cap rastre de les classes tipus.
  • Una lògica poc saludable ha aparegut en els actors amb les seves subscripcions entre ells, donant lloc a una condició de raça.
  • Estructures de dades amb camps de tipus Option[Int] convertit en Int amb valors màgics per defecte com -1. Més tard em vaig adonar que mongoDB emmagatzema json i no hi ha res dolent en emmagatzemar-lo allà Option bé, o almenys analitzar -1 com a Cap, però en aquell moment no ho sabia i vaig creure la meva paraula que "així és com hauria de ser". No vaig escriure aquest codi i de moment no em vaig preocupar de canviar-lo.
  • Vaig descobrir que la meva adreça IP pública acostuma a canviar, i cada vegada l'havia d'afegir a la llista blanca de Mongo. Vaig llançar el bot localment, Monga estava en algun lloc dels servidors de Monga com a empresa.
  • De sobte, la normalització de les etiquetes i el format dels missatges dels telegrames va desaparèixer. (Hmm, per què seria això?)
  • Em va agradar que l'estat del bot s'emmagatzema en una base de dades externa i, quan es reinicia, continua funcionant com si no hagués passat res. Tanmateix, aquest va ser l'únic avantatge.

La segona persona no tenia cap pressa especial, i tots aquests canvis van aparèixer en un gran munt ja a principis de setembre. No vaig apreciar immediatament l'escala de la destrucció resultant i vaig començar a entendre el treball de la base de dades, perquè... No els he tractat mai abans. Només més tard em vaig adonar de quant de codi de treball es va tallar i de quants errors s'hi van afegir al seu lloc.

Setembre

Al principi vaig pensar que seria útil dominar Monga i fer-ho bé. Aleshores, a poc a poc, vaig començar a entendre que organitzar la comunicació amb la base de dades també és un art en el qual pots fer moltes curses i només cometre errors. Per exemple, si l'usuari rep dos missatges com /subscribe - i com a resposta a cadascun crearem una entrada a la taula, perquè en el moment de processar aquests missatges l'usuari no està subscrit. Tinc la sospita que la comunicació amb Monga en la seva forma actual no està escrita de la millor manera. Per exemple, la configuració de l'usuari es va crear en el moment en què es va registrar. Si va intentar canviar-los abans del fet de la subscripció... el bot no va respondre res, perquè el codi de l'actor va entrar a la base de dades per a la configuració, no el va trobar i es va estavellar. A la pregunta: per què no crear la configuració segons sigui necessari, vaig aprendre que no cal canviar-los si l'usuari no s'ha subscrit... El sistema de filtratge de missatges es va fer d'alguna manera no òbvia, i fins i tot després d'una mirada atenta al codi No he pogut entendre si es pensava d'aquesta manera inicialment o hi ha un error.

No hi havia cap llista d'articles enviats al xat, es va suggerir que els escrigués jo mateix. Això em va sorprendre: en general, no estava en contra d'arrossegar tota mena de coses al projecte, però seria lògic per a qui va portar aquestes coses i les va cargolar. Però no, el segon participant semblava renunciar a tot, però va dir que la llista dins del xat suposadament era una mala solució, i calia fer un rètol amb esdeveniments com "s'ha enviat un article y a l'usuari x". Aleshores, si l'usuari sol·licitava enviar nous articles, calia enviar una sol·licitud a la base de dades, que seleccionaria esdeveniments relacionats amb l'usuari dels esdeveniments, també obtindria una llista d'articles nous, filtrar-los, enviar-los a l'usuari. i torneu a llançar esdeveniments sobre això a la base de dades.

El segon participant es va emportar cap a les abstraccions, quan el bot no només rebrà articles d'Habr i s'enviarà no només a telegrama.

D'alguna manera vaig implementar esdeveniments en forma de cartell separat per a la segona quinzena de setembre. No és òptim, però almenys el bot va començar a funcionar i va començar a enviar-me articles de nou, i a poc a poc vaig descobrir què passava al codi.

Ara podeu tornar al principi i recordar que el dipòsit no el vaig crear originalment. Què podria haver anat així? La meva sol·licitud d'extracció va ser rebutjada. Va resultar que tenia codi redneck, que no sabia com treballar en equip i que havia de corregir errors en la corba d'implementació actual i no refinar-lo a un estat utilitzable.

Em vaig molestar i vaig mirar l'historial de commits i la quantitat de codi escrit. Vaig mirar moments que originalment estaven ben escrits i després es van trencar...

Fou-ho

Vaig recordar l'article No ets Google.

Vaig pensar que ningú realment necessita una idea sense implementació. Vaig pensar que volia tenir un bot que funcioni, que funcionés en una sola còpia en un sol ordinador com un simple programa Java. Sé que el meu bot funcionarà durant mesos sense reiniciar, ja que ja he escrit aquests bots en el passat. Si cau de cop i no envia un altre article a l'usuari, el cel no caurà a terra i no passarà res catastròfic.

Per què necessito Docker, mongoDB i un altre culte de càrrega de programari "serios" si el codi simplement no funciona o funciona malament?

Vaig bifurcar el projecte i vaig fer tot el que volia.

Bot de Telegram per a una selecció personalitzada d'articles d'Habr

Al mateix temps, vaig canviar de feina i el temps lliure va faltar molt. Al matí em vaig despertar just al tren, al vespre vaig tornar tard i ja no volia fer res. No vaig fer res durant una estona, després el desig d'acabar el bot em va dominar i vaig començar a reescriure el codi lentament mentre conduïa cap a la feina al matí. No diré que va ser productiu: asseure's en un tren tremolant amb un ordinador portàtil a la falda i mirar el desbordament de la pila des del telèfon no és gaire convenient. Tanmateix, el temps dedicat a escriure codi va passar desapercebut i el projecte va començar a avançar lentament cap a un estat de treball.

En algun lloc de la meva ment hi havia un cuc de dubte que volia utilitzar mongoDB, però vaig pensar que, a més dels avantatges de l'emmagatzematge d'estat "fiable", hi havia desavantatges notables:

  • La base de dades es converteix en un altre punt de fallada.
  • El codi és cada cop més complex i trigaré més a escriure'l.
  • El codi es torna lent i ineficient en lloc de canviar un objecte a la memòria, els canvis s'envien a la base de dades i, si cal, es retiren.
  • Hi ha restriccions sobre el tipus d'emmagatzematge d'esdeveniments en una taula separada, que s'associen a les peculiaritats de la base de dades.
  • La versió de prova de Monga té algunes limitacions, i si us trobeu amb elles, haureu d'iniciar i configurar Monga en alguna cosa.

Vaig tallar el monga, ara l'estat del bot simplement s'emmagatzema a la memòria del programa i de tant en tant es guarda en un fitxer en forma de json. Potser als comentaris escriuran que m'equivoco, que aquí s'ha d'utilitzar la base de dades, etc. Però aquest és el meu projecte, l'enfocament amb el fitxer és el més senzill possible i funciona de manera transparent.

Va llançar valors màgics com -1 i va tornar els normals Option, emmagatzematge afegit d'una taula hash amb articles enviats a l'objecte amb informació de xat. S'ha afegit la supressió d'informació sobre articles de més de cinc dies, per no emmagatzemar-ho tot. Vaig portar el registre a un estat de treball: els registres s'escriuen en quantitats raonables tant al fitxer com a la consola. S'han afegit diverses ordres d'administració com ara desar l'estat o obtenir estadístiques com ara el nombre d'usuaris i articles.

S'han corregit un munt de petites coses: per exemple, per als articles, ara s'indica el nombre de visualitzacions, m'agrada, no m'agrada i comentaris en el moment de passar el filtre de l'usuari. En general, és sorprenent quantes petites coses s'han hagut de corregir. Vaig mantenir una llista, vaig anotar totes les "irregularitats" que hi havia i les vaig corregir en la mesura del possible.

Per exemple, he afegit la possibilitat de configurar tots els paràmetres directament en un missatge:

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

I un altre equip /settings els mostra exactament en aquest formulari, podeu agafar-ne el text i enviar tots els paràmetres a un amic.
Sembla poca cosa, però hi ha desenes de matisos similars.

Filtrat d'articles implementat en forma d'un model lineal senzill: l'usuari pot establir una puntuació addicional per als autors i les etiquetes, així com un valor llindar. Si la suma de la valoració de l'autor, la valoració mitjana de les etiquetes i la valoració real de l'article és superior al valor llindar, l'article es mostra a l'usuari. Pots demanar articles al bot amb l'ordre /new o subscriure't al bot i t'enviarà articles en un missatge personal a qualsevol hora del dia.

En termes generals, vaig tenir una idea perquè cada article extregués més funcions (centres, nombre de comentaris, adreces d'interès, dinàmiques de canvis de valoració, quantitat de text, imatges i codi de l'article, paraules clau) i mostrar a l'usuari un ok/ no està bé votar sota cada article i formar un model per a cada usuari, però em feia massa mandra.

A més, la lògica de l'obra no serà tan evident. Ara puc establir manualment una qualificació de +9000 per a patientZero i amb una qualificació llindar de +20, tindré la garantia de rebre tots els seus articles (tret que, per descomptat, estableixi -100500 per a algunes etiquetes).

L'arquitectura final va resultar bastant senzilla:

  1. Un actor que emmagatzema l'estat de tots els xats i articles. Carrega el seu estat des d'un fitxer al disc i el desa de tant en tant, cada vegada en un fitxer nou.
  2. Un actor que visita el canal RSS de tant en tant, coneix nous articles, mira els enllaços, analitza i envia aquests articles al primer actor. A més, de vegades demana una llista d'articles al primer actor, selecciona aquells que no tenen més de tres dies, però que no s'actualitzen des de fa molt de temps i els actualitza.
  3. Un actor que es comunica amb un telegrama. Encara he portat el missatge analitzant completament aquí. De manera amistosa, m'agradaria dividir-ho en dos, de manera que un analitzés els missatges entrants i el segon s'ocupi de problemes de transport com ara tornar a enviar missatges no enviats. Ara no hi ha cap reenviament, i un missatge que no ha arribat a causa d'un error simplement es perdrà (tret que s'apunta als registres), però fins ara això no ha causat cap problema. Potser sorgiran problemes si un grup de persones es subscriuen al bot i arribo al límit per enviar missatges).

El que m'ha agradat és que gràcies a akka, les caigudes dels actors 2 i 3 en general no afecten el rendiment del bot. Potser alguns articles no s'actualitzen a temps o alguns missatges no arriben al telegrama, però el compte reinicia l'actor i tot continua funcionant. Guardo la informació que l'article es mostra a l'usuari només quan l'actor de telegram respon que ha lliurat el missatge amb èxit. El pitjor que m'amenaça és enviar el missatge diverses vegades (si es lliura, però d'alguna manera es perd la confirmació). En principi, si el primer actor no emmagatzemava l'estat en si mateix, sinó que es comunicava amb alguna base de dades, també podria caure imperceptiblement i tornar a la vida. També podria provar akka persistència per restaurar l'estat dels actors, però la implementació actual m'adapta per la seva senzillesa. No és que el meu codi s'estavelli sovint; al contrari, m'he esforçat molt per fer-ho impossible. Però passa una merda, i la capacitat de dividir el programa en peces aïllades-actors em va semblar realment convenient i pràctica.

He afegit circle-ci perquè si el codi es trenca, immediatament ho descobrireu. Com a mínim, vol dir que el codi s'ha deixat de compilar. Inicialment volia afegir travis, però només mostrava els meus projectes sense forquilles. En general, aquestes dues coses es poden utilitzar lliurement en repositoris oberts.

Resultats de

Ja som novembre. El bot està escrit, l'he estat utilitzant durant les últimes dues setmanes i m'ha agradat. Si tens idees per millorar, escriu. No veig el sentit de monetitzar-lo: deixeu-lo funcionar i envieu articles interessants.

Enllaç del bot: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Petites conclusions:

  • Fins i tot un petit projecte pot trigar molt de temps.
  • No ets Google. No té sentit disparar pardals des d'un canó. Una solució senzilla pot funcionar igual de bé.
  • Els projectes de mascotes són molt bons per experimentar amb noves tecnologies.
  • Els robots de Telegram s'escriuen de manera senzilla. Si no hagués estat pel "treball en equip" i experiments amb tecnologia, el bot s'hauria escrit en una o dues setmanes.
  • El model d'actor és una cosa interessant que va bé amb el codi multiprocés i tolerant a errors.
  • Crec que he fet un tast de per què a la comunitat de codi obert li encanta les forquilles.
  • Les bases de dades són bones perquè l'estat de l'aplicació ja no depèn dels bloquejos/reinicis de l'aplicació, però treballar amb una base de dades complica el codi i imposa restriccions a l'estructura de dades.

Font: www.habr.com

Afegeix comentari