一、什麼是 User-Agent?
當我們使用 Requests 發送 HTTP Request 時,網站其實可以看到一些 Request Header。
其中一個比較常見的就是:
User-Agent
它可以用來表示「我是什麼樣的客戶端」。
例如瀏覽器可能會送出類似:
Mozilla/5.0 ...
而我們直接使用 Requests 時,網站看到的 User-Agent 通常會顯示 Requests。
可以先用程式看看:
import requests
url = "https://books.toscrape.com/"
response = requests.get(url)
print(response.request.headers)
執行後可以看到這次 Request 使用的 Header。
其中會包含:
User-Agent
二、自己設定 User-Agent
我們可以在 Request 裡面加入 headers。
例如:
import requests
url = "https://books.toscrape.com/"
headers = {
"User-Agent": "Mozilla/5.0"
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.request.headers)
這裡的:
headers = {
"User-Agent": "Mozilla/5.0"
}
就是建立一個 Dictionary。
然後:
requests.get(url, headers=headers)
把這個 Header 傳給網站。
這也是之後爬蟲很常看到的寫法。
三、為什麼要使用 Session?
如果我們一直使用:
requests.get(url)
每一次 Request 都是獨立的。
但如果我們正在爬同一個網站,例如:
第 1 頁
第 2 頁
第 3 頁
第 4 頁
第 5 頁
其實可以建立一個 Session。
session = requests.Session()
之後都使用:
session.get(url)
來發送請求。
Session 可以讓我們集中設定一些 Request 的資訊,例如 Header。
四、建立自己的爬蟲 Session
我把前幾天的 Books to Scrape 爬蟲稍微改一下。
import requests
from bs4 import BeautifulSoup
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0"
})
url = "https://books.toscrape.com/"
response = session.get(url)
print("Status Code:", response.status_code)
soup = BeautifulSoup(response.text, "html.parser")
books = soup.select("article.product_pod")
print("Books:", len(books))
這裡最重要的是:
session = requests.Session()
先建立 Session。
接著:
session.headers.update({
"User-Agent": "Mozilla/5.0"
})
設定這個 Session 的預設 Header。
所以之後使用:
session.get(url)
時,就會自動使用我們設定的 User-Agent。
五、把 Session 放進分頁爬蟲
前幾天我們已經學過自動處理下一頁。
今天可以把 Session 加進去。
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import time
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0"
})
url = "https://books.toscrape.com/"
all_books = []
while url:
print("正在抓取:", url)
try:
response = session.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
books = soup.select("article.product_pod")
for book in books:
title = book.h3.a["title"]
price = book.select_one(".price_color").text.strip()
availability = book.select_one(".availability").text.strip()
all_books.append({
"title": title,
"price": price,
"availability": availability
})
next_button = soup.select_one("li.next a")
if next_button:
next_url = next_button.get("href")
url = urljoin(url, next_url)
else:
url = None
time.sleep(1)
except requests.RequestException as e:
print("Request 發生錯誤:", e)
break
print("總共抓到:", len(all_books))
如果成功執行,最後會看到:
總共抓到: 1000
Books to Scrape 總共有 1000 本書,所以這代表整個網站的分頁資料都被抓下來了。
六、為什麼要加入 time.sleep()?
這一段:
time.sleep(1)
代表程式每抓完一頁後,等待 1 秒再繼續。
假設有 50 頁:
沒有等待:
第1頁 → 第2頁 → 第3頁 → 第4頁 → ...
可能會在很短的時間內發送大量 Request。
加入等待:
第1頁
↓ 等1秒
第2頁
↓ 等1秒
第3頁
↓ 等1秒
第4頁
會比較溫和。
這也是實際爬蟲需要注意的地方。
爬蟲不是速度越快越好,而是要在「效率」和「對網站的負擔」之間取得平衡。
七、timeout 也很重要
今天的程式裡還有一個之前比較容易忽略的東西:
response = session.get(url, timeout=10)
這裡的:
timeout=10
代表如果連線一直沒有得到回應,就不要讓程式無限等待。
例如:
try:
response = session.get(url, timeout=10)
except requests.Timeout:
print("Request 超時")
這樣如果網站回應太慢,就可以捕捉到 Timeout。
八、raise_for_status()
前幾天我們有使用:
response.status_code
今天可以進一步使用:
response.raise_for_status()
例如:
response = session.get(url, timeout=10)
response.raise_for_status()
如果是正常的 HTTP Response,例如:
200 OK
程式會繼續執行。
但如果遇到像:
404
500
等 HTTP 錯誤,就會丟出 Exception。
因此我們可以搭配:
try:
response = session.get(url, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
print("發生錯誤:", e)
這樣錯誤處理會比單純判斷 status_code 更方便。
九、把今天學到的東西整合起來
最後整理成一個比較完整的版本。
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import time
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0"
})
url = "https://books.toscrape.com/"
all_books = []
while url:
print("正在抓取:", url)
try:
response = session.get(
url,
timeout=10
)
response.raise_for_status()
soup = BeautifulSoup(
response.text,
"html.parser"
)
books = soup.select(
"article.product_pod"
)
for book in books:
title = book.h3.a["title"]
price = book.select_one(
".price_color"
).text.strip()
availability = book.select_one(
".availability"
).text.strip()
all_books.append({
"title": title,
"price": price,
"availability": availability
})
next_button = soup.select_one(
"li.next a"
)
if next_button:
next_url = next_button.get("href")
url = urljoin(
url,
next_url
)
else:
url = None
time.sleep(1)
except requests.Timeout:
print("Request 超時")
break
except requests.RequestException as e:
print("Request 發生錯誤:", e)
break
print()
print("爬蟲完成")
print("總共取得:", len(all_books), "筆資料")
這個版本其實已經開始有「比較完整的爬蟲」樣子了。
目前我們的流程變成:
建立 Session
↓
設定 User-Agent
↓
Request
↓
Timeout 防止無限等待
↓
確認 HTTP Status
↓
BeautifulSoup 解析
↓
取得資料
↓
尋找下一頁
↓
等待 1 秒
↓
繼續下一頁
十、今天也要注意爬蟲的界線
這裡我覺得是今天很重要的一個觀念。
學爬蟲不代表可以無限制地抓任何網站。
實際抓資料之前,應該注意:
網站的使用條款
robots.txt
網站是否提供公開 API
Request 頻率限制
不要大量、快速地發送 Request
不要嘗試繞過 CAPTCHA、登入限制或其他安全機制
只抓自己需要的公開資料
所以今天學 User-Agent 並不是為了「偽裝成瀏覽器來繞過網站限制」,而是了解 HTTP Request 的基本結構,並讓自己的爬蟲程式寫得更完整。