Try   HackMD

2023-10-18 以Python來做Excel自動化與圖表專題 上課筆記

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)
print('輸入前') a = input() print('輸入後') print('剛剛你輸入的資料是:', a)
  1. 有些涵式執行完後會產生資料,必須透過=指派運算子將其存放下來,否則擋了下一行就會消失了。

變數命名規則

  1. 可以使用英文字母大寫及小寫,大小寫代表不同的變數。

    例如:aA是不同的變數名稱。

  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

格式化字串

name = input('請輸入姓名:') age = input('請輸入年齡:') print('你的名字是:', name, ', 年齡:', age, '歲') # f-string print(f'你的名字是: {name}, 年齡: {age}歲')

數字與四則運算

算數運算子

算術運算子有:

運算子 說明
加法運算
減法運算
乘法運算
** 指數運算
除法運算
// 除法運算,但只保留整數
除法運算,但是只取餘數

比較運算

比較運算子 說明
> 大於
>= 大於或等於
< 小於
<= 小於或等於
== 等於
!= 不等於

布林值與in判斷

in指令用來判斷前面的資料是否存在於後面的群集資料當中,運算結果為TrueFalse

上課範例:
# in print('aa' in 'aaron') print('我' in '天氣')
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()涵式

用來偵測資料型態:

print(type(1)) print(type('1')) print(type(True))

輸出結果:

<class 'int'>
<class 'str'>
<class 'bool'>

備註:
int為Python的整數型態
str為Python的字串型態
bool為Python的布林型態

if判斷式

格式:

if 條件式:
    條件成立執行的程式碼

注意
流程內的程式碼一定要縮排。

score = input('請輸入成績: ') # 只要是input涵式接收到的資料,一律都是字串 # 字串傳換成數字 score = int(score) if score >= 60: print('及格') if score < 60: print('不及格')

補充:
所有的Python資料型態都有對應的涵式用來做資料轉型

群集資料list

透過一堆中括號可以用來建立群集資料,list內可以存放字串,數字,布林直,甚至list

透過索引的方式可以取出指定位置的資料,所以從0開始,例如:

a = [1, 2, 3, 4, 5] print(a[3])

會輸出:

4

for in迴圈

用來將群集資料一筆一筆取出來處理

for 暫存變數 in 群集資料:
    處理迭代出來的變數
    ...

當全部資料都被取出來後,迴圈即結束執行

將不及格的分數顯示出來

# 把不及格的分數顯示到畫面上 a = [41, 72, 73, 54, 95, 86, 27, 18, 59] for score in a: if score < 60: print(score)

字典

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])

一個複雜的資料結構

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

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模組

$ pip3 install openpyxl

2023-10-25

函式

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,需傳入兩個參數,加總後顯示結果到終端機上

def plus(num1, num2): sum = num1 + num2 # 等號式把右邊的資料存到左邊的變數 print(f'{num1} + {num2} = {sum}') plus(3, 4) plus(13, 14) plus(33, 54)

參數預設值

# 一個參數有如果有參數預設值,那她右邊的所有參數都必須要有參數預設值 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號')

關鍵字引數

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關鍵字可以把函式內的資料往外傳遞給呼叫方

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)

打包與解包

a = 1, 2, 3, 4, 5, first, *other, last = a print(first) print(other) print(last)

變數資料交換

a = 1 b = 2 a, b = b, a print(a) print(b)

模組

util.py
import time def get_current_date(): return time.strftime('%Y%m%d')
test.py
import util now_str = util.get_current_date() print(now_str)

例外

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('遊戲結束')

隨機決定出場順序

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檔案後繪製成折線圖

!!! 注意 !!!

因為關鍵字熱度Google Trend有非常嚴格的存取次數限制,而該限制目前不明,因此建議盡可能完成程式功能後再進行執行程式。

如果遇到錯誤代碼:429,即表示因為超過次數被Google Trend封鎖了,此時可以嘗試切換geo參數,例如:

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!
data = pytrend.interest_over_time()
說明:

取得指定時間內被搜尋的次數,回傳為Pandas的DataFrame物件

程式碼

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)

排程

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()