一、今天的專案流程
向網站取得 HTML
擷取書名、價格、評分
轉換資料型態、檢查無效資料
儲存書籍資料、避免重複新增
查詢書籍數量、價格與評分
今天仍然使用 Google Colab 作為開發環境,並以 Books to Scrape 這個練習網站作為資料來源。
二、安裝並匯入套件
首先,匯入今天會用到的套件。
import requests
import sqlite3
import time
import pandas as pd
from bs4 import BeautifulSoup
from urllib.parse import urljoin
print("套件匯入完成!")
其中,requests 負責取得網頁,BeautifulSoup 負責解析 HTML,sqlite3 負責操作資料庫,pandas 則可以用來整理與檢查結果。
SQLite 已經包含在 Python 標準函式庫中,不需要另外安裝。
三、建立 SQLite 資料庫與資料表
先建立資料庫連線,再建立儲存書籍資訊的資料表。
conn = sqlite3.connect("books_crawler.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL UNIQUE,
price REAL NOT NULL,
rating INTEGER
)
""")
conn.commit()
print("資料庫與資料表建立完成!")
這次的資料表新增了一個值得注意的設定:
title TEXT NOT NULL UNIQUE
UNIQUE 表示書名不能重複。這是今天用來練習防止重複資料的簡單方法。
不過,真實資料中可能有不同書籍使用相同名稱,因此正式系統通常會使用網站提供的商品 ID 或網址作為唯一識別,而不是只靠書名。
四、建立價格與評分的轉換函式
網站中的價格可能是 £51.77,評分則可能以 Three、Four 這類英文文字表示。
這些內容在網頁中是文字,但資料庫裡的價格應該是數值,評分也應該轉換成整數,方便後續進行統計。
先建立兩個轉換函式。
def clean_price(price_text):
try:
price = float(
price_text.replace("£", "").strip()
)
return price
except (ValueError, AttributeError):
return None
def clean_rating(rating_text):
rating_map = {
"One": 1,
"Two": 2,
"Three": 3,
"Four": 4,
"Five": 5
}
return rating_map.get(rating_text)
接著測試函式:
print(clean_price("£51.77"))
print(clean_rating("Three"))
預期結果:
51.77
3
透過函式將清理邏輯獨立出來,可以讓後續的爬蟲程式更容易閱讀,也能在遇到不同資料格式時修改對應的處理方式。
五、撰寫爬蟲,取得書籍資料
接下來,使用 Requests 取得網站內容,再利用 BeautifulSoup 解析每本書的資訊。
今天先爬取前兩頁,控制請求數量,方便檢查資料是否正確。
base_url = "https://books.toscrape.com/catalogue/page-1.html"
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (compatible; LearningCrawler/1.0)"
})
all_books = []
url = base_url
for page in range(2):
try:
response = session.get(url, timeout=10)
response.raise_for_status()
except requests.RequestException as error:
print("網頁請求失敗:", error)
break
soup = BeautifulSoup(
response.text,
"html.parser"
)
articles = soup.select("article.product_pod")
for article in articles:
title_tag = article.select_one("h3 a")
price_tag = article.select_one(".price_color")
rating_tag = article.select_one(".star-rating")
if not title_tag or not price_tag or not rating_tag:
continue
title = title_tag.get("title", "").strip()
price = clean_price(price_tag.get_text(strip=True))
rating_class = rating_tag.get("class", [])
rating_text = next(
(item for item in rating_class if item != "star-rating"),
None
)
rating = clean_rating(rating_text)
if not title or price is None or rating is None:
continue
all_books.append({
"title": title,
"price": price,
"rating": rating
})
next_link = soup.select_one("li.next a")
if next_link:
url = urljoin(url, next_link.get("href"))
else:
break
time.sleep(1)
print("成功取得書籍數量:", len(all_books))
print(all_books[:3])
這段程式將前幾天學過的概念串在一起:
requests.Session():重複請求時共用 Session。
raise_for_status():遇到 HTTP 錯誤時產生例外。
select_one():取得符合 CSS 選擇器的第一個元素。
clean_price() 和 clean_rating():將文字轉成可分析的資料型態。
urljoin():將下一頁的相對網址轉換成完整網址。
time.sleep(1):在成功處理一頁後暫停一秒,避免連續發出過於密集的請求。
如果網站暫時無法連線,程式會停止爬取並顯示錯誤,而不是直接中斷整個 Colab 執行環境。
六、將爬取的資料寫入 SQLite
取得書籍資料後,就可以將 all_books 中的資料寫入資料庫。
這次使用 INSERT OR IGNORE。如果書名已經存在,SQLite 就會忽略該筆新增操作,不會因為 UNIQUE 限制而讓程式直接報錯。
cursor.executemany("""
INSERT OR IGNORE INTO books (title, price, rating)
VALUES (?, ?, ?)
""", [
(
book["title"],
book["price"],
book["rating"]
)
for book in all_books
])
conn.commit()
print("書籍資料已寫入 SQLite!")
這裡使用 executemany() 一次處理多筆資料,比逐筆執行 SQL 更方便。
不過,INSERT OR IGNORE 只會忽略衝突,不會更新已存在的書籍。如果網站上的價格後來改變,資料庫仍可能保留舊價格。之後可以進一步學習 ON CONFLICT ... DO UPDATE,讓程式在資料已存在時更新內容。
七、查詢資料庫中的書籍
資料寫入完成後,先確認資料庫裡有哪些書籍。
cursor.execute("""
SELECT title, price, rating
FROM books
ORDER BY price DESC
LIMIT 10
""")
rows = cursor.fetchall()
for row in rows:
print(row)
這裡使用 ORDER BY price DESC,按照價格從高到低排序,再透過 LIMIT 10 只查看前十筆資料。
接著,統計目前資料庫中的書籍數量、平均價格與平均評分。
cursor.execute("""
SELECT
COUNT(*) AS book_count,
ROUND(AVG(price), 2) AS average_price,
ROUND(AVG(rating), 2) AS average_rating
FROM books
""")
result = cursor.fetchone()
print("書籍總數:", result[0])
print("平均價格:", result[1])
print("平均評分:", result[2])
這樣就完成了從網頁取得資料、儲存到資料庫,再從資料庫讀取資料進行統計的流程。
八、使用 Pandas 查看資料庫內容
除了使用 cursor.fetchall() 逐筆列印,也可以將 SQL 查詢結果直接轉成 DataFrame。
df = pd.read_sql_query("""
SELECT title, price, rating
FROM books
ORDER BY price DESC
""", conn)
print(df.head(10))
print("資料筆數:", len(df))
如果想計算各評分的平均價格,可以使用:
rating_summary = (
df.groupby("rating")["price"]
.agg(["count", "mean", "min", "max"])
.round(2)
)
print(rating_summary)
這裡把 SQLite 和 Pandas 結合起來:SQLite 負責儲存與查詢,Pandas 負責進一步整理和分析。
這也是這次實作最重要的地方。前面學過的工具不需要各自獨立使用,而是可以串成一個完整的資料處理流程。
九、關閉連線並保存資料庫
完成操作後,記得關閉 Session 和資料庫連線。
session.close()
conn.close()
print("爬蟲與資料庫作業完成!")
在 Google Colab 中,books_crawler.db 會保存在目前的執行環境。如果想將資料庫檔案下載到電腦,可以在關閉連線後執行:
from google.colab import files
files.download("books_crawler.db")
下載後就能保留這次建立的 SQLite 資料庫,下次也可以重新上傳檔案繼續使用。
十、今天的學習心得
今天把之前學過的 Requests、BeautifulSoup、資料清理和 SQLite 整合起來,完成了一個簡單的書籍爬蟲資料庫專案。
和之前只將資料存成 CSV 相比,使用 SQLite 可以讓我透過 SQL 查詢特定紀錄、統計資料,也能逐步建立更完整的資料管理流程。
這次也讓我注意到,爬蟲專案不只是把網頁內容抓下來就完成了,還需要考慮資料型態、無效資料、重複紀錄,以及後續如何更新資料。
接下來,我會進入資料視覺化的階段,開始使用 Matplotlib 將前面取得和分析的資料轉換成圖表,讓分析結果更容易理解。