0%

[爬蟲] Selenium+Tkinter 製作具有驗證碼的網站登錄器

成品成品

前言

爬取帶有驗證碼登入的網頁時,通常會讓 Selenium 在登入頁面短暫的停留幾秒,讓自己可以手動輸入驗證碼,壞處是在時間上會讓人緊張。而此文章採用另一種方式,使用 Tkinter 模擬登入視窗,讓使用者可以直接在視窗中輸入帳號、密碼與驗證碼,接著交由程式繼續執行登入後的爬蟲動作。

環境

  • Python 3.9.6
  • Pillow 8.4.0
  • selenium 4.0.0

取得驗證碼圖片資訊

這裡同樣以 網路郵局 網頁做為練習,[Python] 使用 Selenium 爬蟲下載當前頁面驗證碼圖片 一文中已清楚的講解取得驗證碼圖片的原理與實作,若還不清楚的讀者們,歡迎隨時複習。

與參考文獻不一樣的地方在於除了得到驗證碼圖片,這裡還想要得到圖片的尺寸 (長、寬),之後在 Tkinter 視窗應用中較容易控制圖片的顯示,因此讓 execute_async_script 回傳的是一個物件,包含 base64heightwidth 三個屬性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from selenium import webdriver

url = "https://ipost.post.gov.tw/pst/home.html"

driverPath = r'chromedriver.exe'
browser = webdriver.Chrome(executable_path=driverPath)
browser.get(url)

captcha = browser.execute_async_script("""
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var img = document.querySelector('div.codes_img img');
canvas.height = img.naturalHeight;
canvas.width = img.naturalWidth;
context.drawImage(img, 0, 0);

callback = arguments[arguments.length - 1];
callback({ 'base64': canvas.toDataURL(), 'height': img.naturalHeight, 'width': img.naturalWidth});
""")

captcha 的資料格式如下

1
2
3
4
5
{
'base64': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI4AAAA...(ignored)',
'height': 48,
'width': 142
}

Tkinter 登入器製作

captcha 物件取得之後可以開始製作登入器,以下透過註解形式講解各功用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import tkinter as tk
import base64, io
from PIL import ImageTk, Image

# 建立 Tkinter 主程式
root = tk.Tk()

# UI 字形
font = ("Comic Sans MS", 30, "")

# 登入器的 Layout 如下
# column=0 column=1
# ┌────────────────────────────┐
# row=0 | Account {accountEntry} |
# row=1 | User ID {userIdEntry} |
# row=2 | Passowrd {passwordEntry}|
# row=3 | {canvas} {captchaEntry} |
# row=4 | {VERITY} |
# └────────────────────────────┘

# 在 (0, 0) 的位置建立一個會顯示 'Account' 的文字區塊
tk.Label(root, text='Account', font=font).grid(row=0, column=0)
# 在 (0, 1) 的位置建立一個輸入 帳號 的欄位
accountEntry = tk.Entry(root, width=15, font=font)
accountEntry.grid(row=0, column=1)

# 在 (1, 0) 的位置建立一個會顯示 'User ID' 的文字區塊
tk.Label(root, text='User ID', font=font).grid(row=1, column=0)
# 在 (1, 1) 的位置建立一個輸入 使用者代碼 的欄位
userIdEntry = tk.Entry(root, width=15, font=font)
userIdEntry.grid(row=1, column=1)

# 在 (2, 0) 的位置建立一個會顯示 'Password' 的文字區塊
tk.Label(root, text='Password', font=font).grid(row=2, column=0)
# 在 (2, 1) 的位置建立一個輸入 密碼 的欄位
passwordEntry = tk.Entry(root, width=15, font=font)
passwordEntry.grid(row=2, column=1)

# 在 (3, 0) 的位置建立一個會顯示驗證碼圖片的區塊
canvas = tk.Canvas(root, width=captcha['width'], height=captcha['height'] * 1.5)
canvas.grid(row=3, column=0)
# 先將 data Url 前綴 (data:image/png;base64) 去除,再將 base64 資料轉為 bytes
i = base64.b64decode(captcha['base64'].split(',')[1])
# 因為 mpimg 讀取的是檔案,但我們不想存檔案於本地端,
# 所以在記憶體中創建一個內容為 i 的 bytes 檔案,
i = io.BytesIO(i)
# 使用 PIL 將檔案轉為 tk.Canvas 可以接收的格式
img = ImageTk.PhotoImage(Image.open(i))
# 將圖片插入 canvas
canvas.create_image(2, 15, anchor=tk.NW, image=img)

# 在 (3, 1) 的位置建立一個輸入 驗證碼 的欄位
captchaEntry = tk.Entry(root, width=15, font=font)
captchaEntry.grid(row=3, column=1)

def login():
# 將 accountEntry 的文字輸入到登入頁面的 帳號 欄位
browser.find_element_by_xpath('//*[@id="userActNo"]').send_keys(accountEntry.get())
# 將 userIdEntry 的文字輸入到登入頁面的 使用者代號 欄位
browser.find_element_by_xpath('//*[@id="userID_2"]/input').send_keys(userIdEntry.get())
# 將 passwordEntry 的文字輸入到登入頁面的 密碼 欄位
browser.find_element_by_xpath('//*[@id="userPWD_2"]/input').send_keys(passwordEntry.get())
# 將 captchaEntry 的文字輸入到登入頁面的 驗證碼 欄位
browser.find_element_by_xpath('//*[@id="tab2"]//input[@placeholder="共四碼"]').send_keys(captchaEntry.get())
# 按下登入按鈕
browser.find_element_by_xpath('//*[@id="tab2"]//a[text()="登入"]').click()
# 關閉 Tkinter 主程式
root.destroy()

# 在 (4, [0:2]) 的位置建立一個確認的按鈕,
# 按鈕按下後會執行 login() 函式
verifyButton = tk.Button(root, text="VERIFY", font=font, command=login)
verifyButton.grid(row=4, column=0, columnspan=2)

# 置頂 Tkinter 視窗
root.attributes('-topmost', True)
# 啟動 Tkinter 主程式
root.mainloop()

# 後續的程式碼為成功登入後,爬蟲要做的事情
# ...
# ...
# ...

隱藏 Chrome 瀏覽器

當我們將登入器完成,Chrome 瀏覽器的顯示可視為非必要的,若想選擇讓它在執行爬蟲時隱藏起來,將開頭的程式碼改為如下

1
2
3
4
5
6
7
8
9
10
from selenium import webdriver
from selenium.webdriver.chrome.options import Options # 增加此行
chrome_options = Options() # 增加此行
chrome_options.add_argument('--headless') # 增加此行

url = "https://ipost.post.gov.tw/pst/home.html"

driverPath = r'chromedriver.exe'
browser = webdriver.Chrome(executable_path=driverPath, chrome_options=chrome_options) # 修改此行
browser.get(url)

參考文獻

  1. [Python] 使用 Selenium 爬蟲下載當前頁面驗證碼圖片

  2. How To Add Images In Tkinter

  3. How to put a tkinter window on top of the others?

  4. 如何使用 Chrome headless 無頭騎士模式教學

很高興能在這裡幫助到您,歡迎登入 Liker 為我鼓掌 5 次,或者成為我的讚賞公民,鼓勵我繼續創造優質文章。
以最優質的內容回應您的鼓勵