1. 程式人生 > >推送公司今日菜單內容到手機

推送公司今日菜單內容到手機

com return supper nic except != type let UNC

此文已由作者張耕源授權網易雲社區發布。

歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。


自從公司的易信公眾服務號有了查詢今日菜單的功能,自己慢慢養成了每次去吃飯前查一 下各個窗口的菜譜,再決定去哪吃飯的習慣。

不過這個功能使用的越多,越來越覺得它不方便。目前在易信公眾號查詢菜單的步驟是:

  1. 打開易信

  2. 打開網易精靈公眾號

  3. 點擊便捷服務

  4. 點擊今日菜單

  5. 等待返回今日菜單的入口鏈接

  6. 點擊入口鏈接查看今日菜單

作為固定的每天至少要操作兩次的動作,整個流程是被動的、並且有點復雜了。特別是 第五步、第六步都需要網絡訪問,如果手機網絡訪問不穩定(WiFi、4G信號不好等,在 坐、等電梯時很容易碰到這個情況),其中任何一步都會卡住導致無法查詢;還有一些 同學由於種種原因根本就沒有關註網易精靈公共號,無從查看今日菜單。

所以我就想,有沒有更簡單的辦法,直接把每日菜單內容直接主動推送到手機,只需要 簡單的點一下就能查看菜單了呢:

  1. 點擊推送消息

  2. 查看今日菜單

有了這個想法就動手做了。

這件事可以拆成三步:

  1. 數據抓取

  2. 數據處理

  3. 數據推送

下面詳細說。

數據抓取

要想爬菜譜的數據,首先需要知道這些信息是從哪裏查詢出來的。我沒有做過易信公眾號 的開發,但是根據一般的經驗,不管是微信還是易信的公眾號發布的文章一般就是一個 簡單的 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

數據處理

數據處理,主要有兩個任務:

  1. 上面也提到了,需要檢查爬取的文章內容是不是今日菜單

  2. 解析 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 $@

今日菜單抓取 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 配置類似上面,僅僅是命令行傳入的參數不一樣,這裏就省略了。 最後效果如下

技術分享圖片

免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點擊。

相關文章:
【推薦】 手把手帶你打造一個 Android 熱修復框架
【推薦】 git 常用命令
【推薦】 從golang的垃圾回收說起(上篇)

推送公司今日菜單內容到手機