Telegram 機器人用於個人化選擇 Habr 的文章

對於諸如“為什麼?”之類的問題有一篇較舊的文章 - 自然極客時代-讓空間更清潔.

文章很多,出於主觀原因,有些我不喜歡,有些則相反,跳過很可惜。 我想優化這個過程並節省時間。

上面的文章建議了一種瀏覽器內腳本方法,但我不太喜歡它(儘管我以前使用過它),原因如下:

  • 對於電腦/手機上的不同瀏覽器,如果可能的話,您必須重新配置。
  • 按作者嚴格過濾並不總是很方便。
  • 那些你不想錯過的作者的文章,即使他們每年發表一次,這個問題還沒有解決。

基於文章評級的網站內建過濾並不總是很方便,因為高度專業化的文章儘管有價值,但可能會獲得相當溫和的評級。

最初,我想產生一個 RSS 提要(甚至幾個),只留下有趣的內容。 但最終發現,閱讀RSS似乎並不是很方便:無論如何,要對一篇文章進行評論/投票/將其添加到收藏夾,都必須通過瀏覽器。 這就是為什麼我編寫了一個電報機器人,它透過個人訊息向我發送有趣的文章。 Telegram 本身製作了精美的預覽,與有關作者/評級/觀點的資訊相結合,看起來資訊量很大。

Telegram 機器人用於個人化選擇 Habr 的文章

剪輯下方是作品特色、寫作過程、技術解決方案等細節。

簡單介紹一下機器人

存儲庫: https://github.com/Kright/habrahabr_reader

電報中的機器人: https://t.me/HabraFilterBot

使用者為標籤和作者設定附加評級。 之後,對文章套用篩選器 - 將文章在 Habré 上的評分、作者的使用者評分以及按標籤劃分的使用者評分平均值相加。 如果金額大於使用者指定的閾值,則文章通過過濾器。

編寫機器人的另一個目標是獲得樂趣和經驗。 此外,我還常提醒自己, 我不是谷歌,因此很多事情都是盡可能簡單甚至原始地完成的。 然而,這並沒有阻止編寫機器人的過程花費了三個月的時間。

外面正值夏天

七月即將結束,我決定寫一個機器人。 而且不是一個人,而是和一個正在掌握 scala 並想在上面寫點東西的朋友一起。 一開始看起來很有希望——程式碼將由一個團隊來削減,任務似乎很容易,我認為在幾週或一個月內機器人就會準備好。

儘管我自己在過去幾年裡時不時地在岩石上編寫程式碼,但通常沒有人看到或查看這些程式碼:寵物項目,測試一些想法,預處理數據,掌握 FP 的一些概念。 我對在團隊中編寫程式碼的樣子非常感興趣,因為搖滾程式碼可以用非常不同的方式編寫。

什麼本來可以消失 所以? 不過,我們不要著急。
發生的所有事情都可以使用提交歷史記錄進行追蹤。

27 月 XNUMX 日,一位熟人創建了一個儲存庫,但沒有做其他任何事情,所以我開始編寫程式碼。

七月30

簡而言之:我寫了 Habr 的 rss feed 的解析。

  • com.github.pureconfig 用於將類型安全性配置直接讀取到案例類別中(事實證明非常方便)
  • scala-xml 用於讀取 xml:因為最初我想為 rss feed 編寫自己的實現,並且 rss feed 是 xml 格式,所以我使用這個庫進行解析。 其實RSS解析也出現了。
  • scalatest 用於測試。 即使對於小型項目,編寫測試也可以節省時間 - 例如,在偵錯 xml 解析時,將其下載到文件、編寫測試和糾正錯誤要容易得多。 當後來在解析一些帶有無效 utf-8 字元的奇怪 html 時出現錯誤時,事實證明將其放入文件並添加測試會更方便。
  • 來自阿卡的演員。 客觀地說,它們根本不需要,但該專案是為了好玩而編寫的,我想嘗試一下。 結果,我準備好說我喜歡它。 OOP的思想可以從另一面來看──有交換訊息的參與者。 更有趣的是,您可以(並且應該)以這樣的方式編寫程式碼:訊息可能不會到達或可能不會被處理(一般來說,當帳戶在一台電腦上運行時,訊息不應該丟失)。 起初我很困惑,程式碼中存在垃圾,演員互相訂閱,但最終我設法想出了一個相當簡單而優雅的架構。 每個 Actor 內部的程式碼可以被認為是單線程的;當 Actor 崩潰時,acca 會重新啟動它 - 結果是一個相當容錯的系統。

八月9

我添加到項目中 scala-scrapper 用於解析 Habr 的 html 頁面(提取文章評級、書籤數量等資訊)。

還有貓。 那些在岩石裡的。

Telegram 機器人用於個人化選擇 Habr 的文章

然後我讀了一本關於分散式資料庫的書,我喜歡CRDT(無衝突複製資料類型, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, 哈布爾),所以我發布了一個交換半群的類型類,以獲取有關哈布雷文章的資訊。

事實上,這個想法非常簡單——我們有單調變化的計數器。 促銷的數量正在逐漸增加,優點(以及缺點)的數量也逐漸增加。 如果我有一篇文章的兩個版本的信息,那麼我可以“將它們合併為一個” - 較大的計數器狀態被認為更相關。

半群意味著具有一篇文章資訊的兩個物件可以合併為一個。 可交換意味著你可以將A+B和B+A都合併,結果不依賴順序,最終保留最新版本。 順便說一句,這裡也有關聯性。

例如,按照計劃,解析後的 rss 提供的有關文章的資訊略有減弱 - 沒有瀏覽量等指標。 然後,一名特殊演員獲取有關文章的信息,並跑到 html 頁面進行更新並將其與舊版本合併。

一般來說,就像在 akka 中一樣,沒有必要這樣做,你可以簡單地存儲文章的 updateDate 並獲取更新的文章,而不需要任何合併,但冒險​​之路引導了我。

八月12

我開始感到更自由,只是為了好玩,我讓每次聊天都變成一個單獨的演員。 理論上,一個 Actor 本身的重量約為 300 字節,並且可以數百萬個位元組來創建,因此這是一種完全正常的方法。 在我看來,這個解決方案非常有趣:

一個 Actor 是 Akka 中電報伺服器和訊息系統之間的橋樑。 他只是接收訊息並將其發送給所需的聊天演員。 聊天演員可以發回一些東西作為回應——並且它會被發回電報。 非常方便的是,這個 actor 盡可能簡單,只包含回應訊息的邏輯。 順便說一下,每次聊天都會出現有關新文章的信息,但我再次認為這沒有任何問題。

一般來說,機器人已經開始工作,響應訊息,儲存發送給用戶的文章列表,我已經認為機器人幾乎準備好了。 我慢慢地添加了一些小功能,例如標準化作者姓名和標籤(用“s_d_f”替換“sd f”)。

只剩下一件事了 小但是 ——國家在任何地方都沒有被拯救。

一切都錯了

您可能已經注意到,這個機器人大部分是我獨自寫的。 於是,第二個參與者參與了開發,程式碼中出現瞭如下變化:

  • MongoDB 似乎可以儲存狀態。 與此同時,專案中的日誌被破壞了,因為出於某種原因 Monga 開始向它們發送垃圾郵件,而有些人乾脆在全球範圍內將它們關閉。
  • 《電報》中的橋牌演員變得面目全非,並開始自己解析訊息。
  • 聊天的演員被無情地砍掉了,取而代之的是一次性隱藏了所有聊天訊息的演員。 每打一個噴嚏,這位演員就會陷入麻煩。 嗯,是的,就像更新一篇文章的資訊時,將其發送給所有聊天參與者是很困難的(我們就像谷歌,數百萬用戶正在等待每個聊天中的一百萬篇文章) ,但是每次更新聊天時,進入蒙加都很正常。 正如我後來意識到的那樣,聊天的工作邏輯也被完全刪除了,取而代之的是一些不起作用的東西。
  • 類型類沒有留下任何痕跡。
  • 參與者之間的訂閱中出現了一些不健康的邏輯,導致了競爭狀況。
  • 具有類型欄位的資料結構 Option[Int] 轉換為具有神奇預設值(如 -1)的 Int。 後來我意識到mongoDB儲存的是json,儲存在那裡並沒有什麼問題 Option 好吧,或者至少將 -1 解析為 None,但當時我不知道這一點,並相信我的話「應該是這樣的」。 那段程式碼不是我寫的,我暫時也懶得修改它。
  • 我發現我的公共IP位址經常發生變化,每次我都必須將其添加到Mongo的白名單中。 我在本地啟動了該機器人,Monga 位於 Monga 公司伺服器上的某個位置。
  • 突然間,電報標籤和訊息格式的標準化消失了。 (嗯,為什麼會這樣呢?)
  • 我喜歡機器人的狀態儲存在外部資料庫中,當重新啟動時,它會繼續工作,就像什麼都沒發生一樣。 然而,這是唯一的優點。

第二個人並不著急,所有這些變化在九月初就已經集中出現了。 我沒有立即意識到所造成的破壞的規模並開始了解資料庫的工作,因為... 我以前從未與他們打過交道。 直到後來我才意識到有多少工作代碼被削減了,並且在其位置上添加了多少錯誤。

九月

起初我認為掌握 Monga 並做好它會很有用。 然後我慢慢開始明白,組織與資料庫的通訊也是一門藝術,你可以參加很多比賽,但也會犯錯。 例如,如果用戶收到兩則訊息,例如 /subscribe - 為了回應每一則訊息,我們將在表中建立一個條目,因為在處理這些訊息時,使用者尚未訂閱。 我懷疑目前與 Monga 的交流形式並不是以最好的方式編寫的。 例如,用戶的設定是在他註冊時創建的。 如果他嘗試在訂閱之前更改它們......機器人不會做出任何回應,因為參與者中的代碼進入資料庫進行設置,沒有找到它並崩潰了。 當被問到為什麼不根據需要創建設定時,我了解到,如果用戶沒有訂閱,則無需更改它們...訊息過濾系統的製作方式並不明顯,即使仔細查看代碼後我也可以不明白最初是這樣的還是有錯誤。

聊天中沒有提交的文章清單;相反,建議我自己寫。 這讓我感到驚訝——總的來說,我並不反對把各種各樣的東西拖到專案中,但對於那些把這些東西帶進來並搞砸的人來說,這是合乎邏輯的。 但不,第二個參與者似乎放棄了一切,但表示聊天中的列表據說是一個糟糕的解決方案,有必要用諸如“一篇文章 y 已發送給用戶 x”之類的事件做一個標誌。 然後,如果用戶要求發送新文章,就需要向資料庫發送請求,資料庫會從事件中選擇與用戶相關的事件,同時獲取新文章列表,過濾後發送給用戶並將有關此事件的事件扔回資料庫中。

第二位參與者被帶到了抽象的地方,此時機器人不僅會收到 Habr 的文章,而且不僅會發送到電報。

我以某種方式在九月下旬以單獨標誌的形式實施了事件。 這不是最優的,但至少機器人開始工作並開始再次向我發送文章,我慢慢地弄清楚程式碼中發生了什麼。

現在您可以回到開頭並記住該存儲庫最初不是我創建的。 什麼事會變成這樣呢? 我的拉取請求被拒絕了。 事實證明,我有鄉巴佬程式碼,我不知道如何在團隊中工作,我必須修復當前實現曲線中的錯誤,而不是將其細化到可用狀態。

我很沮喪,查看了提交歷史記錄和編寫的程式碼量。 我看了一些原本寫得很好的片段,後來又被破壞了…

去他媽的

我記得那篇文章 你不是谷歌.

我認為沒有人真正需要一個沒有被實施的想法。 我想我想要一個工作機器人,它將作為一個簡單的 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 向機器人索取文章,或訂閱機器人,它將在一天中的任何時間以個人訊息的形式發送文章。

一般來說,我的想法是讓每篇文章都拉出更多的功能(中心、評論數量、書籤、評級變化動態、文章中的文字、圖片和代碼量、關鍵字),並向用戶展示一個好的/不好在每篇文章下投票並為每個用戶訓練一個模型,但我太懶了。

另外,作品的邏輯也不會那麼明顯。 現在我可以手動為 PatientZero 設定 +9000 的評級,並且閾值評級為 +20,我將保證收到他的所有文章(當然,除非我為某些標籤設定 -100500)。

最終的架構非常簡單:

  1. 儲存所有聊天和文章狀態的參與者。 它從磁碟上的檔案載入其狀態,並時不時地將其儲存回一個新檔案。
  2. 不時造訪 RSS 提要的參與者了解新文章、查看連結、解析並將這些文章發送給第一個參與者。 另外,它有時會要求第一個演員的文章列表,選擇那些不超過三天、但很長時間沒有更新的文章,並更新它們。
  3. 透過電報進行交流的演員。 我這裡還是把訊息解析完整了。 以友好的方式,我想將其分為兩部分 - 一個解析傳入的訊息,第二個處理傳輸問題,例如重新發送未發送的訊息。 現在沒有重新發送,並且由於錯誤而未到達的訊息將簡單地丟失(除非在日誌中註明),但到目前為止這還沒有造成任何問題。 如果一群人訂閱了機器人並且我達到了發送訊息的限制,也許會出現問題)。

我喜歡的是,多虧了 akka,演員 2 和 3 的跌倒通常不會影響機器人的表現。 也許有些文章沒有按時更新,或者有些消息沒有到達電報,但帳戶重新啟動演員,一切都繼續進行。 只有當電報參與者回應他已成功傳遞訊息時,我才會儲存向使用者顯示文章的資訊。 威脅我的最糟糕的事情是多次發送訊息(如果已發送,但確認訊息不知何故丟失了)。 原則上,如果第一個演員不將狀態儲存在自己體內,而是與某個資料庫進行通信,那麼他也可以在不知不覺中墜落並復活。 我也可以嘗試 akka 持久性來恢復 Actor 的狀態,但當前的實作因其簡單性而適合我。 這並不是說我的程式碼經常崩潰——相反,我付出了相當大的努力來讓它變得不可能。 但糟糕的事情還是發生了,將程式分解成獨立的部分(演員)的能力對我來說似乎非常方便和實用。

我添加了circle-ci,這樣如果程式碼損壞,你會立即發現它。 至少,這意味著程式碼已停止編譯。 最初我想添加 travis,但它只顯示我的項目,沒有分叉。 一般來說,這兩個東西都可以在開放式儲存庫中自由使用。

結果

已經十一月了。 該機器人已編寫完畢,過去兩週我一直在使用它,我喜歡它。 如果您有改進的想法,請寫下來。 我不認為將其貨幣化有什麼意義——讓它發揮作用並發​​送有趣的文章。

機器人連結: https://t.me/HabraFilterBot
GitHub: https://github.com/Kright/habrahabr_reader

小結論:

  • 即使是一個小專案也可能需要很多時間。
  • 你不是谷歌。 用大砲射麻雀是沒有意義的。 一個簡單的解決方案也可能有效。
  • 寵物項目非常適合嘗試新技術。
  • Telegram 機器人的編寫非常簡單。 如果沒有「團隊合作」和技術實驗,這個機器人可能會在一兩週內完成。
  • Actor 模型是一個有趣的東西,它與多執行緒和容錯程式碼配合得很好。
  • 我想我已經體會到為什麼開源社群喜歡分岔了。
  • 資料庫很好,因為應用程式狀態不再依賴應用程式崩潰/重新啟動,但使用資料庫會使程式碼複雜化並對資料結構施加限制。

來源: www.habr.com

添加評論