iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
自我挑戰組

Python 爬蟲與資料分析實戰:從網路資料到資訊視覺化系列 第 14 篇

Day 14|讓爬蟲更穩定:User-Agent、Session 與請求間隔

  • 分享至 

  • xImage
  •  

一、什麼是 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 的基本結構,並讓自己的爬蟲程式寫得更完整。


上一篇
Day 13|Python 爬蟲實戰:爬蟲錯誤處理與資料驗證,讓爬取結果更可靠
下一篇
Day 15|爬蟲資料的進一步處理:清理文字、價格與欄位
系列文
Python 爬蟲與資料分析實戰:從網路資料到資訊視覺化 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言