【祿】邁向財務「自由」之路 II — 育成收發 Telegram 的智能秘書(上篇)

筆者的智能秘書已從只懂發出電郵,進化到懂得接收及發出 Telegram 短訊了 :D

筆者去年發表 Step-by-step 開發 Google Sheet 智能秘書的教學文章,利用 ImportXML 和簡單兩句發送電郵的程序,自動定時提示六合彩下期獎金,幸獲不少讀者的好評。

然而 Google Sheets + Email 這個組合也有不少短處和限制,也許嘗試過一起動手做的讀者也曾感受一二:
  • ImportXML 函數擱取資訊的表現不很穩定,對某些網站的標的偶爾會傳回 #N/A
  • 基於試算表格式的限制,只能擱取資訊的原型,如要進一步處理資訊會頗為麻煩,例如參考筆者回覆讀者 Nelia 的提問,在擱取牛記匯率時需要將幾幅圖片蘊含的數字以及小數點併湊在一起,要在 Google Sheets 處理便很費時失事
  • 資訊的長度不一(也可能是動態),例如不同股票的派息紀錄,長度和所載資訊也不一,很難用統一的試算表格式處理
  • Google Script 每日有頗低的運算資源上限,筆者曾嘗試開發股價數據資料庫,但每 15 分鐘處理一隻股票也爆錶
誠如筆者在半桶水爸爸網誌留言,程式碼是現代語言,編程是現代人不能不認識的技巧,所以筆者把心一橫,將智能秘書的核心技術由 Google Sheets + Script 改寫為 Python 並放上雲端運算,同時賦予秘書收發 Telegram 短訊的能力,一次過解決以上問題。

這回讓筆者以牛記匯率為例,實戰如何用幾句簡單的程式碼擱取網路資訊,並以自己喜歡的方式呈現出來。上篇主要集中討論擱取資訊的技巧,而下篇則集中討論資訊的展現、與 Telegram 的聯繫,以及如何將程序放上雲端運算。

跟去年文章一樣,擱取的對象只是示例,筆者旨在刺激讀者思考,一理通百理明。另外,請注意網絡資訊的版權屬於相關資訊提供者,本篇及下篇文章只作教學及示範用途。


上篇:利用 Python 及其程序包 Beautiful Soup 輕鬆取得並整理匯率資訊


1. 準備開發環境

首先需要準備的是 Python 的開發環境。現在的開發者有福了,因為 Google 最近支援雲端 Python Notebook,也 Pre-install 了一系列常用的程序包(包括下文提及的 requests 和 BeautifulSoup),甚麼安裝也不用做已經可以著手開發自己的程序。Google 用戶只需到 Co-laboratory (colab.research.google.com),然後新增一個 Python 2 Notebook 便可。稍後開發完成,只需把整個程序碼檔案下載(「File」->「Download .py」)便行,方便得不得了。

如果希望在自己的電腦上進行開發,Mac 的用家會比較方便,因為 MacOS 已內置 Python 2.7,直接在 Terminal 輸入 Python 便能進入環境。Windows 的用家也許需要 Google 一下如何安裝 Python 2.7,反正網路上有海量資源,就當成是第一個練習吧!另外,我們會用到兩個程序包(Package),分別是 requests 和 BeautifulSoup(留意在 Python 中,大小寫是有分別的)。安裝 Python 後,我們可以在 Mac Terminal / Windows Command Prompt (往後簡單以 Terminal 代表)用 pip 命令安裝這兩個程序包(需要網路連線): 

  $ pip install requests
  $ pip install beautifulsoup4 

留意 $ 號並不是輸入,而是 Terminal 自動生成的提示碼。至於關於這兩個程序包的詳細資訊,可以參考: 

進入 Python 開發環境(在 Google Co-laboratory 新增 Python 2 Notebook,或者在 Terminal 輸入 $ python,此時提示碼變成 >>> 代表進入成功,否則安裝可能出了問題),輸入以下的程序碼以載入兩個剛下載了的程序包。 

  >>> import requests 
  >>> from bs4 import BeautifulSoup 

留意在 Google Colaboratory 需要按 Shift + Enter 去執行程式碼。 

如果沒有輸出(Terminal 的話,提示碼維持 >>>),代表成功安裝,可以進入下一個步驟。

往後的討論主要假設以 Google Colaboratory 作為開發環境,因為相信有能力操作 Terminal 的讀者應該有能力找到相應的處理。 





2. 探索牛記匯率網頁原始碼 

這一個步驟所用到的技巧,跟系列上一篇探索馬會六合彩網頁的技巧類似。 

讓我們先在瀏覽器探索一下。打開牛記發佈匯率的網頁(http://www.nkcl.com.hk/change-ch.php),利用 Inspect Element 並從原始碼檔了解匯率的排列方式,不難發現其頁面的結構相對簡單: 
  • 所有的匯率資訊都放在頁面上第一個 <table> 裏面
  • <table>的首兩行(<tr>)是標題,並沒有匯率資訊的存在
  • 從第三個 <tr> 開始,每行有四格( <td>)。第一格是國旗和貨幣全名,第二格是貨幣國際通用代碼,第三格是買入價,最後一格是賣出價。
  • 買入價和賣出價以圖片而非文字表示。圖片的名稱(即 <img src="xxx" /> 的 xxx)與其標示數字相符,例如 1.png 代表數字「1」。小數點的圖片名稱則是 vir.png。
  • 我們希望取得的是貨幣代碼、買入價和賣出價,當中後兩者以數字而非圖片表示,並以有條理的方式(例如列表)儲存好。
有了以上對目標擱取資料的認識,已經足夠完成任務了。接下來,只剩下將以上各點編寫成程序了!




3. 將對標的物的描述換成程式碼 

3a. 下載目標網頁並建立結構方便資訊擱取 

回到 Python 開發頁面。第一步是讓程序「瀏覽」即下載目標網頁。先定義我們的目標網頁網址:

  url = "http://www.nkcl.com.hk/change-ch.php" 


按 Shift + Enter (下文略),此時應該沒有輸出。

利用 requests 程序包,將網頁內容從互聯網下載並讀進 r 物件內(r 的名稱可以自定義,另外這一步很明顯需要網絡連線):

  r = requests.get(url).content

此時如果我們檢視 r 的內容(可以用 print r),就會發現 r 只是目標網頁的原始純文字檔;此時對於 Python 來說,文字檔只是一堆沒有意義的符號的集合。




要為 r 賦予意義,即要讓 Python 懂得閱讀這純文字檔的結構,例如讓它知道哪一個 <table> 裏面有哪一些 <tr>,<tr> 裏面又有 <td> 這些從屬關係,而每一個 tag 各自有其標示的屬性,就是簡單而強大的工具包 BeautifulSoup 的工作了。只消以下一句簡單程式碼,即能為 r 賦予意義,回傳的是經過整理、蘊含所有屬性和從屬關係的 BeautifulSoup 物件「Soup」:

  Soup = BeautifulSoup(r, "html.parser")

此時如果讀者用 print Soup 的話,見到的也只是同一個原始碼檔;但請放心,只要沒有 Error 跑出來的話,Python 已經明白了整個 HTML 檔的結構了!有了這個經整理的 Soup ,我們就可以就著對所要求標的物的認識,輕鬆從中尋找並擱取所需資訊。

(至此可能對沒有編程經驗的讀者來說是有點抽象,但只要繼續閱讀下去相信讀者必定能找到端倪,加油!)



3b. 鎖定網頁的資訊目標

就着第 2 節擱取要求的第一點「所有的匯率資訊都放在頁面上第一個 <table> 裏面」,我們要尋找的是 Soup 內第一個 <table> 物件。要取得第一個物件,方法是利用 find 方法:

  TargetTable = Soup.find("table")

所回傳的 TargetTable 其實是一個較小的 BeautifulSoup 物件,內含網頁中第一個 <table> 至 </table> 內所有的屬性和子物件。用一下 print TargetTable 看看,應該就能讓讀者安心了。由於我們需要的匯率資訊都在這個 <table> 內,有了 TargetTable 便能方便收窄其後程式碼的搜索範圍。 





就著要求第二點「<table> 的首兩行(<tr>)是標題,並沒有匯率資訊的存在」,我們要尋找 TargetTable 內所有 <tr> 物件,並去除首兩行。

要取得所有物件,方法是利用 find_all 方法:

  AllRows = TargetTable.find_all("tr")

所回傳的 AllRows 是一個 Python 的列表(List)物件,這個 List 的每一個元素(Elememt)各自是一個 BeautifulSoup 物件,每一個 BeautifulSoup 物件對應在 TargetTable 內找到的一個 <tr>...</tr>。即在概念上,AllRows = [ Soup_tr1, Soup_tr2, ..., Soup_trN ]。

至於除去首兩行,即在 Python List 中除去首兩個 Elements,則是輕而易舉的事:

  TargetRows = AllRows[2:]

留意上述數句的程式碼其實可以簡單一行輕鬆完成: 


  TargetRows = Soup.find("table").find_all("tr")[2:]

這是由於 find 方法接受一個 BeautifulSoup 物件後,回傳的又是另一個BeautifulSoup 物件。這種連鎖寫法,簡單方便直接之餘,又能方便人類閱讀(很容易就能理解為「TargetRows 就是在網頁 Soup 內找第一個 <table>,然後在內找所有 <tr>,再回傳除首兩項外所有的 <tr> 項目」),這就是 Python 之美。



3c. 建立「字典」儲存匯率資訊


接下來我們就可以從每一個 <tr> 行(即 TargetRows 裏面每一個 Element),擱取需要的貨幣代碼、買入價和賣出價。但在討論下去之前,我們先要考慮一下在擱取得實際資料後,我們打算如何有系統、有條理地儲存起來,方便發送 Telegram 短訊的部分使用。

我們的目的,是建立一個「字典」,可以讓我們利用外幣的代號,查詢其買入和賣出價。如果這個字典叫做 FXDatabase,我們希望它讀畢網頁的資訊後,效果如下:

  print FXDatabase['GBP']['Buy']
  10.01

  print FXDatabase['GBP']['Sell']
  10.06

  print FXDatabase['CHF']['Buy']
  7.84

  print FXDatabase['CHF']['Sell']
  7.96

在 Python 裏的確有一種叫做「字典」(Dictionary)的物件,能不費吹灰之力就能做到上述效果。如果我們一早知道英鎊和瑞士法郎的匯價,以上的字典可以如此定義:

  FXDatabase = {
    'GBP' : {'Buy' : 10.01 , 'Sell' : 10.06 },
    'CHF' : {'Buy' : 7.84 , 'Sell' : 7.96 }
    }


留意在 Python 中,字典是以花括號 { } 定義的;另嚴格來說,FXDictionary 是「字典的字典」(留意兩層花括號結構)。

但現在的問題是,我們未讓程式「閱讀」網頁前,並不知道每一種外幣的匯價,甚至連有甚麼外幣提供也不知道(為增加程式的可用性,請盡可能不要將外幣列表手動抄下來然後手動的對號入座,試想像牛記可以隨時更改其可供兌換的貨幣種類)。所以,接下來我們得讓程式從每一個 <tr> 行擱取所需資訊,並續一寫進我們的字典。

要這樣做,我們得先建立一部「空字典」:

  FXDatabase = {}

然後就可以開始命令程式從每一個 <tr> 行進行閱讀並寫進字典裏。要指令程式將 TargetRows 的每個 Element 執行一遍,先要定義一個 for 迴圈:

  for Row in TargetRows:

此時請不要按 Shift + Enter,改用 Enter 直至整個 for 迴圈編寫完畢。同時請留意,Python 是用縮排(Indentation)去區隔不同程序碼的集合的,所以由下一行起,所輸入的在 for 迴圈內的程序碼,請謹記在前面加相同數目的空格。

好了,第一個我們希望取得的資訊是「貨幣國際通用代碼」,存在每一行的第二個 <td> 裏。由於 TargetRows 是一堆 BeautifulSoup 物件的表列,所以 Row 就是一個 BeautifulSoup 物件,可以用我們之前使用過的方法處理。聰明的讀者應該已經想到,可以利用 find_all 函數將所有 <td> 找出來:

  TDs = Row.find_all("td")

然後告訴 Python 需要 TDs 的第二個 Element (注意第一個 Element 的 Index 為 0、第二個 Element 的 Index 為 1,如類推):

  TDCurrency = TDs[1]

此時,TDCurrency 所包含的就是我們目標的那一個 <td> 格的 BeautifulSoup 物件,所以當然也包含了定義該 TD 相關屬性和其他子物件。例如,在英鎊匯價的那一行,我們得到的將會是(部分從略):

  TDCurrency = '<td style="..." width="10%">GBP</td>'

我們需要的,就只是 TDCurrency 裏顯示出來的文字部分,即上面的「GBP」三個字。從 BeautifulSoup 物件取得顯示文字的方法是 text。筆者再後加一個 .encode(‘utf-8’),因為網頁是用 UTF-8 編碼寫成,如此便能讓 Python 對擱取的文字進行相應的解碼:

  Currency = TDCurrency.text.encode('utf-8')

當然,以上的幾句程序可以簡潔的用一行表達:

  Currency = Row.find_all("td")[1].text.encode('utf-8')

至此貨幣代碼擱取完成。之後便輪到買入和賣出價,分別是 <tr> 的第三和第四個 <td>:

  TDBuy = Row.find_all("td")[2]
  TDSell = Row.find_all("td")[3]

由於牛記匯率很聰明地利用圖片而非文字表示匯率,所以我們不可以直接用 text 方法取得匯率資訊。我們得額外做一些功夫,將 <td>...</td> 內的資訊換成是匯率數字。為了不打亂我們的邏輯思考,現在讓我們先假設有一個叫做「GetFX」的自定義的函數,能做出這樣的轉換:

  Buy = GetFX(TDBuy)
  Sell = GetFX(TDSell)

至此我們已經取得所需要的三項資訊,即「貨幣代碼」Currency、「買入價」Buy 和「賣出價」Sell,可以將資料寫入預先建立的字典:

  FXDatabase[Currency] = { 'Buy' : Buy, 'Sell' : Sell }

好了, for 迴圈要執行的動作已編寫完成。但請先不要按 Shift + Enter 執行,因為不要忘記我們其中一步假設了「GetFX」自定義函數的存在,強行執行只會招致程式錯誤:



(在 Terminal 開發的朋友,對不起了 :P)




3d. 建立函數將圖片換成文字再換成數字匯率

現在讓我們先離開 for 迴圈的編寫,在 Google Colaboratory 建立一個新的 Code Cell 去定義 GetFX 函數。前文說過, GetFX 的作用,是將擱取到的 <td>...</td> 內的資訊換成是匯率數字。例如,行文之日,英鎊買入價為 10.01,從上面的程序我們將會讀到(部份從略):

  TDBuy = '<td align="center" style=...>
           <img src="images/1.png"/>
           <img src="images/0.png"/>
           <img src="images/vir.png"/>
           <img src="images/0.png"/>
           <img src="images/1.png"/></td>'

而現在我們就要編寫一個小函數,將 TDBuy 中間的 <img> 煎皮拆骨,使得:

  print GetFX(TDBuy)
  10.01

這回請容許筆者先展示完成的函數,再逐行解釋:

  def GetFX(TD):
      FX = ""
      for ImgTag in TD.find_all('img'):
          Src = ImgTag['src']
          ImgName = Src[(Src.find("/")+1):Src.find(".png")]
          Digit = "." if ImgName == "vir" else ImgName
          FX = FX + Digit
      return float(FX)

  • def GetFX(TD):
    在這行我們定義一個名為 GetFX 的函數,接受一個叫做 TD 的 BeautifulSoup 物件作為參數。TD 就是我們在上面 for 迴圈中傳入蘊含匯率圖片的 <td>...</td> BeautifulSoup 物件。
  • FX = ""
    由於我們要將每一張圖片逐一幻化成文字(每一張圖片將得到一個數字符或小數點),繼而合併再變成匯率數字,而我們不知道 <td> 中圖片的數量,所以我們又得利用 for 迴圈,而在迴圈之前我們先定義一個空字串去裝著逐一得到的文字。
  • for ImgTag in TD.find_all('img'):
    建立 for 迴圈,讓程序將 <td> 內找到的所有 <img> 也逐一執行一次以下的程式碼,就是從 <img> 中抽取所需的字符。
  • Src = ImgTag['src']
    字符存在 <img> 的檔案名稱內,所以我們感興趣的是 <img> 的 src 屬性。Src 執行的結果就是字串 "images/X.png",而 "X就是我們希望得到的字符。
  • ImgName = Src[(Src.find("/")+1):Src.find(".png")]
    要從 "images/X.png" 抽出 "X",方法就是找出字符 "/" 和 ".png" 在 Src 的位置,然後在 Src 本身取得相應位置的字符。這方法跟 Excel 中的 MID() 和 FIND() 混合使用的原理是一樣的。
  • Digit = "." if ImgName == "vir" else ImgName
    這步很容易明白,如果讀到的圖片檔名是 "vir",代表它是小數點,應以 "." 取代之;否則所需字符就是圖片檔名本身。
  • FX = FX + Digit
    將新得到的字符置於代表整個匯率的 FX 最後方,然後重新跑一次 for 迴圈去閱讀下一個字符,直至 <td> 內所有 <img> 完成為止。
  • return float(FX)
    最後,將得到的整個匯率 FX(字串)利用 float 函數轉為數字,並作為整個 GetFX 函數的結果回傳(return)。

現在我們只需先執行這個函數的 Code Cell 將其定義,然後再返回上面的 for 迴圈再執行一次,Error 就會消失。雖然 for 迴圈沒有回傳,但我們的匯率字典已經在瞬間編寫完成了!

如果不相信的話,可以試著執行以下的指令:

  print FXDatabase
  print FXDatabase['CHF']
  print FXDatabase['GBP']['Sell']


雖然開發過程好像很漫長,但回首一看,我們只是用了短短 22 行的程式碼,已經取得了所需的資訊,並有系統地儲存好。 

以下列出本篇訖今整個程序碼,Google Colaboratory 的使用者可以下載 .py 文檔再加以修飾得到;使用 Terminal 的開發者可以另開新純文字檔,將我們的 Python 程序碼儲存成以下 Python Script 檔案:

  ## NgauKeeFX.py

  import requests
  from bs4 import BeautifulSoup

  def GetFX(TD):
      FX = ""
      for ImgTag in TD.find_all('img'):
          Src = ImgTag['src']
          ImgName = Src[(Src.find("/")+1):Src.find(".png")]
          Digit = "." if ImgName == "vir" else ImgName
          FX = FX + Digit
      return float(FX)

  url = "http://www.nkcl.com.hk/change-ch.php"
  r = requests.get(url).content
  Soup = BeautifulSoup(r, "html.parser")
  TargetRows = Soup.find("table").find_all("tr")[2:]

  FXDatabase = {}

  for Row in TargetRows:
      Currency = Row.find_all("td")[1].text.encode('utf-8')
      TDBuy = Row.find_all("td")[2]
      TDSell = Row.find_all("td")[3]
      Buy = GetFX(TDBuy)
      Sell = GetFX(TDSell)
      FXDatabase[Currency] = { 'Buy' : Buy, 'Sell' : Sell }


至此,上篇要討論的,從網際網路擱取資訊的技巧已完成討論。下篇我們會將擱取到的匯率資訊,以喜歡的方式在 Telegram 短訊呈現。敬請期待!



45 則留言:

  1. 福兄實在太強了,十分有用的教學,會仔細研究!

    回覆刪除
    回覆
    1. 謝謝半桶水爸爸!這個 Time Stamp 似是裝了 Push Notification? 😂

      刪除
  2. 全兄的博學令小弟拜服。。。
    離沙發只有一步之遙。。。這麼近那麼遠。。。

    回覆刪除
    回覆
    1. 其實周身刀但無張利係小弟弱點⋯ 識少少但又未到開班授徒拓展第二收入的級數,只能用來自娛過過癮罷了。

      刪除
  3. 留名先, 我決定等埋telegram先一次過睇+試~
    辛苦晒~ 多謝晒~

    回覆刪除
    回覆
    1. 星尼不用謝!得到你這個留言,很有動力快點完成下篇!

      刪除
    2. 岩岩終於開始左, 開到bot father先知我原來以前都曾經開始過step 1去做~
      google個online notebook真係幾好,雖然佢簡潔到我反而唔係太明點用~
      (一來我成日睇doc都會飛黎睇, 明明福兄已經寫明shift+enter可以試run)
      而家連php都唔駛係local度裝, for development真係anywhere可以做到呢~
      真係遲啲我都無飯開了, 要轉行...><

      刪除
    3. // 岩岩終於開始左, 開到bot father先知我原來以前都曾經開始過step 1去做~

      我好多時都係咁,突然間有個好好的 idea,開發到一半就丟淡了,然後下一次 Pick up 就係有更好的 idea 或者發現了更容易 / 更好的作法。呢次智能秘書,我都係有個 AAStock analyzer 寫到一半 :P

      // 而家連php都唔駛係local度裝, for development真係anywhere可以做到呢~

      的確係好方便,以前在路上有 idea 往往需要回家才能試,但回家往往又要處理瑣碎事情⋯ 說回來,我秘書的骨幹程序,是年中某日我去機場接機的兩個小時,在 iPad + Google Co-lab 開發的⋯

      // 真係遲啲我都無飯開了, 要轉行...><

      不會啦,社會只會更需要編程人員和技術。不過如果星尼是當企業硬件維護的就另當別論⋯

      刪除
    4. 用ipad + Google Co lab就開發到真係好強!
      我近期也在考慮換ipad呢,全兄覺得keyboard有無必要?
      我還在心大心細呢,大學優惠到月尾就全完結了

      刪除
    5. 其實有 keyboard 真係唔同 d,主要係無 keyboard 的話打字要長期 dup 低個頭好辛苦。
      如果 surface 個 keyboard 可以拆開用(藍芽),在家時 「long」 高個 mon,出外變 notebook,我一定買。

      刪除
  4. 好強大!
    我只睇得明最頂幅圖. 係正喎~~~~!
    最好有 "MG Nu GUNDAM" 佢出哂FB 內群組留言價0既價錢....哈哈!
    或者"食飯", 佢自己識CRAWL FB 入面D 文....

    回覆刪除
    回覆
    1. 70兄,我都唔敢講。
      其實我都係睇唔明。。。 ><0

      刪除
    2. 感謝兩位讚賞!呢篇技術含量係高咗D,我寫寫吓都擔心會唔會影響日後瀏覽量 :P
      70後兄,這個理論上可以,不過 FB 應該會想你睇多 D 佢的廣告而對爬蟲多番阻撓吧。

      刪除
    3. 旁門左道野應該得。
      爬連登討論區,"阿根廷AV"

      之後出一堆女星名。。。

      刪除
    4. 哇哈哈哈😂
      爬晒每位的 Blog 方便搶沙發 😂

      刪除
  5. 全兄很厲害,有時間必玩玩試試。

    回覆刪除
    回覆
    1. AC兄,是的,其實 Telegram Bot 真係幾好玩。自從上次跟同事利用 Bot 玩了 O-camp 必玩的狼人(或稱 MC版 Killer),就開始對 Bot 著迷了。

      刪除
    2. 我本身無用telegram,搵左少少WhatsApp bot既資料,期待師兄既下篇,睇下可唔可以轉做WhatsApp版

      刪除
    3. 以我所知 WhatsApp 官方沒有支援 API,所以選擇先在 Telegram 進行開發。半桶水爸爸讀完下篇之後,如果發現有方便方法轉成 WhatsApp 麻煩告知!! :)

      刪除
  6. 時代進步,原來php都不必用了,python已經做到效果,我太落後了。全兄厲害👍

    回覆刪除
    回覆
    1. 謝謝 June 的拜訪和留言支持!從前讀書的時候覺得 PHP 和 JavaScript 很難,沒想到十多年後的今天竟然動手寫起 Python 來,有些時候更倒過來回學 JS。所以我沒有經歷過 PHP 的年代,只是搭了前人的順風車吧。

      Python 處理數據的確有一手,而且型態易學又簡潔,自覺從菜鳥到高手都很適合。

      刪除
    2. 真是大開眼界,我還活在很落後的php,asp年代呢。果然時代是不斷進步的,以前寫3d遊戲要計數,現在一個karakara軟件,幾句指令已經呈現很棒的效果了。所以steve jobs是對的,寫程序會越來越大眾化。

      刪除
    3. 現在 3 歲小朋友也有利用實體方塊配合 iPad 練習編程的遊戲了,一方面編程變得愈來愈容易,另一方面現在的小朋友又多一個技能需要學習,否則長大後只會 become irrelevant。

      刪除
  7. 福祿兄真是IT奇材👍👍 分享造福人群呢!

    回覆刪除
    回覆
    1. Cherry 誇獎了,呃吓 Like 還可以,幸好我沒有投身 IT 界,否則這些三腳貓功夫應該笑死人 😂

      刪除
  8. 同意以google sheets 抽取資料確實困難亦不太穩定, 另外文章指出建立"字典", 雖有諗過但不懂如何實行及操作, 這個分享對我不明白程式語言, 又多一點子, 會嘗試一下... 感謝全兄無私分享

    回覆刪除
    回覆
    1. 不用謝,Python 的資料型態本身就很適合數據分析,而且程序碼清晰易懂,我從起初從不認識到現在也不是花了很長時間,有興趣的話絕對建議花點時間鑽研一下。

      刪除
  9. 這個要留下來好好學習一下, 謝謝福祿壽兄。

    回覆刪除
    回覆
    1. 哈哈,全靠二當家兄提議我才有動力寫這篇 😃
      本來想直接寫如何下載 AAStock 財表數據庫的,但恐怕會變大話西遊本 Blog 從此被編入「宅」的行列 😆

      刪除
  10. Python 在香港應該算是非常流行,反而在下用R 真係問十個中九個唔知係咩...

    回覆刪除
    回覆
    1. 幾年前 R 和 Python 也是分庭抗禮的 Data Analytics language,但現在愈來愈多人投向 Python 的懷抱了。我大學時期也有學過 R,但 syntax 實在太簡潔丟下一排好像沒有學過一樣 XD 反之 Python 看起來更像人類的語言。

      刪除

  11. 福祿壽兄,

    之前一路都只做CD ROM, 你既博文關於個人財務計畫好有用, 我諗做完個人財務規劃, 應該對睇年報更有感情, 而家有關於財務問題想請教。
    我的個人/家庭理財基礎5步(中篇)

    https://fukluksau.blogspot.com/2018/04/PersonalFinancePart2.html

    因最近結婚, 不可再唔清唔楚咁做人, 所以想學你做會計三寶。
    但有問題係資產負債表度, 自住單位是否=資產; 按歇餘額=負債?
    謝謝你。

    kit

    回覆刪除
    回覆
    1. Kit 兄您好,多謝留言。做好會計三寶兼每月數銀紙,的確能令生活更加踏實。

      個人會將自住單位算做資產,因為它的確是隻會生金蛋的雞,只不過現在你自己享用了金蛋而已。我明白有些人會 argue 自住單位沒有正現金流不算做資產,但個人認為解決方法不是摒棄自住樓在資產負債表外,而是應該將自住樓當成是自己租給自己。即:

      資產:放自住樓市值
      負債:放按揭餘額
      收入:放自住樓市值租金
      支出:放自住樓市值租金 及 供樓利息支出

      這樣借貸水平、加按空間、實際生活支出和按揭支出都能一清二楚。

      刪除
  12. 謝謝福祿壽兄,

    在根據以上定義做了資產負債表後,我根據網上一些家庭財務比率做了一些計算。想請教下你覺得下面既指標是否已經充分反映一個家庭財政是否健康嗎?
    但係香港剛買完樓就好唔正常。

    財務比率如下:

    解釋 負債比率 目標值
    總負債/總資產 資產負債率 0.4~0.6
    投资资产/净资产 投资与净资产比率 0.5
    流動資產/月支出 流動性比 3~8
    每月還款/稅後收入 償債比率 0.35
    月結餘/稅後收入 結餘比率 >0.3
    被動收入/月支出 財務自由度 >>1

    Kit

    回覆刪除
    回覆
    1. KIT兄, 福兄, 我多口搭一下嗲

      解釋 負債比率 目標值
      總負債/總資產 資產負債率 0.4~0.6 <---如果O岩O岩買左一層樓係好正常, 可以睇埋供滿樓既比率比較一下。 好似二當家咁, 無買樓, 無生意, 正常黎講係無乜借貸 ~0 (最多都係基本私人貸款, 咭數, MARGIN等), 最緊要係睇你既還款能力>利息開支。

      投资资产/净资产 投资与净资产比率 0.5 <---要睇下點 DEFINE, 樓算唔算投資資產? BOND 定 STOCK? 净资产又包埋D咩? 例如: 50萬現金, 30萬股票, 20萬債劵, TOTAL 100萬資產, 咁我覺得現金會太多, 要睇埋資產既 LIQUIDITY。

      流動資產/月支出 流動性比 3~8 <---我一般會預備 「每月支出X 6個月」既現金用作周轉

      每月還款/稅後收入 償債比率 0.35 <---香港供樓咁去計, <50% 已經OK
      月結餘/稅後收入 結餘比率 >0.3 <---即係儲到30%錢, 其實最緊要係正現金流, 當然越多越好
      被動收入/月支出 財務自由度 >>1 <-----我個人目標都係咁.........

      福兄, 有錯請指教。

      刪除
    2. Kit兄的目標看起來沒有很大問題,不過理財目標最緊要是個性化,因為每個人的財務狀況、風險胃納都不同,只要是自己操作起來舒服、根據這個操作向未來推算又能達到自己財務理想的(例如目標財自年齡),就是好目標。Kit 兄 不妨先以這些目標操作,每三個月至半年再檢討一次有否任何「不能」和「不為」的地方 :)

      刪除
    3. 二當家兄,感謝友情客串😉

      總負債/總資產:同意我們個人投資者好難仔細控制這個比率,正正是樓宇和按揭「大大份」佔據資產負債表的大部分。但無樓者也有可能槓桿買債收息。留意比率需因應利率變化而留有彈性。

      投資資產/淨資產:我回答時假設了「現金」和「自住樓」不是投資資產,不過細想其實個人投資者所有資產都應是用作產出投資收入(現金也有賺利息、自住樓賺租金機會成本),所以同意二當家兄所講,這個比率如何定義、又為何要定義。

      流動資產/月支出:我維持在6-9個月。股神更認為需要 12個月。這個跟個人風險胃納、和對未來被動現金流的信心。

      每月還款/稅後收入:這個我主要是量度買樓購買力的。所以我會目標性、根據金管局指引定義。

      月結餘/稅後收入:30% 是基本。我還是那句 —— 40/100 法則。如果我們只工作 40 年而假設能活到 100 歲,我們應該只用 40% 的收入(即儲蓄 60%),以上假設投資回報追到通脹、0-30歲支出是支援自己小朋友。對自己的投資回報信心大些,或者已決定不生仔,才將儲蓄率悉數遞減吧。

      被動收入/月支出:愈大當然愈好,但也不要盲目追求最大化而犧牲生命其他美好事物。我打算達至 150% 就會開展第二人生。

      刪除
  13. 明白,謝謝你百忙中抽空回覆.

    Kit

    回覆刪除
  14. 福兄,最近又反覆看了你的舊文,覺得非常欣賞,可不可以跟你交換blog連結呢?

    回覆刪除
    回覆
    1. 多謝欣賞及支持!當然無問題,互相交流切磋是小弟寫 Blog 的目的及動力!

      刪除
  15. 全兄,想請教一下。我剛跟上述步驟運行了一次。
    但奇怪的是最後如果我運行"print(FXDatabase['BHD'])"會出ERROR MESSAGE.
    反而如果運行print(FXDatabase[b'BHD'])則得到想要的結果。
    我試過運行print(FXDatabase)得出的結果也比較奇怪。在貨幣代號前邊都多了個b,不知為何。

    print(FXDatabase)
    {b'AED': {'Buy': 2.08, 'Sell': 2.18}, b'AUD': {'Buy': 5.56, 'Sell': 5.66}, b'BHD': {'Buy': 19.5..後略.

    回覆刪除
    回覆
    1. 應該是 text encoding 問題。不同平台可能 assume 的 encoding 不一樣。我用 Mac / Google Colab 默認是 unicode,所以會出 u'Text',我用了 encode('utf-8') 去處理。參考:https://www.programiz.com/python-programming/methods/string/encode

      刪除

熱門文章