Bot telegramowy do spersonalizowanego wyboru artykułów z Habr

Na pytania takie jak „dlaczego?” jest starszy artykuł - Natural Geektimes - czystsza przestrzeń.

Artykułów jest mnóstwo, z subiektywnych powodów niektóre mi się nie podobają, a niektóre wręcz przeciwnie, szkoda pominąć. Chciałbym zoptymalizować ten proces i zaoszczędzić czas.

Powyższy artykuł sugerował podejście oparte na skryptach w przeglądarce, ale nie podobało mi się to (mimo że korzystałem z niego wcześniej) z następujących powodów:

  • W przypadku różnych przeglądarek na komputerze/telefonie musisz je ponownie skonfigurować, jeśli to w ogóle możliwe.
  • Ścisłe filtrowanie według autorów nie zawsze jest wygodne.
  • Nie rozwiązano problemu autorów, których artykułów nie można przegapić, nawet jeśli ukazują się one raz w roku.

Filtrowanie wbudowane w witrynę na podstawie ocen artykułów nie zawsze jest wygodne, ponieważ artykuły wysoce specjalistyczne, pomimo swojej wartości, mogą otrzymać raczej skromną ocenę.

Początkowo chciałem wygenerować kanał RSS (lub nawet kilka), zostawiając tam same ciekawe rzeczy. Ostatecznie jednak okazało się, że czytanie RSS nie wydaje się zbyt wygodne: w każdym razie, aby skomentować/zagłosować na artykuł/dodać go do ulubionych, trzeba przejść przez przeglądarkę. Dlatego napisałem bota telegramowego, który w wiadomości prywatnej wysyła mi ciekawe artykuły. Sam Telegram robi z nich piękne podglądy, co w połączeniu z informacjami o autorze/ocenie/wyświetleniach wygląda całkiem pouczająco.

Bot telegramowy do spersonalizowanego wyboru artykułów z Habr

Poniżej kroju znajdują się szczegóły takie jak cechy dzieła, proces pisania i rozwiązania techniczne.

Krótko o bocie

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

Bot w telegramie: https://t.me/HabraFilterBot

Użytkownik ustala dodatkową ocenę dla tagów i autorów. Następnie do artykułów stosowany jest filtr – sumowana jest ocena artykułu w serwisie Habré, ocena autora oraz średnia ocen użytkowników według tagu. Jeśli kwota jest większa niż próg określony przez użytkownika, artykuł przechodzi przez filtr.

Dodatkowym celem napisania bota było zdobycie dobrej zabawy i doświadczenia. Poza tym regularnie sobie o tym przypominałam Nie jestem Googlemi dlatego wiele rzeczy robi się tak prosto, a nawet prymitywnie, jak to możliwe. Nie przeszkodziło to jednak, że proces pisania bota trwał aż trzy miesiące.

Na zewnątrz było lato

Lipiec dobiegał końca i postanowiłem napisać bota. I nie sam, ale z kolegą, który opanował scalę i chciał coś na ten temat napisać. Początek zapowiadał się obiecująco - kod będzie wycinany przez zespół, zadanie wydawało się łatwe i myślałem, że za kilka tygodni, może za miesiąc bot będzie gotowy.

Pomimo tego, że ja sam piszę kod na skale od czasu do czasu przez ostatnie kilka lat, zwykle nikt nie widzi ani nie zagląda do tego kodu: projekty domowe, testowanie niektórych pomysłów, wstępne przetwarzanie danych, opracowywanie niektórych koncepcji z FP. Bardzo mnie ciekawiło, jak wygląda pisanie kodu w zespole, ponieważ kod na skale można pisać na bardzo różne sposoby.

Co mogło zniknąć tak? Nie spieszmy się jednak.
Wszystko, co się dzieje, można śledzić za pomocą historii zatwierdzeń.

Znajomy utworzył repozytorium 27 lipca, ale nie zrobił nic więcej, więc zacząłem pisać kod.

30 lipca

W skrócie: Napisałem analizę kanału rss Habra.

  • com.github.pureconfig do wczytywania konfiguracji typesafe bezpośrednio do klas przypadków (okazało się to bardzo wygodne)
  • scala-xml do czytania xml: ponieważ początkowo chciałem napisać własną implementację kanału rss, a kanał rss jest w formacie xml, użyłem tej biblioteki do analizy. Właściwie pojawiło się również parsowanie RSS.
  • scalatest do testów. Nawet przy małych projektach pisanie testów oszczędza czas - np. podczas debugowania parsowania xml znacznie łatwiej jest pobrać go do pliku, napisać testy i poprawić błędy. Kiedy później pojawił się błąd związany z analizowaniem jakiegoś dziwnego kodu HTML z nieprawidłowymi znakami utf-8, wygodniej było umieścić go w pliku i dodać test.
  • aktorzy z Akki. Obiektywnie nie były mi one w ogóle potrzebne, ale projekt pisany był dla zabawy, chciałem je wypróbować. W rezultacie mogę powiedzieć, że mi się podobało. Na ideę OOP można spojrzeć od drugiej strony – istnieją aktorzy, którzy wymieniają się komunikatami. Co ciekawsze, możesz (i powinieneś) napisać kod w taki sposób, że wiadomość może nie dotrzeć lub nie zostać przetworzona (ogólnie rzecz biorąc, gdy konto działa na jednym komputerze, wiadomości nie powinny zostać utracone). Na początku drapałem się po głowie i w kodzie był badziew z aktorami subskrybującymi się nawzajem, ale ostatecznie udało mi się wymyślić dość prostą i elegancką architekturę. Kod wewnątrz każdego aktora można uznać za jednowątkowy; gdy aktor ulegnie awarii, acca uruchamia go ponownie – w rezultacie otrzymujemy system dość odporny na błędy.

9 sierpnia

Dodałem do projektu scala-scrapper do analizowania stron HTML z Habr (aby wyciągnąć informacje takie jak ocena artykułu, liczba zakładek itp.).

I Koty. Te w skale.

Bot telegramowy do spersonalizowanego wyboru artykułów z Habr

Przeczytałem wtedy książkę o rozproszonych bazach danych, spodobał mi się pomysł CRDT (typ danych replikowanych bezkonfliktowo, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, hab), więc zamieściłem klasę typów półgrupy przemiennej w celach informacyjnych na temat artykułu o Habré.

Tak naprawdę pomysł jest bardzo prosty – mamy liczniki, które zmieniają się monotonicznie. Stopniowo rośnie liczba awansów, liczba plusów (ale także liczba minusów). Jeżeli mam dwie wersje informacji o artykule, to mogę je „połączyć w jedną” – za bardziej istotny uznaje się stan licznika, który jest większy.

Półgrupa oznacza, że ​​dwa obiekty zawierające informacje o artykule można połączyć w jeden. Przemienne oznacza, że ​​można łączyć zarówno A+B, jak i B+A, wynik nie zależy od kolejności i ostatecznie pozostanie najnowsza wersja. Nawiasem mówiąc, istnieje tu również skojarzenie.

Przykładowo, zgodnie z planem, rss po przeanalizowaniu dostarczył nieco osłabioną informację o artykule – bez metryk takich jak liczba wyświetleń. Następnie specjalny aktor zebrał informacje o artykułach i pobiegł do stron HTML, aby je zaktualizować i połączyć ze starą wersją.

Ogólnie rzecz biorąc, podobnie jak w akka, nie było takiej potrzeby, mogłeś po prostu zapisać updateDate dla artykułu i pobrać nowszy bez żadnych łączeń, ale droga przygody poprowadziła mnie.

12 sierpnia

Zacząłem czuć się bardziej wolny i dla zabawy każdy czat stał się osobnym aktorem. Teoretycznie sam aktor waży około 300 bajtów i można ich tworzyć w milionach, więc jest to zupełnie normalne podejście. Wydaje mi się, że rozwiązanie okazało się całkiem ciekawe:

Jeden z aktorów był pomostem między serwerem telegramów a systemem wiadomości w Akce. Po prostu otrzymywał wiadomości i wysyłał je do wybranego aktora czatu. Aktor czatu mógłby wysłać coś w odpowiedzi – i zostałoby to odesłane z powrotem do telegramu. Bardzo wygodne było to, że ten aktor okazał się tak prosty, jak to tylko możliwe i zawierał jedynie logikę odpowiadania na wiadomości. Swoją drogą na każdym czacie pojawiały się informacje o nowych artykułach, ale znowu nie widzę w tym żadnego problemu.

Generalnie bot już działał, odpowiadał na wiadomości, przechowywał listę artykułów wysłanych do użytkownika, a ja już myślałem, że bot jest już prawie gotowy. Powoli dodawałem drobne funkcje, takie jak normalizacja nazw autorów i tagów (zastępowanie „sd f” przez „s_d_f”).

Pozostała tylko jedna rzecz małe, ale — stan nie został nigdzie zapisany.

Wszystko poszło źle

Być może zauważyłeś, że pisałem bota głównie sam. Tak więc drugi uczestnik zaangażował się w rozwój i w kodzie pojawiły się następujące zmiany:

  • Wyglądało na to, że MongoDB przechowuje stan. W tym samym czasie logi w projekcie zostały zepsute, ponieważ z jakiegoś powodu Monga zaczęła je spamować, a niektórzy po prostu je wyłączyli na całym świecie.
  • Aktor mostowy w Telegramie zmienił się nie do poznania i sam zaczął analizować wiadomości.
  • Aktorzy do czatów zostali bezlitośnie wycięci, a zamiast nich zostali zastąpieni przez aktora, który ukrywał wszystkie informacje o wszystkich czatach na raz. Z każdym kichnięciem aktor wpadał w kłopoty. No tak, tak jak przy aktualizacji informacji o artykule, wysłanie go do wszystkich uczestników czatu jest trudne (jesteśmy jak Google, miliony użytkowników czekają na milion artykułów na czacie dla każdego), ale za każdym razem, gdy czat jest aktualizowany, to normalne, że jedziesz do Mongi. Jak zrozumiałem znacznie później, logika działania czatów również została całkowicie wycięta, a na jej miejscu pojawiło się coś, co nie działało.
  • Po klasach typów nie pozostał żaden ślad.
  • W aktorach, którzy subskrybują siebie nawzajem, pojawiła się jakaś niezdrowa logika, co doprowadziło do sytuacji wyścigowej.
  • Struktury danych z polami typu Option[Int] zamieniono na Int z magicznymi wartościami domyślnymi, takimi jak -1. Później zdałem sobie sprawę, że mongoDB przechowuje json i nie ma nic złego w przechowywaniu go tam Option cóż, albo przynajmniej parsuj -1 jako Brak, ale wtedy jeszcze o tym nie wiedziałem i uwierzyłem ci na słowo, że „tak właśnie powinno być”. Nie pisałem tego kodu i na razie nie zawracałem sobie głowy jego zmienianiem.
  • Dowiedziałem się, że mój publiczny adres IP ma tendencję do zmiany i za każdym razem musiałem dodać go do białej listy Mongo. Uruchomiłem bota lokalnie, Monga była gdzieś na serwerach Mongi jako firmy.
  • Nagle zniknęła normalizacja tagów i formatowania wiadomości dla telegramów. (Hmm, dlaczego miałoby to być?)
  • Podobało mi się, że stan bota zapisywany jest w zewnętrznej bazie danych, a po ponownym uruchomieniu działa dalej, jak gdyby nic się nie stało. Jednak to był jedyny plus.

Drugiej osobie specjalnie się nie spieszyło i już na początku września wszystkie te zmiany zebrały się w jedną wielką kupę. Nie od razu doceniłem skalę powstałych zniszczeń i zacząłem rozumieć działanie bazy danych, bo... Nigdy wcześniej z nimi nie miałem do czynienia. Dopiero później zdałem sobie sprawę, jak wiele działającego kodu wycięto i ile błędów dodano w jego miejsce.

Wrzesień

Na początku pomyślałem, że przydałoby się opanować Mongę i zrobić to dobrze. Potem powoli zacząłem rozumieć, że organizowanie komunikacji z bazą danych to też sztuka, w której można popełnić wiele wyścigów i po prostu popełniać błędy. Na przykład, jeśli użytkownik otrzyma dwie wiadomości typu /subscribe - i w odpowiedzi na każdą z nich utworzymy wpis w tabeli, ponieważ w momencie przetwarzania tych wiadomości użytkownik nie jest zapisany. Podejrzewam, że komunikacja z Mongą w jej obecnej formie nie jest napisana najlepiej. Przykładowo ustawienia użytkownika zostały utworzone w momencie jego rejestracji. Gdyby próbował je zmienić przed faktem subskrypcji... bot nic nie odpowiedział, bo kod w aktorze poszedł do bazy ustawień, nie znalazł go i zawiesił się. Na pytanie dlaczego nie stworzyć ustawień według potrzeb dowiedziałem się, że nie ma potrzeby ich zmieniać jeśli użytkownik nie subskrybuje... System filtrowania wiadomości został wykonany jakoś nieoczywistie i nawet po bliższym przyjrzeniu się kodowi mogłem nie rozumiem, czy początkowo tak to miało wyglądać, czy też pojawił się błąd.

Na czacie nie było listy artykułów przesłanych, zamiast tego zasugerowano, żebym sam je napisał. Zdziwiło mnie to – w sumie nie byłem przeciwny wciąganiu w projekt najróżniejszych rzeczy, ale dla tego, kto to wszystko wniósł i schrzanił, byłoby to logiczne. Ale nie, drugi uczestnik jakby dał sobie spokój ze wszystkim, ale stwierdził, że lista na czacie to podobno złe rozwiązanie i trzeba zrobić znak ze zdarzeniami typu „artykuł y został wysłany do użytkownika x”. Następnie, jeśli użytkownik zażądał przesłania nowych artykułów, konieczne było wysłanie zapytania do bazy, która wybrałaby ze zdarzeń zdarzenia powiązane z użytkownikiem, a także uzyskała listę nowych artykułów, przefiltrowała je, wysłała do użytkownika i wrzucaj zdarzenia na ten temat z powrotem do bazy danych.

Drugi uczestnik został poniesiony gdzieś w stronę abstrakcji, kiedy bot będzie otrzymywał nie tylko artykuły od Habra i był wysyłany nie tylko na telegram.

Jakoś zaimplementowałem wydarzenia w formie osobnego znaku na drugą połowę września. Nie jest to optymalne, ale przynajmniej bot zaczął działać i znów zaczął wysyłać mi artykuły, a ja powoli zorientowałem się, co się dzieje w kodzie.

Teraz możesz wrócić do początku i pamiętać, że repozytorium nie zostało pierwotnie utworzone przeze mnie. Co mogło się tak potoczyć? Moja prośba o ściągnięcie została odrzucona. Okazało się, że mam redneck kod, że nie umiem pracować w zespole i muszę poprawiać błędy w bieżącej krzywej implementacji, a nie dopracowywać go do stanu użytkowego.

Zdenerwowałem się i spojrzałem na historię zatwierdzeń i ilość napisanego kodu. Przyjrzałem się momentom, które pierwotnie były dobrze napisane, a potem zostały zerwane…

Pieprzyć to

Przypomniał mi się artykuł Nie jesteś Googlem.

Pomyślałem, że tak naprawdę nikomu nie jest potrzebny pomysł bez wdrożenia. Pomyślałem, że chcę mieć działającego bota, który będzie działał w jednym egzemplarzu na jednym komputerze jako prosty program Java. Wiem, że mój bot będzie działał miesiącami bez restartów, ponieważ pisałem już takie boty w przeszłości. Jeśli nagle spadnie i nie wyśle ​​użytkownikowi kolejnego artykułu, niebo nie spadnie na ziemię i nic katastrofalnego się nie stanie.

Po co mi Docker, mongoDB i inny kult „poważnego” oprogramowania, jeśli kod po prostu nie działa lub działa krzywo?

Rozwiodłem projekt i zrobiłem wszystko tak, jak chciałem.

Bot telegramowy do spersonalizowanego wyboru artykułów z Habr

Mniej więcej w tym samym czasie zmieniłam pracę i zaczęło brakować mi wolnego czasu. Rano obudziłem się już w pociągu, wieczorem wróciłem późno i nie chciało mi się już nic robić. Przez chwilę nic nie robiłem, po czym ogarnęła mnie chęć dokończenia bota i zacząłem powoli przepisywać kod jadąc rano do pracy. Nie powiem, że było produktywnie: siedzenie w drżącym pociągu z laptopem na kolanach i patrzenie na przepełnienie stosu z telefonu nie jest zbyt wygodne. Jednak czas spędzony na pisaniu kodu minął zupełnie niezauważony, a projekt zaczął powoli zbliżać się do stanu roboczego.

Gdzieś z tyłu głowy czaił się robak wątpliwości, który chciał wykorzystać mongoDB, ale pomyślałem, że oprócz zalet „niezawodnego” przechowywania stanu, zauważalne są wady:

  • Baza danych staje się kolejnym punktem awarii.
  • Kod staje się coraz bardziej złożony i jego napisanie zajmie mi więcej czasu.
  • Kod staje się powolny i nieefektywny, zamiast zmieniać obiekt w pamięci, zmiany wysyłane są do bazy danych i w razie potrzeby wycofywane.
  • Istnieją ograniczenia dotyczące rodzaju przechowywania zdarzeń w osobnej tabeli, które są związane ze specyfiką bazy danych.
  • Wersja próbna Mongi ma pewne ograniczenia i jeśli je napotkasz, będziesz musiał uruchomić i skonfigurować Mongę na czymś.

Wyciąłem mongę, teraz stan bota jest po prostu przechowywany w pamięci programu i co jakiś czas zapisywany do pliku w postaci jsona. Być może w komentarzach napiszą, że się mylę, że tu należy skorzystać z bazy danych itp. Ale to jest mój projekt, podejście do pliku jest tak proste, jak to możliwe i działa w sposób przejrzysty.

Wyrzucił magiczne wartości, takie jak -1 i zwrócił normalne Option, dodano przechowywanie tabeli skrótów z artykułami wysłanymi z powrotem do obiektu z informacjami na czacie. Dodano usuwanie informacji o artykułach starszych niż pięć dni, aby nie przechowywać wszystkiego. Doprowadziłem logowanie do stanu roboczego - logi są zapisywane w rozsądnych ilościach zarówno do pliku, jak i do konsoli. Dodano kilka poleceń administracyjnych, takich jak zapisywanie stanu lub uzyskiwanie statystyk, takich jak liczba użytkowników i artykułów.

Naprawiono kilka drobnych rzeczy: na przykład w przypadku artykułów wskazywana jest teraz liczba wyświetleń, polubień, antypatii i komentarzy w momencie przejścia przez filtr użytkownika. Ogólnie rzecz biorąc, zaskakujące jest, jak wiele drobiazgów trzeba było poprawić. Prowadziłem listę, odnotowywałem wszystkie znajdujące się w niej „nieprawidłowości” i w miarę możliwości je poprawiałem.

Przykładowo dodałem możliwość ustawienia wszystkich ustawień bezpośrednio w jednej wiadomości:

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

I kolejny zespół /settings wyświetla je dokładnie w tej formie, możesz pobrać z niego tekst i wysłać wszystkie ustawienia znajomemu.
Wydaje się, że to drobnostka, ale istnieją dziesiątki podobnych niuansów.

Zaimplementowano filtrowanie artykułów w postaci prostego modelu liniowego – użytkownik może ustawić dodatkową ocenę autorów i tagów, a także wartość progową. Jeżeli suma oceny autora, średniej oceny w tagach oraz rzeczywistej oceny artykułu jest większa od wartości progowej, wówczas artykuł zostaje wyświetlony użytkownikowi. Możesz poprosić bota o artykuły za pomocą polecenia /new lub zasubskrybować bota, a on będzie wysyłał artykuły w osobistej wiadomości o dowolnej porze dnia.

Ogólnie rzecz biorąc, miałem pomysł, aby każdy artykuł wyciągał więcej funkcji (huby, liczba komentarzy, zakładek, dynamika zmian ocen, ilość tekstu, obrazki i kod w artykule, słowa kluczowe) i wyświetlał użytkownikowi OK/ nie ok głosuj pod każdym artykułem i trenuj model dla każdego użytkownika, ale byłem zbyt leniwy.

Poza tym logika pracy nie będzie już tak oczywista. Teraz mogę ręcznie ustawić ocenę +9000 dla pacjentaZero, a przy progu oceny +20 mam gwarancję otrzymania wszystkich jego artykułów (chyba że dla niektórych tagów oczywiście ustawię -100500).

Ostateczna architektura okazała się dość prosta:

  1. Aktor przechowujący stan wszystkich czatów i artykułów. Ładuje swój stan z pliku na dysku i co jakiś czas zapisuje go ponownie, za każdym razem do nowego pliku.
  2. Aktor, który co jakiś czas odwiedza kanał RSS, dowiaduje się o nowych artykułach, przegląda linki, analizuje i wysyła te artykuły do ​​pierwszego aktora. Ponadto czasami żąda listy artykułów od pierwszego aktora, wybiera te, które nie są starsze niż trzy dni, ale nie były aktualizowane od dłuższego czasu i aktualizuje je.
  3. Aktor komunikujący się za pomocą telegramu. Nadal przyniosłem tutaj pełną analizę wiadomości. W sposób polubowny chciałbym podzielić to na dwie części - tak, aby jedna analizowała przychodzące wiadomości, a druga zajmowała się problemami transportowymi, takimi jak ponowne wysyłanie niewysłanych wiadomości. Teraz nie ma ponownego wysłania, a wiadomość, która nie dotarła z powodu błędu, zostanie po prostu utracona (chyba, że ​​zostanie to odnotowane w logach), ale jak dotąd nie spowodowało to żadnych problemów. Być może pojawią się problemy, jeśli do bota zapisze się grupa osób i osiągnę limit wysyłania wiadomości).

Podobało mi się to, że dzięki akce upadki aktorów 2 i 3 generalnie nie wpływają na wydajność bota. Być może niektóre artykuły nie są aktualizowane na czas lub niektóre wiadomości nie docierają do telegramu, ale konto aktora uruchamia się ponownie i wszystko nadal działa. Zapisuję informację, że artykuł zostaje wyświetlony użytkownikowi dopiero wtedy, gdy aktor telegramu odpowie, że pomyślnie dostarczył wiadomość. Najgorsze, co mi grozi, to kilkukrotne wysłanie wiadomości (jeśli zostanie dostarczona, ale potwierdzenie jakoś zaginęło). W zasadzie, gdyby pierwszy aktor nie magazynował w sobie stanu, ale skomunikował się z jakąś bazą danych, to także mógłby niepostrzeżenie upaść i powrócić do życia. Mógłbym także spróbować akka Persistance, aby przywrócić stan aktorów, ale obecna implementacja odpowiada mi swoją prostotą. To nie tak, że mój kod często się zawieszał – wręcz przeciwnie, włożyłem sporo wysiłku, aby było to niemożliwe. Ale gówno się zdarza, a możliwość podzielenia programu na pojedyncze części – aktorzy wydawała mi się naprawdę wygodna i praktyczna.

Dodałem kółko-ci, żeby w przypadku złamania kodu od razu się o tym dowiedziałeś. Oznacza to co najmniej, że kod przestał się kompilować. Początkowo chciałem dodać travis, ale pokazywało tylko moje projekty bez widelców. Generalnie z obu tych rzeczy można swobodnie korzystać w otwartych repozytoriach.

Wyniki

Jest już listopad. Bot jest napisany, używam go od dwóch tygodni i przypadł mi do gustu. Jeśli masz pomysły na ulepszenia, napisz. Nie widzę sensu zarabiania na tym - niech po prostu działa i wysyła ciekawe artykuły.

Link do bota: https://t.me/HabraFilterBot
GitHub: https://github.com/Kright/habrahabr_reader

Małe wnioski:

  • Nawet mały projekt może zająć dużo czasu.
  • Nie jesteś Googlem. Nie ma sensu strzelać z armaty do wróbli. Proste rozwiązanie może równie dobrze zadziałać.
  • Projekty zwierząt domowych są bardzo dobre do eksperymentowania z nowymi technologiami.
  • Boty telegramowe są napisane po prostu. Gdyby nie „praca zespołowa” i eksperymenty z technologią, bot zostałby napisany w tydzień lub dwa.
  • Model aktora to interesująca rzecz, która dobrze komponuje się z kodem wielowątkowym i odpornym na błędy.
  • Myślę, że posmakowałem tego, dlaczego społeczność open source uwielbia forki.
  • Bazy danych są dobre, ponieważ stan aplikacji nie zależy już od awarii/ponownego uruchomienia aplikacji, ale praca z bazą danych komplikuje kod i nakłada ograniczenia na strukturę danych.

Źródło: www.habr.com

Dodaj komentarz