這本書有人預約需先還 :
這是旗標出版的好書, 但我沒時間看, 只翻了前言與 ChatGPT 部分. 2025年8月9日 星期六
2025年7月3日 星期四
市圖還書 2 本 : 好想學Excel職場聖經+Python3.x網頁資料擷取與分析特訓教材
今天市圖還書兩本 :
No.1 放在鄉下周日忘記還而逾期, 請爸拿去櫃台還 (有很多人預約); 去年已把爬蟲學完了, 因此 No.2 目前用不到了.
2024年7月10日 星期三
好書 : Python for Excel-自動化與資料分析的現代開發環境
本周開始閱讀下面這本從母校圖書館借的書, 我覺得寫得非常好 :
Source : 博客來
此書作者 Felix Zumstein 是赫赫有名的 xlwings 套件作者, 當然書中會特別介紹 xlwings (以及沒有 Excel 也可以跑的 OpenPyXL), 不過也特別介紹了 Pandas, 全書分成有四大部分 :
- Part 1 : Python 導論
- Part 2 : Pandas 導論
- Part 3 : 在無 Excel 環境讀寫 Excel 檔案
- Part 4 : 以 xlwings 設計 Excel 應用程式
書中範例程式可在 GitHub 下載 :
另外作者也將一些此書的額外資訊放在下列網站 :
2023年2月2日 星期四
高科大還書 3 本 (PyTorch, Excel+Python, NLP)
因為尚未開學, 所以今天中午午休時到母校還下列三本被預約的書 :
No.1 只看了前兩章, 還好市圖也可以借到. No.2 為強國人所寫, 主要是講用 Pandas 取代 Excel, 編排極好, 但我只看了四章, 也沒時間做測試寫筆記, 等我搞定目前學研進度再回借. No.3 借來只看了第一章, 此書是 Oreily 出版的 "Practical Natural Language Processing: A Comprehensive Guide to Building Real-World Nlp Systems" 中譯版, 參考 :
2022年12月18日 星期日
博客來買書一本 : Python ✕ Excel 高效率打造辦公室作業+數據分析自動化
前幾天看到臉書廣告博客來 12/18 日有下列這本書的 66 折活動 :
# Python ✕ Excel 高效率打造辦公室作業+數據分析自動化 $650 -> 429
今天上網查詢確實只有今天, 看到 66 折手癢就買了 :
其實也是因為這本書很實用啦 (或許退休以後會很常用, 哈哈).
2022年12月6日 星期二
市圖還書 1 本 (用Excel學Python資料分析)
今日去河堤快走後順路去河堤還書 :
Source : 博客來
2022年10月18日 星期二
高科大還書 1 本
這本書被預約昨天拿去還 :
雖然還沒看完, 但基本用法已經會了, 此書特點就是將各種常遇到的 Excel 問題以 xlwings 來解決, 相當於是 VBA 的 Python 版, 有空再回借.
2022年10月17日 星期一
Python 學習筆記 : 使用 xlwings 套件操作 Excel 試算表 (一)
我在 6/7 時因為上了高啟昌老師的課而首次接觸到 Python 的 Excel 處理套件 xlwings, 它最大的好處是可以在開啟 xlsx 試算表檔案的情況下操作工作表, 可以同步看到工作表上的資料跳動 (Python 與 Excel 雙向操作); 而另一個較老牌的 Openpyxl 套件則不行, 若 xlsx 檔案被 Excel 開啟中會被鎖住, OpenPyXL 將無法操作工作表.
不過, OpenPyXL 的好處是, 即使沒有安裝 Excel 軟體仍可使用, 而 xlwings 則是必須依賴 Excel 軟體才能使用, 在樹莓派上雖然可以順利安裝 xlwings 套件, 但無法操作 xlsx 檔案 (因為 Excel 只有 Windows 與 macOS 版). 由於我主要的運算機器是樹莓派 (嵌入式設備, 可以 24 小時低功率運轉), 所以之前我著重於 OpenPyXL 的學習, 參考如下筆記 :
在許多可操作 Excel 試算表檔案的套件中, xlwings 是唯一真正能編輯任何格式的 Excel 檔案的套件 (不會改變或丟棄既有內容與格式), 讓使用者可輕鬆地讀寫 Excel 試算表中的資料 (例如可以不必存檔就能動態地讀取 Excel 儲存格內容, 用 OpenpyXL 須先存檔關檔才能存取), 是 Excel VBA 的 Python 版, 最主要的用途是可利用 Excel 作為使用者介面來打造一個互動式應用.
參考書籍 :
- 超簡單! Python 與 Excel 整合應用 150 技 (碁峰 2022)
- Python for Excel|自動化與資料分析的現代開發環境 (歐萊禮 2021) 第 9 章
- Python x Excel VBA x Javascript 網路爬蟲實戰演練 (碁峰 2021) 第 3 章
最近從母校圖書館借到這三本好書, 所以要開始來學習 xlwings 套件了.
Source : 博客來
此書範例程式下載網址 :
Source : 博客來
此書範例程式下載網址 :
Source : 博客來
xlwings 有完整的 API 說明文件, 參考 :
以下是我讀這三本書的測試筆記 :
1. 安裝 xlwings 套件 :
在 Window 下直接用 pip install xlwings 套件 :
C:\Users\User>pip install xlwings
Collecting xlwings
Downloading xlwings-0.27.8-py3-none-any.whl (1.0 MB)
Requirement already satisfied: pywin32>=224 in c:\python37\lib\site-packages (from xlwings) (227)
Installing collected packages: xlwings
Successfully installed xlwings-0.27.8
可見 xlwings 套件很小, 才 1MB 而已. 匯入時通常會取 xw 別名 :
>>> import xlwings as xw
>>> xw.__version__
'0.27.8'
Anaconda 已將 xlwings 預載, 故 Anaconda 使用者毋須安裝 xlwings.
注意, 在 Windows 上使用 xlwings 至少須安裝 Excel 2007 以上版本; macOS 系統則須安裝 Excel 2016 版以上, 或者訂購 Microsoft 365 的桌面 Excel.
2. 檢視 xlwings 套件之成員 :
先來檢視 xlwings 套件的成員內容, 以下使用一個自訂模組 members, 其 list_members() 函式會列出模組或套件中的公開成員 (即屬性與方法), 參考 :
# Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法
>>> import xlwings as xw
>>> import members
>>> members.list_members(xw)
App <class 'type'>
Book <class 'type'>
Chart <class 'type'>
Engine <class 'type'>
LicenseError <class 'type'>
Name <class 'type'>
PRO <class 'bool'>
Picture <class 'type'>
Range <class 'type'>
RangeColumns <class 'type'>
RangeRows <class 'type'>
Shape <class 'type'>
ShapeAlreadyExists <class 'type'>
Sheet <class 'type'>
USER_CONFIG_FILE <class 'str'>
XlwingsError <class 'type'>
apps <class 'xlwings.main.ActiveEngineApps'>
arg <class 'function'>
books <class 'xlwings.main.ActiveAppBooks'>
constants <class 'module'>
conversion <class 'module'>
engines <class 'xlwings.main.Engines'>
expansion <class 'module'>
func <class 'function'>
gencache <class 'module'>
get_udf_module <class 'function'>
import_udfs <class 'function'>
load <class 'function'>
main <class 'module'>
os <class 'module'>
ret <class 'function'>
serve <class 'function'>
server <class 'module'>
sheets <class 'xlwings.main.ActiveBookSheets'>
sub <class 'function'>
sys <class 'module'>
udfs <class 'module'>
utils <class 'module'>
view <class 'function'>
wraps <class 'function'>
其中有四個類別用來建立 Excel 中的物件結構 :
- App : 代表 Excel 應用程式實例
- Book : 代表工作簿 (包含多個工作表)
- Sheet : 代表工作表 (二維網格的資料表)
- Range : 代表儲存格 (單一儲存格或矩形範圍內之儲存格)
這些 xlwings 的物件在結構上是階層式的, 與 Excel 物件的對應如下圖所示 :
最上層物件 App 代表 Excel 應用程式實例 (即 Excel 視窗), Book 為工作簿物件, 裡面包含一個以上的工作表 (Sheet 物件), 每一個工作表又由 Range 物件 (即儲存格) 構成, 不管是單一儲存格還是方形範圍內之多儲存格, 其類型都是 Range 物件.
3. 用 xlwings 建立 Excel 工作簿 :
呼叫 App 類別的的建構子 App() 即可建立一個 App 物件 :
>>> import xlwings as xw
>>> app=xw.App(visible=True, add_book=False)
>>> type(app)
<class 'xlwings.main.App'> # 建立了一個 App 物件
此處傳入參數 visible=True 表示要顯示開啟之 Excel 視窗, 而 add_book=False 則是不要新增預設之工作簿, 通常是為了自行新增工作簿或開啟已存在的 xlsx 檔案, 不希望 App 裡面有預設之工作簿以免干擾. 以上述指令建立 App 後會自動開啟一個 Excel 視窗 :
這是一個空白的 Excel 應用程式實例, 裡面並無工作簿, 可以用 App 物件的 books 屬性檢視 :
>>> app.books
Books([])
>>> type(app.books)
<class 'xlwings.main.Books'>
(1). 呼叫 app.books.add() 建立 Book 物件 :
books 屬性類型為一個 Books 物件, 這是一個存放 Book 物件的容器, 此處內容為空 (因為在建立 App 時傳入 add_book=False 之故). 可以呼叫 Books 物件的 add() 方法建立工作簿 (此方法無傳入參數), 例如 :
>>> wb=app.books.add() # 建立工作簿 Book 物件
>>> type(wb)
<class 'xlwings.main.Book'>
可見 add() 方法會建立一個 Book 工作簿物件, 這時上面自動開啟的 Excel 視窗 (App 物件實例) 也會同步出現工作簿, 裡面有一個預設工作表 '工作表 1' :
這就是 xlwings 與 Excel 同步互動的效果, 對儲存格的操作也會同步反映在 Excel 視窗上.
(2). 利用 wb.sheets[] 取得 Sheet 物件 :
接著可以用工作簿 Book 物件的 sheets 屬性以索引 (0 起始) 取得工作表 Sheet 物件 :
>>> sheet=wb.sheets[0]
>>> type(sheet)
<class 'xlwings.main.Sheet'> # 取得工作簿裡面的一個 Sheet 物件
由於目前只有一個工作表, 所以索引 0 即代表 '工作表1' 這張工作表. 也可以用工作表名稱 (例如此處是 '工作表1') 當作索引, 效果是一樣的 :
>>> sheet=wb.sheets['工作表1']
>>> type(sheet)
<class 'xlwings.main.Sheet'> # 取得工作簿裡面的一個 Sheet 物件
(3). 呼叫 sheet.range() 取得 Range 物件 :
接著便可以用 Sheet 物件的 range() 方法存取此工作表內的儲存格, 可傳入位置索引字串來定位儲存格, 與 Excel 用法一樣, 欄索引由左向右依序是 A, B, C, D, .... Z, AA, AB, AC, .... 而列索引則是由上而下依序為 1, 2, 3, 4, 5, .... 等, 位置索引之範例如下 :
| range() 的儲存格定位字串 | 說明 |
| 'A1' | 儲存格 A1 |
| 'A1:B2' | 從 A1 至 B2 的儲存格 (範圍) |
| 'A:A' | A 欄的所有儲存格 |
| 'A:B' | A 欄與 B 凡的所有儲存格 |
| '1:1' | 第一列的所有儲存格 |
| '1:5' | 第一列到第五列的所有儲存格 |
例如 :
>>> rangeA1=sheet.range('A1')
>>> rangeA1
<Range [活頁簿2]工作表1!$A$1>
>>> type(rangeA1)
<class 'xlwings.main.Range'>
>>> rangeA1B2=sheet.range('A1:B2')
>>> rangeA1B2
<Range [活頁簿2]工作表1!$A$1:$B$2>
>>> type(rangeA1B2)
<class 'xlwings.main.Range'>
可見不管是單一儲存格或是矩形範圍內的多個儲存格, 其資料類型都是 Range 物件. 呼叫 Range 物件的 value() 法即可存取儲存格內容 (傳入參數為 setter, 不傳參數為 getter), 例如在 A1 儲存格填入一個 2*2 串列資料, 它會被存入 A1:B2 範圍內的儲存格裡, 在 A4 儲存格存入 'Hello World' 則只存在 A4 裡 :
>>> sheet.range('A1').value=[[1, 2], [3, 4]]
>>> sheet.range('A4').value='Hello World'
結果如下 :
讀取儲存格內容 :
>>> sheet.range('A1').value
1.0
>>> sheet.range('A1:B2').value
[[1.0, 2.0], [3.0, 4.0]]
>>> sheet.range('A4').value
'Hello World'
Range 物件可以用中括號 [] 進行索引 (indexing, 0 起始), 例如 :
>>> sheet.range('A1:B2')[0, 0]
<Range [活頁簿2]工作表1!$A$1>
>>> sheet.range('A1:B2')[1, 1]
<Range [活頁簿2]工作表1!$B$2>
>>> sheet.range('A4')[0, 0]
<Range [活頁簿2]工作表1!$A$4>
呼叫 Sheet 物件的 range() 方法會傳回一個 Range 物件, 如果它是一個包含多儲存格的矩形範圍, 就可以用 0 起始的 [列, 行] 索引來存取儲存格, 如果傳入 range() 的是像 'A1' 這樣的單一儲存格, 就只能用 [0, 0] 存取, 否則就會出現 IndexError :
>>> sheet.range('A4')[0, 1]
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
File "C:\Python37\lib\site-packages\xlwings\main.py", line 2332, in __getitem__
"Column index %s out of range (%s columns)." % (col, n)
IndexError: Column index 1 out of range (1 columns).
除了可以用索引來存取 Range 物件的儲存格資料外, 也可以用切片 (slicing), 切片的結果也是傳回一個 Range 物件. 注意, 切片的索引是 0 起始的 (0 為首列首欄), 例如 :
>>> sheet.range('A1:B2')[:, 1]
<Range [活頁簿2]工作表1!$B$1:$B$2>
>>> sheet.range('A1:B2')[1, :]
<Range [活頁簿2]工作表1!$A$2:$B$2>
上面第一個例子用 [:, 1] 切片, 表示取出 Range 物件所包含的範圍中 B 欄 (A 欄為 0, B 欄為 1, C 欄為 2, ...) 所有列的儲存格, 所以結果會傳回 B1 與 B2 儲存格組成之 Range 物件. 第二例用 [1, :] 切片, 表示要取出 Range 物件所包含的範圍中列 1 所有欄的儲存格, 故傳回 A2 與 B2 儲存格組成之 Range 物件.
(4). 使用 sheet[] 取得 Range 物件 :
除了呼叫 Sheet 物件的 range() 方法取得指定範圍儲存格之 Range 物件外, 也可以直接在 Sheet 物件上使用中括號 [] 來取得, [] 內可用上面 range() 方法的儲存格定位字元, 也可以 0 起始的 [列, 行] 索引, 例如 :
>>> sheet['A1']
<Range [活頁簿2]工作表1!$A$1>
>>> sheet['A1:B2']
<Range [活頁簿2]工作表1!$A$1:$B$2>
>>> sheet['A4']
<Range [活頁簿2]工作表1!$A$4>
>>> sheet['A1'].value
1.0
>>> sheet['A1:B2'].value
[[1.0, 2.0], [3.0, 4.0]]
>>> sheet['A4'].value
'Hello World'
可見與上面呼叫 range() 方法的結果相同. sheet[] 比 range() 方法還多了可用 0 起始索引取得 Range 物件的方法, 例如 :
>>> sheet[0, 0]
<Range [活頁簿2]工作表1!$A$1>
>>> sheet[:2, :2]
<Range [活頁簿2]工作表1!$A$1:$B$2>
>>> sheet[3, 0] # A4 在列 3 欄 0
<Range [活頁簿2]工作表1!$A$4>
>>> sheet[0, 0].value
1.0
>>> sheet[:2, :2].value
[[1.0, 2.0], [3.0, 4.0]]
>>> sheet[3, 0].value
'Hello World'
可見也與上面呼叫 range() 方法的結果相同. 既然用 [] 運算符也可取得相同的 Range 物件, 當然也可以使用索引, 例如 :
>>> sheet['A1:B2'][0, 0]
<Range [活頁簿2]工作表1!$A$1>
>>> sheet['A1:B2'][1, 1]
<Range [活頁簿2]工作表1!$B$2>
>>> sheet[:2, :2][0, 0]
<Range [活頁簿2]工作表1!$A$1>
>>> sheet[:2, :2][1, 1]
<Range [活頁簿2]工作表1!$B$2>
也可以使用切片 :
>>> sheet['A1:B2'][:, 1]
<Range [活頁簿2]工作表1!$B$1:$B$2>
>>> sheet['A1:B2'][1, :]
<Range [活頁簿2]工作表1!$A$2:$B$2>
>>> sheet[:2, :2][:, 1]
<Range [活頁簿2]工作表1!$B$1:$B$2>
>>> sheet[:2, :2][1, :]
<Range [活頁簿2]工作表1!$A$2:$B$2>
兩組中括號很容易讓人忘記其意義, 第一個中括號是從 Sheet 物件中取得指定範圍的 Range 物件; 而第二個中括號則是在 Range 物件中進行索引或切片.
(5). 呼叫 sheet.cells() 取得 Range 物件 :
取得 Range 物件還可以使用 Sheet 物件的 cells(列, 行) 方法並傳入 1 起始的列與行索引 (所以 A 欄是行 1, 首列是列 1), 但這只能取得單一儲存格, 例如 :
>>> sheet.cells(1, 1)
<Range [活頁簿2]工作表1!$A$1>
>>> sheet.cells(4, 1)
<Range [活頁簿2]工作表1!$A$4>
>>> sheet.cells(1, 1).value
1.0
>>> sheet.cells(4, 1).value
'Hello World'
(6). 修改儲存格內容 :
以上面幾種存取 Range 物件的方式修改儲存格內容 :
>>> sheet.range('A1').value=5 # 修改 A1 儲存格之值
>>> sheet['B1'].value=6 # 修改 B1 儲存格之值
>>> sheet.range('A2:B2').value=[7, 8] # 修改 A2, B2 儲存格之值
>>> sheet.cells(4, 1).value='Hello Tony!' # 修改 A4 儲存格之值
結果如下 :
可見原來的四個儲存格內容都被改變了.
參考 :
2022年6月30日 星期四
Python 學習筆記 : 使用 OpenPyXL 操作 EXCEL 試算表 (五)
自六月初上了 Python 與 Excel 的整合操作課程後, 覺得 Excel 的 xlsx 檔是非常棒的單檔簡單型資料庫, 適合在做原型開發時快速實現商業邏輯, 而毋須去配置標準資料庫中資料表的關聯與 SQL 操作. 它不僅可用 Excel 或 Libre Office 等軟體開啟, 也可以在沒有這些軟體環境下, 只用 OpenPyXL 套件即可存取與操控.
於是我回頭把去年底開始但不久又暫停的 OpenPyXL 套件重新學習, 一口氣將用得到的主要功能測完, 今天終於大功告成啦! 這次總算將整隻魚從頭啃到尾. 不過, 最後還有個小尾巴要完成, 就是把課堂中的股票看板範例, 從原本的 xlwings 套件改成 openpyxl 版, 因為樹莓派上沒辦法跑 xlwings, 它必須安裝微軟的 Excel 軟體才能用, 而 openpyxl 則無此限制.
本系列之前的文章參考:
9. 即時股票看板 :
此股票看板的目標是根據 Excel 工作表中股票欄位所指定股號去聚財網 (也可以用 Yahoo 股市) 查詢即時股價, 計算漲跌幅, 然後利用 Line Notify 推播即時通知.
(1). 建立工作簿並存成 xlsx 檔 :
首先要建立一個 Excel 工作簿, 定義欄名並預先寫入要觀察的股票清單, 雖然此功能只執行一次, 但還是寫成一個函式如下 :
import openpyxl as xl
def create_workbook(filename, stocks):
wb=xl.Workbook()
ws=wb.active
ws.title='股票看板'
# 建立第一列的欄名
ws['A1'].value='股票代號'
ws['B1'].value='股票名稱'
ws['C1'].value='開盤價'
ws['D1'].value='最高價'
ws['E1'].value='最低價'
ws['F1'].value='成交價'
ws['G1'].value='昨日收盤價'
ws['H1'].value='帳跌幅'
ws['I1'].value='最近更新'
ws['J1'].value='Line Notify Token'
# 將觀察清單中的股票代號填入 A2, A3, A4, ...
for idx, stock in enumerate(stocks):
ws[f'A{idx + 2}'].value=stock
# 存檔
try:
wb.save(filename)
except Exception:
print(f"Permission Error : {filename} 開啟中, 請關閉")
return wb, ws
stock_list=['0050', '0056', '2330', '2303', '2412']
wb, ws=create_xlsx("twstock_dashboard.xlsx", stock_list)
此函式先建立一個空白程式庫, 然後在第一列的 A~J 欄位填入 11 個欄名, 並且將傳入的股票觀察清單依序寫入工作表 A 欄 (股票代號), 從第二列開始寫入儲存格, 然後將工作簿存成指定之 xlsx 檔 (例如 twstock_dashboard.xlsx) 後傳回工作簿與工作表物件. 用 Excel 開啟所建立之 twstock_dashboard.xlsx 檔結果如下 :
(2). 撰寫爬蟲擷取聚財網股價資訊 :
建立好試算表資料庫後, 下一步是從公開的資料源 (例如聚財網) 擷取股票資料 :
此網頁會以倒排方式列出所搜尋之股票過去 20 個交易日之收盤資訊, 每個交易日的四點過後會更新資料, 例如 :
檢視其 HTML 原始碼, 可知此網頁使用 big5 編碼 :
網頁中有兩個 table 表格元素, 股價內容是放在第一個表格 (具有屬性 class="mobile_img" ) 內, 表格的第一列是合併儲存格的表格標題 :
<tr>
<td colspan="6" height="30"><div align="center">個股股價行情表﹝0050 元大台灣50﹞</div> </td>
</tr>
表格第 2 列是 6 個欄位的標題 :
<tr>
<th bgcolor="#f0f0f0" width="90" class="table-first-child"><div align="center">日期</div></th>
<th bgcolor="#f0f0f0" ><div align="center">開盤價</div></th>
<th bgcolor="#f0f0f0" ><div align="center">最高價</div></th>
<th bgcolor="#f0f0f0" ><div align="center">最低價</div></th>
<th bgcolor="#f0f0f0" ><div align="center">收盤價</div></th>
<th bgcolor="#f0f0f0" ><div align="center">成交量</div></th>
</tr>
表格第 3 列以後才是過去 20 天的收盤資料 :
<tr class="stockalllistbg2">
<td class="table-first-child">111/06/28</td>
<td align="right">120.80 </td>
<td align="right">120.80 </td>
<td align="right">119.25 </td>
<td align="right">119.80 </td>
<td align="right">10,801 </td>
</tr>
<tr class="stockalllistbg1">
<td class="table-first-child">111/06/27</td>
<td align="right">119.80 </td>
<td align="right">121.45 </td>
<td align="right">119.80 </td>
<td align="right">120.95 </td>
<td align="right">16,926 </td>
</tr>
<tr class="stockalllistbg2">
<td class="table-first-child">111/06/24</td>
<td align="right">118.65 </td>
<td align="right">119.30 </td>
<td align="right">117.95 </td>
<td align="right">118.15 </td>
<td align="right">11,166 </td>
</tr>
..... (略) .....
<tr class="stockalllistbg1">
<td class="table-first-child">111/05/31</td>
<td align="right">128.45 </td>
<td align="right">129.80 </td>
<td align="right">127.60 </td>
<td align="right">129.80 </td>
<td align="right">7,233 </td>
</tr>
這些成交資料可以利用 requests 與 BeautifulSoup 套件輕易地擷取出來, 以元大 0050 為例, 呼叫 requests.get(url) 會傳回一個 HTTP 的 Response 回應物件, 由於網頁是以 big5 編碼, 所以必須設定回應物件的 encoding 屬性設為 'big5' :
>>> import requests
>>> from bs4 import BeautifulSoup
>>> res=requests.get("https://stock.wearn.com/cdata.asp?kind=0050")
>>> res.encoding="big5"
網頁的 HTML 原始碼是放在回應物件的 text 屬性內, 可以利用 BeautifulSoup 來解析 HTML 結構, 它會傳回一個 BeautifulSoup 物件 :
>>> html=BeautifulSoup(res.text, "html.parser")
>>> type(html)
<class 'bs4.BeautifulSoup'>
BeautifulSoup 物件代表經過剖析後的整個 HTML 語法樹結構, 它包含 Tag, NavigableString, 以及 Comment 三個子物件, 其中 Tag 物件是語法樹中的節點, 代表組成網頁的各個標籤元素. 利用 BeautifulSoup 物件的 findAll() 方法可找到全部指定之標籤, 它會傳回 Tag 物件串列, 例如 :
>>> table=html.findAll("table")[0] # 從表格的 Tag 物件串列中取得第一個
>>> type(table)
<class 'bs4.element.Tag'>
這個 table 是整個表格的 Tag 物件, 但我們要的是其中的各列內容, 也就是 tr 標籤元素, 每一個 Tag 物件都繼承了 BeautifulSoup 物件的方法, 所以可以繼續使用 findAll() 方法從 table 中找尋所有的列 Tag 物件 :
>>> trs=table.findAll("tr") # 從 table 中找尋全部 tr 標籤元素
>>> type(trs)
<class 'bs4.element.ResultSet'>
這個 trs 是表格內所有 tr 元素的 Tag 物件依序組成的 ResultSet 物件 (這表示裡面含有一個以上 Tag 物件), 可以用 [索引] 取得裡面的每個 tr 元素 Tag 物件, 其 text 屬性儲存的是該元素開頭標籤與結尾標籤所夾的文字內容, 例如最近一個交易日收盤資料是放在表格的第三列, 故要用索引 2 取得 :
>>> tr=trs[2] # 取得第三列 Tag 物件
>>> type(tr)
<class 'bs4.element.Tag'>
>>> tr
<tr class="stockalllistbg2">
<td class="table-first-child">111/06/28</td>
<td align="right">120.80 </td>
<td align="right">120.80 </td>
<td align="right">119.25 </td>
<td align="right">119.80 </td>
<td align="right">10,801 </td>
</tr>
>>> tr.text
'\n111/06/28\n120.80\xa0\xa0\xa0\n120.80\xa0\xa0\xa0\n119.25\xa0\xa0\xa0\n119.80\xa0\xa0\xa0\n10,801\xa0\xa0\xa0\n'
可見用 print() 顯示 tr 這個 Tag 物件會顯示此元素的完整 HTML 內容, 而其 text 屬性則是 tr 元素內所有的文字內容部分. 雖然 tr.text 就可以濾掉 td 元素而取得開高低收 (OHLC) 收盤資料, 但裡面還是包含了過多資訊 (例如跳行與日期欄位), 應該再用 findAll() 將所有 td 都找出來 :
>>> tds=tr.findAll("td")
>>> type(tds)
<class 'bs4.element.ResultSet'>
同樣地, 因為 tr 裡面含有多個 td 的 Tag 物件, 所以 findAll() 傳回的是 ResultSet 物件, 其中第一欄 td 存放的是交易日期, 第二欄之後才是收盤資料, 可以用串列生成式與切片將第一欄以後的股價資料轉成串列, 例如 :
>>> data=[td.text for td in tds[1:]] # 取出第二欄以後的 td 內容存入串列 :
>>> data
['120.80\xa0\xa0\xa0', '120.80\xa0\xa0\xa0', '119.25\xa0\xa0\xa0', '119.80\xa0\xa0\xa0', '10,801\xa0\xa0\xa0']
其中的 \xa0 是網頁空格 字元 (non-breaking space), 屬於 Latin1 擴展字元集, 與 ASCII 的空格 (\x20) 不同, 參考 :
利用字串的 replace() 方法即可去除 \xa0 字元 :
>>> data=[td.text.replace("\xa0", "") for td in tds[1:]] # 將 \xa0 以空字元取代
>>> data
['120.80', '120.80', '119.25', '119.80', '10,801']
這裡還需要處理最後一欄成交量中的千位逗號, 同樣可用 replace() 取代掉. 另外也要將所有串列元素從字串型態轉成浮點數型態 :
>>> data=[float(td.text.replace("\xa0", "").replace(",", "")) for td in tds[1:]]
>>> data
[120.8, 120.8, 119.25, 119.8, 10801.0]
接下來要從表格第一列 trs[0] 中取得股票名稱, 此列如上所示只有一個 td 儲存格 (合併), 因此可直接用 trs[0].td.text 取得其文字內容 :
>>> name=trs[0].td.text
>>> name
'個股股價行情表﹝0050 元大台灣50﹞'
但真正需要的是位於兩個中括號內後半部的 "元大台灣50", 它與前面的股票代號之間有一個空格, 因此可以用字串的 split() 方法拆分後取得, 例如 :
>>> name=trs[0].td.text.split(" ")[1]
>>> name
'元大台灣50﹞'
可見後面還有個中括號要去除, 這可用切片輕易完成 :
>>> name=trs[0].td.text.split(" ")[1][:-1]
>>> name
'元大台灣50'
最後, 為了計算漲跌幅, 必須取得前一日之收盤價, 也就是表格中第二個收盤資料 (列索引 3), 收盤價位於第五個欄位 (欄索引 4), 例如 :
>>> trs[3].findAll("td")[4].text
'120.95\xa0\xa0\xa0'
同樣只要套用上面取得最近股票資料做法即可 :
>>> last_close=float(trs[3].findAll("td")[4].text.replace("\xa0", "").replace(",", ""))
>>> last_close
120.95
這樣就取得所有的數據了, 茲將以上程式碼寫成如下函式, 它會接收股票代號參數, 並將取得之資料以字典傳回 :
def wearn_crawler(sid):
res=requests.get(f"https://stock.wearn.com/cdata.asp?kind={sid}")
res.encoding="big5"
html=BeautifulSoup(res.text, "html.parser")
table=html.findAll("table")[0]
trs=table.findAll("tr")
tr=trs[2]
tds=tr.findAll("td")
data=[float(td.text.replace("\xa0", "").replace(",", "")) for td in tds[1:]]
name=trs[0].td.text.split(" ")[1][:-1]
last_close=float(trs[3].findAll("td")[4].text.replace("\xa0", "").replace(",", ""))
return {
"name": name,
"open": data[0],
"high": data[1],
"low": data[2],
"close": data[3],
"last": last_close
}
例如 :
>>> data=wearn_crawler('0050')
>>> data
{'name': '元大台灣50', 'open': 120.8, 'high': 120.8, 'low': 119.25, 'close': 119.8, 'last': 120.95}
(3). 更新股票看板 :
完成爬蟲程式後, 便可以撰寫股票看板工作表的更新程式, 基本構想是讀取上面所建立的 "股票看板" 的股票代號欄位, 然後在用迴圈走訪此股票清單, 針對每一支股票去呼叫上面的爬蟲函式, 取得資料後寫入該股票的相關欄位.
首先是讀取所觀察的全部股票代號, 在上面呼叫 create_workbook() 建立工作簿時會傳回工作簿物件 wb 與工作表物件 ws, 股票代號位於工作表中的 B 欄位, 其長度可用工作表的 max_row 屬性或 B 欄位的儲存格物件長度取得 :
>>> wb
<openpyxl.workbook.workbook.Workbook object at 0x000002A07FCFB208>
>>> wb.worksheets
[<Worksheet "股票看板">]
>>> ws
<Worksheet "股票看板">
>>> ws.max_row
6
>>> ws['B']
(<Cell '股票看板'.B1>, <Cell '股票看板'.B2>, <Cell '股票看板'.B3>, <Cell '股票看板'.B4>, <Cell '股票看板'.B5>, <Cell '股票看板'.B6>)
>>> len(ws['B'])
6
>>> last_row=ws.max_row
>>> last_row
6
可見目前預設的觀察清單有 6-1=5 支股票 (扣掉第一列的欄索引), 可用 ws['A2'].value ~
ws['A6'].value 取得這些股票代號 :
>>> ws["A2"].value
'0050'
>>> ws["A3"].value
'0056'
>>> ws["A4"].value
'2330'
>>> ws["A5"].value
'2303'
>>> ws["A6"].value
'2412'
由於股票清單長度是可變的, 可在迴圈中以 ws[f''A{i}"].value 來取得代號 :
>>> for i in range(2, last_row + 1):
stock_id=ws[f"A{i}"].value
print(stock_id)
0050
0056
2330
2303
2412
然後在迴圈中以 stock_id 呼叫爬蟲函式擷取股票收盤資料, 清洗後寫入工作表儲存格中 :
>>> for i in range(2, last_row + 1):
stock_id=ws[f"A{i}"].value
data=wearn_crawler(stock_id)
print(data)
{'name': '元大台灣50', 'open': 120.8, 'high': 120.8, 'low': 119.25, 'close': 119.8, 'last': 120.95}
{'name': '元大高股息', 'open': 28.74, 'high': 28.74, 'low': 28.35, 'close': 28.53, 'last': 28.86}
{'name': '台積電', 'open': 496.0, 'high': 500.0, 'low': 496.0, 'close': 497.5, 'last': 498.5}
{'name': '聯電', 'open': 42.95, 'high': 42.95, 'low': 41.85, 'close': 41.85, 'last': 42.9}
{'name': '中華電', 'open': 128.5, 'high': 129.5, 'low': 128.5, 'close': 129.0, 'last': 128.5}
可見都能正確地爬到所要的資料, 將收盤價 close 減掉昨收價 last 後再除以昨收價即可得到計算今日漲跌幅, 也可以 close 除以 last 後再減 1 :
帳跌幅=data["close"]/data["last"] - 1
然後就可以將它們存入工作表內 :
>>> for i in range(2, last_row + 1):
stock_id=ws[f"A{i}"].value
data=wearn_crawler(stock_id)
print(data)
ws[f'B{i}'].value=data["name"]
ws[f'C{i}'].value=data["open"]
ws[f'D{i}'].value=data["high"]
ws[f'E{i}'].value=data["low"]
ws[f'F{i}'].value=data["close"]
ws[f'G{i}'].value=data["last"]
ws[f'H{i}'].value=data["close"]/data["last"] - 1
{'name': '元大台灣50', 'open': 120.8, 'high': 120.8, 'low': 119.25, 'close': 119.8, 'last': 120.95}
{'name': '元大高股息', 'open': 28.74, 'high': 28.74, 'low': 28.35, 'close': 28.53, 'last': 28.86}
{'name': '台積電', 'open': 496.0, 'high': 500.0, 'low': 496.0, 'close': 497.5, 'last': 498.5}
{'name': '聯電', 'open': 42.95, 'high': 42.95, 'low': 41.85, 'close': 41.85, 'last': 42.9}
{'name': '中華電', 'open': 128.5, 'high': 129.5, 'low': 128.5, 'close': 129.0, 'last': 128.5}
將工作簿物件存檔後以 Excel 軟體開啟 :
>>> wb.save('twstock_dashboard.xlsx')
可見股價資料都已正確寫入 xlsx 檔案內了. 不過, 還有最近更新欄位 (I2) 還沒處理, 此欄位用來記錄資料更新的時間, 這需要用到 time 模組的 strftime() 函式, 使用的日期時間格式為 "%Y%m%d %H:%M:%S", 例如 :
>>> import time
>>> time.strftime("%Y%m%d %H:%M:%S")
'20220629 14:02:57'
關閉檔案後於 I2 欄位寫入更新時間, 再次存檔 :
>>> ws['I2'].value=time.strftime("%Y%m%d %H:%M:%S")
>>> wb.save('twstock_dashboard.xlsx')
以 Excel 軟體開啟可見 I2 欄位已有資料 :
茲將上面的程式碼寫成如下的 update_dashboard() 函式 :
def update_dashboard():
last_row=ws.max_row
for i in range(2, last_row + 1):
stock_id=ws[f'A{i}'].value
data=wearn_crawler(stock_id)
print(data)
ws[f'B{i}'].value=data["name"]
ws[f'C{i}'].value=data["open"]
ws[f'D{i}'].value=data["high"]
ws[f'E{i}'].value=data["low"]
ws[f'F{i}'].value=data["close"]
ws[f'G{i}'].value=data["last"]
ws[f'H{i}'].value=data["close"]/data["last"] - 1
ws['I2'].value=time.strftime("%Y%m%d %H:%M:%S")
print(f"最近更新時間 : {ws['I2'].value}")
呼叫此函式即可更新股票看板資料 :
>>> update_dashboard()
{'name': '元大台灣50', 'open': 120.8, 'high': 120.8, 'low': 119.25, 'close': 119.8, 'last': 120.95}
{'name': '元大高股息', 'open': 28.74, 'high': 28.74, 'low': 28.35, 'close': 28.53, 'last': 28.86}
{'name': '台積電', 'open': 496.0, 'high': 500.0, 'low': 496.0, 'close': 497.5, 'last': 498.5}
{'name': '聯電', 'open': 42.95, 'high': 42.95, 'low': 41.85, 'close': 41.85, 'last': 42.9}
{'name': '中華電', 'open': 128.5, 'high': 129.5, 'low': 128.5, 'close': 129.0, 'last': 128.5}
最近更新時間 : 20220629 14:17:25
>>> wb.save('twstock_dashboard.xlsx')
以 Excel 軟體再次開啟檔案可見 I2 欄位的時間已更新 :
(4). 以 Line Notify 推播收盤訊息 :
上面的股票看板雖然已能正確更新每日收盤資料, 但如果能將重要訊息例如股價漲跌幅等透過 Line Notify 訊息推播功能傳送到手機, 就可以不需要開啟股票 App 便能從最常用的 Line 得知所關心的股票收盤情形, 關於 Line Notify 用法參考 :
首先要先在 Line Notify 官網申請一個權杖 (token), 然後開啟 twstock_dashbiard.xlsx 檔, 將此權杖填入 J2 儲存格中後存檔 :
然後載入此 xlsx 檔後讀取 J2 儲存格即可得到權杖 :
>>> wb=xl.load_workbook('twstock_dashboard.xlsx')
>>> ws=wb1.active
>>> token=ws['J2'].value
>>> token
'ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'
發送推播訊息的功能可以用如下的函式來達成 :
def notify(msg, token):
url="https://notify-api.line.me/api/notify"
headers={"Authorization": "Bearer " + token}
payload={"message": msg}
r=requests.post(url, headers=headers, params=payload)
return "訊息發送成功!"
只要傳入訊息與權杖就可以完成推播了.
最後要修改上面的 update_dashboard() 函式, 加入下列程式碼推播觀察股票的漲跌幅 :
def update_dashboard():
last_row=ws.max_row
msg=['\n']
for i in range(2, last_row + 1):
stock_id=ws[f'A{i}'].value
data=wearn_crawler(stock_id)
print(data)
ws[f'B{i}'].value=data["name"]
ws[f'C{i}'].value=data["open"]
ws[f'D{i}'].value=data["high"]
ws[f'E{i}'].value=data["low"]
ws[f'F{i}'].value=data["close"]
ws[f'G{i}'].value=data["last"]
delta=round((data["close"]/data["last"] - 1) * 100, 2)
ws[f'H{i}'].value=delta
msg.append(f'{data["name"]} {data["close"]} ({delta}%)')
ws['I2'].value=time.strftime("%Y%m%d %H:%M:%S")
print(f"最近更新時間 : {ws['I2'].value}")
notify('\n'.join(msg), token)
此處黃底高亮的是修改的部分, 在函式開頭新增了 msg 串列用來儲存各股收盤與漲跌幅資訊, 預建 '\n' 的目的式為了讓傳送的訊息與 Line 推播標題不要黏在一起. 漲跌幅計算部分增加了 delta 變數來記錄, 原始數據被乘以 100 轉成百分比, 並取到小數後第 2 位, 每支股票的股名, 收盤價, 與漲跌幅都放進 msg 串列中, 最後呼叫字串物件的 join() 以跳列字元串接, 結果如下 :
>>> update_dashboard()
{'name': '元大台灣50', 'open': 120.8, 'high': 120.8, 'low': 119.25, 'close': 119.8, 'last': 120.95}
{'name': '元大高股息', 'open': 28.74, 'high': 28.74, 'low': 28.35, 'close': 28.53, 'last': 28.86}
{'name': '台積電', 'open': 496.0, 'high': 500.0, 'low': 496.0, 'close': 497.5, 'last': 498.5}
{'name': '聯電', 'open': 42.95, 'high': 42.95, 'low': 41.85, 'close': 41.85, 'last': 42.9}
{'name': '中華電', 'open': 128.5, 'high': 129.5, 'low': 128.5, 'close': 129.0, 'last': 128.5}
最近更新時間 : 20220629 15:21:15
'訊息發送成功!'
這時我的 iPhone 手機的 Line 就馬上收到此訊息了 :
Bingo!
(5). 部署到樹莓派 :
以上都是在互動環境中測試, 指令都是人為下達, 可以將程式部署到可 24 小時開機的低功率 Linux 嵌入式設備 (例如樹莓派或香蕉派等) 上, 然後用 crontab 來設定定時或週期性執行. 茲將上面的程式整理為如下之 twstock_dashboard.py 模組 :
# twstock_dashboard.py
import time
import requests
import openpyxl as xl
from bs4 import BeautifulSoup
def create_dashboard(filename, stocks, token=''): # 初始化時執行一次
wb=xl.Workbook()
ws=wb.active
ws.title='股票看板'
# 建立第一列的欄名
ws['A1'].value='股票代號'
ws['B1'].value='股票名稱'
ws['C1'].value='開盤價'
ws['D1'].value='最高價'
ws['E1'].value='最低價'
ws['F1'].value='成交價'
ws['G1'].value='昨日收盤價'
ws['H1'].value='帳跌幅'
ws['I1'].value='最近更新'
ws['J1'].value='Line Notify Token'
# 將欲觀察的股票代號填入 A2, A3, A4, ... 儲存格
for idx, stock in enumerate(stocks):
ws[f'A{idx + 2}'].value=stock
# 將 Line Notify token 填入 J2 儲存格
ws['J2'].value=token
# 將工作簿存檔
try:
wb.save(filename)
except Exception:
print(f"Permission Error : {filename} 開啟中, 請關閉")
return wb, ws
def wearn_crawler(sid):
res=requests.get(f"https://stock.wearn.com/cdata.asp?kind={sid}")
res.encoding="big5"
html=BeautifulSoup(res.text, "html.parser")
table=html.findAll("table")[0]
trs=table.findAll("tr")
tr=trs[2]
tds=tr.findAll("td")
data=[float(td.text.replace("\xa0", "").replace(",", "")) for td in tds[1:]]
name=trs[0].td.text.split(" ")[1][:-1]
last_close=float(trs[3].findAll("td")[4].text.replace("\xa0", "").replace(",", ""))
return {
"name": name,
"open": data[0],
"high": data[1],
"low": data[2],
"close": data[3],
"last": last_close
}
def notify(msg, token):
url="https://notify-api.line.me/api/notify"
headers={"Authorization": "Bearer " + token}
payload={"message": msg}
r=requests.post(url, headers=headers, params=payload)
return "訊息發送成功!"
def update_dashboard(ws):
last_row=ws.max_row # 取得總列數
token=ws['J2'].value
msg=[''] # 儲存推播訊息之串列
for i in range(2, last_row + 1): # 讀取欲觀察股票清單
stock_id=ws[f'A{i}'].value
data=wearn_crawler(stock_id) # 呼叫爬蟲函式擷取聚財網收盤資料
print(data)
ws[f'B{i}'].value=data["name"]
ws[f'C{i}'].value=data["open"]
ws[f'D{i}'].value=data["high"]
ws[f'E{i}'].value=data["low"]
ws[f'F{i}'].value=data["close"]
ws[f'G{i}'].value=data["last"]
delta=round((data["close"]/data["last"] - 1) * 100, 2) # 計算漲跌幅
ws[f'H{i}'].value=delta
msg.append(f'{data["name"]} {data["close"]} ({delta}%)')
ws['I2'].value=time.strftime("%Y%m%d %H:%M:%S")
print(f"最近更新時間 : {ws['I2'].value}")
notify('\n'.join(msg), token) # 呼叫 Line Notify 函式傳送推播訊息
此模組為上面互動測試中的函式總集, 在編輯時做了一些修正, 例如推播訊息串列 msg 的預設值改成了空字串, 因為原先用 '\n' 會使推播標題與訊息內容之間出現一個空列. 此模組的對外界面主要是下列兩個函式 :
- create_dashboard(filename, stocks, token='') : 系統初始化用
- update_dashboard(ws) : 更新股票看板用
使用前要匯入此模組 :
import twstock_dashboard as td
然後撰寫需要自動執行的程式 twstock_dashboard_update.py, 它主要的任務是開啟工作簿與工作表, 呼叫爬蟲與推播函式 update_dashboard(), 最後將工作簿存檔, 程式碼如下 :
# twstock_dashboard_update.py
import openpyxl as xl
import twstock_dashboard as td
wb=xl.load_workbook('twstock_dashboard.xlsx') # 開啟工作簿檔案
ws=wb.active # 取得目前工作表
td.update_dashboard(ws) # 更新股票看板 (爬聚財網 + Line Notify 推播)
wb.save('twstock_dashboard.xlsx') # 將工作簿存檔
接著將這兩個檔案用 WinSCP 傳送到樹莓派上 :
然後開啟樹莓派終端機, 進入 Python3 互動環境, 用下列指令手動初始化建立工作簿 :
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import openpyxl as xl
>>> import twstock_dashboard as td
>>> filename='twstock_dashboard.xlsx'
>>> stocks=['0050', '0056', '2330', '2303', '2412']
>>> token='ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'
>>> td.create_dashboard(filename, stocks, token)
>>> exit()
這樣就會建立一個 twstock_dashboard.xlsx 工作簿檔案. 以上程式碼雖然也可以寫成一個 .py 模組, 但最好還是像上面這樣使用互動介面以人工方式下指令, 不要存成檔案, 因為 token 放在程式中很犯規. 用 ls 檢視檔案 :
pi@raspberrypi:~ $ ls twstock* -ls
4 -rw-r--r-- 1 pi pi 2918 6月 30 11:26 twstock_dashboard.py
4 -rw-r--r-- 1 pi pi 308 6月 30 11:25 twstock_dashboard_update.py
8 -rw-r--r-- 1 pi pi 5084 6月 30 14:24 twstock_dashboard.xlsx
可見已經順利建立工作簿檔案了. 接著用下列指令手動更新收盤的股價資料 :
pi@raspberrypi:~ $ python3 twstock_dashboard_update.py
{'name': '元大台灣50', 'open': 118.7, 'high': 119.55, 'low': 118.5, 'close': 118.8, 'last': 119.8}
{'name': '元大高股息', 'open': 28.29, 'high': 28.39, 'low': 28.1, 'close': 28.14, 'last': 28.53}
{'name': '台積電', 'open': 496.0, 'high': 498.5, 'low': 491.0, 'close': 491.0, 'last': 497.5}
{'name': '聯電', 'open': 41.25, 'high': 41.55, 'low': 40.7, 'close': 40.9, 'last': 41.85}
{'name': '中華電', 'open': 129.0, 'high': 130.5, 'low': 128.5, 'close': 130.5, 'last': 129.0}
最近更新時間 : 20220630 14:35:46
手機馬上就收到 Line Notify 推播訊息了 :
接下來要用 chmod 指令將看板更新程式 twstock_dashboard_update.py 改為可執行檔 :
pi@raspberrypi:~ $ sudo chmod +x /home/pi/twstock_dashboard_update.py
pi@raspberrypi:~ $ ls twstock* -ls
4 -rw-r--r-- 1 pi pi 2918 6月 30 11:26 twstock_dashboard.py
4 -rwxr-xr-x 1 pi pi 308 6月 30 11:25 twstock_dashboard_update.py
8 -rw-r--r-- 1 pi pi 5359 6月 30 14:35 twstock_dashboard.xlsx
這樣就可以設定 crontab 來自動執行看板更新程式了 :
pi@raspberrypi:~ $ crontab -e
no crontab for pi - using an empty one
Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.tiny
3. /bin/ed
Choose 1-3 [1]: 1
crontab: installing new crontab
選擇 nano 來編輯 crontab, 先用每 5 分鐘觸發執行一次, 寫法如下 :
*/5 * * * * /usr/bin/python3 /home/pi/twstock_dashboard_update.py
按 Ctrl + O 存檔後再按 Ctrl + X 跳出 Nano, 再用 crontab -l 確認內容 :
pi@raspberrypi:~ $ crontab -l
*/5 * * * * /usr/bin/python3 /home/pi/twstock_dashboard_update.py
結果確實能每五分鐘收到 Line Notify 推播訊息 :
測試 OK 後就可以修改 crontab 設定, 改為每周一到周五 (交易日) 的下午四點與五點各抓一次, 因為聚財網每日收盤資料大約在 15:50 左右更新, 為了避免爬蟲在 16:00 抓資料時網路剛好斷線, 所以五點再抓一次 :
將 crontab 存檔即生效, 果然 16:00 與 17:00 都有收到推播 :
OK! 這樣就完成樹莓派上的自動化部署了.
參考 :
2022-06-30 補充 :
修改了 twstock_dashboard_update.py 裡面 update_dashboard() 函式的部分程式碼, 一是更改時間格式為 "%Y-%m-%d %H:%M:%S", 其次是將更新時間加到推播訊息最後面 :
def update_dashboard(ws):
last_row=ws.max_row # 取得總列數
token=ws['J2'].value
msg=[''] # 儲存推播訊息之串列
for i in range(2, last_row + 1): # 讀取欲觀察股票清單
stock_id=ws[f'A{i}'].value
data=wearn_crawler(stock_id) # 呼叫爬蟲函式擷取聚財網收盤資料
print(data)
ws[f'B{i}'].value=data["name"]
ws[f'C{i}'].value=data["open"]
ws[f'D{i}'].value=data["high"]
ws[f'E{i}'].value=data["low"]
ws[f'F{i}'].value=data["close"]
ws[f'G{i}'].value=data["last"]
delta=round((data["close"]/data["last"] - 1) * 100, 2) # 計算漲跌幅
ws[f'H{i}'].value=delta
msg.append(f'{data["name"]} {data["close"]} ({delta}%)')
ws['I2'].value=time.strftime("%Y-%m-%d %H:%M:%S")
print(f"最近更新時間 : {ws['I2'].value}")
msg.append(ws['I2'].value) # 訊息以更新時間結尾
notify('\n'.join(msg), token) # 呼叫 Line Notify 函式傳送推播訊息
結果如下 :
這樣往回看訊息時就很清楚這是哪天的收盤資料.
2022-07-02 補充 :
今天修改了 notify() 函式如下 :
def notify(msg, token):
url="https://notify-api.line.me/api/notify"
headers={"Authorization": "Bearer " + token}
payload={"message": msg}
r=requests.post(url, headers=headers, params=payload)
if r.status_code==requests.codes.ok:
return '訊息發送成功!'
else:
return f'訊息發送失敗: {r.status_code}'
原程式沒有判別回應碼一律回應 '訊息發送成功' 似乎不妥, 故加上 if else 判斷.
訂閱:
文章
(
Atom
)























