# 2023-10-18 以Python來做Excel自動化與圖表專題 上課筆記 - [2023年10月18課程筆記](#2023-10-18) - [2023年10月25課程筆記](#2023-10-25) ## 2023-10-18 ## 安裝環境 ### 步驟一:安裝Python 如果你打算使用Python開發,請先安裝Python;Python官網:https://python.org/downloads/ > 提示:安裝Python時請先將VSCode關閉,以避免VSCode無法辨認到Pyhon。 > **注意:** > 安裝時請務必把 Add python.exe to PATH 選項打勾在進行安裝,否則未來在執行pip指令時會無法執行。 ### 步驟二:下載與安裝Visual Studio Code https://code.visualstudio.com/ 點選「Download for Windows」按鈕後直接「下一步」安裝到底即可。 > 如果使用頻果Mac系列電腦的話,按鈕會是「Download for Mac」。 ### 步驟三:安裝Python extension https://marketplace.visualstudio.com/items?itemName=ms-python.python 點擊連結會打開Visual Studio Code進行安裝,或是直接從VSCode內的Market Place安裝也可以。 ### 中文環境 extension(非必須) 在Extension搜尋「chinese」,選擇Chinese (Traditional) Language 中文(繁體)安裝。 > 此功能安裝後需要重新啟動Visual Studio Code ## 確認開發環境 1. 展開VSCode左邊的檔案總管 2. 開啟資料夾: 存放Python程式碼用 3. 建立新檔案: 檔名隨意,副檔名必須為`.py` 4. 檔案內容輸入: > `print('Hello Python')` 5. Ctrl + F5 執行程式碼,此時VSCode右下方會跳出建議安裝Python Extension的對話盒,點擊安裝 6. 再次Ctrl + F5 執行Python程式 7. 下方終端機會出現`Hello Python`及代表開發環境安裝成功 ## 好用快速鍵 | 功能 | MacOS | Windows | | ---------------------- | ------------------ | ------------------- | |執行程式| `Ctrl` - `F5`| `Ctrl` - `F5`| |畫面放大|`CMD` + +| `Ctrl` + `+`| |畫面縮小|`CMD` - +| `Ctrl` + `-`| |快速複製一行程式碼|`CMD` + `C` + `V`| `Ctrl` + `C` + `V`| |註解程式碼|`CMD` + `/`|`Ctrl` + `/`| ## 補充 1. 程式由: 輸入 計算 輸出 三個部分組成 2. 程式碼執行順序為由上往下,例如: ``` print('Hello Python') print('今天天氣很好') ``` 會在畫面上面先看到Hello Python ## 字串 單引號和雙引號的功用一樣,可以視情況做互換,例如字串本身包含有單引號時,就可以用雙引號來包圍字串。 例如: ``` "I'm a good student" 'Here is my "STUFF"!!' ``` 字串裡定義了一些特殊的轉義文字,有特殊功用或意義,不會直接在畫面上輸出。 | 符號 | 說明 | | ---------- | -------------------------------------------------- | | `\\` | 反斜線`\` | | `\'` | 單引號`'`' | | `\"` | 雙引號`"` | ``` 'Hi,\nGood Morning' "1\t2\t3" ``` 如果字串內想要顯示像`\t`這樣的字串的話,必須寫成`\\t`或是可以在字串前面加上`r`代表直接使用原始字串而不需要轉義,例如: ``` print('\\t') print(r'\t') ``` 輸出為: ``` \t \t ``` ## input()輸入 用來從終端視窗上接收使用者的輸入,輸入的資料為字串型態。 ``` a = input() print(a) ``` 1. 執行後畫面會停住,程式碼會暫停在`input()`該行等待使用者輸入,並按下「Enter」結束輸入的動作。 2. 使用者完成輸入後`input()`函式會傳回使用者的資料,此時需要使用指派運算子「=」,將資料存到變數(也就是電腦的記憶體)中,以避免程式碼往下執行時,資料消失,上面範例中`a`即為該變數名稱,透過`print()`函式,就可以將`a`的內容顯示到標準輸出。 可以在等待使用者輸入時,顯示提示文字: ``` a = input('請輸入您的姓名:') print('您的姓名為:', a) ``` ```python= print('輸入前') a = input() print('輸入後') print('剛剛你輸入的資料是:', a) ``` 1. 有些涵式執行完後會產生資料,必須透過`=`指派運算子將其存放下來,否則擋了下一行就會消失了。 ## 變數命名規則 1. 可以使用英文字母大寫及小寫,大小寫代表不同的變數。 > 例如:`a`和`A`是不同的變數名稱。 2. 可以使用數字,但第一個字不能是數字。 > 例如:`1abc`為錯誤命名,`abc1`為合法名字。 3. 不可以使用保留字。 > 保留字: > > If、elif、else、and、or、not、import、from、as、assert、for、while、break、continue、try、except、finally、pass、def、return、lambda、del、global、nonlocal、in、is、with、yield、class、None、True、False ## 格式化字串 ```python= name = input('請輸入姓名:') age = input('請輸入年齡:') print('你的名字是:', name, ', 年齡:', age, '歲') # f-string print(f'你的名字是: {name}, 年齡: {age}歲') ``` ## 數字與四則運算 #### 算數運算子 算術運算子有: | 運算子 | 說明 | | ------ | ---------------------- | | + | 加法運算 | | — | 減法運算 | | * | 乘法運算 | | ** | 指數運算 | | / | 除法運算 | | // | 除法運算,但只保留整數 | | % | 除法運算,但是只取餘數 | #### 比較運算 | 比較運算子 | 說明 | | ---------- | ------------------------------------------ | | > | 大於 | | >= | 大於或等於 | | < | 小於 | | <= | 小於或等於 | | == | 等於 | | != | 不等於 | ## 布林值與`in`判斷 `in`指令用來判斷前面的資料是否存在於後面的群集資料當中,運算結果為`True`或`False` ###### 上課範例: ```python= # in print('aa' in 'aaron') print('我' in '天氣') ``` ```python= print(1 + 1) print(1 - 1) print(2 * 2) print(2 / 2) print(2 ** 3) print(7 // 3) print(7 % 3) print('1' + '1' + '字串') # 串接 print('DE' * 5) # 布林直 print(True) print(False) # 比較運算子 print('3 > 4 =', 3 > 4) print('3 < 4 =', 3 < 4) print('3 >= 4 = ', 3 >= 4) print('3 <= 4 =', 3 <= 4) print('3 == 4 =', 3 == 4) print('3 != 4 =', 3 != 4) ``` ## `type()`涵式 用來偵測資料型態: ```python= print(type(1)) print(type('1')) print(type(True)) ``` 輸出結果: ``` <class 'int'> <class 'str'> <class 'bool'> ``` > **備註:** > `int`為Python的整數型態 > `str`為Python的字串型態 > `bool`為Python的布林型態 ## if判斷式 格式: ``` if 條件式: 條件成立執行的程式碼 ``` > **注意** > 流程內的程式碼一定要縮排。 ```python= score = input('請輸入成績: ') # 只要是input涵式接收到的資料,一律都是字串 # 字串傳換成數字 score = int(score) if score >= 60: print('及格') if score < 60: print('不及格') ``` > **補充:** > 所有的Python資料型態都有對應的涵式用來做資料轉型 ## 群集資料list 透過一堆中括號可以用來建立群集資料,list內可以存放字串,數字,布林直,甚至list 透過索引的方式可以取出指定位置的資料,所以從0開始,例如: ```python= a = [1, 2, 3, 4, 5] print(a[3]) ``` 會輸出: ``` 4 ``` ## `for in`迴圈 用來將群集資料一筆一筆取出來處理 ``` for 暫存變數 in 群集資料: 處理迭代出來的變數 ... ``` 當全部資料都被取出來後,迴圈即結束執行 ## 將不及格的分數顯示出來 ```python= # 把不及格的分數顯示到畫面上 a = [41, 72, 73, 54, 95, 86, 27, 18, 59] for score in a: if score < 60: print(score) ``` ## 字典 ```python= a = {'a': 33, 'b': 999} print(a['b']) a = {'aaron': '0987123456', 'andy': '0933127654'} print(a['aaron']) a = {'aaron': [44, 55, [1, 2, 3]], 'andy': [88, 77, 66]} print(a['aaron'][2][1]) ``` ## 一個複雜的資料結構 ```python= data = { 'status': 'ok', 'result': { 'name': 'aaron', 'info': [ { 'ar': 3, 'bemp': 9, 'addr': '中壢' } ] } } # 取得中壢那筆資料的方式 print(data['result']['info'][0]['addr']) ``` ## 模組的使用 #### 安裝requests模組 ``` $ pip3 install requests ``` #### 抓取桃園市Ubike資訊 ## 資料欄位說明 |索引|欄位名稱|說明| |-|-|-| |0|sareaen|行政區英文名| |1|sarea|行政區中文名| |2|lng|經度| |3|sna|中文站名| |4|snaen|英文站名| |5|bemp|空位數量| |6|ar|中文地址| |7|act|全站禁用狀態(0:禁用、1:啟用)| |8|sno|站編號| |9|aren|英文地址| |10|tot|場站總停車格| |11|_id|資料編號| |12|sbi|場站目前車輛數量| |13|mday|微笑單車各場站來源資料更新時間| |14|lat|緯度| > **資料來源:** > > https://data.gov.tw/dataset/137993 ```python= import requests import json import openpyxl import time url = 'https://data.tycg.gov.tw/api/v1/rest/datastore/a1b4714b-3b75-4ff8-a8f2-cc377e4eaa0f?format=json' # 呼叫request模組的get方法到網址的位置取回資料 response = requests.get(url) # 判斷抓取網址資料是否成功 if response.status_code == 200: # 透過json模組來將json字串轉換成Python資料結構 data = json.loads(response.text) # 接收使用者輸入的尋關鍵字 # user_input = input('請輸入要搜尋的站台部分名稱: ') # for row in data['result']['records']: # if user_input in row['sna']: # print(f"站名: {row['sna']}, 地址: {row['ar']}") # print(f" - 可借: {row['sbi']}") # print(f" - 可還: {row['bemp']}") # print(f" - 總數: {row['tot']}") # print() # 建立新的活頁簿 wb = openpyxl.Workbook() # 修改工作表名稱 wb.active.title = "桃園Ubike" # 先取得目前的工作表 ws = wb.active ws['A1'] = '站名' # ws.cell(column=1, row=1, value='站名2') ws['B1'] = '地址' ws['C1'] = '可借' ws['D1'] = '可還' ws['E1'] = '總數' # 從第二列開始寫入資料 row_index = 2 for row in data['result']['records']: ws.cell(column=1, row=row_index, value=row['sna']) ws.cell(column=2, row=row_index, value=row['ar']) ws.cell(column=3, row=row_index, value=row['sbi']) ws.cell(column=4, row=row_index, value=row['bemp']) ws.cell(column=5, row=row_index, value=row['tot']) row_index = row_index + 1 # 根據時間產生檔名 now = time.localtime() # 取得目前本地時間 time_str = time.strftime('%Y%m%d%H%M') # 格式化後的時間字串 # 存檔 wb.save('桃園Ubike-' + time_str + '.xlsx') ``` #### Excel介紹 Microsoft Excel副檔名為:xlsx或xls,以下為幾本名詞: - 活頁簿(workbook):Excel檔案本身又稱為活頁簿。 - 工作表(worksheet):一個活頁簿由一個或數個工作表組成,一個工作表即為一頁表格文件,可以透過Excel應用程式下方的標籤來切換工作表。 - 欄位(column):直的方向稱為欄位,名稱由英文字A、B、C...等等依序排列。 - 列(row):橫的方向稱為列,名稱由1、2、3...等等依序排列。 - 儲存格(cell):工作表內的每個存放資料的小格子稱為儲存格,可以用欄+列來定位每個儲存格。 #### 安裝`openpyxl`模組 ```bash= $ pip3 install openpyxl ``` ## 2023-10-25 ## 函式 ```python= a = 1 b = 2 c = 3 d = 4 # 比較兩個變數大小的函式 # 函式必須要透過[呼叫]的手段,才會被執行 def compare(n1, n2): if n1 > n2: print(n1, '比', n2, '大') else: print(n1, '真的沒有比', n2, '大') compare(6, 7) compare(9, 4) compare(10, 7) compare(3, 8) compare(78, 41) if a > b: print(a, '比', b, '大') else: print(a, '真的沒有比', b, '大') if c > d: print(c, '比', d, '大') else: print(c, '沒有比', d, '大') ``` #### 練習 寫一函式,名叫plus,需傳入兩個參數,加總後顯示結果到終端機上 ```python= def plus(num1, num2): sum = num1 + num2 # 等號式把右邊的資料存到左邊的變數 print(f'{num1} + {num2} = {sum}') plus(3, 4) plus(13, 14) plus(33, 54) ``` #### 參數預設值 ```python= # 一個參數有如果有參數預設值,那她右邊的所有參數都必須要有參數預設值 def show_me(name, phone = '尚未提供電話', address = '尚未提供地址'): print(f'名字: {name}\n電話: {phone}\n地址: {address}') show_me('Aaron', '台北市') show_me('Andy', '0987654321') show_me('Andy', '0987654321', '中壢區健行路229號') ``` #### 關鍵字引數 ```python= def test(a1, a2, a3, a4, a5): print(a1) print(a2) print(a3) print(a4) print(a5) test(a4=4, a3=3, a2=2, a1=1, a5=5) ``` #### 函式回傳值 使用`return`關鍵字可以把函式內的資料往外傳遞給呼叫方 ```python= def plus(n1, n2): # print(f'{n1} + {n2} = {n1 + n2}') return n1 + n2 def compare(a1, a2): if a1 > a2: print(f'{a1}比{a2}大') else: print(f'{a1}沒有比{a2}大') a = 12 b = 33 c = 21 d = 25 sum1 = plus(a, b) sum2 = plus(c, d) c = compare(sum1, sum2) print(c) ``` #### 打包與解包 ```python= a = 1, 2, 3, 4, 5, first, *other, last = a print(first) print(other) print(last) ``` #### 變數資料交換 ```python= a = 1 b = 2 a, b = b, a print(a) print(b) ``` ## 模組 ###### util.py ```python= import time def get_current_date(): return time.strftime('%Y%m%d') ``` ###### test.py ```python= import util now_str = util.get_current_date() print(now_str) ``` #### 例外 ```python= import random pc = random.randint(0, 10) # 產生0~10之間的隨機整數 print('答案:', pc) while True: # 無窮迴圈 try: # 裡面的程式碼如果發生例外會被捕捉到 user = int( input('猜一個數字: ') ) if user == pc: # 猜對了 print('恭喜,猜對了') break # 迴圈會直接結束掉,跳到12行執行 except ValueError: # 捕捉到發生例外 print('輸入錯誤,請重新輸入') except: print('發生錯誤,請重新輸入') print('遊戲結束') ``` #### 隨機決定出場順序 ```python= import random users = [] # 輸入名單 while True: user = input('請輸出場學生名字(quit=結束): ') if user == 'quit': break else: users.append(user) print(users) # 抽籤決定出場順序 random.shuffle(users) print('出場順序為:') for u in users: print(f' - {u}') ``` # 收集Google Trend關鍵字熱度 > #### 並將結果儲存到Excel檔案後繪製成折線圖 ![](https://hackmd.io/_uploads/ry-JjdHza.png) #### !!! 注意 !!! 因為關鍵字熱度Google Trend有非常嚴格的存取次數限制,而該限制目前不明,因此建議盡可能完成程式功能後再進行執行程式。 如果遇到錯誤代碼:429,即表示因為超過次數被Google Trend封鎖了,此時可以嘗試切換`geo`參數,例如: ```python= pytrend.build_payload(kw_list=[keyword], cat=0, timeframe='today 12-m', geo='TW') ``` 變成: ``` pytrend.build_payload(kw_list=[keyword], cat=0, timeframe='today 12-m', geo='JP') ``` >geo code清單(ISO-3166 alpha 2欄位):https://www.geonames.org/countries/ ## 需安裝的模組 | 模組名稱 | 安裝指令 | 說明 | | -------- | ----------------------- | ---------------- | | openpyxl | `pip3 install openpyxl` | Excel模組 | | pytrends | `pip3 install pytrends` | Google Trend模組 | #### 官方文件 pytrends: https://pypi.org/project/pytrends/#installation openpyxl: https://openpyxl.readthedocs.io/en/stable/tutorial.html ## 常見例外 1. `pytrends.exceptions.TooManyRequestsError: The request failed: Google returned a response with code 429` ##### 說明 因為太頻繁用程式存取,被Google Trend網站擋住了。 2. `pytrends.exceptions.ResponseError: The request failed: Google returned a response with code 400` ##### 說明 呼叫Google Trend時,傳遞了錯誤的參數格式。 ## 程式碼說明 ``` pytrend.build_payload(kw_list=[keyword], cat=0, timeframe='today 12-m') ``` ##### 說明: 設定Google Trend參數 ##### 參數: ###### kw_list 要搜尋關鍵字清單,資料類型為list,可以輸入多個關鍵字 ###### cat 要搜尋的類別 > 所有類別代號:https://github.com/pat310/google-trends-api/wiki/Google-Trends-Categories ###### geo 國家代碼,如果沒有提供則表示:全球 > geo code清單(ISO-3166 alpha 2欄位):https://www.geonames.org/countries/ ###### timeframe 要取得資料的日期區間: - Defaults to last 5yrs, `'today 5-y'`. - Everything `'all'` - Specific dates, 'YYYY-MM-DD YYYY-MM-DD' example `'2016-12-14 2017-01-25'` - Specific datetimes, 'YYYY-MM-DDTHH YYYY-MM-DDTHH' example `'2017-02-06T10 2017-02-12T07'` - Note Time component is based off UTC - Current Time Minus Time Pattern: - By Month: `'today #-m'` where # is the number of months from that date to pull data for - For example: `'today 3-m'` would get data from today to 3months ago - **NOTE** Google uses UTC date as *'today'* - **Works for 1, 3, 12 months only!** - Daily: `'now #-d'` where # is the number of days from that date to pull data for - For example: `'now 7-d'` would get data from the last week - **Works for 1, 7 days only!** - Hourly: `'now #-H'` where # is the number of hours from that date to pull data for - For example: `'now 1-H'` would get data from the last hour - **Works for 1, 4 hours only!** ```python= data = pytrend.interest_over_time() ``` ##### 說明: 取得指定時間內被搜尋的次數,回傳為Pandas的DataFrame物件 #### 程式碼 ```python= from pytrends.request import TrendReq import openpyxl from openpyxl.chart import LineChart, Reference def gt(keywords): # 建立一個Google Trend物件 pytrend = TrendReq() # 設定要傳給google trend參數 # kw_list=關鍵字, 為一個list pytrend.build_payload(kw_list=keywords, cat=0, timeframe='today 12-m', geo='TW') # 連線到google trend取得該時間區間內的熱度資料 data = pytrend.interest_over_time() # 這行有可能會被擋掉 print(data) # # 將資料存到excel data.to_excel('google-trend-explorer.xlsx',sheet_name='GoogleTrend', index=True) # 載入Excel檔案 wb = openpyxl.load_workbook('google-trend-explorer.xlsx') # 取得目前work sheet ws = wb.active # 建立折線圖物件 chart = LineChart() # 準備資料來源 ref = Reference(ws, min_col=2, max_col=ws.max_column-1, min_row=1, max_row=ws.max_row) # 將資料加入圖表 chart.add_data(ref, titles_from_data=True) # 設定圖表標題 chart.title = 'Google Trend' # 設定y軸標題 chart.y_axis.title = '資料比數' # 設定x軸的資料格式 chart.x_axis.number_format = 'yyyy/mm/dd' # 設定圖表大小 chart.width = 15 # 單位: 公分 chart.height = 10 # 單位: 公分 # 修改樣式 chart.style = 20 # 建立x軸的標籤資料 xlabel = Reference(ws, min_col=1, min_row=2, max_row=ws.max_row) # 設定x軸標籤 chart.set_categories(xlabel) # 將圖表放到work sheet的E2上 ws.add_chart(chart, 'E2') # 存檔 wb.save('google-trend-explorer-chart.xlsx') keywords = [] while True: k = input('請輸入關鍵字(quit=離開): ') if k == 'quit': break; keywords.append(k) gt(keywords) ``` #### 排程 ```python= from apscheduler.schedulers.blocking import BlockingScheduler import time # 排程方法1號 def job1(): print(f'工作1啟動: 目前時間{time.strftime("%Y-%m-%d %H:%M:%S")}') def job2(): print(f'工作2啟動: 目前時間{time.strftime("%Y-%m-%d %H:%M:%S")}') def job3(): print(f'工作3啟動: 目前時間{time.strftime("%Y-%m-%d %H:%M:%S")}') def job4(): print(f'工作4啟動: 目前時間{time.strftime("%Y-%m-%d %H:%M:%S")}') # 建立排程器 scheduler = BlockingScheduler(timezone='Asia/Taipei') # 每分鐘執行一次job1 scheduler.add_job(job1, 'interval', minutes=1) # 每5秒執行一次job2 scheduler.add_job(job2, 'interval', seconds=5) # 每1分1秒執行一次job3 scheduler.add_job(job3, 'interval', seconds=1, minutes=1) # scheduler.add_job(job4, 'cron', hour=16, minute=00, day_of_week='0-6') scheduler.start() ```