Python 課程 – 網路與 API(Networking & APIs)
主題:網頁爬蟲(BeautifulSoup、lxml、Scrapy)
簡介
在現代資料驅動的開發環境中,網頁爬蟲已成為取得公開資訊、進行資料分析與機器學習前處理的重要工具。無論是要抓取商品價格、新聞內容、或是社群平台的公開貼文,都離不開對 HTML/XML 結構的解析與資料抽取。
Python 之所以在爬蟲領域佔有領導地位,主要歸功於三個成熟且易上手的套件:BeautifulSoup、lxml 與 Scrapy。它們分別在「簡易快速的單頁抓取」、「高效能的 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 方法內部直接使用 BeautifulSoup 或 lxml 進行更細緻的解析,以取得複雜的 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設定timeout、try/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 為完整的爬蟲框架,支援分散式、去重、資料管線與自動化排程,是企業級解決方案的首選。
- 在實作過程中,遵守網站政策、控制請求速率、妥善處理例外,才能寫出既高效又負責任的爬蟲程式。
透過本文的概念與範例,你已具備從 單頁抓取 到 分散式爬蟲 的完整技術鏈,接下來只要根據實際需求選擇合適的工具,持續優化與測試,即可在資料驅動的專案中發揮更大的價值。祝你爬蟲之旅順利!