靜態與動態網頁的差別
了解網頁的兩種類型,這是選擇爬蟲工具的關鍵基礎
什麼是靜態網頁?什麼是動態網頁?
| 比較項目 | 靜態網頁 | 動態網頁 |
|---|---|---|
| 內容載入 | 伺服器直接回傳完整 HTML | JavaScript 在瀏覽器端動態產生內容 |
| F12 檢視原始碼 | 看到的與頁面一致 | 原始碼可能是空的,內容由 JS 填入 |
| 爬蟲方式 | requests + bs4 |
playwright / selenium |
| 速度 | 快(只需 HTTP 請求) | 慢(需要啟動瀏覽器) |
| 常見範例 | 維基百科、PTT、新聞網站 | 金石堂、蝦皮、Instagram |
🔍 如何判斷?
- 在網頁按
Ctrl+U(檢視原始碼),如果能看到完整內容 → 靜態 - 在網頁按
F12→ Elements,如果原始碼與畫面不同 → 動態 - 在終端用
curl該網址,如果回傳有完整內容 → 靜態
試試看:用 F12 觀察靜態與動態的差別
切換下方的「靜態範例」與「動態範例」,按下 F12 開啟開發者工具,觀察 Elements 面板中 DOM 的變化。
💡 F12 練習任務
- 載入「靜態範例」→ 按 F12 → Elements → 找到
class="price"的元素 - 切換到「動態範例」→ 按按鈕前先看 Elements,找找
#product-list裡有什麼 - 按下「📦 載入商品」按鈕後,再觀察
#product-list的變化 - 思考:如果你用
requests.get()去抓動態網頁,你會得到什麼?
靜態爬蟲:BeautifulSoup4
使用 requests + bs4 爬取靜態網頁的資料
BeautifulSoup4 核心概念
# 安裝套件
pip install requests beautifulsoup4
📖 常用方法速查
soup.find('tag')— 找到第一個符合的元素soup.find_all('tag')— 找到所有符合的元素soup.select('css selector')— 用 CSS 選擇器找元素element.text— 取得元素的文字內容element['href']— 取得元素的屬性值element.get('class')— 安全地取得屬性(不存在不報錯)
爬取維基百科「藍染」頁面
目標網址:🔗 zh.wikipedia.org/zh-tw/藍染
練習目標
- 爬取頁面中所有的章節標題(h2, h3)
- 爬取第一張圖片的網址
- 爬取頁面中的表格與圖片網格(畫廊)資料,包含圖片網址並整理成清單
import requests
from bs4 import BeautifulSoup
# 1. 發送請求
url = "https://zh.wikipedia.org/zh-tw/藍染"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)
# 2. 解析 HTML
soup = BeautifulSoup(response.text, "html.parser")
# 3. 爬取所有章節標題
for heading in soup.select(".mw-heading h2, .mw-heading h3"):
print("📌", heading.text.strip())
# 4. 爬取第一張圖片
img = soup.select_one(".mw-body-content img")
if img:
print("🖼️ 圖片:", "https:" + img["src"])
# 5. 爬取表格與畫廊 (圖片網格) 資料
print("\n📊 表格與畫廊資料:")
# 處理傳統表格 (.wikitable)
for i, table in enumerate(soup.select("table.wikitable"), 1):
print(f"\n--- 第 {i} 個表格 ---")
for row in table.find_all("tr"):
row_data = [col.text.strip() for col in row.find_all(["th", "td"])]
if any(row_data): print(" | ".join(row_data))
# 處理畫廊 (排版上像表格的圖片網格)
for i, gallery in enumerate(soup.select("ul.gallery"), 1):
print(f"\n--- 第 {i} 個圖片網格 (Gallery) ---")
for box in gallery.select(".gallerybox"):
caption = box.select_one(".gallerytext")
img = box.select_one("img")
caption_text = caption.text.strip() if caption else "無說明"
img_url = "https:" + img["src"] if img and img.get("src") else "無圖片"
print(f"🔹 {caption_text} (圖片: {img_url})")
💡 AI Prompt 提示
你可以把以下 prompt 貼給 AI 助手來完成這個練習:
請幫我寫一個 Python 爬蟲,使用 requests 和 BeautifulSoup4:
1. 目標網址:https://zh.wikipedia.org/zh-tw/藍染
2. 爬取所有 h2 和 h3 的章節標題
3. 爬取頁面上所有圖片的網址
4. 將表格以及畫廊 (圖片網格) 內容整理成清單格式,包含說明文字與圖片網址
5. 記得加上 User-Agent header
6. 將結果存成 JSON 檔案
爬取 PTT 電影版文章列表
目標網址:🔗 ptt.cc/bbs/movie/index.html
練習目標
- 爬取文章的標題、作者、日期
- 爬取文章的推文數量
- 實作翻頁功能(爬取前 3 頁)
⚠️ 注意事項
- PTT 需要帶上 Cookie:
over18=1(已滿 18 歲驗證) - 翻頁的網址在「上一頁」的超連結中
- 有些文章可能被刪除,要加上錯誤處理
import requests
from bs4 import BeautifulSoup
url = "https://www.ptt.cc/bbs/movie/index.html"
cookies = {"over18": "1"}
for page in range(3):
resp = requests.get(url, cookies=cookies)
soup = BeautifulSoup(resp.text, "html.parser")
for entry in soup.select(".r-ent"):
title_el = entry.select_one(".title a")
if not title_el:
continue
push = entry.select_one(".nrec span")
author = entry.select_one(".meta .author")
date = entry.select_one(".meta .date")
print(f"[{push.text if push else ' '}] {title_el.text.strip()} - {author.text if author else 'N/A'}")
# 取得上一頁的連結
prev_link = soup.select(".btn-group-paging a")[1]["href"]
url = "https://www.ptt.cc" + prev_link
💡 AI Prompt 提示
請幫我寫一個 Python 爬蟲,爬取 PTT 電影版的文章列表:
1. 網址:https://www.ptt.cc/bbs/movie/index.html
2. 需要處理「已滿 18 歲」的 Cookie 驗證
3. 爬取每篇文章的標題、作者、日期、推文數
4. 實作翻頁功能,爬取最近 3 頁的文章
5. 將結果存成 CSV 檔案
6. 加上適當的錯誤處理和延遲(避免被封鎖)
動態爬蟲:Playwright
使用 Playwright 控制瀏覽器,爬取 JavaScript 動態渲染的網頁
Playwright 核心概念
# 安裝 Playwright
pip install playwright
playwright install chromium
📖 Playwright 重要觀念
- 無頭模式 (headless):不開啟瀏覽器視窗,速度更快
- 等待機制:
page.wait_for_selector()等待元素出現 - 截圖功能:
page.screenshot()擷取當前畫面 - 模擬操作:
page.click()、page.fill()模擬點擊與輸入 - 同步 API:使用
sync_playwright(),初學者更好上手
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 啟動瀏覽器(headless=False 可看到操作過程)
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 前往目標網頁
page.goto("https://example.com")
# 等待特定元素載入
page.wait_for_selector(".content")
# 擷取資料
items = page.query_selector_all(".item")
for item in items:
print(item.inner_text())
# 截圖存證
page.screenshot(path="result.png")
browser.close()
爬取金石堂暢銷書排行榜
練習目標
- 爬取暢銷書的排名、書名、作者、價格
- 嘗試切換不同分類(文學、商業...)
- 將結果整理成表格並存成 CSV
⚠️ 為什麼要用 Playwright?
金石堂的書籍排行資料是透過 JavaScript 動態載入的。如果用 requests.get() 抓取,會發現回傳的 HTML 裡不包含書籍資料。必須用 Playwright 等待
JavaScript 執行完畢後才能擷取。
from playwright.sync_api import sync_playwright
import csv
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.kingstone.com.tw/bestseller/best/book/")
# 等待書籍列表載入
page.wait_for_selector(".modProList", timeout=10000)
page.wait_for_timeout(2000) # 額外等待確保完全載入
# 擷取書籍資訊
books = page.query_selector_all(".modProList")
results = []
for i, book in enumerate(books, 1):
title = book.query_selector(".modProName")
author = book.query_selector(".modProAuthor")
price = book.query_selector(".priceset")
results.append({
"排名": i,
"書名": title.inner_text() if title else "N/A",
"作者": author.inner_text() if author else "N/A",
"價格": price.inner_text() if price else "N/A",
})
print(f"#{i} {results[-1]['書名']}")
# 截圖 & 關閉
page.screenshot(path="kingstone_bestseller.png", full_page=True)
browser.close()
💡 AI Prompt 提示
請用 Python Playwright 幫我爬取金石堂暢銷書排行榜:
1. 網址:https://www.kingstone.com.tw/bestseller/best/book/
2. 這是動態網頁,需要等待 JS 載入完成
3. 爬取每本書的排名、書名、作者、價格
4. 使用 headless=False 讓我看到操作過程
5. 將結果存成 CSV 檔案
6. 截圖保存頁面畫面作為記錄
爬取回收大百科並建立 AI 訓練資料集
目標網址:🔗 recycle.rethinktw.org/trash
練習目標
- 目標是為 LLM (大型語言模型) 建立乾淨的資料集
- 爬取特定按鈕與深層文字(如:價值、材質、說明)
- 處理 SPA (單頁應用) 的動態載入延遲問題
- 將資料結構化並匯出成 JSON 格式
⚠️ 動態載入的等待技巧
在許多現代網頁 (如 React/Vue 建立的 SPA) 裡,網頁標題和結構可能會在 JavaScript 渲染前短暫呈現空白或預設文字。必須善用 wait_for_selector
來確保資料確實出現在畫面上後再開始擷取。
import json
from playwright.sync_api import sync_playwright
def scrape_rethink_details(urls):
results = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
for url in urls:
print(f"🌍 正在爬取:{url}")
page.goto(url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_selector("h1", timeout=15000)
page.wait_for_timeout(1500) # 等待動態元件渲染
# 1. 垃圾名稱
title_el = page.query_selector("h1")
title = title_el.inner_text().strip() if title_el else "未知名稱"
# 2. 價值與說明
value_els = page.query_selector_all("span, p, div")
value = ""
for el in value_els:
text = el.inner_text().strip()
if "高回收價值" in text: value = "高回收價值"
elif "低回收價值" in text: value = "低回收價值"
if value: break
desc_els = page.query_selector_all("p.chakra-text")
description = max([el.inner_text().strip() for el in desc_els], key=len, default="")
item_data = {
"name": title,
"value": value,
"description": description,
"url": url
}
results.append(item_data)
browser.close()
return results
# 執行擷取並存成 JSON
urls = ["https://recycle.rethinktw.org/trash/1/", "https://recycle.rethinktw.org/trash/115/"]
data = scrape_rethink_details(urls)
with open("rethink_data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
💡 AI Prompt 提示
請用 Playwright 爬取回收大百科的垃圾處理資訊:
1. 目標網址:https://recycle.rethinktw.org/trash/1/ 等多個頁面
2. 這是動態載入的 SPA 網頁,需要明確等待標題 h1 出現
3. 爬取垃圾名稱、回收價值(從 span 或 div 中尋找「高回收價值」或「低回收價值」字樣)
4. 爬取垃圾的詳細說明(取得頁面中最長的一段 p.chakra-text 文字)
5. 將爬抓結果整理為結構化的字典清單
6. 將最終資料匯出成 JSON 檔案 (rethink_data.json)
AI 爬蟲:Crawl4AI
使用 AI 自動理解網頁結構並提取結構化資料
Crawl4AI 核心概念
# 安裝 Crawl4AI
pip install crawl4ai
crawl4ai-setup # 安裝瀏覽器引擎
📖 Crawl4AI 是什麼?
- AI 驅動:利用 LLM 自動理解網頁結構,不需手動寫 CSS Selector
- 結構化輸出:可定義 Schema,讓 AI 按格式回傳資料(JSON)
- Markdown 輸出:自動將網頁轉成乾淨的 Markdown 格式
- 支援動態網頁:內建瀏覽器引擎,可處理 JavaScript 渲染
- Extraction Strategy:使用
LLMExtractionStrategy讓 AI 決定如何擷取
import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.extraction_strategy import LLMExtractionStrategy
async def main():
config = CrawlerRunConfig(
extraction_strategy=LLMExtractionStrategy(
provider="openai/gpt-4o-mini",
instruction="擷取所有商品名稱和價格,以 JSON 格式回傳",
)
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://example.com/products",
config=config,
)
print(result.extracted_content)
asyncio.run(main())
用 AI 解析商品價格資訊
目標:了解大型電商網站(如 Momo)結構龐大容易導致 AI 解析超時或觸發 Rate Limit 的限制,並學習改用簡易的教學模板(demo.html)配合 Live Server
進行高速且不受阻礙的本機測試。
練習目標
- 用 Crawl4AI 將網頁轉成 Markdown 格式
- 使用 LLM Extraction 自動擷取商品名稱與價格
- 用 JsonCssExtractionStrategy 或 LLMExtractionStrategy 定義資料結構
- 比較 AI 爬蟲與傳統爬蟲的差異
import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
async def main():
config = CrawlerRunConfig(
word_count_threshold=10, # 過濾太短的內容
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="http://127.0.0.1:5500/demo.html", # ⚠️ 請先使用 Live Server 啟動準備好的本地教學靶機網頁
config=config,
)
# 輸出乾淨的 Markdown
print(result.markdown)
# 也可以看網頁上所有的連結
for link in result.links["internal"][:10]:
print("🔗", link["text"], link["href"])
asyncio.run(main())
import asyncio, json, os
from pydantic import BaseModel, Field
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, LLMConfig
from crawl4ai.extraction_strategy import LLMExtractionStrategy
class Product(BaseModel):
name: str = Field(description="商品的完整名稱")
price: str = Field(description="商品的價格數值")
link: str = Field(description="商品的超連結網址")
# 設定 API Key(或用環境變數)
os.environ["GEMINI_API_KEY"] = "your-gemini-api-key"
async def main():
llm_config = LLMConfig(provider="gemini/gemini-2.5-flash")
strategy = LLMExtractionStrategy(
llm_config=llm_config,
schema=Product.model_json_schema(),
instruction="從這個網頁中擷取所有重點商品資訊,並嚴格遵循提供的 schema 回傳 JSON 陣列",
)
config = CrawlerRunConfig(
extraction_strategy=strategy,
css_selector=".listArea", # 💡 若解析真實大型電商,可利用此過濾器刪減廣告避免中斷;本地靶機則非必要
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="http://127.0.0.1:5500/demo.html", # ⚠️ 針對教學示範本機網頁進行解析
config=config,
)
data = json.loads(result.extracted_content)
for item in data:
print(f"📦 {item.get('name', 'N/A')} — 💰 {item.get('price', 'N/A')}")
asyncio.run(main())
💡 AI Prompt 提示
請用 Crawl4AI 幫我解析本地端的「暢銷商品排行榜」教學網頁:
1. 指導如何先製作一支簡短的 demo.html,包含十幾個商品清單片段
2. 使用 AsyncWebCrawler 搭配 Live Server 測試網址 (http://127.0.0.1:5500/demo.html)
3. 示範免 API Key 的 Markdown 模式,看看純文字無干擾的網頁結構
4. 導入 pydantic 建立 BaseModel (包含 name, price, link 等屬性欄位)
5. 使用 LLMExtractionStrategy,並傳入 schema=Product.model_json_schema() 以強制 AI 遵守目標 JSON 格式
6. 將結果提取並整理印出在終端機
三種爬蟲技術比較
| 項目 | BeautifulSoup4 | Playwright | Crawl4AI |
|---|---|---|---|
| 適用場景 | 靜態 HTML 網頁 | 動態 JS 網頁 | 任何網頁 + AI 解析 |
| 學習難度 | 簡單 | 中等 | 簡單 |
| 速度 | ⚡ 很快 | 🐢 較慢 | 🐢 較慢(含 AI) |
| 需要瀏覽器 | ❌ 不需要 | ✅ 需要 | ✅ 需要 |
| 需要寫選擇器 | ✅ 需要手動 | ✅ 需要手動 | ❌ AI 自動判斷 |
| 費用 | 免費 | 免費 | Markdown 免費 / LLM 需 API Key |