部落格搬遷至https://blog.wangjiegulu.com
RSS訂閱:https://blog.wangjiegulu.com/feed.xml
原文連結:https://blog.wangjiegulu.com/2018/04/03/huginn_douban_high_score_movies_and_slack/
Huginn實作自動通過slack推送豆瓣高分電影
如果尚未安裝 Huginn,可以參考這裡
想象下以下場景:每當有正在上映的電影在豆瓣上的評分超過7.8分,則 huginn 自動編輯一條資訊并通過
Slack
(當然也可以用 telegram 等app)通知到我電腦或者手機上。收到資訊後,點選不喜歡忽略,或者點選購票按鈕直接進入到購票頁面。甚至 Huginn 可以結合
Google Calendar
查詢你這幾天的行程安排,推送高分電影資訊的同時給你選擇一個比較合适觀看電影的時間點,購買好電影票後,huginn 又自動幫你把日程事件寫入到
Google Calendar
中,并設定提醒。是不是很酷?!
Huginn 就如你的貼心管家,按照你的意願自動幫你完成很多事情。
我們先來實作
每當有正在上映的電影在豆瓣上的評分超過7.8分,則給我推送 Slack 資訊
這一部分需求。
最後達到的效果如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLxczMmZGOmVGZ2YGNtYjYkhTL4UWMx0SY0gzMtITOiJmZhZDZtMjM4gDMzgzMvwFN5EzMyQTNvwVbvNmL05WZ052bjJXZzVnY1hGdpdmLzV2Zh1WatIXZzV3Lc9CX6MHc0RHaiojIsJye.png)
手機端效果
PC端效果
建立 Agents
首先進入 Huginn 首頁(預設
localhost:3000
),左上角進入
Scenarios
:
我的了解:Scenario 代表一種場景,一般會包含多個 agent,一個 agent 表示進行一次事件的處理或者變換。拿我們現在的例子來說,自動通過slack推送豆瓣高分電影 這一整個就是一個
Scenario
,但是這個
Scenario
會有很多的
agent
s 組成,比如:
- 有一個 agent 是用來從豆瓣網頁擷取目前上映中的所有電影和它們的分數等資訊;
- 一個 agent 是用來從第一個 agent 裡面拿到的所有電影進行過濾,過濾的标準就是
,score > 7.8
- 還有一個 agent 是用來把過濾後的電影通過 slack 推送到我們手機上。
看着跟
RxJava
的觀察者模式是不是很像?第一個從豆瓣頁面拉取資料的過程就像是
Observable
,然後其它的 agent 就像很多的
operator
用來把資料進行轉換和變化,最終通知到
subscriber
,這裡的
subscriber
就是我們自己。我們通過 huginn 訂閱了
豆瓣高分電影
,就是這麼簡單。
點選左下角的
New Scenario
建立一個名為
douban_high_score_movie
的 Scenario。
建立擷取資料 agent
第一個 agent 用來從豆瓣官網擷取所有正在上映的電影
在
douban_high_score_movie
的 Scenario 中點選
+ New Agent
來建立第一個 Agent。
如上圖,你需要去決定你要建立的 agent 的類型(這裡是目前 Huginn 支援的所有的類型)。
我們通過輸入 "web" 來進行過濾選擇
Website Agent
。
上圖,左邊是我們需要去配置的地方;右邊是每個設定對應的說明。
- Name:給這個 agent 取個名字,我們這裡取名為
,表示這個 agent 是step1_get_douban_playing_movies
這個 Scenario 的第一步,是用來從豆瓣擷取目前正在上映的所有電影。douban_high_score_movie
- Schedule:表示排程周期,表示在什麼時候自動執行這個 agent,比如
表示每一天執行一次、Every 1d
表示每2小時執行一次、Every 2h
表示每天下午8點執行等等;這裡我們選擇8pm
,每天下午3點執行一次。3pm
- Keep events:表示事件保留的時間;比如我們從豆瓣上擷取到所有上映的電影,每一部電影資訊都是一個 event,Huginn會把這些 event 保留在本地,你可以通過這個參數來設定這些 events 在本地保留多少時間,超過這個時間,Huginn會把資料清除。我們這裡設定1小時(為什麼隻設定為1小時,下面我們會再讨論)。
- Sources:表示這個 agent 處理的資料來源是哪個 agent。我們現在建立的 agent 是第一個 agent,是從豆瓣網站上擷取正在上映的所有電影,是以不需要從其他 agent 傳遞資料(也就是上面說的 events)過來,是以這個留白。
- Receivers:表示這個 agent 處理完資料之後把這些資料傳入到哪個 agent。還是用
做類比,因為每個 agnet 都有可能隻是整個觀察者模式中的一個操作符,用來轉化資料,資料轉化完之後,可能還需要其他 agent 把這些資料做進一步的轉化。RxJava
- Scenarios:表示這個 agent 是資料哪個 Scenario 的。
- Options:這個非常關鍵,就是通過這個配置檔案(JSON)來進行網絡請求和豆瓣電影資料解析相關的操作的,這個我們重點講下。
注意:以上沒提到的配置可以留白
Options 配置
Options 配置其實就是一個 JSON 檔案。Website Agent 的 Options 主要的元素有如下:
- url:網站位址,表示我需要從哪個網站擷取資料,現在我們是從豆瓣,是以需要輸入豆瓣正在上映的網址,這裡我們輸入
,當然最後一個地點可以根據你的常駐地點做相應的修改。https://movie.douban.com/cinema/nowplaying/hangzhou/
- type:資料解析的類型,支援的類型有
、xml
html
json
四種,目前豆瓣網址傳回的當然是 html 了,是以這裡我們填寫text
。如果其他場景,比如 調用第三方開放的 api,傳回的類型可能就是html
或者json
了。xml
- mode:表示擷取資料的模式,我們這裡選擇
on_change
- on_change:在資料有更改時才會擷取作為 events。
- merge:把新資料和輸入的資料進行合并。
- all:擷取所有資料。
- extract:用來配置(JSON)從這個網站解析出真正我們想要的資料。如果
是type
,則每個資料通過html
選擇器或者css
來解析出真正的資料。xpath
注意: on_change
這個設定在我們現在的場景下其實用處不大,這個下面我們會再讨論。
最後的 options 如下:
{
"expected_update_period_in_days": "2",
"url": "https://movie.douban.com/cinema/nowplaying/hangzhou/",
"type": "html",
"mode": "on_change",
"extract": {
"title": {
"css": "li[@data-category='nowplaying']",
"value": "@data-title"
},
"score": {
"css": "li[@data-category='nowplaying']",
"value": "@data-score"
},
"star": {
"css": "li[@data-category='nowplaying']",
"value": "@data-star"
},
"release": {
"css": "li[@data-category='nowplaying']",
"value": "@data-release"
},
"region": {
"css": "li[@data-category='nowplaying']",
"value": "@data-region"
},
"actors": {
"css": "li[@data-category='nowplaying']",
"value": "@data-actors"
},
"director": {
"css": "li[@data-category='nowplaying']",
"value": "@data-director"
},
"detail_url": {
"css": "li[@data-category='nowplaying']/ul/li/a[@data-psource='poster']",
"value": "@href"
},
"image_url": {
"css": "li[@data-category='nowplaying']/ul/li/a[@data-psource='poster']/img",
"value": "@src"
}
}
}
以上可以看出,我們從豆瓣的每部電影中擷取了以下資訊:
- title:電影名字
- score:電影分數,滿分10分
- star:電影分數,滿分50分
- release:上映日期
- region:地區
- actors:演員
- director:導演
- detail_url:詳細 url
- image_url:電影封面
注意:擷取具體 xpath 比較簡單的方法:通過 chrome 右鍵的 inspect
來複制拿到。
以上配置完畢後,點選下面的
Dry Run
,應該就會出現以下頁面
最後進行儲存。第一個 agent 就建立完畢了。
同時,這個 agent 在運作的過程中會生成以下 events:
建立過濾 agnet
step1_get_douban_playing_movies
把所有正在上映的電影資料從豆瓣上拉取下來并解析好,生成一個個 events。然後我們第二個 agent 就需要從這些 events 裡面進行過濾篩選出所有分數大于
7.8
(具體的标準可以自己定) 的電影。相當于 RxJava 的 filter 操作符吧。
同樣建立 agent,選擇為
TriggerAgent
,名字為
step2_pick_high_score_movies
。這是把 Sources 填寫為第一個 agent 的名字,即
step1_get_douban_playing_movies
,表示我要建立的 agent 處理的資料(events)是從
step1_get_douban_playing_movies
來的。
然後重點還是在 Options 中
- keep_event:表示是否把我從
這個 agent 收到的 events 原封不動地再傳給下一個 agent(下一個 agent 我們還沒建立),我們設定為 true。因為下一個 agent 我們是用來把資料通過 slack 發送到給我們自己的,那肯定需要第一個 agent 中擷取到的例如電影名字、分數等資訊。step1_get_douban_playing_movies
- rules:表示我們過濾的規則,可以多個,具體下面說。
- must_match:表示 rules 中我必須要滿足幾個規則,如果是1,則意味着 rules 中所有的規則是或關系(隻要滿足 rules 中的1個規則即可);預設不填寫的話是必須要滿足 rules 中所有的規則。,因為我們這裡隻需要滿足一個分數大于7.8就可以,是以可以不填寫。
最後 Options 的配置如下:
{
"expected_receive_period_in_days": "2",
"keep_event": "true",
"rules": [
{
"type": "field>=value",
"value": "7.8",
"path": "$.score"
}
],
"message": "Looks like your pattern matched in '{{value}}'!"
}
如上,在 rules 中添加一個規則,type 表示比對規則,
field>=value
- field: 通過下面 path 從 events 比對出來的資料,這裡是
,是以表示的是電影的分數;$.score
- value:表示下面 json 的
字段的值,這裡為value
7.8
通過簡單的表達式
field>=value
來設定比對規則:電影分數 >= 7.8分。
至此,第二個 agent 建立完畢。
你同樣可以通過下面的
Dry Run
來進行測試,測試時因為有
Sources
,需要你構造一些假資料作為輸入來運作。
建立去重 agnet
step2_pick_high_score_movies
用來把
step1_get_douban_playing_movies
中從豆瓣官網擷取的電影資訊進行高分的過濾(分數>=7.8)。
我們還需要建立一個去重的 agent,來避免重複給我們自己推送高分電影(因為我們現在擷取的頻率是每天進行擷取檢測,但是電影總不可能是每部電影隻上映一天吧,第二天擷取的時候肯定有第一天擷取的資料)。
這裡大家可能會有個問題,因為我們在配置第一個 agent 的時候,已經把
mode
已經設定為
on_change
了,為什麼還是會有重複資料呢?因為這裡的電影資訊中,有諸如
分數
這類的資料,這些資料是随時可能會有變化的,雖然是同一個電影,但是分數從
8.1
上升到
8.2
,那 Huginn 也會認為滿足了
on_change
條件,是以會造成重複推送。是以,我們還需要單獨做去重處理。
> **注意:** 之前提到過 `on_change` 等設定在第一個 agent 其實用處不大,同樣也是由于上面說的原因,我們也不知道同樣的電影什麼時候分數會發生變化,就算用了 `on_change`,也可能會把之前擷取過的資料拿到。是以第一個 agent 的 keep_event 設定的時間比較短,因為這些 events 提供給 `on_change` 比對意義不大,是以還是節省空間,設定短一點。
建立 agent,type 選擇
DeDuplicationAgent
,名字取為
step2_1_deduplication_high_score_movies
,Sources 填寫為上一個 agent 的名字,即
step2_pick_high_score_movies
注意:這裡 keep_event 設定了90天,因為一旦經過我們這個 agent 去重後,events 假設保留1小時,那下一天我再去擷取所有上映的電影并高分過濾後,因為昨天的資料(events)已經被清空了,是以就沒辦法做比較去重了,是以會導緻重複資料。是以這裡儲存時間應該要>=電影上映的時長,是以這裡設定為90天,即3個月左右。
DeDuplicationAgent 的 Options 填寫就比較簡單了
- Property:填寫你要去重依據的字段,我們這裡根據電影名字來去重,也就是
title
- Lookback:表示去重的時候跟之前的多少條曆史 events 做比較,同一時期一起上映的電影應該不會超過100部,是以設定為100了。
建立 slack 通知的 agent
Huginn 自帶有一個
SlackAgent
,用來發送 slack 消息。
它使用了 incoming-webhooks 來實作消息的發送。
但是為了有更多的可玩性,我們這裡選擇,自己建立一個
slack app
,然後通過它的 open api 實作。
是以,我們需要建立一個
PostAgent
。但是在此之前我們先來配置好 Slack 環境。
配置 Slack 環境
安裝 Slack:https://slack.com
- Google Play for Android:https://play.google.com/store/apps/details?id=com.Slack
建立自己的 workspace(單獨建立一個自己私有的,注意不要使用公司、團隊的 workspace),比如我的是
https://wangjie.slack.com
在自己私有的 workspace 中建立一個私有的 channel:
#huginn-movie
這個 channel 就是用來接收高分電影的資料了,當然你也可以使用
#general
然後我們建立一個自己的 app,用來發送電影資訊。進入 https://api.slack.com/
點選
Start Building
- App Name:可以随意填寫
- Development Slack Workspace:選擇你剛剛建立的私有的 workspace
Add features and functionality
中點選
Permissions
進入權限配置。
Scope
中添加如下權限:
添加完以上所有權限後,點選儲存,然後重新打開
Permissions
,點選下面按鈕安裝我們的這個 app 到 slack。
安裝完畢之後,再次進入 `Permissions`,拷貝 `OAuth Access Token`:
然後,我們就可以使用我們的 token 來通路 slack 的 open api 了,具體文檔在這裡:https://api.slack.com/web。
我們需要的發送消息到
#huginn-movie
channel 的接口文檔:
https://api.slack.com/methods/chat.postMessage
有了 api 文檔,有了 token,一切就好辦了。
由上述文檔,我們可以通過 post 請求,把我們要發送的電影資訊封裝到
attachments
參數中執行請求即可。
而且
attachments
參數可以參考文檔 https://api.slack.com/docs/message-attachments 來封裝資訊。
Slack 環境一切就緒,接下來,回到 Huginn。
建立 Agent 發送 Slack 消息
建立
PostAgent
(注意,不是
SlackAgent
),取名為
step3_high_score_movies_to_slack_post
。Sources 填寫為
step2_1_deduplication_high_score_movies
,因為這個 agent 需要把去重後的電影資訊通過 slack 發送給我們。
最終的 Options 配置如下:
{
"post_url": "{% credential slack_huginn_url_post_message %}",
"expected_receive_period_in_days": "1",
"content_type": "json",
"method": "post",
"payload": {
"channel": "huginn-movie",
"username": "Douban Movie",
"icon_url": "https://img3.doubanio.com/pics/douban-icons/favicon_48x48.png",
"attachments": [
{
"fallback": "Required plain-text summary of the attachment.",
"mrkdwn_in": [
"text",
"pretext"
],
"color": "#36a64f",
"pretext": "Hi~ <@{% credential slack_at_user_id %}>, There is *high score* movie.",
"author_name": "{{director}}",
"author_link": "{{detail_url}}",
"author_icon": "",
"title": "《{{title}}》",
"title_link": "{{detail_url}}",
"text": "*Actors*: {{actors}}",
"fields": [
{
"title": "Score",
"value": "{{score}}",
"short": true
},
{
"title": "Star",
"value": "{{star}}",
"short": true
},
{
"title": "Region",
"value": "{{region}}",
"short": true
},
{
"title": "Release",
"value": "{{release}}",
"short": true
}
],
"image_url": "",
"thumb_url": "{{image_url}}",
"footer": "Slack",
"footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
"ts": "{{\"now\" | date: \"%s\"}}"
}
]
},
"headers": {
"Content-Type": "application/json",
"Authorization": "{% credential slack_huginn_token %}"
},
"emit_events": "false",
"no_merge": "false",
"output_mode": "clean"
}
需要注意的是:
-
:此類的表達式為 Liquid-interpolated,具體的值配置在{\% credential slack_huginn_url_post_message %\}
中,可以了解為全局定義,在Credentials
中配置好Credentials
之後,可以在其它地方以諸如key-value
的方式來使用,這裡不做過多介紹了。{\% credential key \%}
- 在消息中使用Slack 中的 `@` 某人的功能時,需要拿到對應使用者的 ID,可以的擷取方式可以通過在 slack 中選中名字然後 `Copy link` 的方式拿到使用者連結,使用者連接配接的最後就是 ID。
儲存該 Agent,至此,所需的所有的 Agent 都已經建立完畢了。
總結
整個 Scenario 的事件流程圖如下:
Huginn 還支援公開你建立的 Scenario,提供給其它人使用,以上的代碼也已經公開:
http://h.wangjiegulu.com/scenarios/8/export.json
大家可以直接下載下傳使用,不過需要在
Credentials
中配置如下參數:
- slack_huginn_token:你建立的 Slack App 的 OAuth Access Token,具體方式可以參考這裡
- slack_at_user_id:你需要 @ 的 slack 使用者 ID,填寫你自己的,拿到你 ID 的方式可以參考這裡
- slack_huginn_url_post_message:填寫
即可。https://slack.com/api/chat.postMessage
除了以上例子,Huginn 還可以完成更多奇思妙想,限制你的隻有你的想象力。