2017年1月9日 星期一

好書 : R語言 : 數學計算、統計模型與金融實務分析應用

這本書是我在母校高應大圖書館找到的, 塊頭雖不大, 但內容卻相當豐富, 特別是金融資料分析的介紹 (但只談到 Yahoo, Google Finance 資料匯入與分析, 能匯入任何指定網址的資料來源嗎?). R 語言功能與風格跟 MATLAB 極為相似, 卻比 MATLAB 更受歡迎. 因目前無暇閱讀測試, 在此先做個紀錄 :


開發 R 語言企業級應用的 Revolution Analytics 公司已經被微軟買下 (但開源計畫據說不受影響), 其統計分析功能被整合到 SQL Server 2016 R Services 中, 甚至 Visual Studio 也已支援 R 語言, 可見微軟相當看好 R 的發展, 參考 :

# Microsoft R Server
# 微軟開源布局再下一成,買下大資料技術R語言工具龍頭Revolution Analytics
# 微軟收購大數據分析公司Revolution Analytics
# 微軟Visual Studio支援R語言!變身R語言開發工具功能直逼 RStudio

2017年1月7日 星期六

如何比對 PHP 陣列的元素

最近在寫 PHP 專案時要用到陣列元素比對的功能, 因為從來沒用過, 就上網搜尋 "PHP 陣列 比對", 結果發現 PHP 有兩個內建的函數 array_intersect() 與 array_diff() 剛好符合我的要求, 參考 :

# php計算兩個陣列的交集和差集

這實在是太棒了! PHP 會這麼受歡迎不是沒道理的.

首先就來測試看看 array_intersect() 函數, 此函數可以傳遞多個參數進去, 每一個都是陣列, 它會將第一個陣列的元素同時也出現在其他陣列者以陣列形式傳回, 換句話說就是傳回第一陣列與其他陣列的交集, 並保留交集元素在第一個陣列內的索引 (index) 或鍵 (key). 參考 :

# PHP Manual : array_intersect

例如下面的範例 :

  $a=["小新","廣治","小葵"];
  $b=["小葵","風間","小新"];
  $c=array_intersect($a, $b);
  print_r($c);

這裡有兩個陣列 $a, $b, 其元素為蠟筆小新中人物的名字, 傳入 array_intersect() 當參數會傳回 $a, $b 元素的交集陣列 $c. 將上面程式碼貼到 TutorialsPoint 網站執行 :

# https://www.tutorialspoint.com/execute_php_online.php

按行號上面的 "Execute" 會在下方命令列看到 $c 陣列的內容 :

Array                                                                                                                                                                
(                                                                                                                                                                    
    [0] => 小新                                                                                                                                                        
    [2] => 小葵                                                                                                                                                        
)     

注意, 交集陣列還保留了這兩個交集元素在第一個陣列 $a 中的鍵值 0 與 2.

對於字串鍵值也是一樣, 如下面範例所示 :

  $a=Array("木瓜", "apple" => "蘋果", "grape" => "葡萄");
  $b=Array("banana" => "香蕉", "木瓜", "apple" => "蘋果");
  $c=array_intersect($a, $b);
  print_r($c);

執行結果如下 :

Array                                                                                                                                                               
(                                                                                                                                                                   
    [0] => 木瓜                                                                                                                                                       
    [apple] => 蘋果                                                                                                                                                   
)  

陣列 $a 元素同時也出現在陣列 $b 的就只有 "木瓜", "蘋果" 這兩個元素, 而且他們在陣列 $a 中的索引與鍵值都有保留.

接下來看 array_diff(), 此函數同樣要傳入多個陣列, 它會傳回第一個陣列中, 沒有出現在其他陣列之元素, 意即傳回第一陣列與其他陣列的差集, 傳回值也是陣列. 參考 :

# PHP Manual : array_diff

例如下面範例 :

  $a=["小新","廣治","小葵"];
  $b=["小葵","風間","小新"];
  $c=array_diff($a, $b);
  print_r($c);

執行結果如下 :

Array                                                                                                                                                               
(                                                                                                                                                                   
    [1] => 廣治                                                                                                                                                       
)     

第一個陣列 $a 的元素中只有 "廣治" 沒有出現在後續的陣列中. 同樣它保留了 "廣治" 在第一個陣列中的索引.

另一個範例是 :

  $a=Array("木瓜", "apple" => "蘋果", "grape" => "葡萄");
  $b=Array("banana" => "香蕉", "木瓜", "apple" => "蘋果");
  $c=array_diff($a, $b);
  print_r($c);

執行結果是 :

Array                                                                                                                                                               
(                                                                                                                                                                   
    [grape] => 葡萄                                                                                                                                                   
)   

即第一個陣列 $a 中只有 "葡萄" 沒有出現在後續的陣列中.

最後紀錄一下這兩個函數在金融運算上的應用, 例如要從證交所公布的法人買賣超資料找出近十日外資買超個股第一次進入排行的股票時就可利用 array_diff() 一次到位找出來. 可將近十日買超排行放在 $fini[0]~ $fini[9] 這十個陣列中 (意即 $fini 是個二維陣列), 其中 $fini[0] 為近一日排行, 而 $fini[9] 則為十天前的排行, 只要將 $fini[0] 作為第一陣列, 就能將近一日排行中出現, 但在前九日排行中未出現的標的抓出來 :

$fini_10days_first=array_diff(
  $fini[0], $fini[1], $fini[2], $fini[3], $fini[4], $fini[5], $fini[6], $fini[7], $fini[8], $fini[9]);

而要找出連續兩日買超或當日同步買超的話就要使用 array_intersect() 函數了.

2017年1月5日 星期四

撰寫 PHP 網頁爬蟲程式的注意事項

最近 PHP 專案中的網頁爬蟲程式都已寫完, 接下來要完善分析邏輯. 趁著記憶猶新, 抽點時間把撰寫網頁爬蟲該注意的一些事項, 以常用的財經網站為例紀錄如下 :

1. 確定網頁編碼格式 :

每一個網頁都要指定內容的編碼格式, 早期中文網頁都使用 big5 碼, 現在幾乎都改用 Unicode 的 utf-8 格式. 首先要檢視目標網頁 meta 標籤裡面的 charset 屬性是 big5 還是 utf-8, 這決定了 PHP 程式要存成哪一種檔案格式. 例如集保公司集保戶股權分散表查詢網頁就是用 big5 碼 :

# 集保戶股權分散表查詢


而鉅亨網類股行情表網頁則是採用 utf-8 格式 :

# 鉅亨網類股行情表


如果要抓的目標網頁是 big5 編碼, 則 PHP 爬蟲程式存檔時要用 ANSI 格式存檔, 例如抓取集保戶股權分散表的程式即是, 下面是 EditPlus 編輯器的存檔畫面 :


若目標網頁是 utf-8 編碼, 則 PHP 程式當然要以 utf-8 格式存檔 :


2. 確定取得網頁的方法為 GET 或 POST :

以程式擷取網頁內容必須根據目標網頁取得資料的方法是 GET 或 POST, 才能知道該呼叫 cURL 的 GET 函數 http_get() 還是 POST 函數 http_post_form(), 因為後端伺服器可能只針對網頁表單使用之方法建置相對應的回應處理函數, 用錯方法伺服器不會回應所要的資料.

例如鉅亨網的類股行情表網頁使用 GET 方法, 它不需要使用者做任何選擇或輸入, 只要連線網址即可獲得全部資料. 但是集保戶股權分散表網頁則是使用 POST 方法, 使用者連線到其網址時只是顯示查詢表單, 必須輸入股號或股名後, 按查詢鈕才會傳回資料. 如果程式中用 GET 方法傳遞參數給伺服器的話將無法取得資料, 仍然傳回查詢表單而已.



而鉅亨網的類股行情表不需表單查詢, 自然是使用 GET 方法.

3. 確定表單要傳遞那些參數 :

有些網頁使用 GET 方法取得, 且不需傳遞參數給伺服器, 這部分就可跳過不管. 但不論是 GET 或 POST 方法都可以傳遞表單參數, 只是 GET 所傳遞的參數會顯現在網址列上, 而 POST 則不會, 因為它是放在 HTTP 的內容中傳送.

如何得知表單向後端伺服器傳送了那些參數呢? 可以在 Firefox 瀏覽器中按 F12, 再重新載入網頁, 點選下方的 "網路", 然後點左下角的查詢網址, 點右下角的 "參數" 即可顯示所有傳遞之參數 :


注意, 最底下的 sub 參數其值為怪碼, 原因可能是 Firefox 的 F12 開發工具使用 utf-8 格式, 而目標網頁卻是 big5 編碼之故. 查看網頁原始碼可知此名為 sub 之參數為 submit 按鈕, 其值為 "查詢", 也就是上面的怪碼. 這在擷取此網頁資料時即為重要, 因為後端伺服器似乎會檢查有無傳出此參數, 且其值必須為 big5 的  "查詢".

以上三點是成功擷取網頁的關鍵. 除此之外, 取得網頁資料後要進一步剖析之前, 可能還是需要用到編碼轉換函數 iconv() 來做適度的轉換, 例如在 ANSI 編碼的 PHP 程式中若要輸出從資料庫讀取的中文資料, 必須使用 iconv() 函數轉成 big5 碼才行, 否則會輸出怪碼 :

$stock_name=iconv("UTF-8","BIG5",$RS[$i]["stock_name"]);

這是因為 MySQL 資料庫裡一般都是使用 Unicode 來儲存中文之故.

另外, 如果欲擷取的目標網頁雖然是 big5 編碼, 但只要用 GET 方法即可取得的話, 那麼 PHP 程式就不需要以 ANSI 格式存檔, 可以直接存成 utf-8 格式, 以便與 MySQL 順利接軌, 但是所取得的網頁內容必須用 iconv() 函數轉成 utf-8 格式再分析處理 :

$file=iconv("BIG5","UTF-8",$web_page['FILE']); //轉成 utf-8 格式

其實集保戶股權分布資料網路上有現成整理好的圖表, 例如 :

# 神秘金字塔

但是若要套用自己的分析邏輯的話, 必須自行記錄追蹤這些資料, 建立自己的數據庫才能辦到. 追蹤集保戶的股權分佈情形有助於了解散戶與大股東的持股動態, 可做為投資決策的參考, 特別是持有千張以上的大股東股權占比變化, 因為公司大股東最了解自家經營狀況.

集保公司的查詢網頁我從去年就測試過, 但是卻一直未能成功, 即使我使用 POST 也是無濟於事. 後來忙著玩 Arduino 就漸漸淡忘這件事了. 直到去年 11 月 17/18 去上 Python 進階班, 老師介紹如何用 Python 寫爬蟲程式, 又勾起了回憶, 於是改用 Python 重新嘗試擷取集保網頁, 但仍然功敗垂成. 我請老師幫我看看到底哪裡出問題, 也是束手無策.

受訓回來後經多方嘗試, 終於搞定這網頁, 原因就出在上面所述的編碼方式與存檔格式問題上, 這也影響到所傳遞的參數值能否通過伺服器檢查, 這些條件都滿足了才會傳回我們所期望的回應.

最後, 擷取集保網頁還有一個問題 (也是最重要的問題), 就是在 POST 表單時須傳送欲擷取之日期 (格式例如 20161230), 但這些日期是放在查詢網頁的下拉式選單中以倒序排列, 集保公司大約每周會更新資料. 解決辦法是在資料庫中設置一個 last_tdcc_date 欄位來儲存最近的更新日期, 先存入一個初始日期, 然後在每次擷取時剖析這個下拉式選單的 option 部分, 其第一筆資料便是最近的統計日期, 在擷取結束時更新這個 last_tdcc_date 欄位即可, 下一次擷取時就會抓最新日期的資料了.


不過處理這部分遇到一個小麻煩, 如上圖所示, 第一個 option (最近統計日期) 預設為 selected, 而且後面有一個跳行, 使用傳統的消除 2 個以上空白的方式無法去除這個跳行, 必須減為去除一個以上空白才行 :

  $start='<select size="1" name="SCA_DATE">';
  $end="</select>";
  $options=return_between($file, $start, $end, EXCL);
  $options=preg_replace("/[\s]{1,}/","",$options); //去除 1 個以上空白

這樣就完美地解決這問題了.

2017年1月2日 星期一

2016 年第 51 周記事

本周末 12/31(六) 正式告別 2016 年, 週日 1/1 則迎接 2017 的到來. 咦? 一年不是 52 周嗎? 怎麼最後一周是第 51 周呢? 不知是哪裡算錯了. 不管了, 過去已不可得, 要向前看.

2017 的第一天, 我帶久未回來高雄的阿英表妹母女去爬旗靈縱走, 花了 8 個小時走完全程, 她們自己也非常驚訝竟然能連續爬 5 座山! 連爬山好手的左營阿姨也說讚! 但是今天早上起來兩腿卻酸得不得了.

爸說頂樓的太陽能熱水器漏水範圍越來越大, 維修可能要花不少錢, 乾脆換成電熱水器好了. 最近要找時間來調查一番.

2017年1月1日 星期日

挑戰旗靈縱走

幾天前阿英在 Line 上 PO 給我一張關於旗尾山的資訊, 我知道她對登山有興趣, 就說不然元旦來爬看看, 她果真訂了車票帶雅晴回來, 說也好久沒回來看舅舅了.

其實我也是今年暑假時老張帶他兒子來邀我去爬靈山步道, 但是我們沒有要走全程, 只走到快到福美山時便折返, 老張那時說找個時間去走全程. 結果我隨便跟阿英說說變成行了, 到讓我有點恐慌, 因為我沒爬過卻要當領隊了.

最麻煩的是接駁問題, 因為從旗尾山走到靈山終點站時沒人在我們回旗山糖廠, 因為車子放在那邊. 我搜尋網路發現有人提供了一個妙招, 以最多七人為例, 開一大 (7 人休旅) 一小 兩台車, 先全部到旗山糖廠下車, 然後兩個司機將車開到目的地靈山雷音寺山腳下, 將 7 人休旅放在那裏, 開小車回糖廠開始登山, 到達目的地後搭放在那邊的 7 人休旅回糖廠, 真是妙計. 我們今天就是這麼做.

9 點開始登山, 第一座是旗尾南山, 這一段路還算好爬, 但越爬發現越來越難爬, 不僅很陡, 而且大都是走稜線, 都要拉繩索. 菁菁原先叫說爬不動, 但後來卻一馬當先, 與水某兩人跟著一隊五人軍人登山隊, 五小時半就到達終點站, 我則為了照顧落隊的阿英語雅晴, 走走停停, 到五點才到達終點, 花了整整八小時, 累!

2016年12月31日 星期六

如何剖析不標準的網頁

最近在寫 PHP 爬蟲程式時遇到一個比較特殊的網頁, 需要費一點功夫來處理, 雖然也不算太麻煩, 但因為值得以後處理類似網頁時參考, 於是就抽出一點時間記錄一下. 這網頁就是鉅亨網的類股行情表 :

# 鉅亨網類股行情表

此網頁將集中市場每日盤後各產業分類的成交量, 比重, 以及指數漲跌幅等資料做成一個報表, 且可查詢近一周的資訊. 紀錄各分類所佔比重變化可以知道資金往哪個產業移動, 若比重逐漸增加, 表示該產業逐漸成長增溫, 資金流向此產業將推升股價, 特別是該產業的龍頭股; 反之則是該產業逐漸降溫, 有可能走入景氣循環的下坡, 市場籌碼漸漸抽出. 另外從台股產業龍頭的電子股與金融股也可以看出市場交易的冷熱情形, 電子類股占比若低於 50% 表示市場過冷; 而金融類股占比若超過 10% 表示市場過熱.

檢視此網頁的原始碼可知, 此網頁的類股資料放在 <!-- 資料區塊:start --> 與 <!-- 資料區塊:end --> 之間的 <div> 區塊裡, 裡面含有三個表格, 而我們需要的類股資料放在第三個表格中的 tbody 元素裡面, 因此我會以 thead 末尾的幾個欄位標題當作開頭標誌, 以 </tbody> 當作結束標誌, 這樣就能輕易抓出 tbody 內部的一堆 tr 與 td 所構成的儲存格內容. 擷取網頁的程式碼如下 :

//===擷取目標網頁 : 鉅亨網類股行情表===
$target="http://www.cnyes.com/twstock/a_price4.aspx";
echo "<br>擷取鉅亨網類股行情報表 : <a href='$target' target='_blank'>原始網頁</a><br>";
$ref=$target;
$web_page=http_get($target,$ref); //下載網頁檔
$file=$web_page['FILE'];
$file=preg_replace("/([\s]{2,})/","",$file); //先去除多餘空白以利尋標

這裡我們用 preg_replace() 函數將原始網頁中的多餘空白去除, 包含跳行字元, 因此取得的原始碼字元串會變得很緊緻, 有利於後續剖析. 接下來要抓出 tbody 內的儲存格資料, 程式碼如下 :

//擷取表格內容 : 最後一個 table
$start='市值(億)</th><th>增減</th></tr></thead><tbody>';
$end="</tbody>";
$data=return_between($file, $start, $end, EXCL);

這裡 $start 就是該表格的 thead 最後面的幾個欄位直到 <tbody>, 具有唯一性 (才不會找錯資料), 呼叫 return_between() 函數後會傳回開頭與結尾中間的儲存格內容, 包含 tr 與 td 元素. 不過這些元素含有樣式屬性與元素 (font), 必須去除才好處理, 程式碼如下 :

//純化表格:去除屬性與不需要的元素
$data=str_replace(' bgcolor="#FFFFFF"', "", $data);; //去除 <tr> 屬性
$data=str_replace(' nowrap align="center"', "", $data);; //去除 <td> 屬性
$data=str_replace(' nowrap align="right"', "", $data);; //去除 <td> 屬性
$data=remove($data, "<font", ">"); //去除 font
$data=str_replace('</font>', "", $data);; //去除 font

經過這樣處理後的 tr 與 td 序列就全部乾淨了, 只含有 tr, td 以及資料. 但是仔細看這些 tr 與 td 元素發現, 這些表格元素不是標準的 HTML 格式, 例如部分 tr 為 <tr >, 裡面含有一個空格; 其次它沒有 tr 與 td 的結束標籤, 格式如下 :

<tr >
  <td>電子
  <td>324.97
  <td>64.69
  ...
<tr>
  <td>金融
  <td>37.33
  <td>7.43
  ...

缺乏結束標籤會造成剖析上的困難, 因為 parse_array() 與 remove() 函數都需要開頭與結束標籤來尋標. 解決之道是對其進行矯正, 修正為標準 HTML 格式. 程式碼如下 :

//修正 HTML 格式
$data=str_replace('<tr >', "<tr>", $data);; //矯正 <tr >
$data=str_replace('<tr><td>', "</tr><tr><td>", $data); //補上 </tr>
$data=substr($data, 5); //刪除最前面多加的 </tr>
$data .= "</tr>";
echo "<br>類股比重漲跌<br><table border=1>".$data."</table><br>";

這裡首先用 str_replace() 將 <tr > 全部改成 <tr>, 去除 tr 中的空格, 其次用 str_replace() 將 <tr><td> 全部改成 </tr><tr><td>, 亦即補上缺漏的 </tr>, 但是這樣一來最前面就會多出一個 </tr> 了, 因此須用 substr() 把前面 5 個字元刪掉. 這樣就把 $data 變成標準的 HTML 格式了.

咦, td 不是還缺結束標籤嗎? 是的, 但這可以不需要處理, 因為拆分欄位不需要再次使用 parse_array(), 只要用 <td> 當拆分符去拆解每一列, 就可以取得所需要的比重與漲幅欄位了, 程式碼如下 :

$arr=parse_array($data, "<tr", "/tr>");  //拆分每一列 (共 30 列)
for ($i=0; $i<count($arr); $i++) { //拜訪每一列 (每一支股票)
  $brr=explode("<td>", $arr[$i]); //拆分每一欄
  $sector=$brr[1];  //類別
  $ratio=$brr[3];   //比重
  $delta=$brr[7];   //漲幅
  echo "$sector : 比重=$ratio% 漲跌=$delta%<br>";
  }

這裡先拆分 tr 列, 然後在迴圈中用 explode 拆分行, 類別欄位在第 1 欄 (索引 1), 比重在第 3 欄 (索引 3), 而指數漲幅在第 7 欄 (索引 7). 咦, 第一欄不是索引 0 嗎? 沒錯, 但因為用 <td> 當分界符, 所以索引 0 是一個空值. 舉個簡單的範例來說明會比較明白 :

$str="<td>1<td>2<td>3";  //沒有結尾 </td> 的儲存格
$arr=explode("<td>",$str);  //[0]為空白, [1]為 1,...
print_r($arr);

在這個範例中, 以  <td> 拆分字串時, 第一個  <td>  前面的空值 "" 是第一個被拆除來的子字串, 它會變成索引 0, 而第一欄的資料會在索引 1, 依此類推.

網頁格式不一定是標準的 HTML, 剖析處理時要視情況個別處理, 以上只是一種特例.

2016年12月30日 星期五

買養命酒與消費高手好關鍵

昨天去巷子口幫二哥買枇杷膏時看見貨架上有擺養命酒, 就問老闆一瓶多少, 答說那是 1000 毫升的, 要 1000 元. 本想買一瓶喝看看是否冬天手腳冰冷的情況會改善. 但轉頭一想, 還是回去比個價再說.

由於年底還有一天休假必休, 所以早上就在家上網查詢, 發現東森購物最便宜的是兩瓶 1599 元, 一瓶 800 元有找, 比巷子口藥局便宜兩百塊! 因此我就買了兩份共 3189 元.

# 【養命酒】藥用養命酒1000mlX2入組



另外, 爸的退化性 (髖) 關節炎四月份時小舅媽介紹去管醫師那裏, 吃坡尿酸剛開始效果非常顯著, 但之後似乎就疲乏了. 最近電視上廣告打得兇的消費高手好關鍵, 據說對退化性關節炎有改善效果, 所以我就進行了一番調查, 才曉得原來膠原蛋白有好幾種型, 第一型就是一般我們所認知的跟防止皮膚退化有關的, 而第二型才是跟關節有關的, 參考 :

# 關節不卡卡:非變性第二型膠原蛋白(UC-II)的實證
# 見証『 UC-II® - 非變性二型膠原蛋白(Undenatured Collagen Type II) 』
# 非變性二型膠原蛋白膠囊,保留膠原蛋白完整活性 讓你步伐好輕盈

消費高手好關鍵主要含非變性二型膠原蛋白, 由雞冠萃取的坡尿酸, 以及橄欖果萃取物. 除了消費高手外, 美商健而婷 NOW 也有類似商品 :

# NOW健而婷-UCII 二型膠原蛋白 (60顆/瓶) $1350 (5 折)

不過它跟消費高手不同處在於它不含坡尿酸, 而是添加海藻鈣. 考慮了一早上, 最後還是買了消費高手好關鍵 3 入兩組共 6 瓶, 任買兩組折 1000 相當於 69 折共 9560 元.

# 【3.4倍升級】破億!愛用者好動見證★升級版★消費高手好關鍵(3盒組)


根據說明這要空腹吃, 搭配維他命 C 更好吸收. 等到貨後再帶回去鄉下給爸吃看看.

市立圖書館的管理問題

今天超不開心, 中午去河堤還書時, 志工阿姨說其中一本 C 語言的書應還光碟, 我說沒有借光碟啊! 因為我若有借光碟一定會用膠帶黏在書後以防遺失 (我是如此謹慎+龜毛的人), 但電腦註記我有借光碟, 只是光碟編號 2821 與館內實際那片 2812 不同, 這根本就是之前的志工記錯了, 第一, 我沒借記成有借, 第二, 光碟編號還記錯. 但是那個阿姨還是說這本書不能還, 要我回去找看看光碟, 這不就是要我跑兩趟嗎?

我對物品管理很有自信, 自知回去根本找不到那片光碟, 我就問那戴眼鏡的阿姨, 如果找不到光碟怎麼辦? 她說那就要買一本書來賠. 哇咧! 如果是志工打錯也要我賠? 我又不是沒經驗, 以前在左新就遇過明明剛拿去還的一本書, 三天後接到 EMAIL 通知說那本書逾期沒還. 我趕緊跑去澄清說已經還了, 但是館員與志工怎麼找也找不到, 電話查詢原藏書分館也說架上沒有, 逾期未還. 這下我真是跳到黃河也洗不清, 跟館長說你們要在櫃台架攝影機, 以免有爭議時吵不完. 如果再找不到我就要買書來賠了, 結果幾天後左新通知我, 書找到了, 因為志工把它放在櫃台底下忘記刷還了. 哇哩咧.

不只這樣, 幾年前還接過前鎮分館來電, 說我們在前鎮借了好多本書未還, 我連左新都嫌遠了, 還大老遠跑到前鎮去借? 電腦資料錯亂了張冠李戴亂栽贓, 我說這樣無憑無據對用書人非常不公平, 因為我們無法舉證, 請他們再查看看電腦哪裡出問題. 後來就沒再打來了, 我覺得至少也要打個電話道個歉吧.

為了避免這個困擾, 我決定以後非必要不借光碟, 跨館一定會有光碟就沒辦法, 只好用多重膠帶給它黏牢. 要不然圖書館志工來來去去對業務不熟悉以及少一根筋的大有人在, 隨便這樣亂一下就有我們受的了.

晚上我又再跑一趟河堤, 索性將一批最近不可能有時間看的 C 語言書全還了. 這回換一位歐吉桑, 他中午也知道這件事, 但在忙別櫃未介入, 他說光碟在館內 (也就是說我根本沒借光碟), 編號打錯, 就收書還了. 真是的, 可以一次搞定的事偏要我們跑兩趟. 要不是家裡沒地方擺書, 我寧可去書局買一本, 才不想這樣奔波去借書呢!

關於 cron jobs 執行頻率的調整問題

這兩周來已把 PHP 專案中的 cron jobs 程式大致寫完, 目前執行頻率大都是 5 分鐘, 但是手動執行時偶而會收到伺服器會回應 "Resource limit reached", 詢問線上客服, 他們回覆說是因為我的 Premium 方案可使用的 CPU 資源已超過之故, 這可能是我設定每五分鐘執行一次, 每一個 cron job 大約都在 0, 5, 10, ... 分同時執行的關係. 雖然等個幾秒鐘重試即恢復正常, 但顯然執行頻率似乎需要調整.

其實 Cron jobs 大致可分成兩類, 一種是所擷取的目標網頁是個別公司的資料, 因此需要靠指標來分批擷取, 也是這次要將擷取頻率從每 5 分鐘一次改成每 15 分鐘一次的對象; 第二種是擷取的網頁已包括全部訊息, 不必依賴指標移動來分批擷取者 (例如美元指數資料), 這種 cron job 只要半小時或一小時擷取一次即可, 我是將其分散在 0~59 分啟動執行, 例如下圖是指定在每小時的 5 分去執行一次 cron job :


而需分批執行的 cron jobs 則要依據資料多久必須全部更新一次來考量執行頻率. 若以目前擷取 976 個公司資料來計, 若要在一天內更新全部資料的話, 每小時要更新約 967/24=40 個, 每次抓 10 個的話, 每小時要抓 40/10=4 次, 亦即大概 60/4=15 分鐘抓一次即可. 在 Hostinger cPanel 的 advanced cron jobs 中有每 15 分鐘執行一次的選項 :


底下時, 日, 月, 周都用 Every ~ 即可, 如下所示 :


當然有些不需要每天更新一輪的 cron 可以設為每 30 分鐘執行一次 (通常一次 10 個), 這樣就是每兩天全部刷新一遍了, 同理可推, 每小時執行一次的話要 4 天才會抓齊全部資料; 每兩小時一次的話則要 8 天.

今天因為還剩休假一天必須休掉, 下午重新整理 cron jobs 設定, 大部分都改成 15 分鐘 run 一次, 少部分需要在 8 小時內分批抓齊資料者 (如 yahoo 的 credit, major, dealing) 仍維持每 5 分鐘跑一次. 在此將做法做個紀錄以便後續參考.

2016年12月27日 星期二

2016 年第 50 周記事

週六 12/24 參加了母校電機系 50 周年慶, 到場的同學還是我們這十個出頭的老面孔, 許多同學還是失聯中. 不過還是非常開心, 見到了三十年未謀面的老師, 大家能聚在一起吃個飯真不容易.

本周閒暇時間還是在忙 PHP 專案, 進度還算不錯, 爬蟲程式越寫越順手, 但是要在元旦前完成恐怕非常拚. 總之, 計畫只是理想, 人生中大部分的場景都是且戰且走.

週日因菁菁要求出去走走 (都是我大嘴巴沒做好計畫就隨口說說), 原本想去茂林, 後來覺得很久沒去三地門, 就臨時改去三地門, 順便測試一下剛買的 Garmin 4592R Plus 導航機. 實測結果非常滿意, 反應速度很快, 圖資也很精確.