如果說
三大 regexp 句型 是 「懶得學 perl 程式語言的系統管理員必學的三句 perl」,
那麼今天要介紹的就是第四重要的 perl 句型:
perl -F, -nale 'print join(", ", @F[2,5,6])'
姑且就稱它為 perl 的 csv 句型吧。
它跟文字瀏覽器 lynx 合作, 可以把網頁的表格抓下來變成試算表。
當你不想為了一點小事開啟 calc 時, 就用它們來馴服 .csv 檔吧。
我們拿台積電歷年每季財務報表摘要來練習。
先把網頁原始碼抓回自己的硬碟, 才不會一直製造無謂的網路流量:
lynx -source 'https://goodinfo.tw/StockInfo/StockBzPerformance.asp?YEAR_PERIOD=9999&RPT_CAT=M_QUAR_ACC&STOCK_ID=2330' > 2330.html
然後可以用 w3m 2330.html
或 lynx 2330.html
檢視確認。
要把 html 檔變成人眼可讀的文字檔, 可以這樣下:
lynx -dump 2330.html
。
注意它會把超連結變成像是參考文獻一樣, 用方括弧數字來表達;
可是我們並不需要超連結啊。
而且, 因為表格太寬, 螢幕放不下, 所以每一列被斷成兩列。
改這樣下好了: lynx -dump -nonumbers -nolist
-width 999 2330.html > 2330.txt
。
如果頁面太長, 只想擷取中一個表格,
而且從原始碼可以看出如何用 css selector 定位你要的表格,
那麼可以先用
extract.php 做前置處理。
以眼前的例子來說, 可以先
extract.php -w -s 'table.solid_1_padding_4_0_tbl'
< 2330.html > table.html
也許你會想說那我們就 extract.php 一路用到底好了。
早些時候我也是這麼想, 畢竟都已經那麼辛苦地寫好 extract.php 了,
當然要像擠柳丁汁一樣把它最後一滴的功能都榨出來。
可以用 extract.php -w -s
'td:nth-child(4)' < table.html |
lynx -dump -nonumbers -nolist -stdin
把第四欄 (股價) 抓出來, 其他各欄如法泡製,
然後再想辦法 (用 paste 指令) 把它們橫向黏貼在一起。
我真的這樣做過幾次, 但這太囉嗦了,
尤其是 paste 時也會怕怕的, 擔心會不會錯位。
所以呢, 倒帶兩段, 還是回到 2330.txt 。 我們用 perl 的 csv 句型, 簡單多了。 它預設處理的不是 csv 格式, 而是 「以空白分格的欄位」, 正好符合我們的需求。
- 先抓季度名稱、 平均股價、 EPS 三欄就好:
perl -nale 'print "$F[0] $F[4] $F[20]" if /^\s*20\d\dQ\d/' 2330.txt
注意欄位從第 0 欄開始數起。 F 是固定的一個內建變數名稱。 - 同樣的效果, 換個寫法:
perl -nale 'print "@F[0,4,20]" if /^\s*20\d\dQ\d/' 2330.txt
只要是常用的功能, 在 perl 裡總是找得到簡寫的方式。 - 連續欄位可以用 .. 簡寫:
perl -nale 'print "@F[0..4, 20..22]" if /^\s*20\d\dQ\d/' 2330.txt
- 如果列印時想改用逗點作為分隔符號, 產生真正的 csv 檔, 就用 join():
perl -nale 'print join(", ", @F[0..4,20..22]) if /^\s*20\d\dQ\d/' 2330.txt > 2330.csv
- 如果是從 csv 檔讀資料, 就加上 -F, 選項:
perl -F, -nale 'print "@F[0,4,5]"' 2330.csv
注意: -F, 不能寫在 -nale 後面 (主要是因為 -e 後面馬上要接程式碼) - 甚至可以直接運算。 例如想要驗證一下我所理解
(但幾乎查不到文件) 的近似公式: ROE 大約等於 EPS / BVPS :
perl -nale 'printf("$F[0] %.2f\n", $F[20]/$F[22]*100/$F[16]) if /^\s*20\d\dQ\d/' 2330.txt
注意: 一旦改用 printf 而不是 print , 那麼就要自己補上換列字元。
Perl 萬歲! 感恩 Larry Wall 大大, 讚嘆 Larry Wall 大大!
光是學會這個句型, 以後凡是遇到 .csv 檔,
大概就有九成的機會可以省下許多寫程式的時間。
(你可以估計看看用 python 寫相同的功能需要多久。 更別提 java 了。)
懶惰是資訊人應有的美德; 最強的程式, 就是用丟即棄、 看起來像是命令而不像程式的程式。
命令列上的 -nale 選項各自是什麼意思, 其實不需要深究;
但如果你受到強大 perl 的震撼與感動, 可以查看 man perlrun
在裡面按 "/^ *-a" (搜尋 -a 選項的說明)。
註: 以上語法細節不需要記憶。 懶惰是資訊人應有的美德。
有印象它能做哪些事就夠了。
有需要時再搜尋 「perl csv 句型」 回來這裡抄指令去改。
應該有讀者快受不了 貴哥的銅臭味 了。 可是同學, 這篇的重點真的是 perl 跟 csv 而不是股票啊~ 好吧, 那我們就跳脫紅塵俗世, 改拿天上的星星來做最後的應用實例好了。 下一篇我們想研究 太陽系裡所有的天然衛星的軌道特性。
wget -O sat.html https://en.wikipedia.org/wiki/List_of_natural_satellites extract.php -w -s table.sortable < sat.html | lynx -dump -stdin -nolist -nonumbers
以上是概念性、 原始的想法。
但是這個檔案裡有些欄位內含空格 (衛星名稱),
有些欄位內含逗點 (軌道半長軸、 發現者姓氏),
會讓我們數錯欄位。 遇到這麼複雜的表格,
還是先找專業的程式幫我們處理第一步好了:
sudo npm install html2csv -g
把 html2csv 安裝起來。
執行 html2csv sat.html
會產生很多個 csv 檔, 我們要的是其中的 06.csv。
但是, 到了這個地步才要處理資料欄位裡面的逗號跟空格, 就太晚了,
尤其現在又多了雙引號, 問題更麻煩。 所以要:
- 先在 html 檔裡面 (1) 把數字之間的逗點刪掉
(2) 把其他逗點 (及後面跟著的空格) 改成中文全型逗點:
perl -pe 's/(\d),(\d)/$1$2/g; s/, */,/g;' sat.html > s.htm
rm -f 0?.csv ; html2csv s.htm
- 刪掉數字欄位裡的 「± ...」、 「–...」、 「(r)」、 「~」、 空格;
把其他空格通通改成底線; 同時刪掉文字欄位的雙引號: 略過空白列:
perl -pe 's/\(r\)//; s/(±|–)[^,]*//g; s/(\d)\s+,/$1,/g; s/ /_/g; s/["~]//g; ' 06.csv | grep -Pv '^\s*$' > s.csv
- 現在終於可以用 perl 的 csv 句型抓出有興趣的欄位:
perl -F, -nale 'print join(", ", @F[2..5,10])' s.csv > satellites.csv
然後就可以拿 satellites.csv (略經手工編輯過) 來進行 天體運動研究~ 期待啊~ (咦?)
沒有留言:
張貼留言
因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。