Python 網路爬蟲 爬取統一發票號碼,自動對獎 --python TKinter
https://invoice.etax.nat.gov.tw/index.html
import tkinter as tk
from tkinter import ttk, messagebox
import requests
from bs4 import BeautifulSoup
import re
# 統一發票開獎號碼網址
URL = 'https://invoice.etax.nat.gov.tw/index.html'
# 全域變數,用於儲存中獎號碼
winning_numbers = None
def fetch_winning_numbers():
"""從網頁爬取最新一期統一發票中獎號碼"""
try:
web = requests.get(URL)
web.encoding = 'utf-8'
soup = BeautifulSoup(web.text, "html.parser")
numbers = {}
# 使用更穩健的方法,透過表格結構來定位號碼
prize_table = soup.find('table', class_='etw-table-bgbox')
if not prize_table:
raise Exception("無法找到中獎號碼表格,網頁結構可能已改變。")
rows = prize_table.find_all('tr')
for row in rows:
tds = row.find_all('td')
if len(tds) < 2:
continue
prize_type = tds[0].text.strip()
if prize_type == '特別獎':
special_num = tds[1].find('span', class_='fw-bold etw-color-red')
if special_num:
numbers['special'] = special_num.text.strip()
elif prize_type == '特獎':
grand_num = tds[1].find('span', class_='fw-bold etw-color-red')
if grand_num:
numbers['grand'] = grand_num.text.strip()
elif prize_type == '頭獎':
first_prize_nums = []
for p_tag in tds[1].find_all('p', class_='etw-tbiggest'):
num_parts = p_tag.find_all('span', class_='fw-bold')
full_num = "".join([part.text.strip() for part in num_parts])
if full_num:
first_prize_nums.append(full_num)
numbers['first'] = first_prize_nums
# 尋找增開六獎
# 這部分在您提供的 HTML 中沒有,因此先使用安全尋找
extra_prize_elements = soup.find('div', class_='etw-award-num-box')
if extra_prize_elements:
extra_prize_nums = [num.text for num in extra_prize_elements.find_all('span', class_='etw-font-size-3 etw-color-red')]
numbers['extra'] = extra_prize_nums
else:
numbers['extra'] = []
if not numbers.get('special') or not numbers.get('first'):
raise Exception("無法從網頁中找到特別獎或頭獎號碼。")
return numbers
except requests.exceptions.RequestException as e:
messagebox.showerror("錯誤", f"無法連線到網站,請檢查網路連線或網頁狀態。\n錯誤訊息: {e}")
return None
except Exception as e:
messagebox.showerror("錯誤", f"解析網頁內容失敗,網頁結構可能已改變。\n錯誤訊息: {e}")
return None
def display_winning_numbers():
"""將中獎號碼顯示在 GUI 上"""
global winning_numbers
winning_numbers_label.config(text="載入中...")
root.update()
winning_numbers = fetch_winning_numbers()
if winning_numbers:
# 格式化中獎號碼字串
text_to_display = (
f"特別獎 (1000萬): {winning_numbers.get('special', 'N/A')}\n"
f"特獎 (200萬): {winning_numbers.get('grand', 'N/A')}\n"
f"頭獎 (20萬): {'、'.join(winning_numbers.get('first', ['N/A']))}\n"
f"增開六獎 (200元): {'、'.join(winning_numbers.get('extra', ['N/A']))}"
)
winning_numbers_label.config(text=text_to_display)
else:
winning_numbers_label.config(text="無法取得中獎號碼,請檢查網路。")
def check_prize_logic(num):
"""根據統一發票規則對獎"""
global winning_numbers
# 確保輸入是8位數字
if not num.isdigit() or len(num) != 8:
return "請輸入正確的8位數發票號碼。"
# 特別獎
if num == winning_numbers.get('special'):
return '恭喜!對中 1000 萬元!'
# 特獎
if num == winning_numbers.get('grand'):
return '恭喜!對中 200 萬元!'
# 頭獎系列
for i in winning_numbers.get('first', []):
if num == i: return '恭喜!對中 20 萬元!'
if num[-7:] == i[-7:]: return '恭喜!對中 4 萬元!'
if num[-6:] == i[-6:]: return '恭喜!對中 1 萬元!'
if num[-5:] == i[-5:]: return '恭喜!對中 4000 元!'
if num[-4:] == i[-4:]: return '恭喜!對中 1000 元!'
if num[-3:] == i[-3:]: return '恭喜!對中 200 元!'
# 增開六獎
for i in winning_numbers.get('extra', []):
if num[-3:] == i[-3:]: return '恭喜!對中 200 元!(增開六獎)'
return "很抱歉,您的號碼未中獎。"
def perform_check():
"""執行對獎功能的函數"""
user_number = entry_number.get()
if not winning_numbers:
result_label.config(text="中獎號碼尚未載入,請稍後再試。", foreground="red")
return
result = check_prize_logic(user_number)
result_label.config(text=result, foreground="blue")
# 建立 Tkinter 視窗
root = tk.Tk()
root.title("統一發票自動對獎")
root.geometry("450x400")
# --- GUI 元件區 ---
# 標題
label_title = ttk.Label(root, text="統一發票自動對獎", font=("Helvetica", 16, "bold"))
label_title.pack(pady=10)
# 中獎號碼顯示區塊
winning_numbers_frame = ttk.LabelFrame(root, text="本期中獎號碼", padding=(10, 5))
winning_numbers_frame.pack(pady=10, padx=20, fill="x")
winning_numbers_label = ttk.Label(winning_numbers_frame, text="載入中...", font=("Helvetica", 12))
winning_numbers_label.pack(anchor="w")
# 對獎功能區塊
check_frame = ttk.Frame(root, padding=(10, 5))
check_frame.pack(pady=10)
label_instruction = ttk.Label(check_frame, text="請輸入您的8位數發票號碼:")
label_instruction.pack()
entry_number = ttk.Entry(check_frame, width=20, font=("Helvetica", 14))
entry_number.pack(pady=5)
check_button = ttk.Button(check_frame, text="自動對獎", command=perform_check)
check_button.pack(pady=10)
result_label = ttk.Label(root, text="", font=("Helvetica", 14, "bold"), foreground="red")
result_label.pack(pady=10)
# 程式啟動時,自動爬取並顯示中獎號碼
root.after(100, display_winning_numbers)
# 啟動 Tkinter 事件迴圈
root.mainloop()
程式碼逐行說明
1. 導入函式庫
import tkinter as tk
from tkinter import ttk, messagebox
import requests
from bs4 import BeautifulSoup
import re
import tkinter as tk:導入 Python 內建的 GUI 函式庫 Tkinter,並給它一個簡寫tk。from tkinter import ttk, messagebox:從 Tkinter 導入ttk模組,它提供了更現代化的 GUI 元件;以及messagebox模組,用於顯示彈出式訊息框。import requests:導入requests函式庫,用於發送 HTTP 請求,從網頁抓取資料。from bs4 import BeautifulSoup:從beautifulsoup4函式庫導入BeautifulSoup,用於解析 HTML 或 XML 文件。import re:導入re模組,提供正則表達式操作,用於處理字串,例如檢查號碼是否為數字。
2. 全域變數與常數
# 統一發票開獎號碼網址
URL = 'https://invoice.etax.nat.gov.tw/index.html'
# 全域變數,用於儲存中獎號碼
winning_numbers = None
URL = '...':定義一個常數URL,儲存財政部統一發票開獎號碼的網址。winning_numbers = None:定義一個全域變數winning_numbers,初始值為None。這個變數將用於儲存從網頁爬取下來的各獎項號碼,以便在程式的不同函數中共享資料。
3. 爬蟲函式:fetch_winning_numbers()
def fetch_winning_numbers():
"""從網頁爬取最新一期統一發票中獎號碼"""
try:
web = requests.get(URL)
web.encoding = 'utf-8'
soup = BeautifulSoup(web.text, "html.parser")
numbers = {}
prize_table = soup.find('table', class_='etw-table-bgbox')
if not prize_table:
raise Exception("無法找到中獎號碼表格,網頁結構可能已改變。")
rows = prize_table.find_all('tr')
for row in rows:
tds = row.find_all('td')
if len(tds) < 2:
continue
prize_type = tds[0].text.strip()
if prize_type == '特別獎':
special_num = tds[1].find('span', class_='fw-bold etw-color-red')
if special_num:
numbers['special'] = special_num.text.strip()
elif prize_type == '特獎':
grand_num = tds[1].find('span', class_='fw-bold etw-color-red')
if grand_num:
numbers['grand'] = grand_num.text.strip()
elif prize_type == '頭獎':
first_prize_nums = []
for p_tag in tds[1].find_all('p', class_='etw-tbiggest'):
num_parts = p_tag.find_all('span', class_='fw-bold')
full_num = "".join([part.text.strip() for part in num_parts])
if full_num:
first_prize_nums.append(full_num)
numbers['first'] = first_prize_nums
# 尋找增開六獎
extra_prize_elements = soup.find('div', class_='etw-award-num-box')
if extra_prize_elements:
extra_prize_nums = [num.text for num in extra_prize_elements.find_all('span', class_='etw-font-size-3 etw-color-red')]
numbers['extra'] = extra_prize_nums
else:
numbers['extra'] = []
if not numbers.get('special') or not numbers.get('first'):
raise Exception("無法從網頁中找到特別獎或頭獎號碼。")
return numbers
except requests.exceptions.RequestException as e:
messagebox.showerror("錯誤", f"無法連線到網站,請檢查網路連線或網頁狀態。\n錯誤訊息: {e}")
return None
except Exception as e:
messagebox.showerror("錯誤", f"解析網頁內容失敗,網頁結構可能已改變。\n錯誤訊息: {e}")
return None
def fetch_winning_numbers()::定義一個用來爬取網頁資料的函數。try...except:這是一個錯誤處理區塊。try裡面的程式碼會被執行,如果發生任何錯誤,程式會跳到except區塊,避免程式崩潰。web = requests.get(URL):發送 GET 請求到指定的 URL,取得網頁內容。web.encoding = 'utf-8':設定網頁內容的編碼為utf-8,以確保中文字元顯示正常。soup = BeautifulSoup(web.text, "html.parser"):使用BeautifulSoup解析網頁內容。prize_table = soup.find('table', class_='etw-table-bgbox'):尋找 HTML 文件中class屬性為etw-table-bgbox的<table>標籤。rows = prize_table.find_all('tr'):找到表格中所有<tr>(行)標籤。for row in rows::遍歷每一行。tds = row.find_all('td'):在每一行中找到所有<td>(儲存格)標籤。prize_type = tds[0].text.strip():取出第一個儲存格的文字內容,並用.strip()移除前後空白。if prize_type == '特別獎': ...:根據儲存格文字判斷獎別,然後在第二個儲存格 (tds[1]) 中尋找對應的號碼標籤,並將其文字內容存入numbers字典。頭獎的特殊處理:
for p_tag in ...迴圈用於處理頭獎號碼被分割成多個<span>的情況,"".join(...)將這些部分拼接成一個完整的號碼。增開六獎的處理:程式碼會尋找一個特定的
div區塊,如果找不到,則將增開六獎設為空列表。if not numbers.get('special') ...:檢查是否成功抓取到重要的獎項號碼,如果沒有,就拋出一個錯誤。return numbers:函數成功執行後,返回包含中獎號碼的字典。
4. GUI 顯示與更新函式:display_winning_numbers()
def display_winning_numbers():
"""將中獎號碼顯示在 GUI 上"""
global winning_numbers
winning_numbers_label.config(text="載入中...")
root.update()
winning_numbers = fetch_winning_numbers()
if winning_numbers:
text_to_display = (
f"特別獎 (1000萬): {winning_numbers.get('special', 'N/A')}\n"
f"特獎 (200萬): {winning_numbers.get('grand', 'N/A')}\n"
f"頭獎 (20萬): {'、'.join(winning_numbers.get('first', ['N/A']))}\n"
f"增開六獎 (200元): {'、'.join(winning_numbers.get('extra', ['N/A']))}"
)
winning_numbers_label.config(text=text_to_display)
else:
winning_numbers_label.config(text="無法取得中獎號碼,請檢查網路。")
global winning_numbers:宣告要使用全域變數winning_numbers。winning_numbers_label.config(...):更新 Tkinter 標籤 (Label) 的文字內容。root.update():強制 Tkinter 視窗立即更新,這樣使用者會先看到「載入中...」的提示。winning_numbers = fetch_winning_numbers():調用爬蟲函數,將結果賦值給全域變數。if winning_numbers::檢查是否成功抓取到號碼。如果成功,就格式化一個包含所有中獎號碼的字串,並更新標籤的文字。f-string和.get('key', 'N/A')的用法讓格式化更簡潔安全。
5. 對獎邏輯函式:check_prize_logic()
def check_prize_logic(num):
"""根據統一發票規則對獎"""
global winning_numbers
if not num.isdigit() or len(num) != 8:
return "請輸入正確的8位數發票號碼。"
if num == winning_numbers.get('special'):
return '恭喜!對中 1000 萬元!'
# ... 其他獎項的對獎邏輯 ...
return "很抱歉,您的號碼未中獎。"
global winning_numbers:使用全域變數。if not num.isdigit() ...:驗證使用者輸入的號碼是否為 8 位數字。if num == winning_numbers.get('special'): ...:這是一個條件判斷,如果輸入號碼等於特別獎號碼,就返回對應的訊息。for i in winning_numbers.get('first', [])::遍歷頭獎號碼列表,並從後三碼、後四碼... 一路比對到後八碼。如果對中任何一組,就立即返回結果。return "很抱歉,您的號碼未中獎。":如果所有獎項都沒對中,則返回此訊息。
6. GUI 事件處理函式:perform_check()
def perform_check():
"""執行對獎功能的函數"""
user_number = entry_number.get()
if not winning_numbers:
result_label.config(text="中獎號碼尚未載入,請稍後再試。", foreground="red")
return
result = check_prize_logic(user_number)
result_label.config(text=result, foreground="blue")
user_number = entry_number.get():獲取使用者在輸入框中輸入的文字。if not winning_numbers::檢查中獎號碼是否已經成功載入。result = check_prize_logic(user_number):將使用者輸入的號碼傳給對獎邏輯函數,並將回傳的結果儲存起來。result_label.config(...):將對獎結果顯示在 GUI 介面的result_label標籤上,並設定文字顏色。
7. 建立 GUI 介面
# 建立 Tkinter 視窗
root = tk.Tk()
root.title("統一發票自動對獎")
root.geometry("450x400")
# --- GUI 元件區 ---
# ... 這裡創建了所有標籤(Label)、輸入框(Entry)、按鈕(Button)等元件 ...
# 程式啟動時,自動爬取並顯示中獎號碼
root.after(100, display_winning_numbers)
# 啟動 Tkinter 事件迴圈
root.mainloop()
root = tk.Tk():創建主視窗。root.title(...):設定視窗的標題。root.geometry(...):設定視窗的初始大小。label_title = ttk.Label(...):創建並設定一個顯示標題的標籤。winning_numbers_frame = ttk.LabelFrame(...):創建一個帶有邊框和標題的框架,用來組織元件。winning_numbers_label = ttk.Label(...):創建一個用來顯示中獎號碼的標籤。entry_number = ttk.Entry(...):創建一個供使用者輸入號碼的輸入框。check_button = ttk.Button(..., command=perform_check):創建一個按鈕,並將perform_check函數綁定到其command屬性,這樣點擊按鈕時就會調用該函數。root.after(100, display_winning_numbers):這是關鍵的一行。它告訴 Tkinter 在 100 毫秒後調用display_winning_numbers函數。這樣做可以讓視窗先顯示出來,再執行網路請求,避免程式在啟動時因為網路延遲而卡住。root.mainloop():啟動 Tkinter 的事件迴圈,讓視窗保持開啟狀態並等待使用者操作,直到視窗關閉。


沒有留言:
張貼留言