Bot de Telegram para unha selección personalizada de artigos de Habr

Para preguntas como "por que?" hai un artigo máis antigo - Natural Geektimes - facendo máis limpo o espazo.

Hai moitos artigos, por motivos subxectivos algúns deles non me gustan, e outros, pola contra, é unha mágoa saltar. Gustaríame optimizar este proceso e aforrar tempo.

O artigo anterior suxeriu un enfoque de scripts no navegador, pero non me gustou moito (aínda que o usei antes) polas seguintes razóns:

  • Para diferentes navegadores do teu ordenador/teléfono, tes que configuralo de novo, se é posible.
  • Non sempre é conveniente un filtrado estrito polos autores.
  • O problema dos autores cuxos artigos non queres perderte, aínda que se publiquen unha vez ao ano, non está resolto.

Non sempre é conveniente filtrar o sitio en función das clasificacións dos artigos, xa que os artigos altamente especializados, a pesar do seu valor, poden recibir unha valoración bastante modesta.

Inicialmente, quería xerar un feed RSS (ou incluso varios), deixando alí só cousas interesantes. Pero ao final, resultou que a lectura de RSS non lle pareceu moi conveniente: en todo caso, para comentar/votar un artigo/engadilo aos favoritos hai que pasar polo navegador. Por iso escribín un bot de telegram que me envía artigos interesantes nunha mensaxe persoal. O propio Telegram fai fermosas vistas previas delas, que, combinadas coa información sobre o autor/valoración/visualizacións, parecen bastante informativas.

Bot de Telegram para unha selección personalizada de artigos de Habr

Debaixo do corte hai detalles como as características da obra, o proceso de redacción e as solucións técnicas.

Brevemente sobre o bot

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

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

O usuario establece unha clasificación adicional para as etiquetas e os autores. Despois diso, aplícase un filtro aos artigos: súmase a valoración do artigo en Habré, a valoración dos usuarios do autor e a media das valoracións dos usuarios por etiqueta. Se a cantidade é superior a un limiar especificado polo usuario, entón o artigo pasa o filtro.

Un obxectivo secundario de escribir un bot era gañar diversión e experiencia. Ademais, lembroume regularmente iso Non son Google, e polo tanto fanse moitas cousas da forma máis sinxela e incluso primitiva posible. Non obstante, isto non impediu que o proceso de escritura do bot tardase tres meses.

Era verán fóra

Xullo estaba rematando e decidín escribir un bot. E non só, senón cun amigo que dominaba a scala e quería escribir algo sobre el. O comezo parecía prometedor: o código sería cortado por un equipo, a tarefa parecía fácil e pensei que nun par de semanas ou un mes o bot estaría listo.

A pesar de que eu mesmo estiven escribindo código na rocha de cando en vez durante os últimos anos, ninguén adoita ver nin mirar este código: proxectos de mascotas, probar algunhas ideas, preprocesar datos, dominar algúns conceptos da FP. Estaba moi interesado en saber como escribir código nun equipo, porque o código no rock pódese escribir de xeitos moi diferentes.

O que puido ir así? Non obstante, non apuremos as cousas.
Todo o que ocorre pódese seguir usando o historial de commit.

Un coñecido creou un repositorio o 27 de xullo, pero non fixo outra cousa, así que comecei a escribir código.

30 xullo

Brevemente: escribín unha análise do feed rss de Habr.

  • com.github.pureconfig para ler as configuracións typesafe directamente nas clases de casos (resultou moi conveniente)
  • scala-xml para ler xml: xa que inicialmente quería escribir a miña propia implementación para a fonte rss, e a fonte rss está en formato xml, usei esta biblioteca para analizar. De feito, tamén apareceu a análise RSS.
  • scalatest para probas. Mesmo para proxectos pequenos, escribir probas aforra tempo; por exemplo, ao depurar a análise xml, é moito máis doado descargalo nun ficheiro, escribir probas e corrixir erros. Cando máis tarde apareceu un erro ao analizar algún html estraño con caracteres utf-8 non válidos, resultou máis conveniente poñelo nun ficheiro e engadir unha proba.
  • actores de Akka. Obxectivamente non eran necesarios para nada, pero o proxecto foi escrito por diversión, quería probalos. Como resultado, estou preparado para dicir que me gustou. A idea de OOP pódese ver desde o outro lado: hai actores que intercambian mensaxes. O máis interesante é que podes (e deberías) escribir código de forma que a mensaxe non chegue ou non se procese (en xeral, cando a conta está a executarse nun só ordenador, as mensaxes non se deben perder). Ao principio estaba a rascarme a cabeza e había lixo no código con actores que se subscribían entre eles, pero ao final conseguín crear unha arquitectura bastante sinxela e elegante. O código dentro de cada actor pódese considerar de fío único; cando un actor falla, o acca reinicialo; o resultado é un sistema bastante tolerante a fallos.

9 agosto

Engadín ao proxecto scala-scrapper para analizar páxinas html de Habr (para sacar información como clasificación do artigo, número de marcadores, etc.).

E Gatos. Os da rocha.

Bot de Telegram para unha selección personalizada de artigos de Habr

Despois lin un libro sobre bases de datos distribuídas, gustoume a idea de CRDT (Tipo de datos replicados sen conflitos, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), polo que publiquei unha clase tipo dun semigrupo conmutativo para obter información sobre o artigo sobre Habré.

De feito, a idea é moi sinxela: temos contadores que cambian monótonamente. O número de promocións vai crecendo aos poucos, así como o número de plus (así como o número de menos). Se teño dúas versións de información sobre un artigo, podo "fusionalas nunha soa": o estado do contador que é maior considérase máis relevante.

Un semigrupo significa que dous obxectos con información sobre un artigo poden fusionarse nun só. Conmutativo significa que pode combinar A + B e B + A, o resultado non depende da orde e, ao final, permanecerá a versión máis nova. Por certo, aquí tamén hai asociatividade.

Por exemplo, como estaba previsto, rss despois da análise proporcionou información lixeiramente debilitada sobre o artigo, sen métricas como o número de visualizacións. A continuación, un actor especial tomou información sobre os artigos e correu ás páxinas html para actualizalo e combinalo coa versión antiga.

En xeral, como en akka, non había necesidade diso, simplemente podías gardar updateDate para o artigo e tomar un máis novo sen fusións, pero o camiño da aventura levoume.

12 agosto

Comecei a sentirme máis libre e, só por diversión, facía de cada charla un actor separado. Teoricamente, un actor en si pesa uns 300 bytes e pódense crear en millóns, polo que este é un enfoque completamente normal. Paréceme que a solución resultou bastante interesante:

Un actor era unha ponte entre o servidor de telegramas e o sistema de mensaxes en Akka. Simplemente recibiu mensaxes e enviounas ao actor de chat desexado. O actor de chat podería enviar algo de volta en resposta e sería enviado de volta ao telegrama. O que era moi conveniente é que este actor resultou o máis sinxelo posible e contiña só a lóxica para responder ás mensaxes. Por certo, a información sobre novos artigos chegou a cada chat, pero de novo non vexo ningún problema nisto.

En xeral, o bot xa funcionaba, respondendo as mensaxes, gardando unha lista de artigos enviados ao usuario, e eu xa pensaba que o bot estaba case listo. Engadín pouco a pouco funcións como normalizar os nomes e as etiquetas dos autores (substituíndo "sd f" por "s_d_f").

Só quedaba unha cousa pequeno pero - o Estado non se salvou en ningures.

Todo saíu mal

Podes ter notado que escribín o bot na súa maioría só. Entón, o segundo participante involucrouse no desenvolvemento e apareceron os seguintes cambios no código:

  • MongoDB parecía almacenar o estado. Ao mesmo tempo, os rexistros do proxecto rompíanse, porque por algún motivo Monga comezou a envialos lixo e algunhas persoas simplemente desactiváronos globalmente.
  • O actor de ponte en Telegram transformouse máis alá do recoñecemento e comezou a analizar mensaxes el mesmo.
  • Os actores dos chats foron eliminados sen piedade e, no seu lugar, foron substituídos por un actor que ocultaba toda a información sobre todos os chats á vez. Por cada estornudo, este actor meteuse en problemas. Pois si, como cando se actualiza a información dun artigo, envialo a todos os actores do chat é difícil (somos como Google, millóns de usuarios están a esperar un millón de artigos no chat para cada un), pero cada vez que se actualiza o chat, o normal é entrar en Monga. Como me decate moito máis tarde, a lóxica de traballo dos chats tamén quedou totalmente recortada e no seu lugar apareceu algo que non funcionaba.
  • Non queda ningún rastro das clases de tipo.
  • Algunha lóxica insalubre apareceu nos actores coas súas subscricións entre si, o que leva a unha condición de raza.
  • Estruturas de datos con campos de tipo Option[Int] convertido en Int con valores predeterminados máxicos como -1. Máis tarde decateime de que mongoDB almacena json e non hai nada de malo en gardalo alí Option ben, ou polo menos analizar -1 como None, pero nese momento non sabía isto e asumín a miña palabra de que "así debería ser". Non escribín ese código e non me molestei en cambialo polo momento.
  • Descubrín que o meu enderezo IP público adoita cambiar, e cada vez tiña que engadilo á lista branca de Mongo. Lancei o bot localmente, Monga estaba nalgún lugar dos servidores de Monga como empresa.
  • De súpeto, a normalización das etiquetas e do formato das mensaxes para os telegramas desapareceu. (Hmm, por que sería iso?)
  • Gustoume que o estado do bot se almacene nunha base de datos externa e, cando se reinicia, segue funcionando coma se nada pasara. Non obstante, esta foi a única vantaxe.

A segunda persoa non tiña ningunha présa especial, e todos estes cambios apareceron nun gran monte xa a principios de setembro. Non apreciei inmediatamente a escala da destrución resultante e comecei a comprender o traballo da base de datos, porque... Nunca tratei con eles antes. Só máis tarde me decatei de canto código de traballo foi cortado e cantos erros se engadiron no seu lugar.

Setembro

Ao principio pensei que sería útil dominar a Monga e facelo ben. Entón, pouco a pouco comecei a entender que organizar a comunicación coa base de datos tamén é unha arte na que se poden facer moitas carreiras e simplemente cometer erros. Por exemplo, se o usuario recibe dúas mensaxes como /subscribe - e en resposta a cada un crearemos unha entrada na táboa, porque no momento de procesar esas mensaxes o usuario non está subscrito. Teño a sospeita de que a comunicación con Monga na súa forma actual non está escrita da mellor maneira. Por exemplo, a configuración do usuario creouse no momento en que se rexistrou. Se intentou cambialos antes do feito da subscrición... o bot non respondeu nada, porque o código do actor entrou na base de datos para a configuración, non o atopou e fallou. Cando se lle preguntou por que non crear a configuración segundo fose necesario, decateime de que non é necesario cambialos se o usuario non se subscribiu... O sistema de filtrado de mensaxes fíxose dalgún xeito de forma non obvia, e mesmo despois dunha ollada atenta ao código puiden non entendo se inicialmente estaba pensado deste xeito ou hai un erro alí.

Non houbo unha lista de artigos enviados ao chat; en cambio, suxeríronse que os escribise eu. Isto sorprendeume: en xeral, non estaba en contra de arrastrar todo tipo de cousas ao proxecto, pero sería lóxico para quen trouxo estas cousas e as afeitou. Pero non, o segundo participante parecía renunciar a todo, pero dixo que a lista dentro do chat era supostamente unha mala solución e que era necesario facer un cartel con eventos como "enviouse un artigo y ao usuario x". Entón, se o usuario solicitaba enviar novos artigos, era necesario enviar unha solicitude á base de datos, que seleccionaría entre os eventos eventos relacionados co usuario, tamén obtería unha lista de artigos novos, filtralos, envialos ao usuario. e devolver eventos sobre isto á base de datos.

O segundo participante foi levado nalgún lugar cara as abstraccións, cando o bot non só recibirá artigos de Habr e será enviado non só a telegrama.

Dalgunha maneira implementei eventos en forma de sinal separado para a segunda quincena de setembro. Non é óptimo, pero polo menos o bot comezou a funcionar e comezou a enviarme artigos de novo, e pouco a pouco descubrín o que pasaba no código.

Agora podes volver ao principio e lembrar que o repositorio non o creou orixinalmente. Que puido ser así? A miña solicitude de extracción foi rexeitada. Resultou que tiña código redneck, que non sabía como traballar en equipo e tiña que corrixir erros na curva de implementación actual e non refinar a un estado utilizable.

Enfadeime e mirei o historial de commit e a cantidade de código escrito. Mirei momentos que orixinalmente estaban ben escritos, e despois volvéronse a romper...

F*rde

Lembreime do artigo Non es Google.

Pensei que ninguén realmente necesita unha idea sen implementación. Pensei que quero ter un bot que funcione, que funcione nunha única copia nun único ordenador como un simple programa Java. Sei que o meu bot funcionará durante meses sen reiniciar, xa que xa escribín tales bots no pasado. Se cae de súpeto e non envía ao usuario outro artigo, o ceo non caerá ao chan e non pasará nada catastrófico.

Por que necesito Docker, mongoDB e outro software de culto de carga "serio" se o código simplemente non funciona ou funciona mal?

Deixei o proxecto e fixen todo o que quería.

Bot de Telegram para unha selección personalizada de artigos de Habr

Ao mesmo tempo cambiei de traballo e faltou moito tempo libre. Pola mañá espertei xusto no tren, pola noite volvín tarde e xa non quería facer nada. Non fixen nada durante un tempo, entón as ganas de rematar o bot impuxéronseme e comecei a reescribir o código lentamente mentres dirixía ao traballo pola mañá. Non direi que fose produtivo: sentarse nun tren tremendo cun portátil no colo e mirar o desbordamento da pila desde o teléfono non é moi cómodo. Non obstante, o tempo dedicado a escribir código pasou voando completamente desapercibido e o proxecto comezou a avanzar lentamente cara a un estado de traballo.

Nalgún lugar no fondo da miña mente había un verme de dúbida que quería usar mongoDB, pero pensei que ademais das vantaxes do almacenamento de estado "fiable", había desvantaxes notables:

  • A base de datos convértese noutro punto de falla.
  • O código é cada vez máis complexo e tardarei máis en escribilo.
  • O código vólvese lento e ineficiente; en lugar de cambiar un obxecto na memoria, os cambios envíanse á base de datos e, se é necesario, retíranse.
  • Existen restricións sobre o tipo de almacenamento de eventos nunha táboa separada, que están asociadas coas peculiaridades da base de datos.
  • A versión de proba de Monga ten algunhas limitacións, e se te atopas con elas, terás que iniciar e configurar Monga en algo.

Cortei o monga, agora o estado do bot simplemente gárdase na memoria do programa e de cando en vez gárdase nun ficheiro en forma de json. Quizais nos comentarios escriban que me equivoco, que aquí hai que usar a base de datos, etc. Pero este é o meu proxecto, o enfoque co ficheiro é o máis sinxelo posible e funciona de forma transparente.

Tirou valores máxicos como -1 e devolveu os normais Option, engadiu o almacenamento dunha táboa hash con artigos enviados de volta ao obxecto con información de chat. Engadida a eliminación de información sobre artigos de máis de cinco días, para non almacenar todo. Poñei o rexistro a un estado de traballo: os rexistros escríbense en cantidades razoables tanto no ficheiro como na consola. Engadíronse varios comandos de administración como gardar o estado ou obter estatísticas como o número de usuarios e artigos.

Arranxáronse un montón de pequenas cousas: por exemplo, para os artigos agora indícase o número de visualizacións, gústame, non me gusta e comentarios no momento de pasar o filtro do usuario. En xeral, é sorprendente cantas pequenas cousas houbo que corrixir. Guardei unha lista, notei todas as "irregularidades" alí e corrixínas na medida do posible.

Por exemplo, engadín a posibilidade de establecer todas as opcións directamente nunha mensaxe:

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

E outro equipo /settings móstraos exactamente neste formulario, podes sacar o texto del e enviar todas as opcións a un amigo.
Parece pouco, pero hai ducias de matices similares.

Implementouse o filtrado de artigos en forma dun modelo lineal sinxelo: o usuario pode establecer unha clasificación adicional para os autores e as etiquetas, así como un valor límite. Se a suma da valoración do autor, a valoración media das etiquetas e a valoración real do artigo é superior ao valor límite, o artigo móstrase ao usuario. Podes solicitar artigos ao bot co comando /new ou subscribirte ao bot e enviará artigos nunha mensaxe persoal a calquera hora do día.

En xeral, tiven unha idea de que cada artigo sacara máis funcións (centros, número de comentarios, marcadores, dinámica dos cambios de valoración, cantidade de texto, imaxes e código do artigo, palabras clave) e mostrarlle ao usuario un ok/ non está ben votar debaixo de cada artigo e adestrar un modelo para cada usuario, pero era demasiado preguiceiro.

Ademais, a lóxica do traballo non será tan evidente. Agora podo establecer manualmente unha clasificación de + 9000 para patientZero e cunha clasificación de limiar de + 20 garantirei recibir todos os seus artigos (a menos que, por suposto, estableza -100500 para algunhas etiquetas).

A arquitectura final resultou moi sinxela:

  1. Un actor que almacena o estado de todos os chats e artigos. Carga o seu estado desde un ficheiro no disco e gárdao de cando en vez, cada vez nun ficheiro novo.
  2. Un actor que visita a fonte RSS de cando en vez, coñece novos artigos, mira as ligazóns, analiza e envía estes artigos ao primeiro actor. Ademais, en ocasións pídelle ao primeiro actor unha lista de artigos, selecciona aqueles que non teñen máis de tres días, pero que non se actualizan dende hai moito tempo e actualízaos.
  3. Un actor que se comunica cun telegrama. Aínda trouxen a mensaxe analizando completamente aquí. Dun xeito amigable, gustaríame dividilo en dous, para que un analice as mensaxes entrantes e o segundo aborde problemas de transporte, como o reenvío de mensaxes non enviadas. Agora non hai reenvío, e unha mensaxe que non chegou debido a un erro simplemente perderase (a non ser que se anote nos rexistros), pero ata agora isto non causou ningún problema. Quizais xurdan problemas se un grupo de persoas se subscriben ao bot e eu chego ao límite para enviar mensaxes).

O que me gustou é que grazas a akka, as caídas dos actores 2 e 3 xeralmente non afectan o rendemento do bot. Quizais algúns artigos non se actualicen a tempo ou algunhas mensaxes non cheguen ao telegrama, pero a conta reinicia o actor e todo segue funcionando. Gardo a información de que o artigo se mostra ao usuario só cando o actor de telegrama responde que entregou a mensaxe con éxito. O peor que me ameaza é enviar a mensaxe varias veces (se é entregada, pero a confirmación pérdese dalgún xeito). En principio, se o primeiro actor non almacenaba o estado dentro de si mesmo, senón que se comunicaba con algunha base de datos, entón tamén podería caer imperceptiblemente e volver á vida. Tamén podería probar akka persistance para restaurar o estado dos actores, pero a implementación actual encaixame coa súa sinxeleza. Non é que o meu código fallase a miúdo; pola contra, esforzo moito para facelo imposible. Pero a merda pasa, e a capacidade de dividir o programa en pezas illadas-actores pareceume realmente conveniente e práctica.

Engadín circle-ci para que, se o código se rompe, o descubrirás inmediatamente. Como mínimo, significa que o código deixou de compilarse. Inicialmente quería engadir travis, pero só mostraba os meus proxectos sen garfos. En xeral, estas dúas cousas poden usarse libremente en repositorios abertos.

Resultados de

Xa é novembro. O bot está escrito, levo usándoo as últimas dúas semanas e gustoume. Se tes ideas para mellorar, escribe. Non vexo o sentido de monetizar-lo: deixe que funcione e envíe artigos interesantes.

Ligazón ao bot: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Pequenas conclusións:

  • Incluso un pequeno proxecto pode levar moito tempo.
  • Non es Google. Non ten sentido disparar gorrións desde un canón. Unha solución sinxela pode funcionar igual de ben.
  • Os proxectos de mascotas son moi bos para experimentar coas novas tecnoloxías.
  • Os bots de Telegram escríbense de forma sinxela. Se non fose polo "traballo en equipo" e os experimentos coa tecnoloxía, o bot teríase escrito nunha ou dúas semanas.
  • O modelo de actor é unha cousa interesante que vai ben con código multi-threading e tolerante a fallos.
  • Creo que probei por que á comunidade de código aberto lle encantan os garfos.
  • As bases de datos son boas porque o estado da aplicación xa non depende dos fallos ou reinicios da aplicación, pero traballar cunha base de datos complica o código e impón restricións á estrutura de datos.

Fonte: www.habr.com

Engadir un comentario