Telegram бот за персонализирана селекция от статии от Habr

За въпроси като "защо?" има по-стара статия - Natural Geektimes - правене на пространството по-чисто.

Има много статии, по субективни причини някои от тях не ми харесват, а други, напротив, е жалко да ги пропусна. Бих искал да оптимизирам този процес и да спестя време.

Статията по-горе предложи подход за скриптове в браузъра, но не ми хареса особено (въпреки че съм го използвал преди) поради следните причини:

  • За различни браузъри на вашия компютър/телефон трябва да го конфигурирате отново, ако изобщо е възможно.
  • Строгото филтриране по автори не винаги е удобно.
  • Проблемът с авторите, чиито статии не искате да пропускате, дори и да излизат веднъж годишно, не е решен.

Филтрирането, вградено в сайта въз основа на оценки на статии, не винаги е удобно, тъй като високоспециализираните статии, въпреки тяхната стойност, могат да получат доста скромна оценка.

Първоначално исках да генерирам RSS емисия (или дори няколко), оставяйки там само интересни неща. Но в крайна сметка се оказа, че четенето на RSS не изглежда много удобно: във всеки случай, за да коментирате / гласувате за статия / да я добавите към любимите си, трябва да преминете през браузъра. Ето защо написах телеграм бот, който ми изпраща интересни статии на лично съобщение. Самият Telegram прави красиви превюта от тях, което в комбинация с информация за автора/рейтинга/гледанията изглежда доста информативно.

Telegram бот за персонализирана селекция от статии от Habr

Под разреза са детайли като особености на произведението, процес на писане и технически решения.

Накратко за бота

Хранилище: https://github.com/Kright/habrahabr_reader

Бот в телеграма: https://t.me/HabraFilterBot

Потребителят задава допълнителна оценка за тагове и автори. След това към статиите се прилага филтър - сумират се оценката на статията на Habré, потребителската оценка на автора и средната за потребителските оценки по етикет. Ако сумата е по-голяма от определен от потребителя праг, тогава статията преминава филтъра.

Странична цел на писането на бот беше да се натрупа забавление и опит. Освен това редовно си го напомнях Аз не съм Google, и затова много неща се правят възможно най-просто и дори примитивно. Това обаче не попречи процесът на писане на бота да отнеме три месеца.

Навън беше лято

Юли беше към края си и реших да напиша бот. И не сам, а с приятел, който майстореше scala и искаше да напише нещо на нея. Началото изглеждаше обещаващо - кодът щеше да бъде изрязан от екип, задачата изглеждаше лесна и си мислех, че след няколко седмици или месец ботът ще е готов.

Въпреки факта, че аз самият пиша код на скалата от време на време през последните няколко години, обикновено никой не вижда или поглежда този код: домашни проекти, тестване на някои идеи, предварителна обработка на данни, усвояване на някои концепции от FP. Много ми беше интересно как изглежда писането на код в екип, защото кодът на рок може да бъде написан по много различни начини.

Какво можеше да отиде така? Нека обаче не бързаме.
Всичко, което се случва, може да бъде проследено с помощта на хронологията на ангажиментите.

Един познат създаде хранилище на 27 юли, но не направи нищо друго, така че започнах да пиша код.

30 юли

Накратко: Написах разбор на rss емисия на Habr.

  • com.github.pureconfig за четене на безопасни за типове конфигурации директно в класове case (оказа се много удобно)
  • scala-xml за четене на xml: тъй като първоначално исках да напиша собствена реализация за rss емисията, а rss емисията е в xml формат, използвах тази библиотека за анализиране. Всъщност се появи и RSS анализ.
  • scalatest за тестове. Дори за малки проекти, писането на тестове спестява време - например, когато се отстраняват грешки в xml парсинга, е много по-лесно да го изтеглите във файл, да напишете тестове и да коригирате грешки. Когато по-късно се появи грешка с анализиране на някакъв странен html с невалидни utf-8 символи, се оказа по-удобно да го поставите във файл и да добавите тест.
  • актьори от Akka. Обективно изобщо не бяха необходими, но проектът беше писан за забавление, исках да ги пробвам. В резултат на това съм готов да кажа, че ми хареса. Идеята за OOP може да се погледне от другата страна – има актьори, които обменят съобщения. По-интересното е, че можете (и трябва) да пишете код по такъв начин, че съобщението да не пристигне или да не бъде обработено (най-общо казано, когато акаунтът работи на един компютър, съобщенията не трябва да се губят). Първоначално се почесвах и в кода имаше боклук с актьори, които се абонираха един за друг, но в крайна сметка успях да измисля доста проста и елегантна архитектура. Кодът вътре във всеки актьор може да се счита за еднонишков; когато актьор се срине, acca го рестартира - резултатът е доста устойчива на грешки система.

9 август

Добавих към проекта scala-scrapper за анализиране на html страници от Habr (за извличане на информация като рейтинг на статия, брой отметки и т.н.).

И Котки. Тези в скалата.

Telegram бот за персонализирана селекция от статии от Habr

След това прочетох книга за разпределени бази данни, хареса ми идеята за CRDT (репликиран тип данни без конфликти, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, хабр), така че публикувах тип клас на комутативна полугрупа за информация относно статията на Хабре.

Всъщност идеята е много проста – имаме броячи, които се сменят монотонно. Броят на промоциите постепенно нараства, както и броят на плюсовете (както и броят на минусите). Ако имам две версии на информация за дадена статия, тогава мога да ги „обединя в една“ - състоянието на брояча, което е по-голямо, се счита за по-подходящо.

Полугрупа означава, че два обекта с информация за статия могат да бъдат обединени в един. Комутативно означава, че можете да обедините както A + B, така и B + A, резултатът не зависи от реда и в крайна сметка ще остане най-новата версия. Между другото, тук също има асоциативност.

Например, както беше планирано, rss след анализиране предостави леко отслабена информация за статията - без показатели като броя на гледанията. След това специален актьор взе информация за статиите и се затича към html страниците, за да я актуализира и обедини със старата версия.

Най-общо казано, както в akka, нямаше нужда от това, можете просто да запазите updateDate за статията и да вземете по-нова без никакви сливания, но пътят на приключението ме доведе.

12 август

Започнах да се чувствам по-свободен и просто за забавление направих всеки чат отделен актьор. Теоретично самият актьор тежи около 300 байта и те могат да бъдат създадени в милиони, така че това е напълно нормален подход. Струва ми се, че решението се оказа доста интересно:

Един актьор беше мост между сървъра на телеграма и системата за съобщения в Akka. Той просто получава съобщения и ги изпраща на желания чат актьор. Чат актьорът може да изпрати нещо обратно в отговор - и то ще бъде изпратено обратно в телеграмата. Много удобно беше, че този актьор се оказа възможно най-опростен и съдържаше само логиката за отговаряне на съобщения. Между другото, информация за нови статии дойде във всеки чат, но отново не виждам никакви проблеми в това.

Като цяло ботът вече работеше, отговаряше на съобщения, съхраняваше списък със статии, изпратени до потребителя, и вече си мислех, че ботът е почти готов. Бавно добавих малки функции като нормализиране на имена на автори и етикети (замяна на „sd f“ с „s_d_f“).

Оставаше само едно нещо малко, но — държавата не беше спасена никъде.

Всичко се обърка

Може би сте забелязали, че написах бота предимно сам. И така, вторият участник се включи в разработката и в кода се появиха следните промени:

  • MongoDB изглежда съхранява състояние. В същото време логовете в проекта бяха разбити, защото по някаква причина Monga започна да ги спами и някои хора просто ги изключиха глобално.
  • Бридж актьорът в Telegram се трансформира до неузнаваемост и започна сам да анализира съобщенията.
  • Актьорите за чатовете бяха безмилостно изрязани и вместо това бяха заменени с актьор, който скри цялата информация за всички чатове наведнъж. При всяко кихане този актьор се забъркваше в беда. Е, да, като при актуализиране на информация за статия, изпращането й до всички участници в чата е трудно (ние сме като Google, милиони потребители чакат милиони статии в чата за всяка), но всеки път, когато чата се актуализира, нормално е да отидеш в Монга. Както разбрах много по-късно, работната логика на чатовете също беше напълно изрязана и на нейно място се появи нещо, което не работеше.
  • От типовите класове не е останала и следа.
  • Появи се някаква нездравословна логика в актьорите с техните абонаменти един към друг, което води до състояние на състезание.
  • Структури от данни с полета от тип Option[Int] се превърна в Int с магически стойности по подразбиране като -1. По-късно разбрах, че mongoDB съхранява json и няма нищо лошо да се съхранява там Option добре, или поне анализирайте -1 като Няма, но по това време не знаех това и вярвах на думата си, че „така трябва да бъде“. Не съм написал този код и не си направих труда да го променя за момента.
  • Открих, че публичният ми IP адрес има тенденция да се променя и всеки път трябваше да го добавям към белия списък на Mongo. Пуснах бота локално, Monga беше някъде на сървърите на Monga като компания.
  • Изведнъж нормализирането на таговете и форматирането на съобщения за телеграми изчезна. (Хм, защо би било така?)
  • Хареса ми, че състоянието на бота се съхранява във външна база данни и при рестартиране продължава да работи, сякаш нищо не се е случило. Това обаче беше единственият плюс.

Вторият човек не бързаше особено и всички тези промени се появиха на една голяма купчина още в началото на септември. Не оцених веднага мащаба на полученото разрушение и започнах да разбирам работата на базата данни, защото... Никога досега не съм се занимавал с тях. Едва по-късно разбрах колко работещ код е изрязан и колко бъгове са добавени на негово място.

Септември

Първо си помислих, че ще е полезно да овладея Монга и да го направя добре. Тогава бавно започнах да разбирам, че организирането на комуникация с базата данни също е изкуство, в което можете да правите много състезания и просто да правите грешки. Например, ако потребителят получи две съобщения като /subscribe - и в отговор на всяко ще създадем запис в таблицата, тъй като към момента на обработка на тези съобщения потребителят не е абониран. Имам подозрение, че комуникацията с Монга в сегашния си вид не е написана по най-добрия начин. Например, настройките на потребителя са създадени в момента, в който той се регистрира. Ако се опита да ги промени преди факта на абонамента... ботът не отговори нищо, защото кодът в актьора влезе в базата данни за настройките, не го намери и се срина. Когато ме попитаха защо не създам настройки според нуждите, научих, че няма нужда да ги променям, ако потребителят не е абониран... Системата за филтриране на съобщения беше направена някак си неочевидно и дори след внимателно разглеждане на кода можех не разбирам дали първоначално е било предвидено по този начин или има грешка там.

Нямаше списък със статии, изпратени в чата; вместо това беше предложено да ги напиша сам. Това ме учуди - като цяло не бях против вкарването на всякакви неща в проекта, но би било логично този, който ги е внесъл, да ги прецака. Но не, вторият участник сякаш се отказа от всичко, но каза, че списъкът в чата уж е лошо решение и е необходимо да се направи знак със събития като „статия y е изпратена на потребител x“. След това, ако потребителят поиска изпращане на нови статии, е необходимо да се изпрати заявка до базата данни, която да избере събития, свързани с потребителя от събитията, също да получи списък с нови статии, да ги филтрира, да ги изпрати на потребителя и хвърляйте събития за това обратно в базата данни.

Вторият участник беше отнесен някъде към абстракции, когато ботът ще получава не само статии от Habr и ще бъде изпратен не само до телеграма.

Някак си реализирах събития под формата на отделен знак за втората половина на септември. Не е оптимално, но поне ботът започна да работи и отново започна да ми изпраща статии и аз бавно разбрах какво се случва в кода.

Сега можете да се върнете в началото и да запомните, че хранилището не е създадено първоначално от мен. Какво можеше да стане така? Заявката ми за изтегляне беше отхвърлена. Оказа се, че имам реднек код, че не знаех как да работя в екип и трябваше да коригирам грешки в текущата крива на внедряване, а не да я прецизирам до използваемо състояние.

Разстроих се и погледнах историята на комитите и количеството написан код. Разгледах моменти, които първоначално бяха написани добре, а след това бяха прекъснати...

Майната му

Сетих се за статията Вие не сте Google.

Мислех, че никой не се нуждае от идея без реализация. Мислех си, че искам да имам работещ бот, който да работи в едно копие на един компютър като проста java програма. Знам, че ботът ми ще работи с месеци без рестартиране, тъй като вече съм писал такива ботове в миналото. Ако внезапно падне и не изпрати на потребителя друга статия, небето няма да падне на земята и няма да се случи нищо катастрофално.

Защо имам нужда от Docker, mongoDB и друг карго култ към „сериозен“ софтуер, ако кодът просто не работи или работи криво?

Разклоних проекта и направих всичко, както исках.

Telegram бот за персонализирана селекция от статии от Habr

Горе-долу по същото време смених работата си и свободното време страшно ми липсваше. Сутринта се събудих точно във влака, вечерта се върнах късно и вече не исках да правя нищо. Известно време не направих нищо, след това желанието да довърша бота ме надви и започнах бавно да пренаписвам кода, докато шофирах за работа сутрин. Няма да кажа, че беше продуктивно: да седите в треперещ влак с лаптоп в скута си и да гледате препълването на стека от телефона си не е много удобно. Въпреки това, времето, прекарано в писане на код, отлетя напълно незабелязано и проектът започна бавно да се движи към работно състояние.

Някъде в съзнанието ми имаше червей на съмнение, който искаше да използва mongoDB, но си помислих, че в допълнение към предимствата на „надеждното“ съхранение на състояние има забележими недостатъци:

  • Базата данни се превръща в друга точка на провал.
  • Кодът става все по-сложен и ще ми отнеме повече време да го напиша.
  • Кодът става бавен и неефективен; вместо да променя обект в паметта, промените се изпращат в базата данни и, ако е необходимо, се изтеглят обратно.
  • Има ограничения за вида на съхранение на събитията в отделна таблица, които са свързани с особеностите на базата данни.
  • Пробната версия на Monga има някои ограничения и ако се натъкнете на тях, ще трябва да стартирате и конфигурирате Monga върху нещо.

Изрязах monga, сега състоянието на бота просто се съхранява в паметта на програмата и от време на време се записва във файл под формата на json. Може би в коментарите ще напишат, че греша, че тук трябва да се използва базата данни и т.н. Но това е моят проект, подходът с файла е възможно най-опростен и работи по прозрачен начин.

Изхвърли магически стойности като -1 и върна нормалните Option, добавено съхранение на хеш таблица с изпратени статии обратно към обекта с информация за чат. Добавено е изтриване на информация за статии, по-стари от пет дни, за да не се съхранява всичко. Доведох регистрирането до работно състояние - регистрационните файлове се записват в разумни количества както във файла, така и в конзолата. Добавени са няколко администраторски команди като запазване на състояние или получаване на статистика като брой потребители и статии.

Поправени са куп дребни неща: например за статиите вече се посочва броят на гледанията, харесванията, нехаресванията и коментарите към момента на преминаване на филтъра на потребителя. Като цяло е изненадващо колко малки неща трябваше да бъдат коригирани. Водех списък, отбелязвах всички „нередности“ там и ги коригирах, доколкото беше възможно.

Например добавих възможност за задаване на всички настройки директно в едно съобщение:

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

И друг отбор /settings ги показва точно в този вид, можете да вземете текста от него и да изпратите всички настройки на приятел.
Изглежда като малко нещо, но има десетки подобни нюанси.

Реализирано филтриране на статии под формата на прост линеен модел - потребителят може да зададе допълнителна оценка за автори и тагове, както и прагова стойност. Ако сумата от оценката на автора, средната оценка за таговете и действителната оценка на статията е по-голяма от праговата стойност, тогава статията се показва на потребителя. Можете или да поискате от бота статии с командата /new, или да се абонирате за бота и той ще изпраща статии на лично съобщение по всяко време на деня.

Най-общо казано, имах идея за всяка статия да извадя повече функции (хъбове, брой коментари, отметки, динамика на промените в рейтинга, количество текст, снимки и код в статията, ключови думи) и да покажа на потребителя добре/ не е добре да гласувам под всяка статия и да обучавам модел за всеки потребител, но бях твърде мързелив.

Освен това логиката на работата няма да бъде толкова очевидна. Сега мога ръчно да задам рейтинг от +9000 за пациентЗеро и с прагов рейтинг от +20 ще бъда гарантиран, че ще получа всичките му статии (освен ако, разбира се, не задам -100500 за някои тагове).

Крайната архитектура се оказа доста проста:

  1. Актьор, който съхранява състоянието на всички чатове и статии. Той зарежда състоянието си от файл на диска и го записва обратно от време на време, всеки път в нов файл.
  2. Актьор, който посещава RSS емисията от време на време, научава за нови статии, преглежда връзките, анализира и изпраща тези статии на първия актьор. Освен това понякога изисква списък със статии от първия актьор, избира тези, които не са по-стари от три дни, но не са актуализирани дълго време, и ги актуализира.
  3. Актьор, който общува с телеграма. Все пак пренесох напълно анализирането на съобщението тук. По приятелски начин бих искал да го разделя на две - така че едното да анализира входящите съобщения, а второто да се занимава с транспортни проблеми като повторно изпращане на неизпратени съобщения. Сега няма повторно изпращане и съобщение, което не е пристигнало поради грешка, просто ще бъде загубено (освен ако не е отбелязано в регистрационните файлове), но досега това не е създавало проблеми. Може би ще възникнат проблеми, ако куп хора се абонират за бота и достигна лимита за изпращане на съобщения).

Това, което ми хареса е, че благодарение на akka, паданията на актьори 2 и 3 обикновено не влияят на работата на бота. Може би някои статии не се актуализират навреме или някои съобщения не достигат до телеграмата, но акаунтът рестартира актьора и всичко продължава да работи. Запазвам информацията, че статията се показва на потребителя само когато актьорът на телеграмата отговори, че е доставил съобщението успешно. Най-лошото нещо, което ме заплашва, е да изпратя съобщението няколко пъти (ако е доставено, но потвърждението се губи някак си). По принцип, ако първият актьор не е съхранявал състоянието в себе си, а е общувал с някаква база данни, тогава той също може неусетно да падне и да се върне към живот. Бих могъл също да опитам akka persistance, за да възстановя състоянието на актьорите, но текущото изпълнение ме устройва със своята простота. Не че кодът ми се срива често - напротив, положих доста усилия, за да го направя невъзможно. Но глупостите се случват и възможността да разбивам програмата на изолирани части-актьори ми се стори наистина удобна и практична.

Добавих circle-ci, така че ако кодът се счупи, веднага ще разберете за това. Като минимум това означава, че кодът е спрял да се компилира. Първоначално исках да добавя travis, но той показваше само моите проекти без разклонения. Като цяло и двете неща могат да се използват свободно в отворени хранилища.

Резултати от

Вече е ноември. Ботът е написан, ползвам го последните две седмици и ми хареса. Ако имате идеи за подобрение пишете. Не виждам смисъл да го монетизирам - оставете го просто да работи и да изпраща интересни статии.

Линк към бот: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Малки заключения:

  • Дори един малък проект може да отнеме много време.
  • Вие не сте Google. Няма смисъл да се стреля по врабчета от оръдие. Едно просто решение може да работи също толкова добре.
  • Проектите за домашни любимци са много добри за експериментиране с нови технологии.
  • Telegram ботовете са написани доста просто. Ако не беше „работата в екип“ и експериментите с технологиите, ботът щеше да бъде написан за седмица или две.
  • Моделът на актьора е интересно нещо, което върви добре с многонишков и устойчив на грешки код.
  • Мисля, че усетих защо общността с отворен код обича разклоненията.
  • Базите данни са добри, защото състоянието на приложението вече не зависи от сривове/рестартиране на приложението, но работата с база данни усложнява кода и налага ограничения върху структурата на данните.

Източник: www.habr.com

Добавяне на нов коментар