2017年1月31日 星期二

旗靈縱走 2

今天又去爬旗靈縱走一次, 上回元旦帶的是婦孺團, 這回是老人團, 團員有小舅, 小舅媽, 以及小阿姨與我共 6 人. 爬旗靈縱走不輕鬆, 爬過一次的人都說不會再爬第二次, 這回過年小阿姨從台北回來, 要我年初三帶他們去爬, 我然說好啊, 其實一想到要花一整天來爬, 就覺得很累. 所以昨晚打去小舅家確認時, 我希望得到 : "今天有點累, 不爬了", 但小阿姨卻回答 : "好啊!". 只好硬著頭皮走第二趟.

不過第二次爬卻不會感到累, 可能是對爬到哪裡比較有概念了, 不像上回還要走多遠才會到目的地無法預期, 感到非常漫長. 此次從 08:40 開始自第一登山口爬, 15:40 到達雷音寺, 總計花了 7 小時, 比上回快了一小時. 可能是年初三準備收假的關係, 今天縱走的隊伍比較少, 大多是到旗山祠折返.

參考 :

# 挑戰旗靈縱走
旗月縱走
# 旗山→美濃@旗靈縱走過年減肥團

2017年1月29日 星期日

2017 年第 4 周記事

本周只上到周四, 下班前將辦公桌整理擦拭一番, 我喜歡開年後第一天上班時煥然一新的感覺. 週五早上去小北買五福紙貼門口與窗戶, 就算完成過年準備了. 週五回到鄉下馬上動手清掃三間浴廁, 就要準備下午祭祖, 所以車也沒洗, 書房也沒整理, 對於完美主義者的我來說實在不完美, 但能利用的閒暇時間不多, 能做到這樣算不錯啦!

每年過年行程重頭戲就是除夕下午祭祖, 晚上發紅包, 初一早上到廟裡與附近兩間土地公拜拜, 初二到小舅家吃飯, 初三到岳家吃飯, 年初四就準備收心操, 年味漸淡了. 基本上過年均不外出, 因為外頭到處是車子, 舟車勞頓人擠人, 不如待在家裡看長片舒服.

昨天與老同學信宏坐在樹下閒聊時, 看到一婦人手提禮盒走進曬穀場, 走近一看感覺似曾相識, 我想應該是隔壁張家伙房的鄰居, 寒暄幾句才搞清楚是孔學伯母嫁到桃園的女兒, 她說好幾年沒回鄉下了, 想來找母親聊聊, 我說我媽已仙遊兩年有餘了, 她聽後非常詫異, 說她都不知道, 感嘆人生無常. 以前除夕夜吃完年夜飯, 母親都會過去張家伙房串門子, 所以跟孔學伯母的女兒媳婦都很熟, 她最小的媳婦還曾熱心地要幫我搓合婚事哩.

2017年1月26日 星期四

在樹莓派上安裝 cURL 與設定 Crontab

昨天搞定布署在樹莓派上的 PHP 專案程式上傳問題後, 馬上就測試一下能否在區域網路中透過 cURL 順利執行網路爬蟲程式, 結果程式雖然有執行, 但卻沒跑出預期的結果. 我查看了 phpinfo 的設定表, 發現 PHP 預設沒有 cURL 模組, 得自行安裝.

安裝 cURL 其實只要一個指令就可以了 :

$ sudo apt-get install php5-curl  

參考 :

How do I install PHP cURL on Linux Debian?
How to enable curl, installed Ubuntu LAMP stack?
# ubuntu开启crontab日志记录及解决No MTA installed, discarding output问题

執行結果如下 :

pi@raspberrypi:~ $ sudo apt-get install php5-curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libasn1-8-heimdal libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal
  libkrb5-26-heimdal libroken18-heimdal libwind0-heimdal libxfce4ui-1-0 xfce-keyboard-shortcuts
Use 'apt-get autoremove' to remove them.
The following NEW packages will be installed:
  php5-curl
0 upgraded, 1 newly installed, 0 to remove and 17 not upgraded.
Need to get 24.5 kB of archives.
After this operation, 79.9 kB of additional disk space will be used.
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5-curl armhf 5.6.29+dfsg-0+deb8u1 [24.5 kB]
Fetched 24.5 kB in 4s (5,884 B/s)
Selecting previously unselected package php5-curl.
(Reading database ... 129241 files and directories currently installed.)
Preparing to unpack .../php5-curl_5.6.29+dfsg-0+deb8u1_armhf.deb ...
Unpacking php5-curl (5.6.29+dfsg-0+deb8u1) ...
Processing triggers for libapache2-mod-php5 (5.6.29+dfsg-0+deb8u1) ...
Setting up php5-curl (5.6.29+dfsg-0+deb8u1) ...

Creating config file /etc/php5/mods-available/curl.ini with new version
php5_invoke: Enable module curl for cli SAPI
php5_invoke: Enable module curl for apache2 SAPI
Processing triggers for libapache2-mod-php5 (5.6.29+dfsg-0+deb8u1) ...
pi@raspberrypi:/var/www/html/tony1966 $ sudo service apache2 restart
Warning: Unit file of apache2.service changed on disk, 'systemctl daemon-reload' recommended.
pi@raspberrypi:~ $

安裝好後須重啟伺服器 :

$ sudo /etc/init.d/apache2 restart
$ sudo service apache2 restart

然後再次執行 phpinfo.php 就可以看到 cURL 了 :


再次執行爬蟲程式果然 OK! 只是 fetch_twse_daily_close.php 的執行時間竟然 110 秒! 這在 Hostinger 的主機大概 60 秒內就跑完了, 當然樹莓派 B+ 跟人家刀鋒伺服器是不能比, 能跑這樣算不錯了. 我全部爬蟲程式都設 120 秒為最長執行時間, 所以程式部分還 OK, 但 Apache2 伺服器預設卻只有 30 秒 , 如下 phpinfo() 的 Core 部分之 max_execution_time  :


我用 nano 去修改 /etc/php5/apache2/php.ini 檔 "Resource Limits" 的 max_executon_time 參數設定, 由 30 提高為 180 秒 :

$ sudo nano /etc/php5/apache2/php.ini

;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;

; Maximum execution time of each script, in seconds
; http://php.net/max-execution-time
; Note: This directive is hardcoded to 0 for the CLI SAPI
max_execution_time = 30    (改成 180)

這樣就能避免程式還沒跑完就因為 timeout 被伺服器停掉了.

參考 :

# 修改PHP的執行時間上限,避免程式執行過久被終止
[php參數修改]允許PHP執行的時間變長(max_execution_time)

搞定 cURL 後, 接下來是設定 Crontab (Cron Table) 來自動執行 PHP 程式. Cron 是 UNIX 作業系統用來定時或週期性執行指定的 Shell 命令或 Script 程式一張設定表. 每一個使用者都有自己的 Crontab, 包括 root 帳號. 關於 Crontab 的格式參考 :

SCHEDULING TASKS WITH CRON
# Cron job not working in Raspberry

編輯 Crontab 指令如下 :

$ crontab -e

初次執行此指令時會出現一張選單, 要求我們指定 Crontab 的編輯器, 建議用 nano :

pi@raspberrypi:~ $ crontab -e
no crontab for pi - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest="" p="">  3. /usr/bin/vim.tiny

Choose 1-3 [2]: 2

(進入 nano 編輯畫面, 編輯完成後按 Ctrl+O 存檔, 按 Enter, 再按 Ctrl+X 退出)

crontab: installing new crontab
pi@raspberrypi:~ $


我在 Crobtab 最底下添加了三個 PHP 程式, 每 5 分鐘執行一次.

顯示 Crontab 內容使用 -l 參數 :

$ crontab -l

顯示內容如下 :

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
*/5 * * * * /var/www/html/tony1966/cron/fetch_yahoo_earning.php
*/5 * * * * /var/www/html/tony1966/cron/fetch_yahoo_major.php
*/5 * * * * /var/www/html/tony1966/cron/fetch_yahoo_usa_stocks.php

並將此三個程式加上 x 權限 (參考 : Cron not working in Raspberry)  :

$ sudo chmod +x  /var/www/html/tony1966/cron/fetch_yahoo_earning.php
$ sudo chmod +x  /var/www/html/tony1966/cron/fetch_yahoo_major.php
$ sudo chmod +x  /var/www/html/tony1966/cron/fetch_yahoo_usa_stocks.php

如果要設定 root 帳號的 Crontab (這跟預設帳號 pi 是不同的), 就在 crontab 前加 sudo :

$ sudo crontab -e

也可以在命令列直接執行 PHP 程式 :

php fetch_yahoo_usa_stocks.php

pi@raspberrypi:/var/www/html/tony1966/cron $ php fetch_yahoo_usa_stocks.php
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>擷取 Yahoo 美股收盤報告</title>
</head>
<body>
<P>擷取 Yahoo 美股收盤報告</P>
Asia/Taipei<br><a href='https://tw.finance.yahoo.com/us/worldidx.php' target='_blank'>原始網頁</a><br>交易日期=01/27/2017<br>交易日期=2017-01-27<br>道瓊工業指數收盤=20100.91<br>道瓊工業指數漲跌=▲32.40(+0.16%)<br>那斯達克指數收盤=5655.18<br>那斯達克指數漲跌=▼1.16(-0.02%)<br>史坦普指數收盤=2296.68<br>史坦普指數漲跌=▼1.69(-0.07%)<br>費城半導體指數收盤=950.32<br>費城半導體指數漲跌=▼5.74(-0.60%)<br><br>處理時間 :1 秒</body>
pi@raspberrypi:/var/www/html/tony1966/cron $

注意, 由於 PHP 程式使用了相對位置存取函式庫, 因此必須切換到 PHP 程式所在目錄執行指令, 否則會出現 "failed to open stream: No such file or directory" 錯誤訊息 :

Asia/Taipei
PHP Warning:  include(../db.php): failed to open stream: No such file or directory in /var/www/html/tony1966/cron/fetch_yahoo_usa_stocks.php on line 43
PHP Warning:  include(): Failed opening '../db.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/html/tony1966/cron/fetch_yahoo_usa_stocks.php on line 43
PHP Warning:  include(../lib/mysqli.php): failed to open stream: No such file or directory in /var/www/html/tony1966/cron/fetch_yahoo_usa_stocks.php on line 44

但是 Crontab 設定好後, 在資料庫中的 cron_log 資料表裡卻沒看到每五分鐘的執行紀錄, 只有我手動執行的紀錄 :


檢視 syslog 中關於 cron 的執行紀錄, 發現 "No MTA installed, discardi ng output" 訊息 :

pi@raspberrypi:~ $ grep CRON /var/log/syslog
Jan 27 06:30:01 raspberrypi CRON[26496]: (pi) CMD (/var/www/html/tony1966/cron/f                                                                             etch_yahoo_major.php)
Jan 27 06:30:01 raspberrypi CRON[26497]: (pi) CMD (/var/www/html/tony1966/cron/f                                                                             etch_yahoo_earning.php)
Jan 27 06:30:01 raspberrypi CRON[26500]: (pi) CMD (/var/www/html/tony1966/cron/f                                                                             etch_yahoo_usa_stocks.php)
Jan 27 06:30:01 raspberrypi CRON[26477]: (CRON) info (No MTA installed, discardi                                                                             ng output)
Jan 27 06:30:01 raspberrypi CRON[26476]: (CRON) info (No MTA installed, discardi                                                                             ng output)
Jan 27 06:30:01 raspberrypi CRON[26475]: (CRON) info (No MTA installed, discardi                                                                             ng output)

我參考了下面這篇文章 :

# “(CRON) info (No MTA installed, discarding output)” error in the syslog

裡面提到此 MTA (Mail Transfer Agent) 錯誤訊息可以透過下列指令安裝郵件伺服器 Postfix 解決 :

$ sudo apt-get install postfix

不過裡面也說, MTA 錯誤並不會影響 Cron jobs 的執行, 可以忽略不管; 但安裝 MTA 可以讓我們從傳送的郵件訊息中獲得一些可判斷錯誤原因的訊息 :

"Or you can ignore it. I don't think the inability of cron to send messages has anything to do with the CPU spike (that's linked to the underlying job that cron is running). It might be safest to install an MTA and then read through the messages"

關於 Postfix 參考 :

# Raspberry Pi Email Server Part 1: Postfix

下面是安裝 Postfix mail server 的紀錄 :

pi@raspberrypi:~/cronjobs $ sudo apt-get install postfix
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libasn1-8-heimdal libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal
  libkrb5-26-heimdal libroken18-heimdal libwind0-heimdal libxfce4ui-1-0 xfce-keyboard-shortcuts
Use 'apt-get autoremove' to remove them.
Suggested packages:
  procmail postfix-mysql postfix-pgsql postfix-ldap postfix-pcre sasl2-bin dovecot-common postfix-cdb ufw postfix-doc
The following NEW packages will be installed:
  postfix
0 upgraded, 1 newly installed, 0 to remove and 17 not upgraded.
Need to get 1,293 kB of archives.
After this operation, 3,095 kB of additional disk space will be used.
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main postfix armhf 2.11.3-1 [1,293 kB]
Fetched 1,293 kB in 4s (310 kB/s)
Preconfiguring packages ...
Selecting previously unselected package postfix.
(Reading database ... 129248 files and directories currently installed.)
Preparing to unpack .../postfix_2.11.3-1_armhf.deb ...
Unpacking postfix (2.11.3-1) ...
Processing triggers for systemd (215-17+deb8u6) ...
Processing triggers for man-db (2.7.0.2-5) ...
Setting up postfix (2.11.3-1) ...
Adding group `postfix' (GID 119) ...
Done.
Adding system user `postfix' (UID 112) ...
Adding new user `postfix' (UID 112) with group `postfix' ...
Not creating home directory `/var/spool/postfix'.
Creating /etc/postfix/dynamicmaps.cf
Adding tcp map entry to /etc/postfix/dynamicmaps.cf
Adding sqlite map entry to /etc/postfix/dynamicmaps.cf
Adding group `postdrop' (GID 120) ...
Done.
setting myhostname: raspberrypi
setting alias maps
setting alias database
mailname is not a fully qualified domain name.  Not changing /etc/mailname.
setting destinations: raspberrypi, localhost.localdomain, , localhost
setting relayhost:
setting mynetworks: 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
setting mailbox_size_limit: 0
setting recipient_delimiter: +
setting inet_interfaces: all
/etc/aliases does not exist, creating it.
WARNING: /etc/aliases exists, but does not have a root alias.

Postfix is now set up with a default configuration.  If you need to make
changes, edit
/etc/postfix/main.cf (and others) as needed.  To view Postfix configuration
values, see postconf(1).

After modifying main.cf, be sure to run '/etc/init.d/postfix reload'.

Running newaliases
Processing triggers for systemd (215-17+deb8u6) ...
Processing triggers for libc-bin (2.19-18+deb8u7) ...
pi@raspberrypi:~/cronjobs $ g

安裝中途會詢問



只要用預設選項 "Internet Site" 與 "raspberrypi" 即可. 安裝好郵件伺服器後, 用 grep CRON /var/log/syslog 指令去撈, 果然 MTA 錯誤已經消失了, Crontab 的執行結果會丟到 /var/mail/帳號, 例如以 pi 帳號登入的話, pi 的 crontab 結果會輸出到 /var/mail/pi 這個檔案裡, 可以用 cat 顯示內容.

但是安裝郵件伺服器後 Cron jobs 依然沒有順利執行, 沒錯, MTA 與此無關. 其實這篇文章有提到要消除 MTA 錯誤訊息不一定要安裝郵件伺服器, 只要在每個 cron 指令後面加上下面導向即可 :

</dev/null 2<&1

回到 Crontab 沒有順利執行問題, 前面試過在 /var/www/html/tony1966/ 專案目錄下, 直接執行 PHP 指令又 OK 呀! 我覺得可能跟 PHP 程式中使用相對路徑有關, 因此參考柯博文的 "Raspberry Pi 超炫專案與完全實戰第二版" 10.3 節的做法, 先為每一個 PHP 爬蟲程式製作一個 shell 指令檔, 然後在 crontab 程式中執行這個 shell 程式. 這些 shell 程式都放在自建的 /home/pi/cronjobs 目錄下 :

pi@raspberrypi:~ $mkdir cronjobs
pi@raspberrypi:~ $cd cronjobs
pi@raspberrypi:~/cronjobs $nano fetch_yahoo_usa_stocks.sh

以 fetch_yahoo_usa_stocks.php 為例, 用 nano 編輯一個 shell 指令檔 fetch_yahoo_usa_stocks.sh, 其內容如下 :

cd /var/www/html/tony1966/cron
/usr/bin/php fetch_yahoo_usa_stocks.php

第一行用 cd 指令切換工作目錄到專案資料夾 tony1966 底下放置 PHP 爬蟲程式的 cron 資料夾, 第二行呼叫 /usr/bin 下的 php 程式執行 cron 下的 PHP 指令稿.

然後用 chmod 指令將此 shell 檔更改為 755 權限 : 

pi@raspberrypi:~/cronjobs $sudo chmod 755 fetch_yahoo_usa_stocks.sh
  
最後修改 crontab 檔, 每 5 分鐘執行此 fetch_yahoo_usa_stocks.sh  :

*/5 * * * * /home/pi/cronjobs/fetch_yahoo_usa_stocks.sh

經過這樣設定, 果然 crontab 就順利執行了 :


由於前面安裝了郵件伺服器, crontab 執行結果會放在 /var/mail/pi 檔案裡, 可用 cat 顯示執行結果 :

You have new mail in /var/mail/pi
pi@raspberrypi:~/cronjobs $ cat /var/mail/pi

From pi@raspberrypi  Wed Feb  1 14:55:09 2017
Return-Path: <pi@raspberrypi>
X-Original-To: pi
Delivered-To: pi@raspberrypi
Received: by raspberrypi (Postfix, from userid 1000)
        id EBECA2AC32; Wed,  1 Feb 2017 14:55:08 +0800 (CST)
From: root@raspberrypi (Cron Daemon)
To: pi@raspberrypi
Subject: Cron <pi@raspberrypi> /home/pi/cronjobs/fetch_yahoo_usa_stocks.sh
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/home/pi>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=pi>
Message-Id: <20170201065508.EBECA2AC32@raspberrypi>
Date: Wed,  1 Feb 2017 14:55:08 +0800 (CST)

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>擷取 Yahoo 美股收盤報告</title>
</head>
<body>
<P>擷取 Yahoo 美股收盤報告</P>
Asia/Taipei<br><a href='https://tw.finance.yahoo.com/us/worldidx.php' target='_blank'>原始網頁</a><br>交易日期=02/01/2017<br>交易日期=2017-02-01<br>道瓊工業指數收盤=19864.09<br>道瓊工業指數漲跌=▼107.04(-0.54%)<br>那斯達克指數收盤=5614.79<br>那斯達克指數漲跌=▲1.07(+0.02%)<br>史坦普指數收盤=2278.87<br>史坦普指數漲跌=▼2.03(-0.09%)<br>費城半導體指數收盤=944.28<br>費城半導體指數漲跌=▼12.56(-1.31%)<br>PHP Notice:  Undefined variable: RS in /var/www/html/tony1966/lib/mysqli.php on line 335
<br>處理時間 :7 秒</body>
</html>

其他 PHP 爬蟲程式都如法炮製即可.

參考 :

Where is the cron / crontab log?
Cron job not working in Raspberry
Running Things Regularly - cron (@reboot)
Raspberry Pi Simple Cron Jobs Explanation

其他 :

# Top 8 IDEs for Programmers, Coders and Beginners on the Raspberry Pi
Tinkering with the Raspberry Pi A+


2017-02-01 補充 :

終於在春節假期結束的年初五完成 PHP 專案移植到樹莓派的計畫了, 驗證了我接觸樹莓派近三年來的假想.

# 樹莓派通過郵件上報實時IP,隨時隨地遠程登錄樹莓派
# 編程篇Python 實現SMTP發送郵件、Web伺服器

2017-02-08 補充 :

解除安裝 Postfix 的方法 :

$ sudo aptitude remove postfix* --purge   

注意, 解除安裝需要 root 權限, 故須加 sudo :

pi@raspberrypi:~ $ aptitude remove postfix* --purge
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
pi@raspberrypi:~ $ sudo aptitude remove postfix* --purge
Couldn't find any package whose name or description matched "postfix*"
Couldn't find any package whose name or description matched "postfix*"
The following packages will be REMOVED:
  libasn1-8-heimdal{pu} libgssapi3-heimdal{pu} libhcrypto4-heimdal{pu}
  libheimbase1-heimdal{pu} libheimntlm0-heimdal{pu} libhx509-5-heimdal{pu}
  libkrb5-26-heimdal{pu} libroken18-heimdal{pu} libwind0-heimdal{pu}
  libxfce4ui-1-0{pu} xfce-keyboard-shortcuts{pu}
0 packages upgraded, 0 newly installed, 11 to remove and 17 not upgraded.
Need to get 0 B of archives. After unpacking 3,368 kB will be freed.
Do you want to continue? [Y/n/?] y
(Reading database ... 129461 files and directories currently installed.)
Removing libgssapi3-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libgssapi3-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libheimntlm0-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libheimntlm0-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libkrb5-26-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libkrb5-26-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libhx509-5-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libhx509-5-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libhcrypto4-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libhcrypto4-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libheimbase1-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libheimbase1-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libwind0-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libwind0-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libxfce4ui-1-0 (4.10.0-6) ...
Purging configuration files for libxfce4ui-1-0 (4.10.0-6) ...
Removing xfce-keyboard-shortcuts (4.10.0-6) ...
Purging configuration files for xfce-keyboard-shortcuts (4.10.0-6) ...
Removing libasn1-8-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libasn1-8-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Removing libroken18-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Purging configuration files for libroken18-heimdal:armhf (1.6~rc2+dfsg-9+rpi1) ...
Processing triggers for libc-bin (2.19-18+deb8u7) ...

參考 :


2017年1月25日 星期三

2017 年第 3 周記事 : 姊姊學測

周五 (1/20) 與週六 (1/21) 姊姊在道明中學參加學測考試, 我想請假陪考, 她卻說不用, 所以早上載她到道明門口我就去上班了, 下午下班剛好去載她回家. 考完學測過完年還有美術班的術科考試, 所以還要去畫室練習.

本周開始放寒假, 我也得抽空大掃除. 但是正在研究樹莓派如何架站, 所以也沒做多少清掃整理工作. 本周將樹莓派板子帶回鄉下研究 wifi 設定, 看看能否在高雄與鄉下依據設定的 priority 自動連線基地台, 週日試了一整個下午的 wpa_supplicant.conf + interaces 調整, 結果還是不行, 決定還是單純利用 interfaces 就好. 其次, 就算只用 interfaces, B+ 搭配 EDUP RT5370 晶片的網卡就是無法連上華為手機的基地台 (使用 WPA2), 連線鄉下的基地台卻可以. 周一回高雄換成 RTL8188CUS 的網卡就沒問題, 所以結論是, 網卡使用的晶片關係很大.

另外, 周三 (1/17) 將 GARMIN 4592R PLUS 導航機軟體與地圖升版後竟然無法開機, 客服說要在 1/19 午前寄達原廠才能在過年前寄回來, 所以趕緊在 1/18 日用宅急便寄回去, 今天收到貨品了, 但是沒註明無法開機原因, 明日再詢問.

2017-01-26 補充 :

今天詢問 Garmin 客服 (02)2642-9199, 為何首次下載軟體與地圖更新後, 電池耗盡即無法開機, 我這是剛買的新機耶! 客服說維修部門註記主機板部分故障, 整機換新處理. 奇怪, 照理軟體升版不會損壞機板啊! 我懷疑可能是充電器搞的鬼, 因為我帶到公司充電與升版後電力還夠, 可以正常使用, 但回家後發現電力下降, 就用另一個充電器充電, 睡覺前取下來就發現開不了機了. 但是那個充電器一直都用來充手機沒問題呀! 無解. 總之, 以後導航機若從車上取下時, 必須使用原廠充電器為宜.

2017年1月24日 星期二

在樹莓派上架設 PHP+MySQL 網站伺服器

由於去年底向英國 Hostinger 租用的虛擬主機在實際使用之後發現有 "Resource Limit Exceeded" 問題, 雖然經過 PHP 程式優化以及刪除不需要的 Cron Jobs 後已經很少出現, 但是我覺得有必要另外準備一個替代方案.

最近想到可用樹莓派來試試看, 因為我的 PHP 專案主要是跑網路爬蟲程式進行資料探勘, 網站不一定要架在 Internet 上, 可以架設在區域網路中, 透過網路連線存取 Internet, 只要定時將資料分析結果丟上雲端即可. 如果測試 OK 的話, 將來 Hostinger 主機就轉變成資料彙總主機, 負載就不會這麼高了.

在樹莓派架設 LAMP (Linux + Apache + MySQL + PHP) 的程序, 我主要是按照柯博文的 "Raspberry Pi 最佳入門與實戰應用 (第二版)" 第 6 章進行測試的.

Source : 金石堂

參考 :

# 在樹莓派上架設Apache+MySQL+PHP

不過這本書中有一些地方需要修正, 例如書中的網頁根目錄預設是在 /var/www 下, 但我使用的 Apache 版本較新,  網頁根目錄預設是在 /var/www/html 下.

1. 更新樹莓派已安裝之套件 : (參考鳥哥的文章)

$ sudo apt-get update        (從 apt 伺服器更新用戶端套件表頭清單)
$ sudo apt-get upgrade      ( 完整升級已安裝的套件)

2. 安裝 Apache2 網頁伺服器 :

$ sudo apt-get install apache2  

3. 安裝 PHP5 語言解譯引擎 :

$ sudo apt-get install php5 libapache2-mod-php5  

4. 重新啟動 Apache 伺服器 :

$ sudo service apache2 restart

經過這四道程序後, Apache + PHP 網頁伺服器就架起來了. 用瀏覽器連線樹莓派 IP, 如果看到下面網頁就是成功了 :


這裡我是以 Headless (無頭) 方式從筆電遠端操控樹莓派, 筆電與樹莓派都同時透過手機分享的 wifi 上網, 所以上圖也是從筆電瀏覽器連線樹莓派的 IP (即與 Putty 遠端連線一樣的 IP).

這個預設網頁是存放於 /var/www/html 下的 index.html 檔, 我們可以撰寫自己的首頁檔覆蓋此檔. 此預設首頁檔有一些值得參考的資訊, 它提到 Apache2 的設定檔放在 /etc/apache2 目錄下, 完整的說明文件則放在下列壓縮檔中 :

/usr/share/doc/apache2/README.Debian.gz

Debian 的 Apache2 設定與一般不同, 分散在不同的設定檔裡面. 例如 , 其中 apache2.conf 是最主要的設定檔; 設定檔 ports.conf 用來確定連線要求之傾聽埠. 而 mods-enabled, conf-enabled 與 sites-enabled 目錄則是用來管理 modules, global configuration, 以及 virtual host 等相關設定.

Apache 伺服器的執行檔放在 /usr/bin/apache2, 但是因為使用環境變數的緣故, 直接呼叫 apache2 來啟動與停止 Apache 是沒有用的, 必須透過 /etc/init.d/apache2 或 /etc/init.d/apache2ctl 才行.

Apache2 的預設網頁根目錄為 /var/www/html (書上寫的是 /var/www, 我放在這底下無效, 可能那是舊版用法), 預設情況下 Debian 只允許透過瀏覽器存取 /var/www/html (網頁程式) 與 /usr/share (網頁應用) 這兩個目錄. 如果要將網頁根目錄放在別處, 必須在 /etc/apache2/apache2.conf 設定檔中將此目錄加進白名單裡.

我用 nano 寫了一個呼叫 phpino() 的程式 phpinfo.php, 放在 /var/www/html 下 :

pi@raspberrypi:~ $ sudo nano /var/www/html/phpinfo.php
pi@raspberrypi:~ $ cat /var/www/html/phpinfo.php
phpinfo();
?>
pi@raspberrypi:~ $

然後在筆電瀏覽器以 headless 方式連線 192.168.43.26/phpinfo.php 結果可順利執行 PHP 程式 :


這樣就完成 Apache + PHP 的安裝了.

值得一提的是, 第一步更新套件列表與升級已安裝的套件會花比較久時間 (大概 1 小時), 而且升級到 lightdm 時會詢問是否要更新 distribution 版本, 只要按 Enter (預設=N) 繼續即可, 但是我卻按了 D 去看看有啥不同, 結果進入顯示頁面就出不來了 :

........
Setting up libservlet2.5-java (6.0.45+dfsg-1~deb8u1) ...
Setting up libsrtp0 (1.4.5~20130609~dfsg-1.1+deb8u1) ...
Setting up libwayland-cursor0:armhf (1.11.0-2) ...
Setting up libxfont1:armhf (1:1.5.2-1) ...
Setting up lightdm (1.10.3-3+rpi) ...

Configuration file '/etc/lightdm/lightdm.conf'
 ==> Modified (by you or by a script) since installation.
 ==> Package distributor has shipped an updated version.
   What would you like to do about it ?  Your options are:
    Y or I  : install the package maintainer's version
    N or O  : keep your currently-installed version
      D     : show the differences between the versions
      Z     : start a shell to examine the situation
 The default action is to keep your current version.
*** lightdm.conf (Y/I/N/O/D/Z) [default=N] ? D
~
~
~
~
~
~
(END)    (在這邊停住無法繼續, 不知是按 Ctrl+D 還是 Ctrl+Z 才跳出來)
[1]+  Stopped                 sudo apt-get upgrade
pi@raspberrypi:~ $

跳出來後想說就安裝 Apache 吧! 結果卻說 dpkg 更新無法繼續, 好像被某程序咬住. 我想可能是 upgrade 被我用 Ctrl+D (?) 中斷之故, 所以在 lightdm 更新時若輸入 N 應該就不會這樣了. 無奈之下只好下 sudo poweroff 關機重開, 再次下 sudo apt-get upgrade, 但是回應說因為 upgrade 被中斷, 要改用 sudo dpkg --configure -a 指令去修正, 以下是 upgrade 的修正過程以及 Apache + PHP 的安裝紀錄 :

=========關機重新登入=========
login as: pi
pi@192.168.2.111's password:

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Jan 24 11:44:37 2017
pi@raspberrypi:~ $ sudo apt-get upgrade
E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to cor
pi@raspberrypi:~ $ sudo dpkg --configure -a
Setting up python3-gpiozero (1.3.1) ...
Setting up bluej (3.1.7b) ...
......
......
......
update-alternatives: using /usr/bin/lxterminal to provide /usr/bin/x-terminal-em                                                      ulator (x-terminal-emulator) in auto mode
Setting up lightdm (1.10.3-3+rpi) ...

Configuration file '/etc/lightdm/lightdm.conf'
 ==> Modified (by you or by a script) since installation.
 ==> Package distributor has shipped an updated version.
   What would you like to do about it ?  Your options are:
    Y or I  : install the package maintainer's version
    N or O  : keep your currently-installed version
      D     : show the differences between the versions
      Z     : start a shell to examine the situation
 The default action is to keep your current version.
*** lightdm.conf (Y/I/N/O/D/Z) [default=N] ? N    (要輸入 N)
Setting up python-gpiozero (1.3.1) ...
Setting up python-picamera (1.12) ...
Setting up raspi-gpio (0.20170105) ...
Processing triggers for dbus (1.8.22-0+deb8u1) ...
Setting up ruby2.1 (2.1.5-2+deb8u3) ...
Setting up pi-package-session (0.2) ...
Setting up x11-common (1:7.7+16) ...
Installing new version of config file /etc/init.d/x11-common ...
update-rc.d: warning: start and stop actions are no longer supported; falling ba                                                      ck to defaults
Setting up ruby (1:2.1.5+deb8u2) ...
Processing triggers for ca-certificates (20141019+deb8u2) ...
Updating certificates in /etc/ssl/certs... 10 added, 10 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.
Setting up raspberrypi-net-mods (1.2.5) ...
Modified /etc/network/interfaces detected. Leaving unchanged and writing new fil                                                      e as interfaces.new.
Setting up lxkeymap (0.8.0~bzr25-1+rpi3) ...
Setting up python-pil:armhf (2.6.1-2+deb8u3) ...
Setting up python-rpi.gpio (0.6.3~jessie-1) ...
Setting up pi-package (0.2) ...
Setting up pipanel (20161206~130650) ...
Setting up xserver-common (2:1.18.4-2+rpi1) ...
Setting up xserver-xorg-core (2:1.18.4-2+rpi1) ...
Setting up rc-gui (1.5) ...
Setting up python-sense-hat (2.2.0-1) ...
Setting up xserver-xorg-video-fbturbo (1.20161111~122359) ...
Setting up xserver-xorg-input-synaptics (1.8.3-2) ...
Setting up xserver-xorg-input-evdev (1:2.10.3-1) ...
Setting up xserver-xorg-video-fbdev (1:0.4.4-1+rpi2) ...
Setting up xserver-xorg (1:7.7+16) ...
Processing triggers for systemd (215-17+deb8u6) ...
pi@raspberrypi:~ $ sudo apt-get install apache2     (安裝 Apache)
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libasn1-8-heimdal libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal
  libkrb5-26-heimdal libroken18-heimdal libwind0-heimdal libxfce4ui-1-0 xfce-keyboard-shortcuts
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
  apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.1-0 ssl-cert
Suggested packages:
  apache2-doc apache2-suexec-pristine apache2-suexec-custom openssl-blacklist
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.1-0 ssl-cert
0 upgraded, 10 newly installed, 0 to remove and 17 not upgraded.
Need to get 1,750 kB of archives.
After this operation, 5,241 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main libapr1 armhf 1.5.1-3 [77.1 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main libaprutil1 armhf 1.5.4-1 [75.9 kB]
Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main libaprutil1-dbd-sqlite3 armhf 1.5.4-1 [17.7 kB]
Get:4 http://mirrordirector.raspbian.org/raspbian/ jessie/main libaprutil1-ldap armhf 1.5.4-1 [16.7 kB]
Get:5 http://mirrordirector.raspbian.org/raspbian/ jessie/main liblua5.1-0 armhf 5.1.5-7.1 [83.7 kB]
Get:6 http://mirrordirector.raspbian.org/raspbian/ jessie/main apache2-bin armhf 2.4.10-10+deb8u7 [893 kB]
Get:7 http://mirrordirector.raspbian.org/raspbian/ jessie/main apache2-utils armhf 2.4.10-10+deb8u7 [194 kB]
Get:8 http://mirrordirector.raspbian.org/raspbian/ jessie/main apache2-data all 2.4.10-10+deb8u7 [163 kB]
Get:9 http://mirrordirector.raspbian.org/raspbian/ jessie/main apache2 armhf 2.4.10-10+deb8u7 [207 kB]
Get:10 http://mirrordirector.raspbian.org/raspbian/ jessie/main ssl-cert all 1.0.35 [20.9 kB]
Fetched 1,750 kB in 6s (269 kB/s)
Preconfiguring packages ...
Selecting previously unselected package libapr1:armhf.
(Reading database ... 127681 files and directories currently installed.)
Preparing to unpack .../libapr1_1.5.1-3_armhf.deb ...
Unpacking libapr1:armhf (1.5.1-3) ...
Selecting previously unselected package libaprutil1:armhf.
Preparing to unpack .../libaprutil1_1.5.4-1_armhf.deb ...
Unpacking libaprutil1:armhf (1.5.4-1) ...
Selecting previously unselected package libaprutil1-dbd-sqlite3:armhf.
Preparing to unpack .../libaprutil1-dbd-sqlite3_1.5.4-1_armhf.deb ...
Unpacking libaprutil1-dbd-sqlite3:armhf (1.5.4-1) ...
Selecting previously unselected package libaprutil1-ldap:armhf.
Preparing to unpack .../libaprutil1-ldap_1.5.4-1_armhf.deb ...
Unpacking libaprutil1-ldap:armhf (1.5.4-1) ...
Selecting previously unselected package liblua5.1-0:armhf.
Preparing to unpack .../liblua5.1-0_5.1.5-7.1_armhf.deb ...
Unpacking liblua5.1-0:armhf (5.1.5-7.1) ...
Selecting previously unselected package apache2-bin.
Preparing to unpack .../apache2-bin_2.4.10-10+deb8u7_armhf.deb ...
Unpacking apache2-bin (2.4.10-10+deb8u7) ...
Selecting previously unselected package apache2-utils.
Preparing to unpack .../apache2-utils_2.4.10-10+deb8u7_armhf.deb ...
Unpacking apache2-utils (2.4.10-10+deb8u7) ...
Selecting previously unselected package apache2-data.
Preparing to unpack .../apache2-data_2.4.10-10+deb8u7_all.deb ...
Unpacking apache2-data (2.4.10-10+deb8u7) ...
Selecting previously unselected package apache2.
Preparing to unpack .../apache2_2.4.10-10+deb8u7_armhf.deb ...
Unpacking apache2 (2.4.10-10+deb8u7) ...
Selecting previously unselected package ssl-cert.
Preparing to unpack .../ssl-cert_1.0.35_all.deb ...
Unpacking ssl-cert (1.0.35) ...
Processing triggers for man-db (2.7.0.2-5) ...
Processing triggers for systemd (215-17+deb8u6) ...
Setting up libapr1:armhf (1.5.1-3) ...
Setting up libaprutil1:armhf (1.5.4-1) ...
Setting up libaprutil1-dbd-sqlite3:armhf (1.5.4-1) ...
Setting up libaprutil1-ldap:armhf (1.5.4-1) ...
Setting up liblua5.1-0:armhf (5.1.5-7.1) ...
Setting up apache2-bin (2.4.10-10+deb8u7) ...
Setting up apache2-utils (2.4.10-10+deb8u7) ...
Setting up apache2-data (2.4.10-10+deb8u7) ...
Setting up apache2 (2.4.10-10+deb8u7) ...
Enabling module mpm_event.
Enabling module authz_core.
Enabling module authz_host.
Enabling module authn_core.
Enabling module auth_basic.
Enabling module access_compat.
Enabling module authn_file.
Enabling module authz_user.
Enabling module alias.
Enabling module dir.
Enabling module autoindex.
Enabling module env.
Enabling module mime.
Enabling module negotiation.
Enabling module setenvif.
Enabling module filter.
Enabling module deflate.
Enabling module status.
Enabling conf charset.
Enabling conf localized-error-pages.
Enabling conf other-vhosts-access-log.
Enabling conf security.
Enabling conf serve-cgi-bin.
Enabling site 000-default.
Setting up ssl-cert (1.0.35) ...
Processing triggers for libc-bin (2.19-18+deb8u7) ...
Processing triggers for systemd (215-17+deb8u6) ...
pi@raspberrypi:~ $ sudo apt-get install php5 libapache2-mod-php5  (安裝 PHP5)
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libasn1-8-heimdal libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal
  libkrb5-26-heimdal libroken18-heimdal libwind0-heimdal libxfce4ui-1-0 xfce-keyboard-shortcuts
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
  libonig2 libperl4-corelibs-perl libqdbm14 lsof php5-cli php5-common php5-json php5-readline
Suggested packages:
  php-pear php5-user-cache
The following NEW packages will be installed:
  libapache2-mod-php5 libonig2 libperl4-corelibs-perl libqdbm14 lsof php5 php5-cli php5-common php5-json php5-readline
0 upgraded, 10 newly installed, 0 to remove and 17 not upgraded.
Need to get 5,085 kB of archives.
After this operation, 18.8 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y    (繼續安裝)
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main libonig2 armhf 5.9.5-3.2 [101 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main libperl4-corelibs-perl all 0.003-1 [43.6 kB]
Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main lsof armhf 4.86+dfsg-1 [321 kB]
Get:4 http://mirrordirector.raspbian.org/raspbian/ jessie/main libqdbm14 armhf 1.8.78-5+b1 [86.0 kB]
Get:5 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5-common armhf 5.6.29+dfsg-0+deb8u1 [720 kB]
Get:6 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5-json armhf 1.3.6-1 [16.9 kB]
Get:7 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5-cli armhf 5.6.29+dfsg-0+deb8u1 [1,904 kB]
Get:8 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5 all 5.6.29+dfsg-0+deb8u1 [1,312 B]
Get:9 http://mirrordirector.raspbian.org/raspbian/ jessie/main libapache2-mod-php5 armhf 5.6.29+dfsg-0+deb8u1 [1,879 kB]
Get:10 http://mirrordirector.raspbian.org/raspbian/ jessie/main php5-readline armhf 5.6.29+dfsg-0+deb8u1 [11.2 kB]
Fetched 5,085 kB in 10s (486 kB/s)
Selecting previously unselected package libonig2:armhf.
(Reading database ... 128369 files and directories currently installed.)
Preparing to unpack .../libonig2_5.9.5-3.2_armhf.deb ...
Unpacking libonig2:armhf (5.9.5-3.2) ...
Selecting previously unselected package libperl4-corelibs-perl.
Preparing to unpack .../libperl4-corelibs-perl_0.003-1_all.deb ...
Unpacking libperl4-corelibs-perl (0.003-1) ...
Selecting previously unselected package lsof.
Preparing to unpack .../lsof_4.86+dfsg-1_armhf.deb ...
Unpacking lsof (4.86+dfsg-1) ...
Selecting previously unselected package libqdbm14.
Preparing to unpack .../libqdbm14_1.8.78-5+b1_armhf.deb ...
Unpacking libqdbm14 (1.8.78-5+b1) ...
Selecting previously unselected package php5-common.
Preparing to unpack .../php5-common_5.6.29+dfsg-0+deb8u1_armhf.deb ...
Unpacking php5-common (5.6.29+dfsg-0+deb8u1) ...
Selecting previously unselected package php5-json.
Preparing to unpack .../php5-json_1.3.6-1_armhf.deb ...
Unpacking php5-json (1.3.6-1) ...
Selecting previously unselected package php5-cli.
Preparing to unpack .../php5-cli_5.6.29+dfsg-0+deb8u1_armhf.deb ...
Unpacking php5-cli (5.6.29+dfsg-0+deb8u1) ...
Selecting previously unselected package libapache2-mod-php5.
Preparing to unpack .../libapache2-mod-php5_5.6.29+dfsg-0+deb8u1_armhf.deb ...
Unpacking libapache2-mod-php5 (5.6.29+dfsg-0+deb8u1) ...
Selecting previously unselected package php5.
Preparing to unpack .../php5_5.6.29+dfsg-0+deb8u1_all.deb ...
Unpacking php5 (5.6.29+dfsg-0+deb8u1) ...
Selecting previously unselected package php5-readline.
Preparing to unpack .../php5-readline_5.6.29+dfsg-0+deb8u1_armhf.deb ...
Unpacking php5-readline (5.6.29+dfsg-0+deb8u1) ...
Processing triggers for man-db (2.7.0.2-5) ...
Setting up libonig2:armhf (5.9.5-3.2) ...
Setting up libperl4-corelibs-perl (0.003-1) ...
Setting up lsof (4.86+dfsg-1) ...
Setting up libqdbm14 (1.8.78-5+b1) ...
Setting up php5-common (5.6.29+dfsg-0+deb8u1) ...

Creating config file /etc/php5/mods-available/pdo.ini with new version
php5_invoke: Enable module pdo for cli SAPI
php5_invoke: Enable module pdo for apache2 SAPI

Creating config file /etc/php5/mods-available/opcache.ini with new version
php5_invoke: Enable module opcache for cli SAPI
php5_invoke: Enable module opcache for apache2 SAPI
Setting up php5-json (1.3.6-1) ...
php5_invoke: Enable module json for cli SAPI
php5_invoke: Enable module json for apache2 SAPI
Setting up php5-cli (5.6.29+dfsg-0+deb8u1) ...
update-alternatives: using /usr/bin/php5 to provide /usr/bin/php (php) in auto mode
update-alternatives: using /usr/bin/phar5 to provide /usr/bin/phar (phar) in auto mode

Creating config file /etc/php5/cli/php.ini with new version
Setting up libapache2-mod-php5 (5.6.29+dfsg-0+deb8u1) ...

Creating config file /etc/php5/apache2/php.ini with new version
Module mpm_event disabled.
Enabling module mpm_prefork.
apache2_switch_mpm Switch to prefork
apache2_invoke: Enable module php5
Setting up php5 (5.6.29+dfsg-0+deb8u1) ...
Setting up php5-readline (5.6.29+dfsg-0+deb8u1) ...

Creating config file /etc/php5/mods-available/readline.ini with new version
php5_invoke: Enable module readline for cli SAPI
php5_invoke: Enable module readline for apache2 SAPI
Processing triggers for libc-bin (2.19-18+deb8u7) ...
Processing triggers for libapache2-mod-php5 (5.6.29+dfsg-0+deb8u1) ...
pi@raspberrypi:~ $ sudo service apache2 restart  (重新啟動 Apache)

這樣 PHP 網站伺服器就架起來了. 注意, PHP 的設定檔位置在 :

/etc/php5/apache2/php.ini

接下來是安裝 MySQL 資料庫 :

5. 安裝 MySQL 資料庫 :

首先是安裝與 MySQL 連線的相關模組, 其中 php5-mysql 提供 PHP 與 MySQL 連結之函數 :

$ sudo apt-get install mysql-server mysql-client php5-mysql  (安裝 MySQL)

在安裝途中 (出現 preconfiguring package 的地方) 會要求設定 MySQL 管理員 ( root 帳號) 的密碼, 我通常設為 mysql, 輸入完按 tab 鍵就會移到 OK 按鈕, 按 Enter 即可 :



然後是安裝  PHP5 安全性模組 :

$ sudo apt-get install php5-mcrypt  (安裝  PHP5 安全性模組)

最後是下載安裝 phpMyAdmin 程式來管理 MySQL 資料庫 :

6. 安裝 MySQL 管理程式 phpMyAdmin :

https://www.phpmyadmin.net/downloads/

目前最新版是 4.6.6 版, 滑鼠移到要下載的 phpMyAdmin-4.6.6-all-languages.tar.bz2 上按右鍵, 選擇 "複製連結網址" :


然後將此網址放在 wget 指令後面進行下載 :

$ wget https://files.phpmyadmin.net/phpMyAdmin/4.6.6/phpMyAdmin-4.6.6-all-languages.tar.bz2   (從 myPHPadmin 網站下載最新版)

完成後就用 tar 指令進行解壓縮, 這會在 /home/pi 下面產生 phpMyAdmin-4.6.6-all-languages 目錄來存放 phpMyAdmin 程式 :

$ tar -jxf phpMyAdmin-4.6.6-all-languages.tar.bz2  (解壓縮下載檔)

由於資料夾名稱太長了, 用 mv 指令改為較短的 mysql, 然後搬移到網頁根目錄 /var/www/html 下面 :

$ mv phpMyAdmin-4.6.6-all-languages mysql  (將解壓縮後目錄改成 mysql)
$ sudo mv mysql /var/www/html  (將 phpMyAdmin 目錄 mysql 搬移到網頁根目錄下)

這樣就完成了 phpMyAdmin 的安裝了, 但是如果這時馬上瀏覽 192.168.2.111/mysql 將顯示 "缺少 mysqli 擴充套件" 的錯誤 :


其實只要重新啟動 Apache 伺服器即可 :

sudo service apache2 restart

或者 :

pi@raspberrypi:~ $ sudo /etc/init.d/apache2 restart
[ ok ] Restarting apache2 (via systemctl): apache2.service.
pi@raspberrypi:~ $

然後重新整理 phpMyAdmin 網頁就會正常顯示 MySQL 登入頁面了 :

 手動新增資料庫時, 對於中文系統應該將伺服器連線與編碼 (collation) 設為 utf8-unicode_ci :


參考 :

# Extension mysqli is missing, phpmyadmin doesn't work
# Ubuntu系統-phpmyadmin 缺少 mysqli 擴充套件。請檢查 PHP 設定。 請參考 our 文件 以取得更多的資訊。
# 在 Ubuntu 上安裝 LAMP (Apache, PHP, MySQL)

2017-01-25 補充 :

昨天架好網站後就迫不及待將我的 PHP 專案壓縮成 zip 檔, 在 Win10 筆電中透過 pscp.exe 程式將 EasyUICMS 專案壓縮檔傳送到樹莓派的網頁根目錄 /var/www/html/ 下, 解壓縮並調整好 db.php 為 localhost.php 後, 果然能正常依照 install.php 檔建立起整個網站.



但是當我要上傳應用程式時卻發現傳檔失敗, 原因是檔案 "移動失敗" :


我檢視了 sys.php 中關於檔案上傳的原始碼以及 /etc/php5/apache2/php.ini 檔, 並未發現有需要調整的地方. 用 Google 找了好久還是無線索. 後來想到既然是移動失敗, 上傳應該沒問題, 只是要從系統預設暫存區移到 apps 目錄時失敗而已, 那是否跟目錄的寫入權限有關呢? 我檢查了專案目錄權限, 發現 apps 與 files 這兩個會儲存上傳檔案的目錄在 group 與 others 部分只有讀取權限 (r-x), 亦即只有 755 權限 :

pi@raspberrypi:~ $ ls -l /var/www/html/twstockbot
total 2332
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 apps
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 cron
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 data
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 db
-rw-r--r-- 1 pi pi     141 Jan 25 11:10 db.php
drwxr-xr-x 2 pi pi    4096 Jan 25 11:33 files

應該改為 rw- (766) :

pi@raspberrypi:~ $ sudo chmod 766 /var/www/html/twstockbot/apps
pi@raspberrypi:~ $ sudo chmod 766 /var/www/html/twstockbot/files
pi@raspberrypi:~ $ ls -l /var/www/html/tony1966
total 2332
drwxrw-rw- 2 pi pi    4096 Nov 28 21:45 apps
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 cron
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 data
drwxr-xr-x 2 pi pi    4096 Nov 28 21:45 db
-rw-r--r-- 1 pi pi     141 Jan 25 11:10 db.php
drwxrw-rw- 2 pi pi    4096 Jan 25 11:33 files

但是, 這樣還是檔案移動失敗, 難道權限還要放寬到 777 嗎? 只好試試看 :

pi@raspberrypi:~ $ sudo chmod 777 /var/www/html/twstockbot/apps
pi@raspberrypi:~ $ sudo chmod 777 /var/www/html/twstockbot/files
pi@raspberrypi:~ $ ls -l /var/www/html/tony1966
total 2332
drwxrwxrwx 2 root root    4096 Jan 25 23:37 apps
drwxr-xr-x 2 root root    4096 Nov 28 21:45 cron
drwxr-xr-x 2 root root    4096 Nov 28 21:45 data
drwxr-xr-x 2 root root    4096 Nov 28 21:45 db
-rw-r--r-- 1 root root     143 Jan 25 23:34 db.php
drwxrwxrwx 2 root root    4096 Jan 25 23:25 files

哈哈哈! 改成 777 後就可以順利上傳了. 我在下列文章讀到上傳目錄最適合的權限是 755, 但我試過改成 755 也是不行 :

# What are the proper permissions for an upload folder with PHP/Apache?

此問題暫且 hold 住, 以後再研究, 反正 777 能用就行了.

參考 :

PHP檔案上傳
PHP 檔案上傳、多檔案上傳
# 第五章、Linux 的檔案權限與目錄配置
# php 檔案上傳error code [handling file upload error code]
# Uploading big files > 512MB (as set by default)

2017年1月21日 星期六

如何更改樹莓派登入密碼

菁菁的愛貓小咪來到我們家已兩個月了, 體型有稍微長大, 每天吃飽喝足都要來挑釁我的拖鞋, 天天對戰訓練下來已經變成戰鬥小貓, 就算不小心走失, 行走江湖也不會被欺負矣. 每次打電腦都給我跳上電腦桌, 坐在鍵盤旁邊看我寫程式, 看到螢幕鼠標移動還會跑去抓游標, 還三不五時跳到我鍵盤上攪亂我的程式碼. 被我噴酒精消毒液警告後, 就躲到桌上書櫃最底下一格趴在裡面, 我以為牠會一整晚在那裏睡覺, 結果不是耶, 我關掉電腦準備就寢時, 牠就跑到椅子底下去了. 看來牠可能就是想要陪我打電腦哩.

為了監看這隻戰鬥小貓白天都在幹啥, 前天上小米網站買了一個小蟻智慧攝影機放在鋼琴上, 偵測到客廳有物體移動時會傳訊息到我手機, 並附上 6 秒鐘錄影. 幾天來發現白天牠就在客廳跳上跳下, 或者去喝水; 但是在攝影機照不到的地方 (如椅子底下) 就沒轍了. 看來得動手把智慧小車組好才有辦法監視牠的一舉一動了. 智慧小車要裝上攝影頭的話以樹莓派為宜 (Arduino 這種小腦袋處理能力不足), 於是我把塵封已久的樹莓派板子拿出來複習, 但是久沒有摸竟然忘記SSH 遠端登入密碼!

所幸在之前寫的樹莓派筆記裡找到線索, 原來樹莓派預設登入帳號是 pi, 密碼為 raspberry, 我通常會把密碼也改成 pi, 懶得敲這麼長的字 (雖然嚴重違反資安規則). 結果用預設密碼去試, 果然沒錯! 關於使用 putty 遠端存取樹莓派的方法 (充分利用 PC 的鍵鼠螢幕操控樹莓派) 參考 :

樹莓派 Linux 常用指令
# 樹莓派的 "無頭存取" (headless access)

登入後當然立刻進行密碼更改, 免得下次又忘了. 我使用 Linux 指令 passwd 想將預設密碼 raspberry 改成 pi, 結果發現 pi 長度太短不讓我改 :

pi@raspberrypi:~ $ passwd
Changing password for pi.
(current) UNIX password:   (輸入預設密碼 raspberry)
Enter new UNIX password:  (輸入新密碼 pi)
Retype new UNIX password:  (輸入新密碼 pi)
You must choose a longer password   (失敗 : 太短)

沒辦法, 只好輸入 sudo raspi-config 進入系統設定表去改 :

login as: pi                                                    (預設值)
pi@192.168.2.105's password:raspberry     (預設值)

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Jan 21 09:48:16 2017 from 192.168.2.110
pi@raspberrypi:~ $ sudo raspi-config         (進入系統設定表)

移動鍵盤上下鍵到 2 (Change User Password), 按 Enter :


這張頁面是說待會兒會詢問新密碼, 按 OK :


這時設定表單消失, 回到命令列, 出現 Enter new UNIX password 提示, 輸入 pi 按 Enter 再輸入一次 pi 再按 Enter 就可以了.


設定好後輸入 sudo poweroff 關機, 等 20 秒後拔掉電源重插, 再用 putty 重新連線數莓派, 用新密碼登入系統即可.




2017年1月19日 星期四

一例一休後首次加班

昨晚因為系統切換加班, 到凌晨兩點半才搞定, 實在太睏了, 就在辦公室睡到五點半才回家, 順路買了早餐, 回到家剛好叫姐姐與二哥起床. 還好早上有補休四小時, 等菁菁 7 點上學後就跑回去補眠到 11 點半.

加班真累, 雖然只是敲敲電腦下下指令, 但該睡覺時不能睡覺真痛苦, 不僅頻頻打哈欠, 思考也混亂, 連一起加班對系統非常熟悉的搭襠在下指令時都頻頻出錯. 中年大叔最好別熬夜加班.

2017年1月16日 星期一

如何更改 Easyui Datagrid 的分頁選單 pageList 設定

最近在寫 PHP 專案時發現, EasyUI 的 Datagrid 元件底下的分頁工具列, 控制分頁的下拉式選單如果改成 5 似乎無效, 查閱了使用說明, 發現 pageSize 屬性預設就是 10, 一般設定方式如下 :

    $('#sector_changes').datagrid({
      url:"apps/STOCK.php",
      queryParams:{op:"list_sector_changes"},
      fitColumns:true,
      singleSelect:true,    
      pagination:true,
      pageSize:10,       //只能使用 pageList 裡面的 10, 20, 30, 40, 50
      rownumbers:true
      });

在提供 json 內容的程式中, 預設也是 10 列 :

  case "list_sector_changes" : {
    $page=isset($_REQUEST['page']) ? intval($_REQUEST['page']) : 1;
    $rows=isset($_REQUEST['rows']) ? intval($_REQUEST['rows']) : 10;
    $sort=isset($_REQUEST['sort']) ? $_REQUEST['sort'] : 'trade_date';
    $order=isset($_REQUEST['order']) ? $_REQUEST['order'] : 'desc';
    if (isset($_REQUEST['search_trade_date'])) { //有 search
      $where="WHERE `trade_date`='".$_REQUEST['search_trade_date']."'";
      }
    else {$where="";}
    $SQL="SELECT trade_date,listed_volume_ratio,listed_volume_change,".
         "listed_index_change FROM sector_changes ".$where." ORDER BY ".
         $sort." ".$order." LIMIT ".$start.",".$rows;
    $RS=run_sql($SQL);

其效果如下 :


可正常顯示表格內容

但是在上面顯示類股行情排序的表格中, 我覺得 10 列對一般 22 吋螢幕會太長, 於是就直接將程式中的兩個 10 改成 5, 結果竟然整個表格內容都不見了, 而下拉式選單仍然是以 10 為步階的選單, 可見這樣改是不行的 :

無法正常顯示表格內容


網路上也找到類似問題, 提到要使用 pageList 屬性, 參考 :

# easyui datagrid pagesize设置无效
http://www.jeasyui.com/forum/index.php?topic=3111.0

我找了官方文件, 果然找到這個 pageList 屬性用法, 原來它就是用來設定下拉式選單中的選項用的, 參考 :

http://www.jeasyui.com/documentation/pagination.php

我在程式中添加 pageList 屬性, 包含了 5 這個選項, 這樣 pageSize 屬性才可以設定預設值 5 :

    $('#listed_sector_changes').datagrid({
      url:"apps/STOCK.php",
      queryParams:{op:"list_listed_sector_changes"},
      fitColumns:true,
      singleSelect:true,
      striped: true,
      pagination:true,
      pageSize:5,
      pageList:[5,10,15,20,50],
      rownumbers:true
      });

此外我也參考下列文章添加了 striped 屬性, 它會讓各列以不同背景色交替顯示, 參考 :

http://www.jeasyui.com/forum/index.php?topic=64.0 (Datagrid 顏色交替 striped)


總之, 如果 pageSize 使用了 pageList 中沒有的選項,  Datagrid 就無法正常顯示內容了.

另外, 我也將 sector_changes 資料表大翻新, 原先的結構如下, 我將其中的 Sx, dSx 欄位都刪除 (藍色部分), 只留下 trade_date, volume_ratio, volume_change, index_change 這四個欄位就夠了 :

--
-- 表的結構 `sector_changes`
--

CREATE TABLE IF NOT EXISTS `sector_changes` (
  `trade_date` date DEFAULT NULL,
  `S1` float DEFAULT NULL,
  `dS1` float DEFAULT NULL,
  `S2` float DEFAULT NULL,
  `dS2` float DEFAULT NULL,
  `S3` float DEFAULT NULL,
  `dS3` float DEFAULT NULL,
  `S4` float DEFAULT NULL,
  `dS4` float DEFAULT NULL,
  `S5` float DEFAULT NULL,
  `dS5` float DEFAULT NULL,
  `S6` float DEFAULT NULL,
  `dS6` float DEFAULT NULL,
  `S7` float DEFAULT NULL,
  `dS7` float DEFAULT NULL,
  `S8` float DEFAULT NULL,
  `dS8` float DEFAULT NULL,
  `S9` float DEFAULT NULL,
  `dS9` float DEFAULT NULL,
  `S10` float DEFAULT NULL,
  `dS10` float DEFAULT NULL,
  `S11` float DEFAULT NULL,
  `dS11` float DEFAULT NULL,
  `S12` float DEFAULT NULL,
  `dS12` float DEFAULT NULL,
  `S13` float DEFAULT NULL,
  `dS13` float DEFAULT NULL,
  `S14` float DEFAULT NULL,
  `dS14` float DEFAULT NULL,
  `S15` float DEFAULT NULL,
  `dS15` float DEFAULT NULL,
  `S16` float DEFAULT NULL,
  `dS16` float DEFAULT NULL,
  `S17` float DEFAULT NULL,
  `dS17` float DEFAULT NULL,
  `S18` float DEFAULT NULL,
  `dS18` float DEFAULT NULL,
  `S19` float DEFAULT NULL,
  `dS19` float DEFAULT NULL,
  `volume_ratio` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '類股成交量比重排序',
  `volume_change` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '類股成交量增減幅度排序',
  `index_change` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '類股指數漲幅排序'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

2017 年第 2 周記事 : 二路不見了

週日 (1/15) 下午去文昌帝君廟拜拜回來想說很久沒去跑步了, 就繞產業道路跑一大圈, 經過山下廟前的農舍時, 赫然發現門口看家的二路不見了, 換成一隻體型較小的黑狗. 二路哪裡去了?

這陣子姊姊要準備學測沒回鄉下, 我跟菁菁就很少去騎腳踏車了, 以前我們都會帶餅乾或不吃的雞肉去犒賞牠, 牠在我們一轉彎映入眼簾就認得我們了, 馬上雀躍起來. 這期間沒去那裏, 不知發生甚麼事, 難道二路逃走了? 還是被毒死了? 鄉下常發生看門狗被小偷毒死之事.

本周開始大掃除了, 鄉下與高雄家都要. 但是東西實在太多, 要花很多時間整理. 每年我都是挑重點 (浴室, 前後門) 清掃, 弄不完的 .... 就算了.

下周日要參加堂叔的分年祭祀, 週六要提醒爸買供品, 時辰很早, 拜完順便去市場買菜. 猶記得前年堂叔才來參加母親的分年祭祀, 沒想到才兩年不到, 他也不在了, 人生無常, 就是這樣啊.

今天早上去茶水間倒茶時突然想到, 不管這一天會是如何 (無聊的開會, 麻煩的工作 ...), 在人生中都是絕無僅有的一天, 每一分鐘都非常寶貴, 千萬不要在怨嘆中度過. 不同的態度決定了我們過的是怎樣的的人生.

我若知道 2014 年是母親要走的那一年, 我會把 30 天的假全部都在上半年請完, 陪她到處去玩. 而心肌梗塞突然走的堂叔, 如果知道那是他的最後一天, 該會把爸爸節聚餐提前一天舉行吧! 但是上帝不會讓任何人偷看劇本.  這就是人生.

2017年1月12日 星期四

Hostinger 主機運算資源限制與 PHP 效能優化

前天打開我的 Hostinger 網頁時發現如下畫面 :


之前也偶而出現過, 只要過一會兒就消失了, 但這回卻很久一段時間都如此, 所以我發了一封信去問客服, 答覆如下 :

Hello Tony, Your website exceeded the number of allowed processes: "user: u125672222 (125672222) Limits reached - numproc: 27/20". We use the 'CloudLinux' system to ensure that no single hosting account can monopolize server resources to the detriment of any other. If your site is consistently hitting its resource limits, we recommend you to consider optimizing your website so it would not use so many resources. However, if you need more server resources please consider upgrading your plan to "Business" or "VPS" plan to avoid such problems in the future. Regards, Catalin R. Customer Success team http://www.hostinger.co.uk

甚麼是 CloudLinux 呢? 我查 Google 找到這位威廉獅的文章 :

# 不要用Serverzoo 提供的CloudLinux 的五大原因 Linode 強大VPS 資源為你解密
# CloudLinux 的中文說明,解釋,使用和初步認知教學

他介紹的是日本虛擬主機商 Linode.com 提供的進階的 CPanel 管理功能 (貴啊), 可以看到網站的資源耗用情形. 但是我問了 Hostinger 客服, 他們沒有提供這些功能, 所以無法得知耗用狀態. 難道真要再花錢升級到 Business 或 VPS 方案嗎?

昨天用 Live Chat 跟 Hostinger 的客服 Philip 請教了關於 Resource Limit 的問題, 他說 Premium 與 Business 的 hosting plan 都有運算資源限制, 只有升級為 VPS 才不受限制 (貴啊). 他們沒有如 linode.com 那樣的資源檢視網頁, 只提供 phpinfo 頁面, 但一大堆表格我根本不知道哪個才是跟 Resource 相關的, Philip 因此截了一張圖給我 :


原來就是在 Core 項目下面.

沒辦法, 只好照 Hostinger 客服建議回頭檢視自己的程式碼, 看看是否有哪些地方可以改善. 但我寫程式向來功能優先, 效能其次, 所以從沒在管甚麼優化不優化的. 但現在火燒屁股, 不管也不行了. 於是找到了 Tsung's Blog 的這篇文章 :

PHP 程式效能優化的 40 條建議

裡面洋洋灑灑 40 條準則令人眼花撩亂, 其中有 8 條是比較常見, 而且規則明確可立即改正的, 摘要轉載如下 :
  1. 使用 echo 的多重參數(譯註:指用逗號而不是句點)代替字元串連接 (3)
  2. 註銷那些不用的變數尤其是大陣列,以便釋放內存 (5)
  3. require_once() 代價昂貴 (7)
  4. str_replace 函數比 preg_replace 函數快,但strtr函數的效率是 str_replace 函數的四倍 (11)
  5. 資料庫連接當使用完畢時應關掉 (16)
  6. 盡量不要在 for 循環中使用函數,比如for ($x=0; $x < count($array); $x) 每循環一次都會調用 count() 函數 (19)
  7. 用單引號代替雙引號來包含字元串,這樣做會更快一些。因為 PHP 會在雙引號包圍的字元串中搜尋變數,單引號則不會。當然,只有當你不需要在字元串中包含變數時才可以這麼做 (28)
  8. 輸出多個字元串時,用逗號代替句點來分隔字元串,速度更快。註意:只有 echo 能這麼做,它是一種可以把多個字元串當作參數的“函數”(譯註:PHP 手冊中說 echo 是語言結構,不是真正的函數,故把函數加上了雙引號) (29)
其中第 6 條 (原文第 19 條) 關於在迴圈中呼叫 count() 或 sizeof() 函數以取得陣列長度的作法, 正是我常犯的毛病, 檢視專案原始碼, 每一支程式都違反這條規則, 例如 :

$SQL="SELECT * FROM `stocks_list` ";
$RS=run_sql($SQL);
for ($i=0; $i<count($RS); $i++) {
  .....
  }

像這樣如果有 1000 筆紀錄, 則 count() 函數會被呼叫 1000 次, 造成無謂的資源耗用 (記憶體, 執行時間), 應該改成下面做法 :

$SQL="SELECT * FROM `stocks_list` ";
$RS=run_sql($SQL);
$cnt=count($RS);
for ($i=0; $i<$cnt; $i++) {
  .....
  }

這樣 count() 函數只被呼叫一次而已.

其次是第 2 條 (原文第 5 條), 占用大量記憶體的變數不用時就該設為 null 以便盡速釋出記憶體, 特別是陣列, 儲存大量文字的變數, 例如檔案讀取函數讀進來的資料, 或者利用 cURL 函數從網路中下載的網頁檔.

最後是第 3 條 (原文第 7 條), 盡量用 include 與 require 取代 include_once  與 require_once, 因為後者在程式執行時要額外花時間與資源去檢查是否有重複載入情形, 會拖慢效能. 參考 :

include 與 require 的差別
[PHP]require 和 include
談php的include和require

上面列舉的這三項我都違反, 特別是前兩項我認為影響效能與資源耗用甚鉅, 所以這兩天就地毯式地把專案中的每一支程式全部爬梳一遍, 上傳伺服器更新後似乎有效, 至少今天比較少看到 Resource Limit Exceeded 回應了.

關於 PHP 程式碼優化, 下面這網頁列出的 15 的準則更明確, 尤其是資料庫連線用完就要關掉, 但是以前書上卻說程式執行完會自動關掉, 所以打算修改 mysql.php 函式庫, 在每個資料庫操作結尾處加入 mysqli_close($conn) 指令.

# 15 Tips to Optimize Your PHP Script for Better Performance for Developers (15 個優化原則)

其他參考 :

# What are some good PHP performance tips?
Hacking with PHP

2017-01-14 補充 :

今天依照上面 15 個優化準則的第 10 條 (Close the connection), 在資料庫存取函式庫 musqli.php 中添加 mysqli_close($conn) 指令, 每次存取資料庫完畢時都將連線關閉, 以節省記憶體.

準則 2 建議字串應該使用單引號, 避免使用雙引號, 因為使用雙引號時 PHP 解譯器必須花時間去檢查裡面是否有夾帶變數, 而單引號不會. 如果需要嵌入變數就用點符號串接.

準則 3 建議以 "===" 取代 "==" 進行等值比對, 因為前者檢驗條件較嚴格 (包含型別), 因此速度較快.

準則 4 建議能用 strtr() 達成功能要求的話就不要用 str_replace() 或 preg_replace(), 因為 strtr() 速度比 str_replace() 快四倍, 更別說使用正規運算式的 preg_replace() 了. 不過下面這篇文章經過實測發現, str_replace() 還是字串取代的首選 :

# php:strtr, str_replace和preg_replace的效率对比
# 比较strtr, str_replace和preg_replace三个函数的效率_php技巧
# php字符串替换函数str_replace速度比preg_replace快

最後一個原則 (Use isset) 也很有用, 就是在判斷是否為空字串時會用 strlen(), 在判斷陣列是否為空, 或函數或資料庫存取是否有傳回值時, 我們一般會用 count(), sizeof() 等函數, 但這些函數需要額外運算, 比較耗費資源, 應該改用 isset() 函數. 當然若需要取得資料長度時, isset() 就派不上用場了.

2017年1月9日 星期一

棄 Java 擁 Python

最近在 Tsung's Blog 看到這篇 :

# Oracle 取締未經適當授權的 Java 用戶

自從 2009 年甲骨文 (Oracle) 要併購昇陽 (SUN Micro System) 的消息曝光後, 我就對 Java 的未來就感到不樂觀, 一部分是因為我對 Oracle 的老闆 Lary Ellison 的印象不是很好, 但最重要的原因是 Java 作為一個廣受產業界與學術界歡迎, 最有影響力的 OOP 語言, 如果主導權落在一個完全商業導向的公司手裏, 那它的 Open Source 精神還能維持多久? 參考 :

我看 Java 的過去與未來
Oracle比Sun更聰明?細看JCP及Java的未來之路
Flash 未死Java 先躺平,Oracle 未來不再推出Java 瀏覽器外掛

另外一個原因是 Java 的版本問題. 公司有多套系統採用 Java, 但是使用的 JVM 版本不同, 同一台電腦上必須安裝不同版本的 JVM, 照理說各軟體會選擇對應的版本, 但問題就是只有一個能運作, 其他的都不行. MIS 的人研究了好久還是沒輒. 很諷刺的是, 標榜跨平台的 Java 竟然連自己的版本差異都跨不過去 (當然有可能是我們對設定不夠了解之故).

在 Google 決定採用 Java 作為其 Android 作業系統的語言後, Oracle 就併購了 Sun, 並隨後對 Google 提起了長達六年的侵權訴訟, 但最終 Google 獲得勝訴. 這件官司也讓 Google 決定未來的 Android 版本不再採用 Java JDK, 改用 OpenJDK, 而且開發語言也要從 Java 改為蘋果的 Swift 語言, 參見 :

# Google 贏了 Oracle 官司!使用 Java 合理合法
Google 宣布:下一代 Android 系統不用 Java!
Google 受夠了 Java,將改用 Apple 推出的程式語言 Swift

對於這種發展我不樂見, 因為 Java 是我第一個認真學習的程式語言 (在它還不是很紅的時代), 多年後還被公司要求去考了 SCJP 6 認證, 具有一種革命情感, 看到它即將被 Oracle 玩死, 心裡有一種莫名的哀傷 (同樣的喟嘆也發生在 PHP 的絕配 MySQL 上).

其實我在兩年前還在學習 Java 的 Swing. 後來由於 GAE 的關係, 我的興趣轉到 Python, 由於 Python 的簡潔俐落以及龐大的套件支持, 對比之下使我漸漸對 Java 的繁瑣擁腫感到有些不耐. 我非常看好 Python 的發展, 同樣是跨平台 (但 Java 無法跨到 iOS), 但 Python 仍是生龍活虎的 Open Source 語言, 而 Java 卻暮氣沉沉了.

附帶一提的是, 我雖然不太認同 Lary Ellison 的作風, 但網路上一篇關於 Larry 在耶魯大學的演講卻是捏造的, 不要被騙了 :

史上最狂妄的演讲
歷史上最牛的畢業典禮演說
# 孰真孰假:号称史上“最狂妄”演讲实为世纪骗局?

2017 年第 1 周記事

本周回到鄉下發現菜園的第一批的 20 株玉米高度已經及腰了, 第二批的也約莫 15 公分高, 一個禮拜見一次真的有一暝大一吋的感覺哩. 週日 1/8 早上去買菜時順路經過種子行, 又買了 20 株甜玉米與 2 株花蒲回來種, 花蒲種在瓜棚架那邊, 甜玉米則是種在這兩周整理的兩畦菜圃中的一個, 另外一畦則種秋葵, 但我不確定是否會發芽, 因為那是同事陳小姐去年給我的一包種子, 但我卻一直放在鉛筆盒中.

買菜回來途中想到姊姊即將參加學測, 所以回家後隨即準備水果籃與金香, 前往文昌帝君祠祈福. 回途在十字路口的蔬果店看到寄售的百香果苗, 三盆才 100 元, 店家說是比較甜的品種, 就買回家試試看, 也跟花蒲一樣種在瓜棚架那邊. 本周還真是栽種了不少東西哩! 但是自動灌溉系統到現在都還沒搞定, 殘念!

但是結實累累的紅心芭樂卻因為太重而從接枝處整個折斷, 可能接枝比較脆弱, 加上結果太多不堪負荷之故. 之前就有聽隔壁芭樂園阿伯說要進行剪枝與汰除, 但是實在捨不得把過多的果實摘除一些, 結果變成這樣. 可見婦人之仁有時會鑄成大禍, 狠心反而是一種大愛.

菜園中的萬惡雜草香附子又在恣意蔓延, 我之前有記錄過此種極為難纏的惡草, 參考 :

http://yhhuang1966.blogspot.tw/2016/02/2016-8.html

香附子難搞的是地底下有塊莖像神經叢一樣到處滋長, 只將地表的草除去是沒有用的, 如果用鋤頭將其砍碎結果會更糟, 如同將地瓜砍成數片, 每一片都會發展新生命一樣, 會越鋤越多. 爸說有兩個辦法可以根除它 :
  1. 浸在水裡 : 例如種水稻或水芋, 塊莖長久泡在水裡會爛掉.
  2. 日照曝曬 : 地毯式挖出塊莖, 在日照下離土曝曬.
這兩周以來我每周日下午都鋤一小塊地, 約莫半公尺見方, 挖深 30 公分, 把香附子塊莖一個個揪出來, 然後倒在柏油路上任來往車輛輾壓與炙烈太陽曝曬, 不出三天全都變成乾草矣. 只是這需要一些時間, 而且總會有漏網之魚, 只要再次出現就必須盡早挖出根除.

週四早上參加了一個線上研討會, 請到資料探勘專家尹相志先生來演講人工智慧的應用, 中途休息時我線上請問了尹先生是否已有 "意識引擎" 這東西, 因為我認為機器人要能像人一樣思考, 必須模擬人類心智活動才能辦到, 而這類資訊目前還很少. 尹先生認為這部分雖然有人在研究, 但還屬於 "玄學" 的層次, 在我們有生之年可能還看不到. 是啊, 意識這東西太複雜了, 還無法以數學來建構一個模型去模擬. 不過目前深度學習倒是可以在人機對談中揣摩 "意圖 (Intention)". 他表示中國在人工智慧領域非常熱中, 台灣在這方面比起來似乎投入不夠多, 有點可惜.

本期商周 (1520) 有一篇蘇宇庭撰寫的 "科技巨頭的AI新武器:聊天機器人", 裡面有訪問尹相志談到企業可利用聊天機器人蒐集用戶的 "小數據", 這會比以廣度為主的 "大數據" 更有機會找出具有潛力的商業模式, 因為透過聊天機器人可以對單一客戶的理解達到前所未有的 "深度". 尹相志是台大化學系, 新聞研究所畢業的 (彭文正教授的學生), 2001 年創立亞洲資採國際公司 (AsiaMiner), 旗下有超過 20 位現在炙手可熱的資料科學家. 他從一位門外漢透過自修成為資料採礦專家的經歷足以為所有領域的門外漢效法. 參考 :

# DigiTime : 亞洲資採國際
# dcplus : 尹相志
21世紀最性感的工作 資料科學家

好書 : 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() 函數了.