Python 網頁爬蟲 教學指引

從零開始學習網頁爬蟲,掌握靜態、動態、AI 三種爬蟲技術

📄 靜態爬蟲 ⚡ 動態爬蟲 🤖 AI 爬蟲 🎯 實戰練習
1

靜態與動態網頁的差別

了解網頁的兩種類型,這是選擇爬蟲工具的關鍵基礎

📚 核心概念

什麼是靜態網頁?什麼是動態網頁?

比較項目 靜態網頁 動態網頁
內容載入 伺服器直接回傳完整 HTML JavaScript 在瀏覽器端動態產生內容
F12 檢視原始碼 看到的與頁面一致 原始碼可能是空的,內容由 JS 填入
爬蟲方式 requests + bs4 playwright / selenium
速度 快(只需 HTTP 請求) 慢(需要啟動瀏覽器)
常見範例 維基百科、PTT、新聞網站 金石堂、蝦皮、Instagram

🔍 如何判斷?

  1. 在網頁按 Ctrl+U(檢視原始碼),如果能看到完整內容 → 靜態
  2. 在網頁按 F12 → Elements,如果原始碼與畫面不同 → 動態
  3. 在終端用 curl 該網址,如果回傳有完整內容 → 靜態
🎯 互動練習

試試看:用 F12 觀察靜態與動態的差別

切換下方的「靜態範例」與「動態範例」,按下 F12 開啟開發者工具,觀察 Elements 面板中 DOM 的變化。

預覽

💡 F12 練習任務

  1. 載入「靜態範例」→ 按 F12 → Elements → 找到 class="price" 的元素
  2. 切換到「動態範例」→ 按按鈕前先看 Elements,找找 #product-list 裡有什麼
  3. 按下「📦 載入商品」按鈕後,再觀察 #product-list 的變化
  4. 思考:如果你用 requests.get() 去抓動態網頁,你會得到什麼?

2

靜態爬蟲:BeautifulSoup4

使用 requests + bs4 爬取靜態網頁的資料

🔧 技術知識

BeautifulSoup4 核心概念

1 安裝套件
2 發送請求
3 解析 HTML
4 擷取資料
bash
# 安裝套件
pip install requests beautifulsoup4

📖 常用方法速查

  • soup.find('tag') — 找到第一個符合的元素
  • soup.find_all('tag') — 找到所有符合的元素
  • soup.select('css selector') — 用 CSS 選擇器找元素
  • element.text — 取得元素的文字內容
  • element['href'] — 取得元素的屬性值
  • element.get('class') — 安全地取得屬性(不存在不報錯)
🎯 練習題 A

爬取維基百科「藍染」頁面

目標網址:🔗 zh.wikipedia.org/zh-tw/藍染

🎯

練習目標

  • 爬取頁面中所有的章節標題(h2, h3)
  • 爬取第一張圖片的網址
  • 爬取頁面中的表格與圖片網格(畫廊)資料,包含圖片網址並整理成清單
python
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 助手來完成這個練習:

prompt
請幫我寫一個 Python 爬蟲,使用 requests 和 BeautifulSoup4:
1. 目標網址:https://zh.wikipedia.org/zh-tw/藍染
2. 爬取所有 h2 和 h3 的章節標題
3. 爬取頁面上所有圖片的網址
4. 將表格以及畫廊 (圖片網格) 內容整理成清單格式,包含說明文字與圖片網址
5. 記得加上 User-Agent header
6. 將結果存成 JSON 檔案
🎯 練習題 B

爬取 PTT 電影版文章列表

目標網址:🔗 ptt.cc/bbs/movie/index.html

🎯

練習目標

  • 爬取文章的標題、作者、日期
  • 爬取文章的推文數量
  • 實作翻頁功能(爬取前 3 頁)

⚠️ 注意事項

  • PTT 需要帶上 Cookie:over18=1(已滿 18 歲驗證)
  • 翻頁的網址在「上一頁」的超連結中
  • 有些文章可能被刪除,要加上錯誤處理
python
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 提示

prompt
請幫我寫一個 Python 爬蟲,爬取 PTT 電影版的文章列表:
1. 網址:https://www.ptt.cc/bbs/movie/index.html
2. 需要處理「已滿 18 歲」的 Cookie 驗證
3. 爬取每篇文章的標題、作者、日期、推文數
4. 實作翻頁功能,爬取最近 3 頁的文章
5. 將結果存成 CSV 檔案
6. 加上適當的錯誤處理和延遲(避免被封鎖)

3

動態爬蟲:Playwright

使用 Playwright 控制瀏覽器,爬取 JavaScript 動態渲染的網頁

🔧 技術知識

Playwright 核心概念

bash
# 安裝 Playwright
pip install playwright
playwright install chromium

📖 Playwright 重要觀念

  • 無頭模式 (headless):不開啟瀏覽器視窗,速度更快
  • 等待機制page.wait_for_selector() 等待元素出現
  • 截圖功能page.screenshot() 擷取當前畫面
  • 模擬操作page.click()page.fill() 模擬點擊與輸入
  • 同步 API:使用 sync_playwright(),初學者更好上手
python
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()
🎯 練習題 A

爬取金石堂暢銷書排行榜

目標網址:🔗 kingstone.com.tw 暢銷排行榜

🎯

練習目標

  • 爬取暢銷書的排名、書名、作者、價格
  • 嘗試切換不同分類(文學、商業...)
  • 將結果整理成表格並存成 CSV

⚠️ 為什麼要用 Playwright?

金石堂的書籍排行資料是透過 JavaScript 動態載入的。如果用 requests.get() 抓取,會發現回傳的 HTML 裡不包含書籍資料。必須用 Playwright 等待 JavaScript 執行完畢後才能擷取。

python
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 提示

prompt
請用 Python Playwright 幫我爬取金石堂暢銷書排行榜:
1. 網址:https://www.kingstone.com.tw/bestseller/best/book/
2. 這是動態網頁,需要等待 JS 載入完成
3. 爬取每本書的排名、書名、作者、價格
4. 使用 headless=False 讓我看到操作過程
5. 將結果存成 CSV 檔案
6. 截圖保存頁面畫面作為記錄
🎯 練習題 B

爬取回收大百科並建立 AI 訓練資料集

目標網址:🔗 recycle.rethinktw.org/trash

🎯

練習目標

  • 目標是為 LLM (大型語言模型) 建立乾淨的資料集
  • 爬取特定按鈕與深層文字(如:價值、材質、說明
  • 處理 SPA (單頁應用) 的動態載入延遲問題
  • 將資料結構化並匯出成 JSON 格式

⚠️ 動態載入的等待技巧

在許多現代網頁 (如 React/Vue 建立的 SPA) 裡,網頁標題和結構可能會在 JavaScript 渲染前短暫呈現空白或預設文字。必須善用 wait_for_selector 來確保資料確實出現在畫面上後再開始擷取。

python
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 提示

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)

4

AI 爬蟲:Crawl4AI

使用 AI 自動理解網頁結構並提取結構化資料

🔧 技術知識

Crawl4AI 核心概念

bash
# 安裝 Crawl4AI
pip install crawl4ai
crawl4ai-setup  # 安裝瀏覽器引擎

📖 Crawl4AI 是什麼?

  • AI 驅動:利用 LLM 自動理解網頁結構,不需手動寫 CSS Selector
  • 結構化輸出:可定義 Schema,讓 AI 按格式回傳資料(JSON)
  • Markdown 輸出:自動將網頁轉成乾淨的 Markdown 格式
  • 支援動態網頁:內建瀏覽器引擎,可處理 JavaScript 渲染
  • Extraction Strategy:使用 LLMExtractionStrategy 讓 AI 決定如何擷取
python
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 自動擷取商品名稱與價格
  • JsonCssExtractionStrategyLLMExtractionStrategy 定義資料結構
  • 比較 AI 爬蟲與傳統爬蟲的差異
python
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())
python
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 提示

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