Python爬蟲實戰:從零構建完整項目(數據采集+存儲+異常處理)
爬蟲不是簡單的請求+解析,而是一個系統工程。本文將帶你體驗企業級爬蟲開發的核心流程。
一、前言:為什么需要完整的爬蟲項目?
作為初學者,你可能寫過一些單文件的爬蟲腳本,但當面對真實復雜的網絡環境、海量數據存儲需求及各種突發異常時,這些腳本往往脆弱不堪。真正的爬蟲項目,是網絡請求、數據解析、持久化存儲和健壯性設計的交響樂。本次我們將綜合運用Python核心技術,構建一個可投入生產環境的爬蟲系統。
二、項目目標與技術棧
項目目標:爬取圖書網站(以豆瓣讀書為例)的書籍信息,包含:
- 書名
- 作者
- 評分
- 價格
- ISBN
- 簡介
技術棧:
- 網絡請求:
requests
(處理HTTP請求/響應) - 數據解析:
BeautifulSoup4
(HTML解析) - 數據存儲:
SQLite3
(輕量級數據庫) - 異常處理:Python內置異常機制 + 自定義重試
- 輔助工具:
logging
(日志記錄),time
(速率控制)
三、環境搭建(新手友好)
# 創建虛擬環境(可選但推薦)
python -m venv spider-env
source spider-env/bin/activate # Linux/Mac
spider-env\Scripts\activate # Windows# 安裝核心庫
pip install requests beautifulsoup4
📌 重要提示:真實項目中務必添加
requirements.txt
管理依賴!
四、分步構建爬蟲系統
4.1 數據采集模塊:與網絡對話
核心任務:穩定獲取目標網頁內容
import requests
from fake_useragent import UserAgent # pip install fake-useragentdef fetch_page(url, retries=3):"""獲取網頁內容,含簡單重試機制:param url: 目標URL:param retries: 最大重試次數:return: HTML內容 (str) 或 None"""headers = {'User-Agent': UserAgent().random} # 動態生成UAfor attempt in range(retries):try:response = requests.get(url, headers=headers, timeout=10)response.raise_for_status() # 自動拋出HTTP錯誤return response.textexcept (requests.exceptions.RequestException, requests.exceptions.Timeout) as e:print(f"請求失敗 (嘗試 {attempt+1}/{retries}): {e}")time.sleep(2 ** attempt) # 指數退避策略return None
關鍵技術解析:
User-Agent
輪換:繞過基礎反爬timeout
設置:防止請求阻塞raise_for_status()
:自動處理HTTP狀態碼(404, 500等)- 指數退避重試:網絡波動時的自愈能力
4.2 數據解析模塊:從混沌中提取信息
核心任務:精準抽取目標數據
from bs4 import BeautifulSoupdef parse_book_page(html):"""解析圖書詳情頁:param html: 網頁HTML:return: 字典形式的結構化數據"""if not html:return Nonesoup = BeautifulSoup(html, 'html.parser')book_data = {}try:# 書名 (使用CSS選擇器更穩定)book_data['title'] = soup.select_one('h1 span').text.strip()# 作者信息 (處理多作者情況)authors = [a.text.strip() for a in soup.select('.author a')]book_data['author'] = '; '.join(authors)# 評分 (處理可能缺失的情況)rating_tag = soup.select_one('.rating_num')book_data['rating'] = float(rating_tag.text) if rating_tag else 0.0# 使用更健壯的屬性提取book_data['isbn'] = soup.find('meta', {'property': 'books:isbn'})['content']# 簡介 (處理多段文本)summary_tag = soup.select_one('.intro')book_data['summary'] = '\n'.join([p.text for p in summary_tag.find_all('p')]) except (AttributeError, TypeError) as e:print(f"解析錯誤: {e}")return Nonereturn book_data
避坑指南:
- 優先使用CSS選擇器而非XPath(更簡潔)
- 所有解析操作都需考慮標簽不存在的情況
- 多層數據使用
try-except
局部捕獲 - 處理多值數據時用分隔符連接
4.3 數據存儲模塊:持久化藝術
為什么用數據庫而非CSV?
- 避免重復爬取
- 支持復雜查詢
- 數據一致性保障
import sqlite3DB_NAME = 'books.db'def init_database():"""初始化數據庫表結構"""conn = sqlite3.connect(DB_NAME)c = conn.cursor()c.execute('''CREATE TABLE IF NOT EXISTS books(id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,author TEXT,rating REAL,isbn TEXT UNIQUE, -- ISBN唯一標識書籍summary TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')conn.commit()conn.close()def save_book_data(book):"""保存單條圖書數據"""if not book: return Falsetry:conn = sqlite3.connect(DB_NAME)c = conn.cursor()# 使用UPSERT操作避免重復c.execute('''INSERT OR IGNORE INTO books (title, author, rating, isbn, summary)VALUES (?, ?, ?, ?, ?)''',(book['title'], book['author'], book['rating'], book['isbn'], book['summary']))conn.commit()return c.rowcount > 0except sqlite3.Error as e:print(f"數據庫錯誤: {e}")return Falsefinally:conn.close() # 確保連接關閉
高級技巧:
- 使用
UNIQUE
約束防止重復數據 INSERT OR IGNORE
實現去重插入finally
保證資源釋放- 添加時間戳追蹤數據采集時間
4.4 異常處理模塊:構建抗脆弱系統
爬蟲常見崩潰原因:
- 網絡波動(超時/斷連)
- 網站改版(解析失敗)
- 反爬機制(IP被封)
- 數據異常(類型轉換錯誤)
class SpiderException(Exception):"""自定義爬蟲異常基類"""passclass RetryExhaustedError(SpiderException):"""重試耗盡異常"""def __init__(self, url):self.url = urlsuper().__init__(f"重試耗盡: {url}")def safe_crawl(url):"""帶完整異常處理的爬取流程:return: 成功保存返回True"""try:# 1. 網絡請求html = fetch_page(url)if not html:raise SpiderException(f"獲取頁面失敗: {url}")# 2. 數據解析book_data = parse_book_page(html)if not book_data:raise SpiderException(f"解析頁面失敗: {url}")# 3. 數據存儲if not save_book_data(book_data):raise SpiderException(f"數據保存失敗: {url}")print(f"成功處理: {url}")return Trueexcept SpiderException as e:print(f"業務流程異常: {e}")return Falseexcept Exception as e: # 捕獲未預料異常print(f"系統未知異常: {type(e).__name__} - {e}")# 此處可添加郵件/釘釘報警return False
異常處理金字塔:
- 基礎異常:網絡/解析/存儲錯誤
- 業務異常:自定義
SpiderException
- 全局兜底:捕獲所有未處理異常
- 重試機制:網絡請求層已實現
五、項目整合:讓模塊協同工作
import time
from urllib.parse import urljoinBASE_URL = "https://book.douban.com/tag/小說"
MAX_PAGES = 5 # 演示用控制頁數def main():init_database()session = requests.Session() # 復用連接提升性能for page in range(MAX_PAGES):list_url = f"{BASE_URL}?start={page*20}"print(f"正在爬取列表頁: {list_url}")try:list_html = fetch_page(list_url)if not list_html: continuesoup = BeautifulSoup(list_html, 'html.parser')book_links = soup.select('.subject-item .info h2 a')for link in book_links:detail_url = link['href']safe_crawl(detail_url) # 核心調度time.sleep(1.5) # 禮貌爬取間隔except Exception as e:print(f"列表頁處理異常: {e}")time.sleep(3) # 頁間延遲if __name__ == "__main__":main()
六、生產級優化建議
- 代理IP池:解決IP封鎖問題
proxies = {"http": "http://10.10.1.10:3128"} requests.get(url, proxies=proxies)
- 分布式任務隊列:使用Celery+Redis
- 瀏覽器渲染:對JS渲染頁面使用Selenium
- 增量爬取:基于數據庫時間戳過濾
- 配置文件:分離敏感參數(數據庫密碼等)
七、常見問題排查手冊
現象 | 可能原因 | 解決方案 |
---|---|---|
返回403錯誤 | IP被封/請求頭異常 | 更換代理/更新UserAgent |
解析到空數據 | 網站改版 | 重新分析DOM結構 |
數據庫插入失敗 | 字段超長/類型不匹配 | 增加字段長度檢查 |
內存持續增長 | 未關閉數據庫連接 | 使用with 語句管理資源 |
被重定向到登錄頁 | 觸發反爬 | 添加Cookie模擬登錄 |
八、總結:爬蟲工程師的思維模式
- 防御性編程:所有外部交互都可能失敗
- 可觀測性:完善的日志體系(推薦使用logging模塊)
- 彈性設計:重試/降級/熔斷機制
- 倫理邊界:遵守robots.txt,控制爬取頻率
項目完整代碼已托管Github:https://github.com/yourname/douban-spider (包含詳細注釋)
爬蟲項目的終極目標不是獲取數據,而是構建可持續的數據管道。當你掌握了數據采集、結構化存儲和異常處理這三駕馬車,就能應對互聯網上90%的數據獲取需求。