September 27, 2015

自動檢查 PTT 某文章是否有新推文,並寄信通知使用者


目錄


(一) 目的

自動檢查 PTT 某文章是否有新推文,並寄信通知使用者。


(二) 架構

很醜的原始碼請參考 github

使用者可透過網頁介面(index.php,index.js,index.css) 註冊感興趣的 PTT 文章(e.g. https://www.ptt.cc/bbs/Gossiping/M.1443172611.A.FA7.html),填入自己的 e-mail,按下確定後,透過 AJAX 將資料丟給後方(response.php)檢查、抓取文章 meta 等資料後存入資料庫。

在 Unix 機器設定 cron 去跑 cron.php,如果發現訂閱的文章有新的推文,就自動寄信通知使用者。

大概是下面這個樣子:

原本想拿來練習 Angular.js,但最後還是用 jQuery。有蠻多地方可以改進,code 很醜,不必要的 Ajax ,但目前已能符合我自己的使用需求,暫時不修改。


(三) 警告:您即將進入之看板內容需滿十八歲方可瀏覽

當欲進入 八卦版 或 西斯板 等 PTT 看板時,會先被轉到上方畫面。

此時用 php curl 會抓不到東西,

curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);

配上 curl_setopt($ch, CURLOPT_COOKIE, 'over18=1');

就可以繞過這個確認機制,直接抓取頁面。


(四) 解法

小魯我對這方面概念其實沒有很熟,所以紀錄一下解題過程。

當發現 php curl 抓不到東西後,首先先多次交叉測試,發現只有八卦版與西斯版有上述情形。

用 firefox developer tool 觀察後會發現,這是因為網頁版 www.ptt.cc 處理八卦版的文章時,會用 HTTP 302 轉址。

舉例來說,欲進入 https://www.ptt.cc/bbs/Gossiping/M.1443066563.A.CB1.html

會被轉址到 https://www.ptt.cc/ask/over18?from=%2Fbbs%2FGossiping%2FM.1443066563.A.CB1.html

於是根據這篇文章 http://evertpot.com/curl-redirect-requestbody/,使用

curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);

就可以正確用 php curl 抓出確認 18 歲的那個畫面。

觀察 18 歲那個畫面的 HTML 之後,發現其用 html form 的 HTTP POST,action 是自己本身,參數是 'yes'='yes''from'='%2Fbbs%2FGossiping%2FM.1443066563.A.CB1.html'

再度用 developer tool 的 Network 觀察,按下確定 18 歲的按鈕後,其 HTTP Request headers from upload stream 也的確只包含這兩個參數,同時 https://www.ptt.cc/ask/over18,會回覆一個 HTTP 302,將

https://www.ptt.cc/ask/over18?from=%2Fbbs%2FGossiping%2FM.1443066563.A.CB1.html

轉址到

https://www.ptt.cc/bbs/Gossiping/M.1443066563.A.CB1.html

於是我寫一個 php 自動加入 yes 與 from 的參數 fields 去發 HTTP POST 給 https://www.ptt.cc/ask/over18,但是不意外的還是會跳出確認 18 歲的畫面,無法直接看到文章。

模擬 POST 的方式不成功,但是在按下「我滿 18 歲」按鈕時,一定有什麼資料被記起來或傳送過去,

不斷清除 cache,重複觀察按下 18 歲按鈕前後瀏覽器有什麼變化,

發現當按下 18 歲後,再轉址之前

https://www.ptt.cc/ask/over18

會 Response 一個 cookie 叫做 over18,值為 1

用 firefox 或 chrome 觀察這個 cookie,重複實驗之後,確認這個 cookie 就是影響是否能直接看到文章的變數。

偷懶一下,用 php 直接從我的網域用 setcookie() 將該 cookie 設定到 ptt.cc 的 domain,但果然沒有作用,應該說如果可以這樣做的話網路安全就要崩潰惹,真是太白癡了,but totally worth it。

於是改回到 curl 來,在抓文時順便連同 cookie 丟出去,參考 這篇文章

curl_setopt($ch, CURLOPT_COOKIE, 'over18=1');

大概就這樣。