本文 AI 產出,尚未審核

Python 課程 – 網路與 API(Networking & APIs)

主題:網頁爬蟲(BeautifulSoup、lxml、Scrapy)


簡介

在現代資料驅動的開發環境中,網頁爬蟲已成為取得公開資訊、進行資料分析與機器學習前處理的重要工具。無論是要抓取商品價格、新聞內容、或是社群平台的公開貼文,都離不開對 HTML/XML 結構的解析與資料抽取。

Python 之所以在爬蟲領域佔有領導地位,主要歸功於三個成熟且易上手的套件:BeautifulSouplxmlScrapy。它們分別在「簡易快速的單頁抓取」、「高效能的 XML/HTML 解析」以及「大型、分散式爬蟲框架」上各有千秋。掌握這三者的核心概念與實作方式,能讓開發者在 從零到一 的過程中,快速完成從小型腳本到企業級爬蟲的轉換。

本篇文章將以 淺顯易懂 的語言,帶你一步步了解這三個工具的使用方式、常見陷阱與最佳實踐,並透過實務範例說明它們在不同情境下的應用。


核心概念

1. HTTP 請求與回應基礎

爬蟲的第一步是向目標網站發送 HTTP GET(或 POST)請求,取得原始的 HTML 或 JSON 內容。Python 最常使用的套件是 requests

import requests

url = "https://www.example.com"
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
if response.status_code == 200:
    html = response.text          # 取得 HTML 原始碼
else:
    raise Exception(f"HTTP {response.status_code}")

小提醒User-Agent 必須偽裝成瀏覽器,避免被網站直接封鎖。

2. BeautifulSoup:簡易的單頁解析

BeautifulSoup容錯性 著稱,能夠處理不完整或錯誤的 HTML。常見的建立方式如下:

from bs4 import BeautifulSoup

soup = BeautifulSoup(html, "html.parser")   # 內建解析器
# 若需要更快的解析,可改用 lxml 解析器:BeautifulSoup(html, "lxml")

範例 1:抓取新聞標題

# 假設網頁結構如下:
# <div class="article">
#   <h2 class="title">新聞標題</h2>
#   <p class="summary">摘要...</p>
# </div>

titles = soup.select("div.article h2.title")   # CSS selector
for idx, title in enumerate(titles, 1):
    print(f"{idx}. {title.get_text(strip=True)}")

範例 2:取得表格資料

table = soup.find("table", {"id": "price-table"})
rows = table.find_all("tr")
for row in rows[1:]:  # 跳過表頭
    cols = row.find_all("td")
    product = cols[0].get_text(strip=True)
    price   = cols[1].get_text(strip=True)
    print(f"{product}: {price}")

3. lxml:高速且支援 XPath

lxml 是基於 C 語言實作的解析器,速度遠快於純 Python 的解析器,且支援 XPath 查詢,對於結構複雜的 XML/HTML 非常有幫助。

from lxml import etree

parser = etree.HTMLParser()
tree = etree.fromstring(html, parser)

# XPath 範例:找出所有連結
links = tree.xpath("//a/@href")
for link in links[:5]:
    print(link)

範例 3:使用 XPath 抓取商品資訊

# 假設商品區塊使用 <div class="item"> 包裹
items = tree.xpath("//div[@class='item']")
for item in items:
    name  = item.xpath(".//h3/text()")[0].strip()
    price = item.xpath(".//span[@class='price']/text()")[0].strip()
    print(f"{name} - {price}")

4. Scrapy:構建可擴充的爬蟲專案

當爬取需求從「單一頁面」升級到「多層級、分散式」時,Scrapy 提供了 框架化 的開發體驗。它內建請求排程、資料管線(pipeline)與自動去重(duplicate filter)。

建立 Scrapy 專案

# 建立新專案
scrapy startproject news_crawler
cd news_crawler

# 產生爬蟲
scrapy genspider tech_spider technews.example.com

範例 4:簡易的新聞爬蟲

news_crawler/spiders/tech_spider.py

import scrapy

class TechSpider(scrapy.Spider):
    name = "tech_spider"
    allowed_domains = ["technews.example.com"]
    start_urls = ["https://technews.example.com/latest"]

    def parse(self, response):
        # 使用 CSS selector 抓取每篇文章的連結
        for href in response.css("article h2.title a::attr(href)").getall():
            yield response.follow(href, self.parse_detail)

    def parse_detail(self, response):
        title   = response.css("h1.article-title::text").get().strip()
        date    = response.css("time.pub-date::attr(datetime)").get()
        content = "\n".join(response.css("div.article-body p::text").getall())
        yield {
            "title": title,
            "date": date,
            "content": content,
            "url": response.url,
        }

執行爬蟲並將結果輸出為 JSON:

scrapy crawl tech_spider -o tech_news.json

5. 結合三者的實務範例

有時候我們會在 Scrapy 的 parse 方法內部直接使用 BeautifulSouplxml 進行更細緻的解析,以取得複雜的 DOM 結構:

def parse_detail(self, response):
    # 把 response.body 交給 BeautifulSoup 處理
    soup = BeautifulSoup(response.body, "lxml")
    # 再利用 CSS selector 取得特定欄位
    author = soup.select_one("span.author-name").get_text(strip=True)
    # 交給 Scrapy pipeline
    yield {
        "title": response.css("h1::text").get(),
        "author": author,
        "content": soup.select_one("div.content").get_text(separator="\n", strip=True),
    }

常見陷阱與最佳實踐

常見問題 為何會發生 解決方式
被網站封鎖 缺乏 User-Agent、請求頻率過高、未處理 Cookies 使用 headers 偽裝、加入 隨機延遲 (time.sleep 或 Scrapy 的 DOWNLOAD_DELAY)、使用 Session 保存 Cookies
解析失敗 HTML 結構不完整或使用了 JavaScript 產生的動態內容 1. 先檢查原始碼是否包含目標元素;2. 必要時使用 Selenium/Playwright 模擬瀏覽器;3. 盡量使用 lxml 的容錯模式
資料重複 多頁面或分頁中同一筆資料出現兩次 在 Scrapy 中啟用 去重機制 (DUPEFILTER_CLASS);自行在 pipeline 中比對唯一鍵(如 ID)
法規與倫理問題 未遵守 robots.txt、過度抓取導致伺服器負荷 1. 尊重 robots.txt(Scrapy 內建支援);2. 設定合理的 爬取速率;3. 若涉及個人資料,必須遵守 GDPR、個資法等相關規範
記憶體泄漏 大量資料一次性載入、未釋放物件 使用 生成器 (yield) 逐筆處理;在 Scrapy 中透過 Item Pipeline 分批寫入資料庫或檔案

最佳實踐

  • 模組化:將請求、解析、資料儲存分離,方便測試與維護。
  • 日誌紀錄:使用 logging 記錄爬取狀態、錯誤與重試次數。
  • 錯誤處理:對 requests 設定 timeouttry/except 捕捉例外,必要時實作 重試機制(如 urllib3.util.retry)。
  • 測試環境:在開發階段使用 本機測試網站(如 httpbin.org)或抓取自己的測試頁面,避免對正式網站造成影響。

實際應用場景

場景 使用工具 為何選擇
商品價格監控 requests + BeautifulSoup 只需抓取單一商品頁面,簡單快速
新聞聚合平台 Scrapy + lxml 多站點、多頁面、需要排程與去重
政府開放資料的 HTML 版 lxml + XPath 結構固定、需精準定位欄位
社群平台的公開貼文 Selenium + BeautifulSoup 內容由 JavaScript 動態載入
大型影像庫批次下載 Scrapy + Pipeline (MongoDB) 高併發、需持久化與斷點續傳

案例說明:某電商公司想要每日自動比對競品價格,使用 requests 抓取競品商品頁面,搭配 BeautifulSoup 解析價格欄位,最後把結果寫入 MySQL。若商品數量突破千筆,則改用 Scrapy 並在 Pipeline 中寫入 PostgreSQL,以提升效能與穩定性。


總結

  • 網頁爬蟲 是資料取得的基礎能力,掌握 HTTP 請求與 HTML 解析是第一步。
  • BeautifulSoup 以易用與容錯著稱,適合快速原型與小規模任務。
  • lxml 提供高速與 XPath 支援,適合需要精準定位或大量資料的情境。
  • Scrapy 為完整的爬蟲框架,支援分散式、去重、資料管線與自動化排程,是企業級解決方案的首選。
  • 在實作過程中,遵守網站政策、控制請求速率、妥善處理例外,才能寫出既高效又負責任的爬蟲程式。

透過本文的概念與範例,你已具備從 單頁抓取分散式爬蟲 的完整技術鏈,接下來只要根據實際需求選擇合適的工具,持續優化與測試,即可在資料驅動的專案中發揮更大的價值。祝你爬蟲之旅順利!