2017年11月13日 星期一

證交所上市櫃股票列表

由於證交所網站在 2017-05-23 左右改版, 新版網頁拋棄全部資訊集中顯示在一張網頁的方式, 改採輸入股號股名查詢, 導致之前寫的爬蟲軟體無法再抓取到資料, 必須進行改版. 之前一頁式網頁股票名單就在網頁表單中, 只要單純剖析網頁即可獲得全部收盤資料, 但現在得分成兩步驟, 首先須取得最新上市櫃股票名單, 然後再一一查詢各股收盤資料. 但問題是如何得知最新的上市櫃股票名單呢?

原來證交所網站就有提供此最新名單, 參考 :

# 本國上市證券國際證券辨識號碼一覽表
# 本國上櫃證券國際證券辨識號碼一覽表

這兩張表格都只需要抓 "股票" 與 "ETF" 部分, 剖析表格中的 "有價證券代號及名稱", "上市日", "市場別", "產業別" 這四個欄位即可, 將其存入 stocks_list 資料表的 stock_id, stock_name, listed_date, market, 以及 category 等 5 個欄位中, 其中 listed_date 與 market 是此次改版要新增的欄位.


上市股票

上市 ETF

上櫃股票

上櫃 ETF


以擷取上市股票資料為例, 開頭位置在標示 "股票" 這一列; 而結束位置在 標示 "上市認購(售)權證" 字樣前面, 如下所示 :




檢視網頁原始碼可以找到具體的開始與結束位置 (保留 tr), 如下所示 :





用這兩個頭尾信標為界就可以抓到上市股票的資料了.

$start="<B> 股票 <B> </td></tr>";  //起始字串
$end="<tr><td bgcolor=#FAFAD2 colspan=7 ><B> 上市認購(售)權證 <B>";   //結束字串

抓下來之後就用迴圈一列列去拆解表格內容, 在迴圈內又將每一列的儲存格拆解成陣列, 這樣就可分離出各欄資料了. 注意, 在拆解股名與股號時要注意, 分隔兩者的是全形空白, 我是直接由網頁複製到記事本, 再複製到程式中 :

list($stock_id,$stock_name)=explode(" ", $brr[0]); //分出股號與股名

抓 ETF 方式一樣, 只要更改頭尾信標即可 :





$start="<B> ETF <B> </td></tr>";  //起始字串
$end="<tr><td bgcolor=#FAFAD2 colspan=7 ><B> 臺灣存託憑證(TDR) <B>";   //結束字串

注意, 證交所的原始網頁使用 MS950 編碼, 可從網頁的標頭中看出 :

Server: Apache-Coyote/1.1
Content-Type: text/html;charset=MS950 
Transfer-Encoding: chunked
Date: Tue, 14 Nov 2017 12:54:07 GMT

MS950 是在 BIG5 碼的基礎上進行的編碼擴充, 增加了 7 個中文字以及一些符號, 也是 Windows 繁中版預設字元集, 關於編碼參考 :



在 PHP 中 MS950 編碼可以當成 BIG5 碼來處理, 但是以前用 iconv() 將 BIG5 轉成 UTF-8 都沒問題 : 

$file=iconv("BIG5","UTF-8",$web_page['FILE']);  //MS950 轉 UTF-8 有問題

但這回用此指令轉換後, 剖析時讀取到宏碁的 "碁" 時卻停止轉換, WHY?  後來在下面這篇看到作者用 mb_convert_encoding() 函數做轉換 : 


$file=mb_convert_encoding($web_page['FILE'],"utf8","big5");

這樣就沒問題了. 如果要偵測一個字串是甚麼編碼則要用 mb_detect_encoding() 函數, 參考 :


由上市日期可以計算至今該公司已上市多久, 在選股時通常上市未滿 5 年者我都不予考慮. 從證交所網頁取得的資料經剖析後每一列會放在陣列 $brr 中, 上市日期位於第三欄, 因此索引為 2 即 $brr[2], 格式為 YYYY/MM/DD, 先用 str_replace() 將 "/" 換成 "-",  拆出年月日後呼叫 mktime() 轉成時戳, 再與目前時戳相減即得上市秒數, 最後轉換成年 :

  $listed_date=str_replace("/","-",$brr[2]);
  list($Y,$M,$D)=explode("-",$listed_date); //拆分 1987-02-21
  $listed_time=mktime(0,0,0,$M,$D,$Y); //計算 1970-01-01 以來至上市日秒數
  $listed_years=round((time()-$listed_time)/3600/24/365,1); //小數一位

另外, 漲跌家數資訊已改在下列網頁提供 :


2017年11月11日 星期六

鄉村基礎木作第七課 : 完成上蓋板與門板安裝

今天因二哥要準備功課不去上 APCS, 剛好木樂園有開, 所以早上就去學木工了. 距離上一課已超過兩個多月沒去了, 因為小狐狸們開學後我就比較忙, 而有空的周六又碰巧木工坊休息.

今天劉老師不在, 改由郭老師指導. 上回做到剩下上蓋板, 背板, 以及門板還沒裝上去, 今天就從鎖門板開始. 首先拿一個木板與門板垂直, 將蝴蝶絞鍊放在垂直角上, 上絞鍊的上端與門板內框上緣對齊; 下絞鍊的底端與門板內框下緣對齊, 然後用鉛筆在螺絲洞口畫圓作記號, 用小電鑽先鑽個小洞, 深度約螺絲的一半即可. 先給門板這側的絞鍊上螺絲, 然後拿名片厚度的卡片 2~4 張塞上下左右門縫以便有足夠寬的門縫, 然後在門框上的絞鍊螺絲洞做記號, 同樣先鑽小洞再鎖螺絲, 這樣門板便固定住了.




接著打開門板, 在外側上方兩塊木板接縫處鎖上鐵片, 這是用來給門檔磁鐵吸引用的 :




接下來是裝門檔磁鐵, 先將整個收納櫃倒過來, 在下抽屜底部畫出門檔螺絲孔位置後再鑽洞上螺絲固定 :




至此整個門板部分就完成了, 最後就是裝上背板, 然後上膠蓋上上蓋板, 讓上蓋板的溝槽將背板卡住, 左右用長 F 夾從底部起夾住上蓋板後上螺絲, 最後用小木栓上膠塞住螺絲孔, 這樣整個收納櫃就大功告成了 :





不過在上漆前必須先將外面磨光, 以免吃漆不均勻讓色澤不好看. 郭老師給我的是 1/6 吋 AA-180 的砂紙, 將其包覆磨光機底盤固定後開始打磨, 先從有小木栓的地方開始, 將凸出的部分磨掉, 其餘就是順著紋路打磨, 除了將邊角很利的地方稍微磨圓一些外, 也順便將之前用鉛筆畫的線磨掉 :




完工後時間還早, 本想直接上漆, 但因為這兩天沒太陽, 還下著毛毛雨, 濕度大漆不易乾, 所以只好下一堂課有太陽時再上漆了.

WordPress 學習筆記 (一) : 系統安裝

前陣子樓上同事阿達因為要幫教會架設網站來問我怎麼做, 我向他展示如何申請 Hostinger 免費網站, 請他自己試試看, 不過似乎一直無法得心應手, 要我幫忙處理. 但因為我很忙, 所以一直沒幫上甚麼忙. 今天讀到下列文章, 想起了架站這件事, 我想何不從最簡單的 WordPress 開始玩看看, 然後試試 Joomla 或 Drupal, 看看哪一個適合. 因為 Hostinger 免費版沒有網頁 FTP 功能, 所以就去 000a.biz 申請了一個免費虛擬主機來架站.

# Joomla!、WordPress、Drupal,三個CMS系統的比較與選擇;建立網站要用那一個比較符合我的需求?

000a.biz 的虛擬主機服務免費版提供 11GB 硬碟容量與 400GB 流量, 參考 :

# http://000a.biz/hosting.htm

免費主機申請方式很簡單, 點官網上方的 SIGNUP 連結 :




填寫想要的網域名稱, 密碼, 以及 e-mail 即可 :




然後去收信, 點擊確認信函中的超連結後即開通虛擬主機帳號了. 接下來要進入主機管理介面 VistaPanel 進行後台管理 :

# http://cpanel.000a.biz/index.php




注意, 這個 username 是 000a.biz 核准申請時給定的, 無法修改.

登入 VistaPanel 後先去 DATABASES 項下按 MySQL Databases 按鈕建立一個資料庫 :





注意資料庫名稱為 username 後面加上自訂名稱例如 wordpress. 然後按 Files 項下的 Online Files Manager 按鈕, 進入檔案上傳介面 :




進入 htdocs 子目錄, 這是放置網頁檔案的地方 :




按左上方的 "New Dir" 鈕新增目錄例如 wordpress :



點 wordpress 進入此空的子目錄, 準備上傳 WordPress 的 zip 壓縮檔. 在此之前需先到 WordPress 官網下載最新的正體中文版 zip 檔 :

# https://tw.wordpress.org/latest-zh_TW.zip

我下載的是 wordpress-4.8.2-zh_TW.zip, 大小約為 9.1MB, 符合 000a.biz 上傳檔案每個須小於 10MB 之限制. 按左上方 "Upload" 鈕上傳此 zip 檔至 wordpress 目錄下. 注意, 要用上傳頁面右邊的 Archives 來上傳 zip 檔, 它會在上傳完畢後自動解壓縮; 若使用左邊的 Files 上傳的話, 完成後還需要按 Unzip 鈕手動解壓.




注意, 使用網頁上傳檔案至 000a.biz 有容量限制, 單一檔案不可超過 10 MB, 超過的話要用 FTP 方式上傳 :

Restrictions:
  • The maximum size of one file is restricted by net2ftp to 9.54 MB and by PHP to 10 MB
  • The maximum execution time is 800 seconds
  • The FTP transfer mode (ASCII or BINARY) will be automatically determined, based on the filename extension
  • If the destination file already exists, it will be overwritten

而 wordpress 4.8.2 版大約 9.2 MB, 剛好符合網頁上傳限制之內, 只是網頁上傳速度較慢需等待 (約 5 分鐘). 完成 WordPress 上傳後會自動解壓縮, 接下來就可以進入安裝程序了. 我在上面申請的網域名稱為 hahaha.000a.biz, 而 WordPress 是放在 /wordpress 目錄下, 因此網站的位址為 hahaha.000a.biz/wordpress. 於瀏覽器輸入此網址即進入安裝頁面 :




此為安裝說明, 按 "衝吧" 進入資料庫設定頁面, 只要輸入前四個欄位即可, 按 "送出" :





按開始安裝進入管理者設定介面, 需設定網站標題, 使用者帳號密碼與郵件地址, 按 "安裝 WordPress" 即開始執行安裝程序 :




哈! 三兩下就搞定了, 按 "登入" 進入使用者登入頁面 :




輸入帳密進入後台管理頁面 : 




大功告成! WordPress 的超簡單安裝果然名不虛傳. 接下來的工作就是學習如何設定與管理 WordPress 後台啦! 登出後再瀏覽網站即可看到前台網頁長相 :;


這是預設版面, WordPress 走的是清爽的 Blog 風.

2017年11月10日 星期五

製作太陽能鳥屋 (一)

想要木作一個太陽能鳥屋已經很久了, 去年買了一個 1W 太陽能板, 也去特力屋買了幾塊松木, 但就是遲遲無法動手. 這鳥屋並不是真的要給鳥住的, 只是我進行太陽能與物聯網測試的小玩意, 屋頂用來安裝 1~3 W 的小太陽能板, 屋內則用來放置 Arduino/ESP8266/Raspberry Pi, 18650 電池組, LoRa 模組, 以及一些感測器如溫溼度, PIR, 土壤溼度偵測器等監測模組.

我收集了好幾種鳥屋款式, 下面這四張是去年逛特力屋時看到的 :







下面這張則是小舅家門口的鳥屋式信箱 :




下面這四張則是光興街日光貓餐廳門口擺放的鳥屋造型 :






不過, 這些鳥屋的屋頂角度都太陡, 太陽能板需要約 19~23.5 度的仰角才能得到最大的功率輸出. 參考 :

# 太陽能發電系統的性能的決定因素

2017年11月8日 星期三

2017 年第 44 周記事

本周六水某要上台北開會, 順便要去師大宿舍找姊姊, 菁菁也要去, 所以跟補習班請假. 而我則是要陪大帥與小慧伉儷去爬旗靈縱走, 這是我今年第四趟了. 第一次是元旦陪阿英, 第二次是年初四陪小舅媽與小阿姨, 第三次是四月時與建興等同學, 這次除仲仔, 峰仔老班底外, 還加上喜仔, 國中同學阿錦, 以及仲仔的登山社朋友們共 13 人.




這次路線與四月時一樣, 從雷音寺走到旗尾山, 不同的是這回走全程, 路線圖如下 (取自仲仔 Line 上截圖) :



根據峰仔所傳圖片, 此行耗時 5:42 分, 不過我大概還要加上 20 分鐘, 約 6 小時, 因為從旗山祠開始我就一路抽筋, 邊走邊休息, 所以沒有在第二登山口終點處跟大家合照 :




向 winjack88 購買充電器與手機充電線

因 USB 連接線可用的越來越少需添購, 同時加購 Pi 3 需要的 2A 電源以及手機行充等如下 (此賣家價格較優) :

# 鋁合金編織數據傳輸線充電線 安卓V8接頭 USB轉Micro $20*10=200
# 雙USB插口 通用充電器 充電座 方塊充 旅充 USB手機充電2.1A 雙插孔 $25*2=50
# USB插口DC 5V1A 恒流恒壓防爆手機充電器 $22*3=66
# 3插孔USB充電器帶LED指示燈 5V1A-3USB插口3.1A $35*3=105
# USB插口 DC 5V2A通用電源供應器 電源變壓器 $49*2=98
# 電池檢測器 電池測試器 可測1號 2號 3號 4號 5號 9V 鈕釦 電池 水銀方型電池 $60

200+50+66+105+98+60=579+60(運費)=639

自製小型抽水機 (Water pump)

今天在 Youtube 上看到兩個自製抽水機的教學, 作法與取材都相當有創意. 第一個是使用塑膠罐, 瓶蓋, 注射筒等材料製作沉水式抽水機 :

# How to make a water pump DIY. Homemade pump




下面這部影片則是使用 PVC 水管來做 :

# How to make Powerful Water Pump 12volt With 775 Motor




不過我上回買一顆小型沉水式抽水機才 300 元, 如果要自製的話光是馬達就不只 300 元了.

2017年11月7日 星期二

Hostinger crontab 的正確執行路徑

昨晚想起我的 Hostinger 主機 Domain name 快到期了, 有來信詢問是否要續約, 因為租虛擬主機有贈送第一年免費, 第二年開始收費, 兩年 8.49 英鎊, 以匯率 39.6 計算, 折合台幣約 336 元 :




我在今年 2 月初因為試著將專案移到樹莓派上, 看看能否順利執行, 結果可以, 但也因此回到樹莓派與 Python 的研究上, 但不久又轉向到 MicroPython on ESP8266 上. 雖然發現 Hostinger 上跑的 crontab 有異常, 但實在無暇檢查到底哪裡出錯. 既然來到 Hostinger 後台, 那就順便檢查看看. 打開 cron job 執行報告, 發現是匯入函式庫失敗 :


PHP Warning:  include(../db.php): failed to open stream: No such file or directory in /home/u865685xxx/public_html/cron/fetch_taifex_options_pcr.php on line 141

檢查匯入函式庫原始碼如下 :

//載入 webbot 函式庫
include_once("../db.php");           //匯入資料庫設定檔 (必須)
include_once("../lib/mysqli.php");   //匯入資料庫模組   (必須)
include_once("../lib/parse.php");    //匯入剖析模組     (必須)
include_once("../lib/http.php");     //匯入http模組     (必須)

這是因為 cron job 程式放在根目錄的 cron 子目錄下, 而資料庫設定檔放在根目錄下, 因此要用 ".." 跳回上一層; 而函式庫放在根目錄的 lib 子目錄下, 所以先跳回上一層再往下走到 lib. 這個寫法在瀏覽器測試 OK, 但在 cron job 執行卻不行. 我在下面兩篇中看到, 應該要用 $_SERVER['DOCUMENT_ROOT'] 取得根目錄路徑才能正確匯入函式庫 :

# PHP absolute path to root
# PHP Warning: include_once() Failed opening '' for inclusion

所以我將程式碼改成如下 :

$root=$_SERVER['DOCUMENT_ROOT'];        //$root="/home/u865685122/public_html"
include_once($root."/db.php");          //匯入資料庫設定檔 (必須)
include_once($root."/lib/mysqli.php");  //匯入資料庫模組   (必須)
include_once($root."/lib/parse.php");   //匯入剖析模組     (必須)
include_once($root."/lib/http.php");    //匯入http模組     (必須)

但結果還是一樣, 用瀏覽器測試 OK, 但 cron job 還是不行.

後來我用線上客服的 Hecko 先生, 他說應該用明確的根目錄路徑, 可在 "HOSTING/HOME ROOT" 中查得 :




"No, that would be /home/u865685xxx/public_html/db.php, It must start with a "/" to show that it's a complete path"




改為如下寫法後果然已可正常運作 :

/* 載入 webbot 函式庫 */
$root="/home/u865685xxx/public_html";
include_once($root."/db.php");          //匯入資料庫設定檔 (必須)
include_once($root."/lib/mysqli.php");  //匯入資料庫模組   (必須)
include_once($root."/lib/parse.php");   //匯入剖析模組     (必須)
include_once($root."/lib/http.php");    //匯入http模組     (必須)

參考 :

# 關於 cron jobs 執行頻率的調整問題
# cron jobs crontab 排程教學

2017年11月6日 星期一

購買電腦椅彈力帶

小狐狸房間重新裝潢時買床墊同時也跟家具行買了三張彈性繩椅面的電腦椅. 放在高雄的兩張其中一個因為常坐, 彈力繩已經疲乏了, 原本要買新椅, 但在露天找到有販售彈力帶的賣家, 決定買來自行更換, 要不然現在電腦椅一張都 1000 元以上, 還真不便宜 :

# 正專利椅帶字號M240912電腦椅帶辦公椅帶彈性繩彈力帶 $40

椅面全換需 11 條, 11*40+80(運費)=520 元. 

2017-11-08 :

今日下班時收到貨, 吃過飯後著手安裝, 發現彈力超強, 鎖一邊後要拉到另一邊非常費勁, 就算兩個人一起拉也弄得汗流浹背, 手非常痠. 

Arduino 超音波模組測試 (二) : 使用 NewPing 函式庫

這幾天在翻閱楊佩璐寫的 "跨入Maker物聯網時代 : 誰都可以用Arduino" 一書時, 在 12.2 節看到其超音波測試範例使用 NewPing 函式庫, 記得去年大概這個時候我也做過 Arduino 的超音波測試, 當時是依據聲波原理使用 pulseIn() 等函式土法煉鋼計算偵測的距離, 參考 :

# Arduino 超音波模組測試

今天換個口味, 改用 NewPing 函式庫重新測試超音波模組 HC-SR04, 這是作者 Tim Eckel
因為對傳統的作法不滿意而撰寫的改進版, 參考 :

# http://playground.arduino.cc/Code/NewPing

NewPing 函式庫下載網址 :

# https://bitbucket.org/teckel12/arduino-new-ping/downloads/

解壓縮後整個目錄複製到 Arduino IDE 安裝目錄的 libraries 目錄下, 重啟 IDE 即可.

使用前須先建立 NewPing 物件 :

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);

三個傳入參數都是整數 , TRIGGER_PIN 是 HC-SR04 的 Trig 腳與 Arduino  相連的 DIO 腳位; ECHO_PIN 是 Echo 腳與 Arduino  相連的 DIO 腳位; MAX_DISTANCE 則是最遠的偵測距離, 一般是 HC-SR04 是 400 cm, 不過實測並沒有這麼遠, 大約 200 cm 而已.

NewPing 函式庫提供之函數如下 :

 函數 說明
 ping() 送出偵測訊號, 若收到回應傳回回應時間 (毫秒), 否則傳回 0.
 ping_in() 送出偵測訊號, 若收到回應傳回距離 (英吋), 否則傳回 0.
 ping_cm() 送出偵測訊號, 若收到回應傳回距離 (公分), 否則傳回 0.
 ping_median([iterations])  重複送出偵測訊號 (預設 5 次), 傳回回應時間之中數 (微秒).
 convert_in(echoTime) 傳入毫秒數傳回距離 (英吋)
 convert_cm(echoTime) 傳入毫秒數傳回距離 (公分)
 ping_timer(func) 送出偵測訊號, 並呼叫 func 函數以檢查收到回應.
 check_timer() 檢查是否在指定距離內收到回應.
 timer_us(freq, func) 啟動計時器每 freq 微秒呼叫 func 函數
 timer_ms(freq, func) 啟動計時器每 freq 毫秒呼叫 func 函數
 timer_stop() 停止計時器.

常用的函數是 ping_cm(), ping_median(), convert_cm() 等. 注意, 有整數傳回值者型態為 unsigned int.


測試 1 :  測試距離 (公分)

#include <NewPing.h>

#define TRIGGER_PIN  5
#define ECHO_PIN     6
#define MAX_DISTANCE 200

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);

void setup() {
  Serial.begin(9600);
  }

void loop() {
  Serial.print("Ping: ");
  Serial.print(sonar.ping_cm());  //傳回距離 (公分)
  Serial.println("cm");
  delay(500);
  }

少了計算程序真的簡單不少, 序列埠監控視窗輸出如下 :

Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 5cm
Ping: 23cm
Ping: 16cm
Ping: 13cm
Ping: 8cm
Ping: 12cm
Ping: 101cm
Ping: 113cm
Ping: 55cm

可知當偵測距離變化時, 即時的傳回距離變化大, 比較好的方法是用 ping_median() 取得多次偵測的中數 (微秒), 再呼叫 convert_cm() 轉成公分, 如下列測試 2 :


測試 2 :  測試距離 (公分, 傳回多次偵測回應時間之中數) 

#include <NewPing.h>

#define TRIGGER_PIN  5
#define ECHO_PIN     6
#define MAX_DISTANCE 200

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);

void setup() {
  Serial.begin(9600);
  }

void loop() {
  Serial.print("Ping: ");
  unsigned int us=sonar.ping_median();     //傳回 5 次偵測的回應時間中數 (微秒)
  Serial.print(sonar.convert_cm(us));         //將回應時間 (微秒) 轉成距離 (公分)
  Serial.println("cm");
  delay(500);
  }