欣迪

上一篇文章 Side Project – beautyptt.cc 表特版看圖工具 提到文章按讚數破百來做一個製作說明。結果短短幾天就破了 300,可見這看圖工具在工程師圈的迴響還是不錯的,今天就來回應自己許下的承諾發個技術文吧。

還沒看過作品點這裡 beautyptt.cc

簡單的說,就是爬蟲不斷的定時去爬資料寫到資料庫,還有一些繁瑣的細節設定。

1. PHP 爬蟲外掛 – QueryList

身為一個懶散的自由工作者,有套件就絕對不自己造輪子。使用的第一個工具是強國開發的 QueryList 。這個套件的強大之處在於,它可以使用類似 css selector 的方式選擇要爬的頁面,而且在連線的同時可以保持登入的 cookie。

那該爬哪裡呢? PTT 表特版的網頁版本 :

https://www.ptt.cc/bbs/Beauty/index.html

由於現在表特版已經變成 18 禁版,所以只要沒有已滿 18 歲的 cookie 點擊紀錄就會無法順利的爬文,所以第一件事是必須取得一個有 18 禁的 cookie 連線。方法如下:

use QL\QueryList; 

// 想要爬的頁面 URL
$url = 'https://www.ptt.cc/bbs/Beauty/index.html';

// 因為它網址產生的 redirect 是相對路徑所以把前面的 https://www.ptt.cc 拿掉
$url = str_replace("https://www.ptt.cc", "", $url);

//開始一個新的爬蟲
$_ql = QueryList::getInstance();

//是否已滿 18 歲的問答網址
$_18_plus_url = "https://www.ptt.cc/ask/over18";

//使用 post 的方法把重新導向的 URL 放進去,"yes" => "yes" 就是已滿 18 歲的回答。

$_ql->post($_18_plus_url,[
'from' => $url,
'yes' => 'yes'
],[
'timeout' => 30,
'headers' => [
'Accept' => 'application/json',
]
]);

//用這個 $_ql 可以繼續爬文章

$_html = $_ql->getHtml();
$top_bar = $_ql->html($_html)->find('.action-bar .btn-group.btn-group-paging')->html();
$htmls = $_ql->html($_html)->find('.r-ent')->htmls();

就不說太多了,剩下就是依照需求去做爬文章和寫入資料庫的功能。

2. Imgur API

API 位置 : https://apidocs.imgur.com/?version=latest#2078c7e0-c2b8-4bc8-a646-6e544b087d0f

申請方法: https://letswrite.tw/imgur-api-upload-load/

測試了一段時間,發現表特版的文章圖片幾乎都是貼在 imgur 上面,無論是動圖 (gif) 或是一般常見的 .jpg 、 .png ,它的網址常常是 https://imgur.com/ZNH7I9T ,無法從圖片位置判斷檔案格式,而且我也不想整張圖複製到我的主機,這樣太傷流量。所以我申請了一個 Imgur 的帳號,來解析圖片內容。

我先寫了一個 function 來判斷是不是 imgur 的圖片。

如果是 imgur 的圖片就回傳它的 ID,不是則回傳空白字串。

imgur 連結的開頭有這幾種可能: “http://imgur.com/”,”https://imgur.com/”,”http://i.imgur.com/”,”https://i.imgur.com/”,”//imgur.com/”,”//i.imgur.com/”

function is_imgurLink($url){
   if(!$url){
      return "";
   }
   
   if(beauty_functions::is_img($url)){
      return "";
   }

   $check_strings = ["http://imgur.com/","https://imgur.com/","http://i.imgur.com/","https://i.imgur.com/","//imgur.com/","//i.imgur.com/"];

   foreach ($check_strings as $key => $check_str) {
      $check_str_len = strlen( $check_str ); 
      if(substr( $url , 0, $check_str_len) ==  $check_str ){
         return $id = substr($url, $check_str_len);
      }
   }

   return "";

}

確定是 imgur 的圖片後就用 API 來取得更詳細的資料, function 如下:

$Client_id 帶入申請的 Imgur client id
$imgur_id 帶入網址 route 的 id

function getimgurImgInfo($Client_id ="",$imgur_id=""){
   if(!$Client_id || !$imgur_id){
      return "";
   }
   $parts = explode("/", $imgur_id);
   
   if(count($parts) === 1){
      $imgur_id = $parts[0];
      $api = "https://api.imgur.com/3/image/{$imgur_id}";
   }else{
      $type = $parts[0];
      $imgur_id = $parts[1];
      switch ($type) {
         case 'a':
            $api = "https://api.imgur.com/3/album/{$imgur_id}";
            break;
         case 'gallery':
            $api = "https://api.imgur.com/3/gallery/album/{$imgur_id}";
         default:
            # code...
            break;
      }
   }
   // echo $api;
   // $gallery_pic_api = "https://api.imgur.com/3/gallery/{$imgur_id}";
   // $album_pic_api = "https://api.imgur.com/3/album/{$imgur_id}";
   $curl = curl_init();      
   curl_setopt_array($curl, array(
     CURLOPT_URL => $api,
     CURLOPT_RETURNTRANSFER => true,
     CURLOPT_ENCODING => "",
     CURLOPT_MAXREDIRS => 10,
     CURLOPT_TIMEOUT => 0,
     CURLOPT_FOLLOWLOCATION => false,
     CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
     CURLOPT_CUSTOMREQUEST => "GET",
     CURLOPT_HTTPHEADER => array(
       "Authorization: Client-ID $Client_id"
     ),
   ));

   $response = curl_exec($curl);
   $err = curl_error($curl);

   curl_close($curl);

   if ($err) {
     return "";
   } else {
     $output = json_decode($response,true);

      // var_dump($output);
     return $output;
   } 
} 

回傳的 json 結果如下:

{
"data": {
"id": "orunSTu",
"title": null,
"description": null,
"datetime": 1495556889,
"type": "image/gif",
"animated": false,
"width": 1,
"height": 1,
"size": 42,
"views": 0,
"bandwidth": 0,
"vote": null,
"favorite": false,
"nsfw": null,
"section": null,
"account_url": null,
"account_id": 0,
"is_ad": false,
"in_most_viral": false,
"tags": [],
"ad_type": 0,
"ad_url": "",
"in_gallery": false,
"deletehash": "x70po4w7BVvSUzZ",
"name": "",
"link": "http://i.imgur.com/orunSTu.gif"
},
"success": true,
"status": 200
}


這樣就可以從 “link” ,來取得圖片的真實資訊。接來我就設定 cronjob 來定時爬文。

這邊一點要注意 imgur 的 API 使用次數限制,上傳圖片 12,50 次 / 日,讀取圖片資訊 12,500 次 / 日。其實正常使用是綽綽有餘的。

一開始為了求快,我設定每 1 分鐘爬一篇文章去追朔以前的貼文,一頁一頁網舊的庫存頁面去爬。結果很快就把單日流量用完。

接下來我開始去看表特版以前庫存的文章,發現大概 2014 年左右圖片都已經失效的差不多了,所以就設定爬蟲抓取到 2014 年的內文就不再繼續往前爬。

新的發文則是每隔 5 分鐘左右到表特版首頁檢查文章的更新和貼文數量。

接下來是一些細節

1. 爬文時設定了文章標題有 [公告]、教學、本文已被刪除 這幾個關鍵字會直接跳過。

2.每隔一段時間自動檢查文章是否被刪除,以免有些人不想上表特或各種原因刪文時在這邊發現庫存頁面。

3.推文數量是負數時,不在前台顯示。

Show 一下目前的收錄內容統計

一周的時間,文章比上次又增加了 100 篇左右,圖片多了快 1000 張。

ReiKuromiya 大大真的很神, 2014 年後的表特文章占比將近 1/3。

小結

呼~ 技術文章反覆檢查真的好吃力。

下一篇解說完這個 side project 的前端,就要來好好增加一下幹話文的數量,好好的暖身來參加朋友舉辦的活動 工程師大腸花 !

畢竟從今年二月開始,我就已經沒有再講過幹話了。

訂閱 IT-Monk

訂閱最新文章的發布消息! 😚😚😚
Loading

作者介紹 - 欣迪

欣迪

從設計到寫程式,發現自己有追求前端技巧的自虐傾向。不斷的踩坑,再從坑裡爬出來,慢慢對攀岩有點心得。 目前在多間公司擔任網站設計顧問。 同時也是網站架設公司負責人。

Comments are closed.