Python 程式設計 (第七課) (ver.4)

Eugene Chang (張佑成)

October 27th 2024

# 功課解答 加强版剪刀石頭布:[程式碼](https://gist.github.com/yuyueugene84/703d606241013d29500d06539d0eca6e) --- # 之前我們實作了一支台股爬蟲 --- # 但是我們不滿足... 因爲寫爬蟲其實十分費時,維護成本可能也會很高 --- # 如何從網路截取資料? 假設我們今天不是透過手動,而是透過電腦程式去擷取資料時 一般的作法有以下兩種: - 透過**網頁爬蟲** - 透過**廠商客制化的 API** 今天我們來講解一下 API --- # 先説一下 API 是什麽? **Application Programming Interface (API)** 應用程式界面 可以想象成程式 / 系統的界面,不過這個界面並非是給人使用,而是讓其它程式使用的 其目的就是希望不同語言/架構/廠商寫的程式,若能夠有一些共同的界面, 彼此之間就可以互相串連起來,達成共享資料或是擴充功能的目的 --- # API 在今天... 普偏會被直接理解成 "網頁程式的 API", 這個世界其實就是由許多 API 透過 HTTP 這個網路的溝通協定串起來的。 --- # 來看一下我們這堂課會使用的 Fugle API 連結:https://developer.fugle.tw/realtime/document --- # 如何使用 Web API? 要成功使用 Web API,通常需要以下: 1. 網址 2. 參數 3. HTTP 動詞 --- # 網際網路 101 --- # HTTP 溝通協定 - 用白話說就是電腦與電腦之間的**共同語言** - 網路的運作方式與電話綫不同,是一個**非持續性的連綫** - 今天我們常用的網站都是一個個運行在雲端伺服器上的程式 --- # HTTP 溝通協定 **運作方式**: 1. 客戶端 (瀏覽器) 發送一個 HTTP request (請求) 2. 對方伺服器接受到請求,根據請求計算出相對應的資料 (html, json, etc.) 3. 回應資料給呼叫它的客戶端 4. 客戶端 (瀏覽器) 將回應的 html 等程式碼轉解析、並且呈現成使用者看的懂得網頁 --- # HTTP 動詞 一般來説,人與資料之間的互動,不外乎就是需要做以下四個操作,而 HTTP 針對每一個操作也都有一個相對應的動詞,用來區分不同的請求: - 建立資料 (GET) - 讀取資料 (POST) - 更新資料 (PUT) - 刪除資料 (DELETE) 補充資料:[Mozilla 官方文件](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods) --- # GET vs POST Request 當你不確定該用哪一種 Request 時,一個簡單的原則是: **GET** - 當你要**讀取資料**時 (讀取靜態頁面時) **POST** - 當你要**創造資料**時 (發送表單時) --- # Fugle API 會回傳給我們以下: ```json { "apiVersion": "0.1.0", "data": { "info": { "countryCode": "TW", "date": "2019-05-24", "lastUpdatedAt": "2019-05-24T05:55:13.375Z", "mode": "twse-sem", "symbolId": "2884", "timeZone": "Asia/Taipei" }, "quote": { "isCloseDelayed": false, "isClosed": true, "isHalting": false, "isOpenDelayed": false, "order": { "bestAsks": [ { "price": 26.15, "unit": 595, "volume": 595000 }, { "price": 26.2, "unit": 2090, "volume": 2090000 }, { "price": 26.25, "unit": 596, "volume": 596000 }, { "price": 26.3, "unit": 947, "volume": 947000 }, { "price": 26.35, "unit": 252, "volume": 252000 } ], "bestBids": [ { "price": 25.9, "unit": 925, "volume": 925000 }, { "price": 25.95, "unit": 872, "volume": 872000 }, { "price": 26, "unit": 983, "volume": 983000 }, { "price": 26.05, "unit": 362, "volume": 362000 }, { "price": 26.1, "unit": 29, "volume": 29000 } ], "at": "2019-05-24T05:30:00.000Z" }, "priceHigh": { "at": "2019-05-24T01:46:52.461Z", "price": 26.2 }, "priceLow": { "at": "2019-05-24T01:00:29.591Z", "price": 25.9 }, "priceOpen": { "at": "2019-05-24T01:00:04.498Z", "price": 25.95 }, "isCurbing": false, "isTrial": false, "total": { "at": "2019-05-24T05:55:13.375Z", "unit": 21132, "volume": 21132000 }, "trial": { "at": "2019-05-24T05:29:55.631Z", "price": 26.1, "unit": 989, "volume": 989000 }, "trade": { "at": "2019-05-24T05:30:00.000Z", "price": 26.1, "serial": 971215, "unit": 989, "volume": 989000 } } } } ``` 這個看似像亂碼的東西其實是一包裝滿資料的 JSON 物件 --- ## 什麼是 JSON? - JSON(JavaScript Object Notation,JavaScript 物件表示法) - 以純文字為基礎,來儲存和交換簡單結構的輕量級「資料交換格式」 - 獨立於語言 ```javascript { "foo": "bar" } ``` 和 Python 的 dict (字典) 可以說是一樣的東西 --- # 來試試用 Python 呼叫它 ```python import requests result = requests.get("https://api.fugle.tw/realtime/v0/intraday/quote?symbolId=2884&apiToken=demo") print(result.json()) ``` --- # 從這個 JSON 内找出我們想要的資料 ```python import requests result = requests.get("https://api.fugle.tw/realtime/v0/intraday/quote?symbolId=2884&apiToken=demo") json_data = result.json() # .json() 會幫你把 JSON 資料格式轉成 python 的 dict,所以接下來我們就可透過 python 到 dict 取值的方法得到我們需要的資料: price_high = json_data["data"]["quote"]["priceHigh"]["price"] price_high ``` --- # 隨堂練習 請解析我們剛才的 json 資料,並且使用 Python 讀出玉山銀行在上個交易日的: - 開盤價 - 最高價 - 最低價 - 收盤價 --- # 原來用 Python 截取資料是那麽簡單的事啊! 沒錯! 不過前提是**有人很佛心的提供 API** 給你使用... --- # 網頁爬蟲看似高大上,但是... 對於網頁的擁有者 / 維護者來説,開發 API: - 網頁爬蟲需要花費大量的時間去開發 👎 - 網頁爬蟲需要花費大量的時間去維護 👎 - 網頁爬蟲門檻高,需要熟悉 html, css, javascript 等前端技術 👎 - 現在多數熱門網站都有做出防爬蟲的機制,大幅提升了實作爬蟲的難度 👎 --- # 網頁爬蟲 vs API - 網頁爬蟲需要花費大量的時間去開發、分析、和維護,從時間報酬率的角度來看,**不一定值得** - 今天我們寫程式**只是手段,而非目的**,若花費太多時間在寫程式,而沒有從投資中賺到錢,那反而得不償失 --- # 但是我們還是不滿足... 因爲要手動執行寫好的程式,太麻煩了... --- # 範例 Excel 檔案 [範例 Excel 檔](https://www.dropbox.com/s/ps6owuou4nk33ch/tw_stock_portfolio.xlsx?dl=1) --- # 完整版程式碼 [截取、分析、多筆台股資料、寫入 Excel、與上傳分析報告至 Line](https://gist.github.com/yuyueugene84/e9663b63cd1de818544218e2a03335a7) --- # 完整版程式碼 [下載連結](https://www.dropbox.com/s/e2d16ezxhdya0sa/tw_crawler.py?dl=1) --- ## 如何能夠讓電腦定時自動執行寫好的程式? 這時我們就需要學習如何**排程**了 --- ## Windows 排程 **Windows Scheduler** ![alt text](https://www.dropbox.com/s/6h9vc5ywwegx048/scheduler.PNG?dl=1) --- ## 如何排程 我們必須知道如何在指令列上把 Python 的程式跑起來 --- ## 指令列簡介 簡單的邏輯是: ```bash command [-options] arg1 arg2 ... 指令名稱 選項 參數1 參數2 ``` 今天我們要用某一個應用程式跑一個檔案,指令就是: ```bash 你執行檔案用的程式 你要跑的那支程式的絕對路徑 ``` --- ## 如何排程 請搜尋一下 `python.exe` 檔的位置 在 cmd.exe 輸入: ``` where python ``` 應該會顯示: ```bash C:\Python\Python36\python.exe ``` ```bash # 若是使用 anaconda 的話... C:\Users\你的使用者名稱\Anaconda3\python.exe ``` --- ## 如何排程 接下來是找到你要執行的 Python 檔案的絕對路徑 (以我自己的電腦爲例): ```bash C:\Users\Eugene\Python\automate_boring_stuff_with_python\65_auto_whether_report.py ``` --- ## 最後我們要在指令列下的指令(以我自己的電腦爲例) ```bash C:\Users\Eugene\Anaconda3\python.exe C:\Users\Eugene\Python\automate_boring_stuff_with_python\66_auto_tsmc_stock_report.py ``` --- ## 接下來我們就可以到工作排程器建立一個新工作 --- ## 全自動的股價觀測機器人完工! 😎 撒花ing 😎 --- ## Mac 排程 **crontab** [教學](http://honglu.me/2014/09/20/OSX%E7%B3%BB%E7%BB%9F%E6%B7%BB%E5%8A%A0%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/) --- # 大量資料的運算 爬下來的資料,總是需要做分析和運算 意味著我們要學會如何有效的去處理資料 --- # 回顧一下串列 之前我們學會了串列(List) 今天若我需要對串列做 Element-wise(逐元)運算 也就是將串列内的每一個元素都做一樣的運算或操作 必須要用迴圈處理 --- # 範例 將 `km_list` 内的每一個長度從公里換算成英里: ```python km_list = [3, 5, 10, 21, 42.195] mile_list = [] km_to_mile = 0.621371192 for km in km_list: mile_list.append(km * km_to_mile) print(mile_list) # [1.864113576, 3.10685596, 6.21371192, 13.048795032000001, 26.21875744644] ``` --- # for 迴圈雖然可以幫我們做逐元運算 但是程式碼還是過渡冗長... --- # 串列生成式(List Comprehension) 今天我們知道上述範例最後計算的結果會是一個串列(需要封裝多筆資料) 因此,Python 就提供了串列生成式,允許開發者用簡單的程式碼做出逐元運算 --- # 串列生成式(List Comprehension) ```python [運算式 for 項目 in 可迭代項目] ``` --- # 串列生成式(List Comprehension) ```python km_list = [3, 5, 10, 21, 42.195] km_to_mile = 0.621371192 # 將 km_list 的每一筆資料逐個放入 km,在 for 左邊的運算式算出結果之後放入 mile_list mile_list = [km * km_to_mile for km in km_list] print(mile_list) # [1.864113576, 3.10685596, 6.21371192, 13.048795032000001, 26.21875744644] ``` --- # 串列生成式(List Comprehension) ```python km_list = [3, 5, 10, 21, 42.195] km_to_mile = 0.621371192 # 將 km_list 的每一筆資料逐個放入 km,在 for 左邊的運算式算出結果之後放入 mile_list mile_list = [km * km_to_mile for km in km_list] print(mile_list) # [1.864113576, 3.10685596, 6.21371192, 13.048795032000001, 26.21875744644] ``` --- # 串列生成式(List Comprehension) 另外,若今天需要做逐元的判斷 ```python even_numbers = [] for i in range(1, 11): if i % 2 == 0: even_numbers.append(i) print(even_numbers) # [2, 4, 6, 8, 10] ``` --- # 串列生成式(List Comprehension) 我們可以在串列生成式加上 `if` 判斷式 ```python even_numbers = [i for i in range(1, 11) if i % 2 == 0] print(even_numbers) # [2, 4, 6, 8, 10] ``` --- # 一行搞定判斷式 一個 if...else 判斷式再簡單,也需要四行程式碼才能完成 ```python def greater_than_five(x): if x > 5: return True else: return False ``` --- # 一行搞定判斷式 ```python x = 6 True if x > 5 else False # True ``` --- # 最後我們的函式就可以簡化成... ```python def greater_than_five(x): return True if x > 5 else False ``` --- # lambda 匿名函數 我們在寫函式 (Function) 的時候,若是遇上相對簡單的邏輯: ```python def plus_one(n): result = n + 1 return result ``` 這樣的語法就相對的有些囉嗦... --- # lambda 匿名函數 這裡就跟各位介紹 lambda 函數 **lambda 參數1, 參數2 : 算式** ```python # 注意我可以把 lambda 函數賦值給一個變數 plus_one = lambda n : n + 1 # 再把變數當成函數使用 plus_one(10) # 11 ``` --- # lambda 匿名函數 - 不需要 def、return - 也不需要為函數取名 --- # 隨堂練習 用 lambda 函數計算營業稅金額 ```python calculate_tax = lambda amount : ___________ calculate_tax(100000) ``` --- # lambda 匿名函數 如果今天lambda 函數有超過一個參數時... ```python area_of_rectangle = lambda width, height : width * height area_of_rectangle(10, 20) # 200 ``` --- # lambda 匿名函數 背後的原理:λ演算法 [λ演算法 Wiki](https://en.wikipedia.org/wiki/Lambda_calculus) --- # map 函數 若今天我需要針對一個 list 的資料做逐元運算 得出的結果也是一個 list, 實作上就相對麻煩... ```python items = [1, 2, 3, 4, 5] squared = [] for i in items: squared.append(i**2) ``` --- # map 函數 除了用迴圈之外,其實還可以使用 map 函數: map(要使用的函數, 用 list 等資料結構封裝的輸入) ```python items = [1, 2, 3, 4, 5] # 把 items 裡面所有數字的二次方算出來 result = map(lambda x: x**2, items) # 最後別忘了把回傳的 map 物件轉成 list list(result) # [1, 4, 9, 16, 25] ``` --- # lambda vs def - 在函數的邏輯比較簡單的狀況下,會讓程式碼簡潔很多 - 在處理大量的資料 (通常都是 list / array) 可以讓程式碼簡潔一些 - 對於習慣 functional programming 的人來説 (像是 R),語法比較直覺 --- # 隨堂練習 用 lambda 與 map 函數算出所有在 receivables 裡的營業稅 ```python receivables = [1000, 100, 1000000, 2344550, 543000] taxes = _________________________ ``` --- # filter 函數 別被它的名字誤導了! 若今天你想把一個集合的資料全部都迭代過一次 以傳入的boolean function作為條件函式 迭代每一個集合的元素,並收集結果為True的元素到一個List --- # filter 函數 ```python def greater_than_five(x): if x > 5: return True else: return False data = [1, 2, 3, 4, 5, 6, 7, 8, 9] result = filter(greater_than_five, data) list(result) # [6, 7, 8, 9] ``` --- # filter 函數 上述的範例也可以改成用 lambda 函數寫: ```python data = [1, 2, 3, 4, 5, 6, 7, 8, 9] list(filter(lambda n : n > 5, data)) # [6, 7, 8, 9] ``` --- # map() vs filter() - map 是算出每一個集合的值 (通常使用的 function 會回傳數值) - filter 是判斷一個集合有哪些值會被保留下來 (通常使用的 function 會回傳布林值) --- # 隨堂練習 請用 lambda + filter 過濾出所有在 Infinity War 存活下來的復仇者 ```python avengers_survival = { "iron_man": True, "captain_america": True, "thor": True, "war_machine": True, "hulk": True, "vision": False, "spider_man": False, "dr_strange": False, "black_panther": False } # 最後希望看到的結果: ['iron_man', 'captain_america', 'thor', 'war_machine', 'hulk'] ``` --- # 解答 ```python list(filter(lambda n : avengers_survival[n] == True, avengers_survival.keys())) ``` --- # 資料科學 目前 Python 最大的應用之一 --- # 之前我們已經學會了 list 我想一次把這幾個距離從公里換算成英里 ```python km_list = [3, 5, 10, 21, 42.195] km_to_mile = 0.621371192 ``` --- # list 的問題 - Python 的 list 無法使用 element-wise(逐元)運算 - 在不引用套件的情況下我們可以使用迴圈來處理 ```python km_list = [3, 5, 10, 21, 42.195] km_to_mile = 0.621371192 mile_list = [] for km in km_list: mile_list.append(km * km_to_mile) print(mile_list) ``` --- # list 的問題 當然,我們也可以使用 map() 和 lambda 函數來處理 ```python km_list = [3, 5, 10, 21, 42.195] mile_list = list(map(lambda x: x * 0.621371192, km_list)) print(mile_list) ``` --- # 但是這樣似乎把程式碼變得更複雜了... --- # 這時就跟大家介紹一下 numpy [官網](https://docs.scipy.org/doc/numpy/user/quickstart.html) - 它是 Python 的一個重要模組,主要用於大量資料處理上 - 功能强大,語法簡單易懂 - Numpy 底層以 C 和 Fortran 語言實作,操作多重維度的陣列效能極佳 --- # import numpy ```python import numpy as np ``` --- # numpy 牛刀小試 ```python km_list = [3, 5, 10, 21, 42.195] # 將一個串列轉換成一個 numpy array km_array = np.array(km_list) print(type(km_array)) # numpy.ndarray ``` --- # numpy 牛刀小試 ```python km_list = [3, 5, 10, 21, 42.195] km_array = np.array(km_list) km_to_mile = 0.621371192 mile_array = km_array * km_to_mile print(mile_array) # [ 1.86411358 3.10685596 6.21371192 13.04879503 26.21875745] ``` --- # numpy.arange() 方法 功能與 `range()` 函數相同,宣告一個數字的範圍 ```python import numpy as np arr1 = np.arange(10) print(arr1) arr2 = np.arange(1, 10) print(arr2) arr3 = np.arange(1, 10, 2) print(arr3) # [0 1 2 3 4 5 6 7 8 9] # [1 2 3 4 5 6 7 8 9] # [1 3 5 7 9] ``` --- # 提取單筆資料 語法與 Python 串列一樣,透過指定索引值 ```python arr = np.array([1,2,3]) arr[0] # 1 ``` --- # 切片(Slicing) ```python arr1 = np.arange(0, 10) print(arr1) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) arr1[1:3] # array([1, 2]) ``` --- # 切片(Slicing) ```python arr1 = np.arange(0, 10) print(arr1) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) arr1[5:] # array([5, 6, 7, 8, 9]) ``` --- # 切片(Slicing) ```python arr1 = np.arange(0, 10) arr1[:5] # array([0, 1, 2, 3, 4]) ``` --- # numpy 更新資料功能 ```python arr1 = np.arange(0, 10) arr1[2] = 8 print(arr1) # array([0, 1, 8, 3, 4, 5, 6, 7, 8, 9]) ``` --- # numpy 刪除資料功能 ```python arr1 = np.arange(0, 10) print(np.delete(arr1, 1)) # array([0, 2, 3, 4, 5, 6, 7, 8, 9]) ``` --- # numpy 刪除資料功能 ```python arr1 = np.arange(0, 10) print(np.delete(arr1, slice(0, 5))) # array([5, 6, 7, 8, 9]) ``` --- # ndArray 與 ndArray 的運算 ```python arr2 = np.arange(5) # array([0, 1, 2, 3, 4]) arr3 = np.arange(2, 12, 2) # array([1., 2., 3., 4., 5.]) ``` --- # ndArray 與 ndArray 的運算 ```python arr3 / 2 # array([1., 2., 3., 4., 5.]) ``` --- # ndArray 與 ndArray 的運算 ```python arr2 + arr3 # array([ 2, 5, 8, 11, 14]) ``` --- # ndArray 與 ndArray 的運算 ```python arr2 - arr3 # array([-2, -3, -4, -5, -6]) ``` --- # ndArray 與 ndArray 的運算 ```python arr2 * arr3 # array([ 0, 4, 12, 24, 40]) ``` --- # ndArray 與 ndArray 的運算 ```python arr2 / arr3 # array([0. , 0.25 , 0.33333333, 0.375 , 0.4 ]) ``` --- # 隨堂練習 - 請用 numpy 練習計算這五個人的 BMI - 回傳的多筆 bmi 值必須是一個陣列 ```python heights = [173, 168, 171, 189, 179] weights = [65.4, 59.2, 63.6, 88.4, 68.7] ``` --- # 多維串列 若今天我們想透過 Python 的資料結構表達一個矩陣(Matrix) 這就有一點麻煩了,因爲矩陣的資料是有序的,但是矩陣多半不會只有一個維度,用 Python 的串列表示,肯定不夠 --- # 多維串列 Python 可以在一個串列内放入一個或多個串列: ```python matrx = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]] ``` --- # 多維串列 把它展開來看: ```python matrx = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ] ``` 就會發現這種巢狀串列很適合用來表達像矩陣的多維資料 --- # 多維串列 一般我們在表達多維串列大小的方式,與表達矩陣大小的方式是一樣的,下面的範例程式碼就是一個 3 x 4 的多維串列 ```python matrx = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ] ``` --- # 提取多維串列的資料 今天若需要讀取多維串列内的值 ```python matrx[第一維的索引值, 第二維的索引值] ``` --- # 提取多維串列串列的資料 今天若需要讀取多維串列内的值 ```python matrx[0] # [1, 2, 3, 4] matrx[0][3] # 4 ``` --- # Nested Loop (巢狀迴圈) 若要將多維串列内的每一個元素都遍歷一次,可以用巢狀迴圈,也就是在迴圈内再寫一個迴圈 ```python for i in range(0, 3): for j in range(0, 4): print(matrx[i][j]) ``` --- # np 對多維串列的支援 ```python arr = np.arange(12) arr # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) ``` --- # np.reshape() 改變一個 ndArray 的形狀 ```python arr.reshape(4,3) # array([[ 0, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]]) ``` --- # np.shape 檢查一個 ndArray 的大小 ```python arr1 = arr.reshape(4,3) arr1.shape # (4, 3) ``` --- # 多維 ndArray 的切片 ```python arr1[:2] # array([[0, 1, 2], # [3, 4, 5]]) ``` --- # 多維 ndArray 的切片 ```python arr1[:2] # array([[0, 1, 2], # [3, 4, 5]]) ``` --- # 多維 ndArray 的切片 ```python arr4[2:] # array([[ 6, 7, 8], # [ 9, 10, 11]]) ``` --- # 多維 ndArray 的切片 ```python arr4[2:,1:] # array([[ 7, 8], # [10, 11]]) ``` --- # np.zeros ```python np.zeros((3,2)) # array([[0., 0.], # [0., 0.], # [0., 0.]]) ``` --- # np.ones ```python np.ones((3,4)) # array([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]]) ``` --- # xlwings 對 NumPy 的支援 ```python import xlwings as xw wb = xw.Book(r"你的 tsmc_back_test.xlsx 檔案路徑") sheet = wb.sheets["2330"] tmsc_prices = sheet.range("B2:B193").value ``` --- # xlwings 對 NumPy 的支援 ```python tsmc_prices = sheet.range("B2:B193").options(np.array).value tsmc_prices ``` --- # xlwings 對 NumPy 的支援 ```python tsmc_prices2 = sheet.range("B2").options(np.array, expand="down").value tsmc_prices2 ``` --- # xlwings 對 NumPy 的支援 ```python tsmc_prices2.reshape(len(tsmc_prices2), 1) ``` --- # xlwings 對 NumPy 的支援 ```python sheet.range("C2").value = tsmc_prices2.reshape(len(tsmc_prices2), 1) ``` --- # 補充 有興趣的可以參考到 Numpy 的官方文件做進一步的學習。 官方教學:[https://docs.scipy.org/doc/numpy/user/quickstart.html](https://docs.scipy.org/doc/numpy/user/quickstart.html) Github 專案:[https://github.com/numpy/numpy](https://github.com/numpy/numpy)

Thanks for Watching

Contact: yuyueugene84@gmail.com

Download PDF