推送公司今日選單內容到手機
此文已由作者張耕源授權網易雲社群釋出。
歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。
自從公司的易信公眾服務號有了查詢今日選單的功能,自己慢慢養成了每次去吃飯前查一 下各個視窗的菜譜,再決定去哪吃飯的習慣。
不過這個功能使用的越多,越來越覺得它不方便。目前在易信公眾號查詢選單的步驟是:
開啟易信
開啟網易精靈公眾號
點選便捷服務
點選今日選單
等待返回今日選單的入口連結
點選入口連結檢視今日選單
作為固定的每天至少要操作兩次的動作,整個流程是被動的、並且有點複雜了。特別是 第五步、第六步都需要網路訪問,如果手機網路訪問不穩定(WiFi、4G訊號不好等,在 坐、等電梯時很容易碰到這個情況),其中任何一步都會卡住導致無法查詢;還有一些 同學由於種種原因根本就沒有關注網易精靈公共號,無從檢視今日選單。
所以我就想,有沒有更簡單的辦法,直接把每日選單內容直接主動推送到手機,只需要 簡單的點一下就能檢視選單了呢:
點選推送訊息
檢視今日選單
有了這個想法就動手做了。
這件事可以拆成三步:
資料抓取
資料處理
資料推送
下面詳細說。
資料抓取
要想爬菜譜的資料,首先需要知道這些資訊是從哪裡查詢出來的。我沒有做過易信公眾號 的開發,但是根據一般的經驗,不管是微信還是易信的公眾號釋出的文章一般就是一個 簡單的 HTTP 頁面。想要找出每日選單資料的來源,找出這些 HTTP 頁面地址的 pattern 一般就搞定了。
抓取手機網路請求的方法很多,最方便的辦法應該是在手機後臺跑一個類似 tcpdump 功能 的工具的同時,訪問易信的今日選單,就可以抓到想要的結果。不過由於我的手機是沒有 越獄的 iOS 系統,由於沙盒機制的限制無法做到。
最後用的辦法是,在相同網路的電腦上跑一個 mitmproxy1 服務,將手機的 HTTP Proxy 地址指定為電腦的地址,在易信裡開啟今日選單的連結,就可以在 mitmproxy 裡看到一串 手機的 HTTP 訪問記錄,其中就包含我們要抓取今日選單的 HTTP URL 。
可以發現今日選單的 HTTP URL 連結就是下面的 pattern:
http://numenplus.yixin.im/singleNewsWap.do?companyId=1&materialId=${id}
其中只有一個變數 ${id} ,是個正整數,應該就是文章的ID。我們要爬的每日選單內容, 都在這些連結裡面,還包含網易精靈公眾號釋出的其他一些廣告文章,這個頁面就是簡單 的 HTTP GET 就可以爬到內容,不需要做額外的處理。
自己研究了一下沒有找出今日選單的文章的 ID 生成的規律,推測應該是在易信後端生成 的,手機客戶端無法直接拿到這個 ID。於是乾脆就每個ID都爬一遍,檢查內容是今日選單 的文章就處理,不是就忽略。這樣就搞定今日選單的資料來源了。
比較熟悉 Python,就用 Python 實現了:
import requestsdef http_get(url, timeout=3): try: res = requests.get(url, timeout) except: LOG.exception("Failed to GET: %s" % url) else: if res.status_code != 200: return None else: return resdef fetch(start, step=300): last_id = start for i in iter(range(start, start + step)): url = ("http://numenplus.yixin.im/singleNewsWap.do?" "companyId=1&materialId=%d" % i) response = http_get(url) if not response: continue # handle menu data here
資料處理
資料處理,主要有兩個任務:
上面也提到了,需要檢查爬取的文章內容是不是今日選單
解析 HTML 內容,獲得我們想要的選單資訊
第一個問題比較簡單,我們可以直接通過簡單的關鍵詞正則表示式匹配來檢查。比如文章 內容含有“今日選單”這四個字,我們就認為這篇文章內容是今日選單。
第二個問題稍微複雜一些,我們需要從爬取的 HTML 源資料提取出其中的文字資料,然後 從中生成這份選單的日期、早餐、午餐、晚餐資訊。這個用稍微複雜一點的正則表示式也 可以搞定。
Python 中有一個比較有名的處理 HTML 格式內容的第三方庫 BeautifulSoup , 使用非常方便:
獲取選單正文內容
def _parse(self, content): try: bs = BS(content, "html.parser") if bs.find_all(class_="m-error"): return None else: return bs except: LOG.exception("Failed to Parse content: %s" % content)def _handle_menu(bs): try: content = bs.find(id="divCNT") except: LOG.warn("Failed to get content") return None else: return content
HTML 解析前是這個樣子
解析後就是這個樣子,已經把 HTML 的 tag 都脫掉了
判斷是否是今日選單
def _is_menu(text): # \u4eca\u65e5\u83dc\u5355 => 今日選單 if re.findall(ur"\u4eca\u65e5\u83dc\u5355", text, re.UNICODE): return True else: return False
提取選單日期
def _handle_date(content): # \u6708 => 月 \u65e5 => 日 res = re.findall(ur"(\d+)\u6708(\d+)\u65e5", content.text, re.UNICODE) if not res: LOG.warn("Failed to parse date") return None else: month, day = tuple([int(i) for i in res[0]]) year = datetime.datetime.now().year return datetime.datetime(year, month, day)
提取選單早餐、午餐、晚餐內容
def _menu_to_text(content): # \u65e9\u9910 => 早餐 # \u4e2d\u9910 => 中餐 # \u665a\u9910 => 晚餐 # \u591c\u5bb5 => 夜宵 text = content.get_text() res = re.findall(ur"\u65e9\u9910([\s\S]+)\u4e2d\u9910([\s\S]+)" ur"\u665a\u9910([\s\S]+)\u591c\u5bb5", text, re.UNICODE | re.MULTILINE) if not res: LOG.warn("Failed to match menu") return None else: menu = {} menu[BREAKFAST] = res[0][0] menu[LUNCH] = res[0][1] menu[SUPPER] = res[0][2] return menu
資料推送
現在已經解決了今日選單的資料爬取、處理,就差如何把選單內容推送到手機了。
經過調研,iOS 平臺上比較好用的第三方訊息推送服務有 Pushover、Pushbullet、 Boxcar、Amazon SNS 等。
Amazon SNS 沒有提供現成的客戶端首先否決掉;Pushover 綜合看起來是最好的選擇, 不過每個手機客戶端使用需要付 5$ 的一次性授權費用,窮,也否決掉;綜合看起來, Pushbullet 功能較全、免費、文件清晰、全平臺支援,最後選擇 Pushbullet 推送訊息。
按照 Pushbullet 提供的 API 文件寫一個 HTTP POST 請求就可以實現推送功能了:
def send_notification(subject, content, channel=PUSHBULLET_CHANNEL): try: res = requests.post( "%s/pushes" % PUSHBULLET_API, headers={"Access-Token": PUSHBULLET_TOKEN}, data={"title": subject, "body": content, "type": "note", "channel_tag": channel}, timeout=30) except: LOG.exception("Failed to send notification") else: if res.status_code != 200: LOG.warn("Error when pushing notification")
推送過來的選單就是這樣了:
PC/Mac 端同樣支援:
把上面這些程式碼片段拼起來,就是一個可以抓取、推送今日選單的小專案了,最後能跑的 程式碼放在這裡(程式碼裡還包含之前寫的把選單內容發郵件通知的功能):
https://g.hz.netease.com/hzzhanggy/what2eat2day_ntes
自動化
整個資料爬取、推送的流程都寫好了,最後剩下的需要做的事情就是讓整個流程自動化 執行,我們只需要每天飯點看手機推送訊息就可以了。
其實就是將資料爬取、推送做成定時任務就可以了。我通過 systemd timer 實現:
在 virtualenv 中執行指令碼的 wrapper run.sh
#!/bin/bashBASE=/home/stanzgy/workspace/what2eat2day_ntes$BASE/.venv/bin/python $BASE/fetch.py [email protected]
今日選單抓取 service 檔案 menu_fetch.service
[Unit]Description=Fetch NetEase menu today[Service]Type=oneshotExecStart=/home/stanzgy/workspace/what2eat2day_ntes/run.sh f[Install]WantedBy=multi-user.target
今日選單抓取 timer 檔案 menu_fetch.timer
[Unit]Description=Fetch NetEase menu everyday[Timer]OnCalendar=Mon-Fri *-*-* 10:00:00Unit=menu_fetch.service[Install]WantedBy=multi-user.target
推送今日選單的 timer 配置類似上面,僅僅是命令列傳入的引數不一樣,這裡就省略了。 最後效果如下
更多網易技術、產品、運營經驗分享請點選。
相關文章:
【推薦】 BRVAH(讓RecyclerView變得更高效) (2)
【推薦】 GitLab 自動觸發 Jenkins 構建