Python
爬蟲入門:讀懂 robots.txt,用 BeautifulSoup 合規抓資料
爬蟲入門:讀懂 robots.txt,用 BeautifulSoup 合規抓資料
很多有用的資料網站都沒有開放 API,競品分析、價格監控、新聞彙整常常得靠爬蟲自己動手抓。但 AI 給的爬蟲程式碼常常「能跑一次」就沒了,網站結構一改就壞、被反爬蟲機制擋下來,你需要看得懂、改得動。這一課從最容易被忽略的合規檢查開始,一路教到 BeautifulSoup 解析、CSS 選擇器定位、反爬蟲應對,最後把資料存成可用的格式。
動手之前先想清楚三件事:可以爬嗎(robots.txt 有沒有禁止、服務條款允不允許)、有沒有更簡單的方法(官方 API、公開資料集)、頻率合理嗎(不要讓伺服器過載)。這三個問題會貫穿整堂課。
你將學到什麼
先讀 robots.txt
抓資料前先確認網站允不允許爬,這是爬蟲的合規底線。
requests + BeautifulSoup
用 requests 抓頁面,BeautifulSoup 把 HTML 轉成可以操作的物件。
CSS 選擇器精準定位
用瀏覽器複製 CSS Selector,直接貼進程式碼就能用。
反爬蟲機制怎麼應對
User-Agent、頻率限制、JavaScript 動態載入各有合理的應對方式。
存成可用的格式
少量資料存 CSV,資料量大或需要查詢就用 SQLite。
三道遞進題自我檢驗
讀懂、改寫、抓錯,練到能獨立寫出生產等級的爬蟲。
爬之前先讀 robots.txt
robots.txt 是網站對爬蟲的使用說明書,放在網站根目錄,告訴你哪些路徑可以爬、哪些不行。這是爬蟲的倫理底線,也是法律風險的第一道防護,動手寫程式之前一定要先看過。
# https://example.com/robots.txt
User-agent: * # 對所有爬蟲
Disallow: /admin/ # 不能爬 /admin/ 路徑
Disallow: /private/ # 不能爬 /private/ 路徑
Allow: /public/ # 允許爬 /public/ 路徑
Crawl-delay: 2 # 請求間隔至少 2 秒
User-agent: Googlebot # 只對 Google 爬蟲
Allow: / # 允許爬全站
用 Python 讀 robots.txt 不用自己解析文字,標準函式庫就有現成工具:
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()
can_fetch = rp.can_fetch("*", "https://example.com/products")
print(f"可以爬 /products:{can_fetch}") # True 或 False
① 服務條款是否禁止自動化存取。② 爬取的資料是否涉及個資(個資法)。③ 是否會對伺服器造成過度負載,請求頻率要合理。
BeautifulSoup:把 HTML 變成 Python 物件
BeautifulSoup(pip install beautifulsoup4)把 HTML 字串解析成可以用 Python 操作的樹狀結構,搭配 requests 就是完整的爬蟲工具鏈。
import requests, time
from bs4 import BeautifulSoup
headers = {"User-Agent": "Mozilla/5.0 學術研究用途"}
def fetch_page(url: str) -> BeautifulSoup:
"""取得並解析單一頁面。"""
r = requests.get(url, headers=headers, timeout=10)
r.raise_for_status()
return BeautifulSoup(r.text, "html.parser")
soup = fetch_page("https://example.com/products")
# 選取元素
title = soup.find("h1") # 第一個 h1
items = soup.find_all("div", class_="item") # 所有 class="item" 的 div
link = soup.find("a", href=True) # 第一個有 href 屬性的 a 標籤
# 取出內容
print(title.get_text(strip=True)) # 純文字
print(link["href"]) # 屬性值
# 批次爬取時加間隔,尊重伺服器
time.sleep(2) # 兩秒間隔
CSS 選擇器:精準定位元素
BeautifulSoup 支援 CSS 選擇器語法(.select()),和瀏覽器開發者工具的「複製 selector」功能直接對接,是最快速定位元素的方法。
# .select() 回傳 list;.select_one() 回傳第一個或 None
# 標籤
soup.select("h2") # 所有 h2
# class
soup.select(".product-name") # class="product-name"
# id
soup.select("#main-title") # id="main-title"
# 後代(空格)
soup.select("div.card h3") # div.card 裡面的 h3
# 屬性
soup.select("a[href]") # 有 href 屬性的 a
soup.select('a[href^="https"]') # href 開頭是 https 的 a
# 組合範例:抓商品清單
products = soup.select("ul.product-list li")
for p in products:
name = p.select_one(".name")
price = p.select_one(".price")
if name and price:
print(name.get_text(strip=True), price.get_text(strip=True))
在 Chrome 開發者工具裡,對目標元素按右鍵,選 Copy 再選 Copy selector,貼到 .select_one() 裡就能直接用,省去猜 HTML 結構的時間。
反爬蟲機制與常見應對
現代網站通常有反爬蟲機制。了解這些機制,是為了讓你的爬蟲更禮貌、更穩定,不是為了繞過限制去爬不該爬的資料。
| 反爬蟲機制 | 症狀 | 合理應對 |
|---|---|---|
| User-Agent 封鎖 | 回傳 403 或空頁面 | 設置合理的 User-Agent 並說明用途 |
| 頻率限制 | 429 Too Many Requests | 加請求間隔,秒數落在 2 到 5 秒之間 |
| IP 封鎖 | 連線拒絕或逾時 | 降低頻率,或確認是否有官方 API |
| JavaScript 動態載入 | HTML 裡看不到資料 | 用 Playwright 或 Selenium,或找 XHR API |
| 需要登入 | 回傳登入頁面 | 使用 requests.Session 帶 cookie |
| CAPTCHA | 出現驗證圖片 | 考慮是否有官方授權或 API 替代 |
用 Chrome 開發者工具的 Network 分頁找 XHR,看網站是不是直接呼叫 API 取資料。如果有,直接用 requests 呼叫那個 API,比用 Playwright 模擬瀏覽器簡單十倍。
整合:爬完存到哪裡
爬蟲的最後一步是把資料存成可用的格式。根據資料量和用途選擇:少量存 CSV 或 JSON;大量或需要查詢就用 SQLite。
import requests, time, csv
from bs4 import BeautifulSoup
from pathlib import Path
def scrape_products(base_url: str, pages: int = 3) -> list:
"""爬取商品清單,回傳 list of dict。"""
results = []
headers = {"User-Agent": "Mozilla/5.0 學術研究"}
for page in range(1, pages + 1):
url = f"{base_url}?page={page}"
try:
r = requests.get(url, headers=headers, timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
for item in soup.select(".product-card"):
name = item.select_one(".name")
price = item.select_one(".price")
results.append({
"name": name.get_text(strip=True) if name else "",
"price": price.get_text(strip=True) if price else "",
"page": page
})
except requests.exceptions.RequestException as e:
print(f"第 {page} 頁失敗:{e}")
time.sleep(2) # 禮貌間隔
return results
def save_csv(data: list, path: str) -> None:
"""存成 UTF-8 CSV。"""
if not data:
return
with open(path, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
三道遞進題:讀懂、改寫、抓錯
三道題各練一種能力:讀懂是追蹤 BeautifulSoup 怎麼從 HTML 抓出資料;改寫是把不安全的爬蟲升級到生產標準;抓錯是找出合規、效能、可靠性上的問題。動手前先自己想過一輪,再對答案。
題目一:讀懂(基礎)
說明以下爬蟲程式每個步驟的作用:
import requests
from bs4 import BeautifulSoup
r = requests.get("https://news.example.com", timeout=8)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
headlines = soup.select("article.news-item h2 a")
results = [
{"title": h.get_text(strip=True), "url": h.get("href", "")}
for h in headlines
]
print(f"共取得 {len(results)} 篇新聞")
任務:說明 "html.parser" 的意思、.select() 選取的元素結構,以及 h.get("href","") 和 h["href"] 的差異。
① "html.parser" 是 Python 內建的 HTML 解析器,不需要額外安裝;"lxml" 速度更快但需要另外安裝。
② select("article.news-item h2 a") 選取所有 article.news-item 裡面 h2 裡面的 a 標籤,是三層後代選擇。
③ h.get("href","") 在屬性不存在時回傳空字串不報錯;h["href"] 屬性不存在時會拋出 KeyError,爬蟲要用 .get() 比較安全。
題目二:改寫(進階)
AI 給的初版爬蟲通常能跑一次,但不穩定:沒有錯誤處理、沒有間隔、不儲存資料。真正可用的爬蟲需要禮貌頻率、例外處理、結構化儲存,以及遇到失敗還能繼續的能力。
請 AI 把以下爬蟲升級到生產等級:
import requests
from bs4 import BeautifulSoup
for page in range(1, 11):
r = requests.get(f"https://shop.example.com/p{page}")
soup = BeautifulSoup(r.text, "html.parser")
items = soup.select(".item")
print(items)
「請升級這段爬蟲:① 加 timeout=10 和 raise_for_status;② 每頁之間加 time.sleep(2);③ 加 try/except 捕捉 RequestException,失敗時記錄並繼續;④ 把結果存成 UTF-8 CSV;⑤ 加 User-Agent header 說明用途;⑥ 加 docstring 和中文註解。」
① timeout 加 raise_for_status 能防止程式卡住,或默默吞掉錯誤回應。
② time.sleep(2) 加在每次請求後,對伺服器表示禮貌,也降低被封鎖的機率。
③ try/except 搭配 continue,讓單頁失敗不中斷整個爬取,失敗的頁碼記錄下來事後補爬。
題目三:抓錯(高階)
爬蟲的問題分三層:合規問題(爬得對不對)、可靠性問題(爬不爬得到)、資料品質問題(爬到的對不對)。找出以下爬蟲的所有問題:
import requests
from bs4 import BeautifulSoup
# 問題一:完全沒有檢查 robots.txt
urls = [f"https://target.com/user/{i}" for i in range(1, 10001)]
for url in urls: # 問題二
r = requests.get(url) # 問題三、四
soup = BeautifulSoup(r.text, "html.parser")
name = soup.find("span", class_="username").text
email = soup.find("span", class_="email").text # 問題五
print(name, email) # 問題六
問題一:沒有檢查 robots.txt,可能違反網站使用規範,爬用戶資料也涉及個資法。
問題二:一萬筆不間斷請求,可能對伺服器造成類似 DDoS 等級的負載,觸法或被永久封鎖。
問題三:沒有 timeout,任一請求卡住就讓整個程式停住。
問題四:沒有 raise_for_status 和 try/except,任一頁失敗程式就崩潰。
問題五:.text 在元素不存在時(find 回傳 None)會拋出 AttributeError,改用 x.get_text(strip=True)if x else "" 比較安全。
問題六:爬取並印出用戶 email 屬於個人資料,蒐集前需符合個資法的目的告知義務。
重點整理與完成清單
| 步驟 | 做什麼 | 工具 |
|---|---|---|
| 合規確認 | 讀 robots.txt、服務條款 | urllib.robotparser |
| 取得頁面 | 發送 HTTP 請求 | requests |
| 解析 HTML | 找到目標元素 | BeautifulSoup |
| 提取資料 | 取出文字和屬性 | .get_text()、.get() |
| 禮貌間隔 | 避免過度請求 | time.sleep(2 到 5 秒) |
| 存儲結果 | CSV 或 SQLite | csv、sqlite3 |
可以爬嗎(robots.txt 和條款)?有沒有 timeout 和例外處理?有沒有加 sleep 和 User-Agent?三個問題都想清楚了再按下執行。
- 爬之前先讀 robots.txt,確認合規
- 能用 requests 搭配 BeautifulSoup 取得並解析 HTML
- 能用 CSS 選擇器精準定位元素,配合瀏覽器複製 Selector
- 知道 .get_text(strip=True)和 .get(attr,"")的安全取值方式
- 每次請求加 timeout、raise_for_status、try/except、time.sleep
- 完成題目一:說明 html.parser、select、.get()和 [] 的差異
- 完成題目二:請 AI 把初版爬蟲升級到生產等級
- 完成題目三:找出合規、可靠性、個資三層問題
延伸學習
思維邏輯自我訓練:28 天養成計畫(含入門練習版)
四週把「有感覺」練成「有觀點」。每天一題、一個高階任務配一個低階任務,忙的日子也接得上。買這門課直接附贈《入門練習版》八單元 22 課完整講義與練習單,排在課程最前面,先把思維訓練三部曲、知識吸收金三角這些底層工具建立起來,再進入 28 天的每日練習。
NT$ 999
知識變現切割地圖(高清版下載及陪跑型教材)
《知識變現切割地圖》的完整陪跑版:地圖高清檔,加上五章 27 萬字的網頁版講義與200 張練習卡。從語感引流設計、互動與轉化、內容分層與產品路徑,到角色鏡像與心理設計、內容資源切割再利用,一層一層把「會做內容」變成「能賣內容」。每一節後面都接著可以直接動手填的練習卡。
NT$ 19,800

