Python Data Crawl

1.初始階段:判斷靜態或是動態網站

Requests + Beautiful Soup 適用於:靜態網站
API 資料(JSON)

靜態網站:可直接用 requests 抓取資料
1.頁面內容的所有內容都可以在網頁原始碼內看到
2.頁面內容不會在同一個網址下變動
3.處理速度通常較快

Selenium 適用於:動態載入的內容(JavaScript)
需要點擊、滾動或填表等互動操作。

動態網站:要使用 Selenium 或 Scrapy 等模擬瀏覽器抓取資料
1.原始碼通常是「空的」,真正的內容是透過 JavaScript 程式碼,向後端伺服器發出請求後(XHR 或 Fetch 請求),再把資料填進來。
3.互動性高,可以滑動頁面(無限滾動)、點擊按鈕,頁面內容會即時更新。

2.請求階段:檢查驗證憑證、登入請求、驗證碼等等請求

requests.get():當只需要獲取網頁資料時。
params={}:處理網址參數。
headers={}:偽裝成瀏覽器。
requests.post():當需要提交資料時。
data={}json={}:處理表單資料。
requests.Session():處理登入狀態和保持 Cookie。
驗證碼處理:先 get 驗證碼圖片,再用 ddddocr 辨識結果,最後將結果填入 post 請求。

3.解析階段:抓取需求的資料跟內容

BeautifulSoup:將網頁原始碼轉換成可解析的結構。
尋找單一元素:soup.find()soup.select_one()
尋找多個元素:soup.find_all()soup.select()
獲取內容:.text (文字).get(‘div’) (標籤內屬性的值)。
獲得標籤與標籤夾層中的文字:next_siblings (搭配for迴圈與if判斷是否有屬性名稱來篩選出純文字內容)

4.結果處理:對資料做最後處理

print():印出結果,進行除錯或確認。
os:處理檔案儲存、建立資料夾等。

Requests 請求

Requests 模組的核心就是使用不同的請求方式,來對應到指定的HTTP 協定。

headers (自訂表頭):用於添加使用者代理,模擬真人使用
my_headers = {
‘user-agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36’
}

GET 方法 查詢 (讀取) — params:
位置:資料會被放在 HTTP 請求的網址列 (URL) 上。
機制:當你使用 requests.get() 並帶上 params 參數時,requests 會自動將你的字典轉換成查詢字串,並附加在 URL 的後面,例如 url?key1=value1&key2=value2。
用途:主要用於讀取或查詢資料,因為資料會公開顯示在網址列上,且有長度限制。

r = requests.get(url, params={}, headers={})
# 從伺服器獲取網頁內容或 API 資料

params={}:用途是讓你在網址中加入參數 (query parameters)
範例:
url = ‘https://www.google.com/search’
search_params = { ‘q’: ‘蘋果’ }
r = requests.get( url , params = search_params)
# 發送的請求: https://www.google.com/search?q=蘋果

當你發出一個請求後,Requests 會回傳一個 Response 物件

html = r.text
# 取得內容的文字格式
image = r.content
# 取得內容的位元組格式(圖片、影片等二進位檔案)
if r.status_code == 200:
# 取得 HTTP 狀態碼
r.raise_for_status()
# 如果狀態碼不是 200,會拋出錯誤
data = r.json()
# 將 JSON 格式的回應自動轉換成 Python 的字典或列表
headers = r.headers
# 取得伺服器回應的標頭資訊
cookies = r.cookies
# 取得伺服器回傳的 cookie

cookies(資訊標籤):用於讓伺服器記住你的資訊,以維持連線狀態或記錄個人偏好
r = requests.get( url + “/coolies” ) # 找出含有cookie的內容
print( r.cookies[“cookies_name”]) # 取出cookie內容

my_cookies = {cookies_name : “cookie”} # 設定cookies內容
r = requests.get( url , cookies = my_cookies )

Session(會話物件):用於維持和伺服器之間的連線狀態,重複使用連線,維持憑證狀態、處理多頁爬取
session = request.Session()
r = session.get(url , cookies = {cookies_name : “cookie”} )
r = requests.get((url , cookies = {cookies_name : “cookie”} ) # 與使用Session效果一樣

Session與requests的差異在於,

Requests (單次處理)
發送請求時,都會建立一個新的 TCP 連線,請求結束後,這個連線就會被關閉。
優點:
1.適合一次性的簡單請求
2.不佔用資源,每次用完就釋放
缺點:
1.效率較低,每次請求都建立新連線
2.無法自動保持狀態,例如登入後的 cookie

Session (批次處理、驗證狀態)
會保存所有請求的狀態資訊(cookie),重複使用底層的 TCP 連線。
優點:
1.自動保存狀態,例如 cookie
2.減少了每次重新建立連線的時間,增加效率
3.先設定好通用的 headers、params 或其他參數,之後的所有請求都會自動帶上這些設定。
缺點:
1.需要手動建立 Session 物件,程式碼多一行 session = requests.Session()

POST 方法 新增 (寫入) — data:
位置:資料會被放在 HTTP 請求的主體 (Request Body) 內。
機制:當你使用 requests.post() 並帶上 data 參數時,requests 會將你的字典資料進行編碼,並將其作為請求的有效載荷 (payload) 發送到伺服器,資料不會在網址列上顯示。
用途:用來提交表單、上傳資料或發送 API 請求,且沒有大小限制。

data(資料表單):用於加入的資料表單
my_data = { “key1″:”value1” , “key2″:”value2”}
my_data = (( “key1” , “value1” ) , (“key1′:”value2”)) #重複的key值
r = requests.post( url , data = my_data )

files(檔案):用於上傳檔案
my_files = { “file_name” : open(“my_files.docx” , “rb”)}
r = requests.post( url , files = my_files )

為何傳檔案一定使用post不是使用get?
1-1.因為get會把資料全部放在網址上,由於網址長度有限制,所以對於大容量的檔案是不夠用的。
1-2.而post會把資料放在本體內,並由請求者的本機發送出去,所以沒有容量限制問題。
2-1.get會將資料直接顯示在網址列上,並記錄在瀏覽器內,對於敏感性資料不安全
2-2.post的資料不會顯示在網址上,也不會被瀏覽器紀錄。
3-1.get的語意是讀取(read),所以不會改變伺服器上的資料
3-2.post的語意是建立(creat)或更新(update),所以適用於伺服器產生變動的動作。

https:// 網址 (傳統外部連結)
資料來源:遠端伺服器上的檔案
網路請求:每個圖片需一個單獨請求
檔案大小:HTML 檔案較小,圖片獨立
快取:可獨立快取
主要用途:大部分網頁圖片
其他應用:腳本檔案、樣式表、影片、音訊、字型等

data: 網址 (內嵌資料)
資料來源:內嵌在網頁檔案中的字串
網路請求:無額外請求
檔案大小:HTML 檔案體積增大
快取:無法獨立快取
主要用途:小圖標、背景圖,或需減少 HTTP 請求的場合
其他應用:樣式表中的圖標、SVG 向量圖、網站圖示等

PUT 方法 修改 (寫入)
用途:用於更新 (Update) 資源,或在指定的 URI 上建立新資源(如果該資源不存在),常用於更新會員資料、文章內容

r = requests.put(url, data={‘name’: ‘peter’})
# 用於更新伺服器上的完整資源

DELETE 方法 刪除 (寫入)
用途:用於刪除 (Delete) 資源,常用於刪除文章或商品

r = requests.delete(url)
# 用於刪除伺服器上的指定資源。

HTTP 狀態碼 (Status Codes)

1xx:資訊回應 (Informational)
這類狀態碼表示伺服器已經接收到請求,並且正在處理中。這是一個臨時的回應,通常不會包含任何內容,最終會跟隨一個最終的回應。
100 Continue:表示伺服器已經接收到請求的標頭,並且客戶端應繼續傳送請求的主體。

2xx:成功 (Successful)
這類狀態碼表示客戶端發送的請求已經被伺服器成功接收、理解和處理。
200 OK:最常見的成功狀態碼,表示請求成功。
201 Created:表示請求成功並在伺服器上創建了新的資源。這通常是 POST 請求成功的回應。
204 No Content:表示請求成功,但回應中不包含任何實體主體。這常用於 DELETE 請求的回應。

3xx:重新導向 (Redirection)
這類狀態碼表示客戶端需要採取額外步驟來完成請求。
301 Moved Permanently:表示請求的資源已經被永久移動到新的 URI。客戶端應更新其書籤並使用新的 URI。
302 Found:表示請求的資源暫時位於另一個 URI。客戶端應繼續使用原來的 URI,但暫時導向新的 URI。
304 Not Modified:表示請求的資源自上次請求以來沒有被修改。伺服器可以讓客戶端使用快取中的資料,而不是重新發送資源。

4xx:客戶端錯誤 (Client Error)
這類狀態碼表示伺服器無法處理請求,因為客戶端發送了錯誤的請求。
400 Bad Request:表示伺服器無法理解客戶端發送的請求,可能是因為請求語法錯誤。
401 Unauthorized:表示客戶端沒有進行身份驗證,或者身份驗證失敗。
403 Forbidden:表示伺服器理解請求,但拒絕執行。客戶端可能沒有權限存取該資源。
404 Not Found:最常見的錯誤碼,表示伺服器找不到請求的資源。
405 Method Not Allowed:表示請求方法(如 GET, POST)不適用於指定的資源。例如,對一個只允許 GET 的資源發送 POST 請求。

5xx:伺服器錯誤 (Server Error)
這類狀態碼表示伺服器在處理一個有效的請求時發生了錯誤。
500 Internal Server Error:最常見的伺服器錯誤碼,表示伺服器遇到一個未知的錯誤,無法完成請求。
502 Bad Gateway:表示伺服器作為閘道器或代理,在處理上游伺服器的請求時收到無效的回應。
503 Service Unavailable:表示伺服器目前無法處理請求,通常是因為伺服器過載或正在進行維護。這是一個臨時狀態,客戶端可以稍後再試。

更多狀態碼 : https://developer.mozilla.org/

Beautiful Soup 的核心就是讓你用簡單的語法來「導航」整個 HTML 或 XML文件

from bs4 import BeautifulSoup
import requests
url = “https://www.google.com/”
r = requests.get( url )
soup = BeautifulSoup( r , ‘html.parser’)
# 使用Beautiful Soup來解析要爬取的網站的html程式碼

取得指定標籤:
title = soup.title
print(title)
# <title>Hello world</title>

取得指定標籤內文字:
print(title.string # .string 只處理沒有子標籤的純文字,若是有標籤會回傳None
print(title.text) # .text會抓取標籤內的所有文字
# Hello world用於尋找第一個符合條件的標籤

tag = soup.find(‘div’ , class = ‘box’)

find_all( ):用於尋找所有子孫標籤,由上而下取得所有特定標籤的節點

a = soup.find_all(‘a’ , limit = 1) # 使用limit可以限制抓取的上限值
a = soup.find_all([ ‘a’ , ‘b’ ]) # 用清單來搜尋多個指定標籤
使用迴圈依序印出:
for tag in a :
    print(tag.string) # 標籤文字
    print(tag.get(“href”) # 標籤內屬性為 “href” 的內容

取得特定標籤的第一個節點:
google_link = ‘https://www.google.com/’
tag = soup.find(‘div’ , class_ = ‘class_name’ , href = google_link , id = ‘my_id’ , string = ‘Hello’)
搭配各種屬性條件來精準抓取節點:
‘div’ = 標籤、class_ = class屬性、href = 網址、id = id屬性、string = 文字內容

class_ :當有多個值,只要有一個符合條件,就會去抓取,當輸入多個值的時候,輸入順序要正確。
<div class = ‘box apple’></div>
tag = soup.find(‘div’ , class = ‘box’)
print(tag)
# <div class = ‘box apple’></div>

當有特定字元時會產生錯誤訊息,但可使用字典給屬性指定參數:
tag = soup.find_all( attrs = {‘data-value’ : ‘value’} )

find_parents():用於在指定的標籤往上找所屬的分類、容器或父層結構。
a_tag = tag.find_parents( ‘a’ ) # 抓取所屬父節點的 a 標籤

from bs4 import BeautifulSoup

html_doc = “””
<body>
    <p>第一個標籤</p>
    <span>這是一段文字</span>
    這是一段插入的文字
    <p>第二個標籤</p>
    <p>第三個標籤</p>
</body>
“””

soup = BeautifulSoup(html_doc, ‘html.parser’)
p_tag = soup.find(‘p’)

next_sibling 屬性: 尋找任何下一個節點
next_node = p_tag.next_sibling
print(f”p_tag.next_sibling: ‘{next_node}'”)
# 輸出: ‘\n’ (這裡是一個註解節點)

find_next_sibling() 函式: 尋找下一個標籤
next_tag = p_tag.find_next_sibling()
print(f”p_tag.find_next_sibling(): ‘{next_tag}'”)
# 輸出: ‘<span>這是一段文字</span>’ (跳過了註解)

next_siblings 屬性: 尋找指定標籤的所有文字、特定標籤或物件
print(“next_siblings 屬性:”)
for sibling in p_tag.next_siblings: # 用迴圈來篩選獲得純文字內容
    if sibling.name is None:  # 如果沒有 .name 屬性,代表它是文字節點String
    text = sibling.strip()      # 用.strip() 移除前後的空白和換行
    if text != “”:                # 確保文字內容不為空,才印出來
        print(f” 找到節點: ‘{text}'”)
     # 輸出: 找到節點: ‘這是一段插入的文字’

find_next_siblings() 函式: 尋找所有後續標籤,會跳過文字
print(“find_next_siblings() 函式:”)
for sibling in p_tag.find_next_siblings():
    print(f” 找到標籤: ‘{sibling}'”)
# 輸出會依序是: <span> 標籤、<p> 標籤、<p> 標籤 (它跳過了非標籤的節點)

select( ):可以使用CSS語法來精準抓取指定條件,但是支援較少參數

當抓取指定的內容後,可透過切片語法來限制印出的範圍,
from bs4 import BeautifulSoup

html_doc = 
<ul>
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
</ul>

soup = BeautifulSoup(html_doc, ‘html.parser’)

all_list_items = soup.select(‘li’) # 使用 select() 找到所有 <li> 標籤

first_three_items = all_list_items[:3] # 只取出前三個
print(f”前三個項目:{first_three_items}”)
# 輸出:前三個項目:[<li>item 1</li>, <li>item 2</li>, <li>item 3</li>]


最常用的 CSS 語法:
1.標籤名稱
—– soup.select(‘p’)
===選擇所有 <p> 標籤
2..類別名稱 # 類別名稱前面要加一個點 (.)
—– soup.select(‘.product’)
===選擇所有 class=”product” 的標籤
3.#ID名稱 # ID 名稱前面要加一個井字號 (#)
—– soup.select(‘#main-content’)
===選擇 id=”main-content” 的標籤
4.後代選擇器 # 中間用空格隔開
soup.select(‘div p’)
===選擇所有在 <div> 裡面的 <p> 標籤
5.子代選擇器 # 中間用大於號 (>) 隔開
—– soup.select(‘div > p’)
===選擇直接在 <div> 底下的 <p> 標籤
6.屬性選擇器 # [ ]內代表屬性attribute
—– soup.select(‘a[href]’)
===選擇所有有 href 屬性的 <a> 標籤
7.屬性值選擇器
—– soup.select(‘a[target=”_value”]’)
===選擇所有 target 屬性值為 _value 的 <a> 標籤
8.複合選擇器
—– soup.select(‘div.content’)
===選擇所有同時是 <div> 且 class=”content” 的標籤
9.多重選擇器 # 中間用逗號 (,) 隔開
—– soup.select(‘h1, h2, h3’)
===選擇所有 <h1>、<h2> 或 <h3> 標籤
10.相鄰選擇器 # 中間用 (+) 隔開
—– soup.select(‘p + p’)
===選擇所有緊跟在 <p> 後面的下一個 <p> 標籤
11.兄弟選擇器 # 中間用 (~) 隔開
—– soup.select(‘p ~ p’)
===選擇所有在 <p> 後面的所有 <p> 標籤。

select_one( ):就像find( ),只會抓取符合條件的一個內容

正規表達式 re (Regular Expression):

re 模組在 Python 中主要有三個最常見的用途

搜尋:
re.search(): 在整個字串中尋找第一個符合模式的結果。
re.match(): 檢查字串開頭是否符合模式。
re.findall(): 找出所有符合模式的結果,並回傳一個列表。

替換:re.sub()用來將所有符合模式的文字,替換成你想要的內容。

提取:re.search().group(0) , re.findall().group(5)
在搜尋的同時,將你感興趣的特定部分提取出來,數字0代表提取全部內容,其他數字代表提取抓到的第幾組

範例:phone_check = r’^09\d{8}$’
範例:email_check = r’^[a-zA-Z0-9]+@[a-z]+\.com$’
範例:id_check = r’^[A-Z][12]\d{8}$’

符號 ^$:代表開頭跟結尾
符號 { }:代表內容顯示次數,\d{8},代表至少出現8個數字,\d{1,5}代表出現1~5個數字
符號 [ ]:匹配方括號內的任何一個字元,[12]代表1或2
符號 \d:匹配任何數字,等同於 [0-9]
符號 [A-Za-z]:匹配任何一個大寫和小寫英文字母
符號 +:匹配前面的元 1 次或多次[a-zA-Z0-9]+,代表大小寫英文字母或數字至少出現1次以上
符號 ():匹配特定分組內容,(123)代表123
符號 \w:匹配任何文字字元(字母、數字、底線),等同於[a-zA-Z0-9_]
符號 . :匹配任何單一字元(除了換行符)
符號 *:匹配前面的字元 0 次或多次,a*c,代表a可多可無,c一定要有
符號 ?:匹配前面的字元 0 次或 1 次,a?c,代表a可無或只出現一次,c一定要有
符號 \s:匹配任何空白字元(空格、tab、換行)

符號 .* (貪婪模式):匹配兩個固定文字之間的所有內容,可能會誤抓取預期外的範圍
import re
text = “<div>內容一</div><div>內容二</div>”
pattern = r”<div>(.*)</div>” # 貪婪模式
match = re.search(pattern, text)

if match:
    content = match.group(1)
    print(f”找到的內容是:{content}”)
# 輸出:找到的內容是:內容一</div><div>內容二

符號 .*? (非貪婪模式):匹配任何字元,出現 0 次或多次,但盡可能少,
import re
text = “<div>內容一</div><div>內容二</div>”
pattern = r”<div>(.*?)</div>” # 非貪婪模式
matches = re.findall(pattern, text)

for match in matches:
    print(f”找到的內容是:{match}”)
# 輸出:
# 找到的內容是:內容一
# 找到的內容是:內容二

ddddocr 光學字元辨識 & os “Operating System” 系統互動功能

ddddocr 基本使用流程:
ocr = ddddocr.DdddOcr() # 使用時要初始化一次
image = open(“example.jpg”, “rb”).read()
result = ocr.classification(image)
print(result)

os 常用函式:
os.makedirs(path,exist_ok = True):根據指定路徑來建立資料夾,若父資料夾不存在也會一併建立。
exist_ok = True:如果目標資料夾已經存在,它不會報錯,而是會直接跳過。
os.mkdir(path):根據指定路徑建立單一資料夾。如果父資料夾不存在,會報錯。
os.remove(path):刪除檔案。
os.path.exists(path):檢查檔案或資料夾是否存在,回傳 True 或 False。

獲取驗證碼流程 & 使用os建立資料夾:

HTML

<form action=”/login” method=”post”>
    <img id=”captcha_img” src=”/static/captcha_image.png” alt=”驗證碼”>
    <input type=”text” name=”username”>
    <input type=”text” name=”password”>
    <input type=”text” name=”captcha_code”>
    <button type=”submit”>登入</button>
</form>

安裝:pip install ddddocr

import requests
from bs4 import BeautifulSoup
import ddddocr
import os
import time

# 初始化 ddddocr 辨識器,只需要在程式開始時初始化一次
ocr = ddddocr.DdddOcr()

def CaptchaPng(login_url, session, max_retries = 5):

# max_retries (int): 最大重試次數,預設為 5 次。

# 第 1 步:設置資料夾名稱
folder_name = ‘aaa’
if not os.path.exists(folder_name):
    os.makedirs(folder_name)
print(f”資料夾 ‘{folder_name}’ 已建立。”)

# 第 2 步:開始重試迴圈
for retry_count in range(1, max_retries + 1):
    print(f”\n— 第 {retry_count} 次嘗試 —“)

try:
# 第 3 步:取得登入頁面,取得驗證碼圖片 URL
    response = session.get(login_url)
    soup = BeautifulSoup(response.text, ‘html.parser’)
    img_tag = soup.find(‘img’, id=’captcha_img’)

# 檢查圖片標籤是否存在
    if not img_tag:
        print(“找不到驗證碼圖片標籤,請檢查 HTML 結構。”)
        return False

    img_url = img_tag[‘src’]

# 第 4 步:組合成完整的圖片 URL
    if not img_url.startswith(‘http’):
        img_url = login_url.rstrip(‘/’) + img_url

    print(f”找到驗證碼圖片 URL:{img_url}”)

# 第 5 步:下載驗證碼圖片到指定資料夾
    img_response = session.get(img_url, stream=True)
    img_path = os.path.join(folder_name, f’captcha{retry_count}.png’)
    with open(img_path, ‘wb’) as f:
        for chunk in img_response.iter_content(chunk_size=1024):
        f.write(chunk)

    print(f”驗證碼圖片已下載到:{img_path}”)

# 第 6 步:讀取圖片內容給 ddddocr 辨識
    image_bytes = open(img_path, “rb”).read()
    rec_result = ocr.classification(image_bytes)
    print(f”ddddocr 辨識結果:{rec_result}”)

# 第 7 步:準備登入資料並發送請求
    login_data = {
        ‘username’: ‘your_username’,
        ‘password’: ‘your_password’,
        ‘captcha_code’: rec_result
    }

    login_response = session.post(login_url, data=login_data)

# 第 8 步:檢查登入結果
    if “登入成功” in login_response.text:
        print(“— 登入成功!—“)
        return True
    else:
# 第 9 步:若驗證碼錯誤,印出訊息並準備重試
        print(“登入失敗,可能是驗證碼錯誤。”)
    print(“準備進行下一次嘗試…”)
# 這裡可以加上延遲,避免短時間內發送太多請求
    time.sleep(2)

except requests.exceptions.RequestException as e:
    print(f”網路請求發生錯誤:{e}”)
    return False
except Exception as e:
    print(f”發生未知錯誤:{e}”)
# 為了除錯,我們仍返回 False 以便停止執行
    return False

# 第 10 步:如果超過最大重試次數,則返回 False
    print(f”\n— 已經連續失敗 {max_retries} 次,放棄登入。—“)
    return False

# — 程式主體 —
if __name__ == “__main__“:
login_url = “http://your-website.com/login” # 替換成實際網址
session = requests.Session()

if CaptchaPng(login_url, session):
    print(“程序結束。”)
else:
    print(“程序因失敗而終止。”)

# import shutil
# shutil.rmtree(‘aaa’) # 用於清理資料夾
# print(“資料夾已刪除。”)


Selenium 自動化測試工具

常見用途跟適用情況:
1.處理動態內容
當你發現網頁原始碼 (Ctrl+U) 和你在瀏覽器上看到的內容不一致時,很可能就是內容由 JavaScript 動態載入。
2.模擬登入
網站需要帳號密碼登入後才能查看內容,或是登入需要驗證碼。
3.處理無限滾動
像 IG、FB 或某些購物網站,你往下滑動時會自動載入更多內容。
4.點擊按鈕與連結
你的目標資料隱藏在需要點擊按鈕後才能顯示的地方。

要使用 Selenium,需要先安裝它和瀏覽器驅動程式 (WebDriver)。
安裝 Selenium: pip install selenium

下載 WebDriver:
Chrome: https://developer.chrome.com/docs/chromedriver/downloads?hl=zh-tw
將下載的驅動程式解壓縮後,放到 Python 腳本所在的資料夾,或是系統的 PATH 中。

常用取得資料函式

常用取得資料函式:
from selenium import webdriver

driver = webdriver.Chrome()
啟動 Chrome 瀏覽器。

from time import sleep
sleep(5)
強制暫停程式執行,等待網頁載入。(不推薦使用)

from selenium import webdriver
driver.implicitly_wait(10)
隱性等待,設定WebDriver一個最長等待時間,若是在時間內網頁載入完成,則進行下一步
(但是會遇到要抓取的資料已載入完成,卻要等待瀏覽器完全載入的情況)

——————————————————————————————

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 20 , 0.5).until(EC.presence_of_element_located((By.ID, ‘main’))) # 直到符合
WebDriverWait(driver,20,0.5).until_not(EC.presence_of_element_located((By.ID, ‘main’))) # 直到不符合
顯性等待,每隔幾秒確認一次,如果條件滿足則進行下步,(每隔0.5秒,最長20秒)
或是直到超過設置的時間,會跳出TimeoutException訊息。(推薦使用)

EC expected_conditions 的簡寫:
他會告訴 Selenium:「我希望你等到某個條件滿足後再繼續,例如等到某個元素出現或變得可見。」
presence_of_element_located
這是 EC 模組中的一個函式,它的名字非常直觀:「當某個元素 (of_element) 出現 (presence) 時,條件滿足」。
(By.ID, ‘main’):
這是指定要等待的目標元素。
總結:「程式暫停執行,直到一個 By.ID, ‘main’ 出現在 HTML 結構中,最多等待 20 秒」

EC 其他常用函式:

EC.visibility_of_element_located((By.ID, ‘ads’))
等待元素不僅在 DOM 中,而且可見(例如,沒有 display:none)

EC.element_to_be_clickable((By.CSS_SELECTOR, ‘button.submit’))
等待元素可以被點擊

EC.title_is(‘登入成功’)
等待頁面的標題變成指定內容

EC.alert_is_present()
等待彈出視窗出現

——————————————————————————————

driver.get(‘https://www.google.com/’)
導航到指定的 URL。

driver.current_url
取得目前開啟網頁的URL

drive.title
取得開啟網頁的標題

from selenium.webdriver.common.by import By
driver.find_element(By.ID, ‘username’)
尋找一個符合條件的元素。
By.ID, By.CLASS_NAME, By.NAME, By.CSS_SELECTOR, By.XPATH, By.LINK_TEXT, By.TAG_NAME

from selenium.webdriver.common.by import By
driver.find_elements(By.CLASS_NAME, ‘product’)
尋找所有符合條件的元素,回傳一個列表,可搭配for迴圈來依序顯示內容。

html_source = driver.page_source
取得當前網頁的 HTML 原始碼。

drive.session_id
取得網頁連線的id

driver.quit()
關閉瀏覽器。(一定都要記得關閉瀏覽器,避免電腦資源的浪費)

常用控制視窗函式:

driver.get_window_position()
取得瀏覽器視窗左上角位置

driver.set_window_position(x, y)
設定瀏覽器視窗左上角位置

driver.get_window_size( )
取得瀏覽器視窗大小

driver.set_window_size(x, y)
設定瀏覽器視窗大小

driver.maximize_window()
將瀏覽器視窗最大化

driver.minimize_window()
將瀏覽器視窗最小化

常用互動函式:

button.click()
點擊元素。

input_box.send_keys(‘hello’)
在輸入框中輸入文字。

driver.back()
回前一頁

driver.forward()
回後一頁

driver.refresh()
刷新頁面

send_keys(Keys.DOWN)、send_keys(Keys.UP)、send_keys(Keys.PAGE_DOWN)、send_keys(Keys.PAGE_UP)
用於滾動頁面向下、向下、最底部、最頂部

driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) 
execute_script( ) 代表執行JavaScript 程式碼,對網頁框架進行操作
使用 scrollTo(x, y),用來將瀏覽器的滾動條移動到指定位置
document.body.scrollHeight 代表網頁內容的完整高度,包含那些目前在螢幕上看不到的部分

driver.save_screenshot(“screenshot.png”)
driver.save_screenshot(“c:\\screenshot.png”)
螢幕截圖與儲存照片到指定路徑,須使用雙倒斜線

Select 操作下拉式選單
from selenium.webdriver.support.ui import Select
location_select_element = driver.find_element(By.ID, “location-select”) # 尋找下拉式選單標籤
select = Select(location_select_element) # 設定下拉式選單的變數
select.select_by_visible_text(“臺北”) # 給選單一個值

Options 瀏覽器功能:
from selenium.webdriver.chrome.options import Options

options = Options( )

無頭模式
options.add_argument(‘–headless‘)
讓瀏覽器在背景中執行,不顯示任何視窗。適合在伺服器上執行爬蟲,能減少資源佔用並加快速度。

隱藏自動化標記
options.add_argument(“–disable-blink-features=AutomationControlled“)
隱藏瀏覽器被自動化工具控制的痕跡,有助於繞過部分網站的反爬蟲機制。

設定視窗大小
options.add_argument(‘–window-size=1920,1080‘)
設定瀏覽器視窗的尺寸。這在抓取響應式網頁或確保特定元素可見時很有用。

禁用擴充功能
options.add_argument(‘–disable-extensions‘)
禁用瀏覽器的所有擴充功能,可以加快啟動速度,並避免因擴充功能而導致的錯誤。

設定使用者代理
options.add_argument(‘user-agent=…‘)
假冒成不同的瀏覽器或裝置(例如手機),來抓取對應版本的網頁內容。

忽略 SSL 錯誤
options.add_argument(‘–ignore-certificate-errors‘)
讓瀏覽器忽略 SSL 憑證錯誤。這在連接到有憑證問題的網站時很有用,但會降低安全性。

停用圖形處理
options.add_argument(‘–disable-gpu’)
在某些作業系統或虛擬環境中,停用 GPU 加速可以提高穩定性和效能。

範例將模擬一個購票流程,假設該網站有以下幾個特點
1.登入:需要輸入帳號、密碼。
2.人機驗證:登入時需要點擊「我不是機器人」的 reCAPTCHA。
3.下拉式選單:需要選擇區域場所,內容由 JavaScript 控制。
4.無限滾動:頁面底部有更多票券,需要滑到底部才能載入。
# 需要先安裝 Selenium (pip install selenium) 和瀏覽器驅動程式 (ChromeDriver)
 
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
import time
 
# 設定你自己的帳號密碼
USERNAME = “your_username”
PASSWORD = “your_password”
 
# 購票網站的 URL
login_url = “http://your-ticket-website.com/login”
tickets_url = “http://your-ticket-website.com/tickets”
 
def scrape_tickets():
    print(“— 步驟 1: 啟動瀏覽器並前往登入頁面 —“)
     # 步驟 1: 啟動瀏覽器
    driver = webdriver.Chrome()
    driver.get(login_url)
 
    try:
        # 使用 WebDriverWait 等待最長10秒直到元素出現,避免因網速造成的錯誤
        wait = WebDriverWait(driver, 10)
 
        # 步驟 2: 填寫登入表單
        print(“— 步驟 2: 填寫帳號密碼 —“)
        username_input = wait.until(EC.presence_of_element_located((By.NAME, “username”)))
        password_input = driver.find_element(By.NAME, “password”)
 
        username_input.send_keys(USERNAME)
        password_input.send_keys(PASSWORD)
 
        # 步驟 3: 處理 reCAPTCHA 人機驗證
        print(“— 步驟 3: 點擊 ‘我不是機器人’ —“)
        recaptcha_iframe = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, “iframe[title=’reCAPTCHA’]”)))
        driver.switch_to.frame(recaptcha_iframe) # 切到驗證框
        
        recaptcha_checkbox = wait.until(EC.element_to_be_clickable((By.ID, “recaptcha-anchor”)))
        recaptcha_checkbox.click() # 點擊人機驗證
        
        driver.switch_to.default_content() # 切回主頁面
 
        # 若失敗需要等待人工驗證完成 
        print(“— 驗證失敗,請手動完成 reCAPTCHA 驗證,程式將在 20 秒後繼續 —“)
        time.sleep(20)
 
        # 步驟 4: 點擊登入按鈕
        print(“— 步驟 4: 點擊登入按鈕 —“)
        login_button = wait.until(EC.element_to_be_clickable((By.ID, “login-btn”)))
        login_button.click()
 
        # 步驟 5: 導航至票券頁面,並顯性等待內容載入
        print(“— 步驟 5: 成功登入,前往票券頁面 —“)
        driver.get(tickets_url)
        
        # 等待下拉式選單出現
        wait.until(EC.presence_of_element_located((By.ID, “location-select”)))
 
        # 步驟 6: 選擇下拉式選單中的指定區域
        print(“— 步驟 6: 選擇區域:臺北 —“)
        from selenium.webdriver.support.ui import Select
        location_select_element = driver.find_element(By.ID, “location-select”)
        select = Select(location_select_element)
        select.select_by_visible_text(“臺北”)
 
        # 步驟 7: 滾動網頁到最底部,載入所有票券
        print(“— 步驟 7: 滾動頁面載入所有票券 —“)
        last_height = driver.execute_script(“return document.body.scrollHeight“)  
# execute_script( ) 代表執行JavaScript 程式碼,對網頁框架進行操作
# document.body.scrollHeight 代表網頁內容的完整高度,包含那些目前在螢幕上看不到的部分
window.scrollTo(0, document.body.scrollHeight)
        while True:
            # 滾動到頁面底部
            driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) 
    # 使用 scrollTo(x, y),將瀏覽器的滾動條移動到指定位置
            
            # 給網頁一些時間來載入新的內容
            time.sleep(2)
            
            # 取得新的滾動高度
            new_height = driver.execute_script(“return document.body.scrollHeight“)
            
            # 如果滾動高度沒有變化,表示已經到底部
            if new_height == last_height:
                break
            last_height = new_height
        
        print(“— 頁面已滾動至底部,所有內容已載入 —“)
 
        # 步驟 8: 抓取所有票券的標題
        print(“— 步驟 8: 抓取所有票券標題 —“)
        # 使用 CSS 選擇器來找到所有票券項目
        ticket_titles = driver.find_elements(By.CSS_SELECTOR, “.ticket-item .ticket-title”)
        
        print(“\n— 找到的票券標題列表 —“)
        for title in ticket_titles:
            print(title.text)
 
    except TimeoutException:
        print(“— 錯誤: 等待元素逾時 —“)
    except NoSuchElementException as e:
        print(f”— 錯誤: 找不到指定的元素:{e} —“)
    except WebDriverException as e:
        print(f”— 錯誤: 瀏覽器驅動程式問題:{e} —“)
    finally:
        # 步驟 9: 關閉瀏覽器
        print(“\n— 步驟 9: 關閉瀏覽器 —“)
        driver.quit()
 
# 執行函式
if __name__ == “__main__”:
    scrape_tickets()

PyAutoGUI 是一個很強大的系統操作功能,它模擬的是滑鼠和鍵盤的硬體輸入,但需要一個可見的圖形使用者介面 (GUI),所以不能在後台操作。

import pyautogui # 要載入才能使用

控制滑鼠常用函式:

pyautogui.moveTo(x, y, duration = 0.2)
將滑鼠游標移動到指定座標 (x, y)。duration 參數可以讓移動更平滑。

pyautogui.click(x, y, button = ‘left’)
在指定座標點擊滑鼠。也可以用 right 或 middle 來點擊。

pyautogui.doubleClick()
執行滑鼠雙擊。

pyautogui.mouseDown(x,y,button)
在x、y處按下指定滑鼠按鍵。

pyautogui.mouseUp(x,y,button)
在x、y處放開指定鍵。

pyautogui.scroll(100)
滾動滑鼠滾輪,正數向上滾動,負數則向下滾動。

控制鍵盤常用函式:

pyautogui.write(‘hello world’, interval=0.1)
輸入字串。interval 參數可以讓輸入更像真人,每個字元之間有延遲。

pyautogui.press(‘F1’)
按下並放開單個按鍵(F1)。

pyautogui.keyDown(key):
持續按下單個按鍵。

pyautogui.keyUp(key):
放開單個按鍵。

pyautogui.hotkey(‘ctrl’, ‘v’)
模擬熱鍵組合。

螢幕操作常用函式:

pyautogui.screenshot(‘my_screenshot.png’)
截取整個螢幕,並存成圖片。

pyautogui.locateOnScreen(‘button.png’)
在螢幕上尋找圖片,回傳位置座標。這是一個非常強大的功能。

發佈留言