Telegrambot voor een gepersonaliseerde selectie van artikelen van Habr

Voor vragen als “waarom?” er is een ouder artikel - Natural Geektimes - ruimte schoner maken.

Er zijn veel artikelen, om subjectieve redenen vind ik sommige niet leuk, en sommige zijn integendeel jammer om over te slaan. Ik wil dit proces graag optimaliseren en tijd besparen.

Het bovenstaande artikel suggereerde een scripting-aanpak in de browser, maar ik vond het niet echt leuk (ook al heb ik het eerder gebruikt) om de volgende redenen:

  • Voor verschillende browsers op uw computer/telefoon moet u deze indien mogelijk opnieuw configureren.
  • Strikt filteren op auteurs is niet altijd handig.
  • Het probleem met auteurs van wie je artikelen niet wilt missen, ook al verschijnen ze één keer per jaar, is niet opgelost.

Het in de site ingebouwde filter op basis van artikelbeoordelingen is niet altijd handig, omdat zeer gespecialiseerde artikelen, ondanks hun waarde, een vrij bescheiden beoordeling kunnen krijgen.

Aanvankelijk wilde ik een RSS-feed genereren (of zelfs meerdere), waardoor er alleen interessante dingen overbleven. Maar uiteindelijk bleek dat het lezen van RSS niet erg handig leek: om op een artikel te reageren/te stemmen/het aan je favorieten toe te voegen, moet je in ieder geval via de browser gaan. Daarom heb ik een telegrambot geschreven die interessante artikelen naar mij stuurt in een persoonlijk bericht. Telegram maakt er zelf prachtige previews van, die, gecombineerd met informatie over de auteur/beoordeling/views, er behoorlijk informatief uitzien.

Telegrambot voor een gepersonaliseerde selectie van artikelen van Habr

Onder de snit staan ​​details zoals de kenmerken van het werk, het schrijfproces en technische oplossingen.

Kort over de bot

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

Bot in telegram: https://t.me/HabraFilterBot

De gebruiker stelt een extra beoordeling in voor tags en auteurs. Daarna wordt er een filter op de artikelen toegepast: de beoordeling van het artikel op Habré, de gebruikersbeoordeling van de auteur en het gemiddelde van de gebruikersbeoordelingen per tag worden bij elkaar opgeteld. Als het bedrag groter is dan een door de gebruiker opgegeven drempel, passeert het artikel het filter.

Een bijdoel van het schrijven van een bot was om plezier en ervaring op te doen. Bovendien herinnerde ik mezelf daar regelmatig aan Ik ben Google niet, en daarom worden veel dingen zo eenvoudig en zelfs primitief mogelijk gedaan. Dit verhinderde echter niet dat het schrijven van de bot drie maanden in beslag nam.

Het was zomer buiten

Juli liep ten einde en ik besloot een bot te schrijven. En niet alleen, maar met een vriend die scala onder de knie had en er iets over wilde schrijven. Het begin zag er veelbelovend uit: de code zou door een team worden geknipt, de taak leek eenvoudig en ik dacht dat de bot binnen een paar weken of een maand klaar zou zijn.

Ondanks het feit dat ik zelf de afgelopen jaren af ​​en toe code op de rots heb geschreven, ziet of kijkt niemand normaal gesproken naar deze code: lievelingsprojecten, het testen van enkele ideeën, het voorbewerken van gegevens, het beheersen van enkele concepten uit FP. Ik was erg geïnteresseerd in hoe het schrijven van code in een team eruit ziet, omdat code op steen op heel verschillende manieren kan worden geschreven.

Wat had er kunnen gebeuren zo? Laten we de zaken echter niet overhaasten.
Alles wat er gebeurt, kan worden gevolgd met behulp van de commitgeschiedenis.

Een kennis maakte op 27 juli een repository aan, maar deed verder niets, dus begon ik code te schrijven.

juli 30

In het kort: ik heb een parsering van de RSS-feed van Habr geschreven.

  • com.github.pureconfig voor het rechtstreeks inlezen van typesafe-configuraties in case-klassen (het bleek erg handig)
  • scala-xml voor het lezen van xml: aangezien ik aanvankelijk mijn eigen implementatie voor de RSS-feed wilde schrijven, en de RSS-feed in XML-formaat is, heb ik deze bibliotheek gebruikt voor het parseren. Eigenlijk verscheen ook RSS-parsing.
  • scalatest voor testen. Zelfs voor kleine projecten bespaart het schrijven van tests tijd. Bij het debuggen van xml-parsering is het bijvoorbeeld veel eenvoudiger om het naar een bestand te downloaden, tests te schrijven en fouten te corrigeren. Toen er later een bug verscheen bij het parseren van vreemde html met ongeldige utf-8-tekens, bleek het handiger om deze in een bestand te zetten en een test toe te voegen.
  • acteurs uit Akka. Objectief gezien waren ze helemaal niet nodig, maar het project was voor de lol geschreven, ik wilde ze proberen. Als gevolg hiervan ben ik klaar om te zeggen dat ik het leuk vond. Het idee van OOP kan van de andere kant worden bekeken: er zijn acteurs die berichten uitwisselen. Wat interessanter is, is dat je code zo kunt (en moet) schrijven dat het bericht misschien niet aankomt of niet wordt verwerkt (in het algemeen mogen berichten niet verloren gaan als het account op één enkele computer draait). In eerste instantie zat ik op mijn hoofd te krabben en er zat rommel in de code met acteurs die elkaar abonneerden, maar uiteindelijk slaagde ik erin om met een vrij eenvoudige en elegante architectuur te komen. De code binnen elke actor kan als single-threaded worden beschouwd; wanneer een actor crasht, start de acca deze opnieuw op - het resultaat is een redelijk fouttolerant systeem.

9 augustus

Ik heb toegevoegd aan het project scala-scrapper voor het parseren van html-pagina's uit Habr (om informatie op te halen zoals artikelbeoordeling, aantal bladwijzers, enz.).

En katten. Die in de rots.

Telegrambot voor een gepersonaliseerde selectie van artikelen van Habr

Ik las toen een boek over gedistribueerde databases, ik vond het idee van CRDT (Conflict-free replicated data type, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), dus plaatste ik een typeklasse van een commutatieve semigroep voor informatie over het artikel over Habré.

In feite is het idee heel eenvoudig: we hebben tellers die monotoon veranderen. Het aantal promoties groeit geleidelijk, evenals het aantal plussen (maar ook het aantal minnen). Als ik twee versies van informatie over een artikel heb, kan ik ze "samenvoegen tot één" - de status van de teller die groter is, wordt als relevanter beschouwd.

Een semigroep betekent dat twee objecten met informatie over een artikel tot één kunnen worden samengevoegd. Commutatief betekent dat je zowel A+B als B+A kunt samenvoegen, het resultaat is niet afhankelijk van de volgorde en uiteindelijk blijft de nieuwste versie over. Overigens is er ook hier sprake van associativiteit.

Zoals gepland leverde RSS na het parseren bijvoorbeeld enigszins verzwakte informatie over het artikel op - zonder statistieken zoals het aantal weergaven. Een speciale acteur nam vervolgens informatie over de artikelen en rende naar de HTML-pagina's om deze bij te werken en samen te voegen met de oude versie.

Over het algemeen was dit, net als in akka, niet nodig, je kon eenvoudig updateDate voor het artikel opslaan en een nieuwer artikel nemen zonder enige samenvoeging, maar de weg van avontuur leidde mij.

12 augustus

Ik begon me vrijer te voelen en, gewoon voor de lol, maakte ik van elke chat een aparte acteur. Theoretisch weegt een actor zelf ongeveer 300 bytes en er kunnen er miljoenen worden aangemaakt, dus dit is een volkomen normale aanpak. Het lijkt mij dat de oplossing behoorlijk interessant bleek te zijn:

Eén actor vormde een brug tussen de telegramserver en het berichtensysteem in Akka. Hij ontving eenvoudig berichten en stuurde deze naar de gewenste chatacteur. De chatacteur kon als reactie iets terugsturen - en het zou teruggestuurd worden naar het telegram. Wat erg handig was, is dat deze acteur zo eenvoudig mogelijk bleek te zijn en alleen de logica bevatte voor het reageren op berichten. Overigens kwam er in elke chat informatie over nieuwe artikelen, maar nogmaals, ik zie hier geen problemen in.

Over het algemeen werkte de bot al, reageerde op berichten, bewaarde een lijst met artikelen die naar de gebruiker waren gestuurd, en ik dacht al dat de bot bijna klaar was. Ik heb langzaam kleine functies toegevoegd, zoals het normaliseren van auteursnamen en tags (vervanging van “sd f” door “s_d_f”).

Er bleef nog maar één ding over klein maar – de staat werd nergens gered.

Alles ging fout

Het is je misschien opgevallen dat ik de bot grotendeels alleen heb geschreven. Dus de tweede deelnemer raakte betrokken bij de ontwikkeling en de volgende wijzigingen verschenen in de code:

  • MongoDB leek de status op te slaan. Tegelijkertijd werden de logs in het project verbroken, omdat Monga ze om de een of andere reden begon te spammen en sommige mensen ze eenvoudigweg wereldwijd hadden uitgeschakeld.
  • De bridge-acteur in Telegram transformeerde onherkenbaar en begon zelf berichten te ontleden.
  • Acteurs voor chats werden genadeloos weggelaten, en in plaats daarvan werden ze vervangen door een acteur die alle informatie over alle chats in één keer verborgen hield. Voor elke nies kwam deze acteur in de problemen. Nou ja, net als bij het bijwerken van informatie over een artikel is het moeilijk om deze naar alle chatacteurs te sturen (we zijn net als Google, miljoenen gebruikers wachten op een miljoen artikelen in de chat voor elk), maar elke keer dat de chat wordt bijgewerkt, het is normaal om Monga binnen te gaan. Zoals ik me veel later realiseerde, was de werkende logica van de chats ook volledig weggesneden en verscheen er iets in de plaats dat niet werkte.
  • Van de typeklassen is geen spoor meer over.
  • Er is een ongezonde logica ontstaan ​​in de acteurs met hun abonnementen op elkaar, wat tot een raceconditie heeft geleid.
  • Gegevensstructuren met velden van type Option[Int] omgezet in Int met magische standaardwaarden zoals -1. Later realiseerde ik me dat mongoDB json opslaat en dat er niets mis mee is om het daar op te slaan Option Nou ja, of in ieder geval -1 ontleden als Geen, maar op dat moment wist ik dit niet en geloofde ik mijn woord: “zo zou het moeten zijn.” Ik heb die code niet geschreven en heb voorlopig niet de moeite genomen om deze te wijzigen.
  • Ik kwam erachter dat mijn openbare IP-adres de neiging heeft te veranderen, en elke keer moest ik het toevoegen aan de witte lijst van Mongo. Ik lanceerde de bot lokaal, Monga stond ergens op de servers van Monga als bedrijf.
  • Plotseling verdween de normalisatie van tags en berichtopmaak voor telegrammen. (Hmm, waarom zou dat zijn?)
  • Ik vond het leuk dat de status van de bot wordt opgeslagen in een externe database, en wanneer hij opnieuw wordt opgestart, blijft hij werken alsof er niets is gebeurd. Dit was echter het enige pluspunt.

De tweede persoon had geen bijzondere haast, en al deze veranderingen verschenen al begin september op één grote hoop. Ik besefte niet onmiddellijk de omvang van de resulterende vernietiging en begon het werk van de database te begrijpen, omdat... Ik heb nog nooit eerder met ze te maken gehad. Pas later besefte ik hoeveel werkende code er was weggelaten en hoeveel bugs daarvoor in de plaats waren gekomen.

September

In eerste instantie dacht ik dat het nuttig zou zijn om Monga onder de knie te krijgen en het goed te doen. Toen begon ik langzaam te begrijpen dat het organiseren van de communicatie met de database ook een kunst is waarin je veel races kunt maken en alleen maar fouten kunt maken. Als de gebruiker bijvoorbeeld twee berichten ontvangt, zoals /subscribe - en als reactie op elk bericht zullen we een vermelding in de tabel aanmaken, omdat de gebruiker op het moment dat deze berichten worden verwerkt niet is geabonneerd. Ik heb het vermoeden dat de communicatie met Monga in zijn huidige vorm niet op de beste manier is geschreven. De instellingen van de gebruiker zijn bijvoorbeeld aangemaakt op het moment dat hij zich aanmeldde. Als hij ze probeerde te wijzigen voordat hij zich had aangemeld... reageerde de bot niets, omdat de code in de acteur in de database voor de instellingen ging, deze niet vond en crashte. Toen hem werd gevraagd waarom ik de instellingen niet indien nodig zou maken, leerde ik dat het niet nodig is om deze te wijzigen als de gebruiker zich niet heeft geabonneerd... Het berichtenfiltersysteem is op de een of andere manier niet voor de hand liggend gemaakt, en zelfs na een nadere blik op de code kon ik Ik begrijp niet of het in eerste instantie zo bedoeld was of dat er een fout in zit.

Er was geen lijst met artikelen die in de chat waren ingediend; in plaats daarvan werd voorgesteld dat ik ze zelf zou schrijven. Dit verraste mij - over het algemeen was ik er niet tegen om allerlei dingen in het project te slepen, maar het zou logisch zijn voor degene die deze dingen binnenbracht en vastschroefde. Maar nee, de tweede deelnemer leek alles op te geven, maar zei dat de lijst in de chat vermoedelijk een slechte oplossing was, en dat het nodig was om een ​​bord te maken met gebeurtenissen als “een artikel y is naar gebruiker x gestuurd.” Als de gebruiker vervolgens vroeg om nieuwe artikelen te verzenden, was het nodig om een ​​verzoek naar de database te sturen, die uit de gebeurtenissen gebeurtenissen met betrekking tot de gebruiker zou selecteren, ook een lijst met nieuwe artikelen zou krijgen, deze zou filteren en naar de gebruiker zou sturen en gebeurtenissen hierover terug in de database gooien.

De tweede deelnemer werd ergens meegesleept in de richting van abstracties, wanneer de bot niet alleen artikelen van Habr zal ontvangen en niet alleen naar telegrammen zal worden gestuurd.

Ik heb op de een of andere manier evenementen geïmplementeerd in de vorm van een apart bord voor de tweede helft van september. Het is niet optimaal, maar de bot begon tenminste te werken en begon me weer artikelen te sturen, en ik kwam er langzaam achter wat er in de code gebeurde.

Nu kun je teruggaan naar het begin en onthouden dat de repository oorspronkelijk niet door mij is gemaakt. Wat zou er zo kunnen zijn gegaan? Mijn pull-verzoek is afgewezen. Het bleek dat ik redneck-code had, dat ik niet wist hoe ik in een team moest werken, en dat ik bugs in de huidige implementatiecurve moest oplossen, en deze niet moest verfijnen tot een bruikbare staat.

Ik raakte van streek en keek naar de commitgeschiedenis en de hoeveelheid geschreven code. Ik keek naar momenten die oorspronkelijk goed waren geschreven, en vervolgens werden afgebroken...

F*rk het

Ik heb het artikel onthouden Jij bent Google niet.

Ik dacht dat niemand echt een idee nodig heeft zonder implementatie. Ik dacht dat ik een werkende bot wilde hebben, die in één exemplaar op één enkele computer zou werken als een eenvoudig Java-programma. Ik weet dat mijn bot maandenlang zal werken zonder opnieuw op te starten, aangezien ik in het verleden al dergelijke bots heb geschreven. Als het plotseling valt en de gebruiker geen ander artikel stuurt, zal de lucht niet op de grond vallen en zal er niets catastrofaals gebeuren.

Waarom heb ik Docker, mongoDB en andere cargocultus van ‘serieuze’ software nodig als de code simpelweg niet werkt of scheef werkt?

Ik heb het project opgesplitst en alles gedaan zoals ik wilde.

Telegrambot voor een gepersonaliseerde selectie van artikelen van Habr

Rond dezelfde tijd veranderde ik van baan en er kwam een ​​tekort aan vrije tijd. 'S Ochtends werd ik midden in de trein wakker,' s avonds kwam ik laat terug en wilde ik niets meer doen. Ik deed een tijdje niets, maar toen werd ik overweldigd door de wens om de bot af te maken en begon ik de code langzaam te herschrijven terwijl ik 's ochtends naar mijn werk reed. Ik zal niet zeggen dat het productief was: in een trillende trein zitten met een laptop op schoot en vanaf je telefoon naar de stack-overflow kijken is niet erg handig. De tijd die besteed werd aan het schrijven van code vloog echter volledig onopgemerkt voorbij en het project begon langzaam naar een werkende staat te evolueren.

Ergens in mijn achterhoofd was er een worm van twijfel die mongoDB wilde gebruiken, maar ik dacht dat er naast de voordelen van "betrouwbare" staatsopslag ook merkbare nadelen waren:

  • De database wordt een ander faalpunt.
  • De code wordt steeds complexer en het schrijven ervan zal langer duren.
  • De code wordt traag en inefficiënt; in plaats van een object in het geheugen te wijzigen, worden de wijzigingen naar de database gestuurd en indien nodig teruggetrokken.
  • Er zijn beperkingen aan het type opslag van gebeurtenissen in een aparte tabel, die verband houden met de eigenaardigheden van de database.
  • De proefversie van Monga heeft enkele beperkingen, en als u deze tegenkomt, zult u Monga ergens op moeten starten en configureren.

Ik heb de monga eruit gehaald, nu wordt de status van de bot eenvoudigweg opgeslagen in het geheugen van het programma en van tijd tot tijd opgeslagen in een bestand in de vorm van json. Misschien zullen ze in de commentaren schrijven dat ik ongelijk heb, dat dit is waar de database gebruikt moet worden, enz. Maar dit is mijn project, de aanpak met het bestand is zo eenvoudig mogelijk en het werkt op een transparante manier.

Gooide magische waarden zoals -1 weg en retourneerde normale waarden Option, opslag toegevoegd van een hashtabel met verzonden artikelen terug naar het object met chatinformatie. Verwijdering toegevoegd van informatie over artikelen ouder dan vijf dagen, om niet alles te bewaren. Ik heb het loggen in een werkende staat gebracht - logs worden in redelijke hoeveelheden naar zowel het bestand als de console geschreven. Verschillende beheerdersopdrachten toegevoegd, zoals het opslaan van de status of het verkrijgen van statistieken zoals het aantal gebruikers en artikelen.

Een aantal kleine dingen opgelost: voor artikelen wordt nu bijvoorbeeld het aantal views, likes, dislikes en reacties aangegeven op het moment dat de gebruiker het filter passeert. Over het algemeen is het verrassend hoeveel kleine dingen moesten worden gecorrigeerd. Ik hield een lijst bij, noteerde daarin alle ‘onregelmatigheden’ en corrigeerde ze voor zover mogelijk.

Zo heb ik de mogelijkheid toegevoegd om alle instellingen direct in één bericht in te stellen:

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

En nog een elftal /settings geeft ze precies in dit formulier weer, u kunt de tekst eruit halen en alle instellingen naar een vriend sturen.
Het lijkt een kleinigheid, maar er zijn tientallen vergelijkbare nuances.

Geïmplementeerde artikelfiltering in de vorm van een eenvoudig lineair model - de gebruiker kan een extra beoordeling voor auteurs en tags instellen, evenals een drempelwaarde. Als de som van de auteurswaardering, de gemiddelde waardering voor tags en de daadwerkelijke waardering van het artikel groter is dan de drempelwaarde, dan wordt het artikel aan de gebruiker getoond. Je kunt de bot om artikelen vragen met het commando /new, of je abonneren op de bot en deze stuurt op elk moment van de dag artikelen in een persoonlijk bericht.

Over het algemeen had ik het idee om voor elk artikel meer functies naar voren te halen (hubs, aantal reacties, bladwijzers, dynamiek van beoordelingswijzigingen, hoeveelheid tekst, afbeeldingen en code in het artikel, trefwoorden) en de gebruiker een ok/ niet ok, stem onder elk artikel en train een model voor elke gebruiker, maar ik was te lui.

Bovendien zal de logica van het werk niet zo voor de hand liggend zijn. Nu kan ik handmatig een beoordeling van +9000 instellen voor patientZero en met een drempelwaardering van +20 ontvang ik gegarandeerd al zijn artikelen (tenzij ik natuurlijk -100500 instel voor sommige tags).

De uiteindelijke architectuur bleek vrij eenvoudig:

  1. Een actor die de status van alle chats en artikelen opslaat. Het laadt zijn status uit een bestand op de schijf en slaat het van tijd tot tijd op, elke keer in een nieuw bestand.
  2. Een acteur die van tijd tot tijd de RSS-feed bezoekt, nieuwe artikelen leert kennen, de links bekijkt, deze artikelen ontleedt en naar de eerste acteur stuurt. Bovendien vraagt ​​het soms een lijst met artikelen op van de eerste acteur, selecteert het de artikelen die niet ouder zijn dan drie dagen, maar al lange tijd niet zijn bijgewerkt, en werkt het bij.
  3. Een acteur die communiceert met een telegram. Ik heb het bericht nog steeds volledig hierheen gebracht. Op een minnelijke manier zou ik het in tweeën willen delen - zodat de ene inkomende berichten analyseert, en de tweede zich bezighoudt met transportproblemen zoals het opnieuw verzenden van niet-verzonden berichten. Nu vindt er geen herverzending plaats en gaat een bericht dat vanwege een fout niet is aangekomen, eenvoudigweg verloren (tenzij dit in de logboeken wordt vermeld), maar tot nu toe heeft dit geen problemen veroorzaakt. Misschien ontstaan ​​er problemen als een aantal mensen zich op de bot abonneren en ik de limiet voor het verzenden van berichten bereik).

Wat ik leuk vond, is dat dankzij akka de valpartijen van acteurs 2 en 3 over het algemeen geen invloed hebben op de prestaties van de bot. Misschien worden sommige artikelen niet op tijd bijgewerkt of bereiken sommige berichten het telegram niet, maar het account start de acteur opnieuw op en alles blijft werken. Ik bewaar de informatie dat het artikel alleen aan de gebruiker wordt getoond als de telegramacteur antwoordt dat hij het bericht succesvol heeft afgeleverd. Het ergste dat mij bedreigt, is het bericht meerdere keren te verzenden (als het wordt afgeleverd, maar de bevestiging op de een of andere manier verloren gaat). Als de eerste actor de staat niet in zichzelf opsloeg, maar met een database communiceerde, zou hij in principe ook onmerkbaar kunnen vallen en weer tot leven kunnen komen. Ik zou ook akka volharding kunnen proberen om de toestand van actoren te herstellen, maar de huidige implementatie past bij mij vanwege zijn eenvoud. Het is niet zo dat mijn code vaak crashte - integendeel, ik heb heel veel moeite gedaan om het onmogelijk te maken. Maar er gebeurt iets, en de mogelijkheid om het programma in geïsoleerde stukken op te delen (acteurs) leek mij erg handig en praktisch.

Ik heb Circle-ci toegevoegd, zodat als de code kapot gaat, je er meteen achter komt. Dit betekent op zijn minst dat de code is gestopt met compileren. Aanvankelijk wilde ik Travis toevoegen, maar het toonde alleen mijn projecten zonder vorken. Over het algemeen kunnen beide dingen vrijelijk worden gebruikt in open repositories.

Resultaten van

Het is al november. De bot is geschreven, ik gebruik hem de afgelopen twee weken en ik vond hem leuk. Als je ideeën hebt voor verbetering, schrijf dan. Ik zie het nut er niet van in om er inkomsten mee te genereren: laat het gewoon werken en stuur interessante artikelen.

Beschrijving van het type: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Kleine conclusies:

  • Zelfs een klein project kan veel tijd in beslag nemen.
  • Jij bent Google niet. Het heeft geen zin om mussen met een kanon af te schieten. Een eenvoudige oplossing kan net zo goed werken.
  • Huisdierenprojecten zijn erg goed om met nieuwe technologieën te experimenteren.
  • Telegram-bots zijn vrij eenvoudig geschreven. Zonder ‘teamwerk’ en experimenten met technologie zou de bot binnen een week of twee geschreven zijn.
  • Het actormodel is een interessant iets dat goed samengaat met multi-threading en fouttolerante code.
  • Ik denk dat ik een voorproefje heb gekregen van waarom de open source-gemeenschap van forks houdt.
  • Databases zijn goed omdat de applicatiestatus niet langer afhankelijk is van het crashen/herstarten van applicaties, maar het werken met een database compliceert de code en legt beperkingen op aan de datastructuur.

Bron: www.habr.com

Voeg een reactie