目錄:
(一) 目的
自動檢查 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');
大概就這樣。