1. 项目概述
作为一名长期深耕Python爬虫领域的开发者,我经常需要从各类网站采集结构化数据。最近接到一个需求:从Goodreads的Listopia书单中提取书籍信息,包括书名、作者、评分等关键数据,并进行清洗和存储。这个项目看似简单,但实际涉及多个技术难点,比如如何处理动态加载内容、清洗不规则文本、设计合理的存储方案等。
2. 技术选型与整体流程
2.1 为什么选择这些技术
对于这个项目,我选择了以下技术栈:
- Requests + BeautifulSoup:Goodreads的Listopia页面主要是静态内容,这套经典组合足够应对
- Pandas:用于数据清洗和CSV导出
- SQLite:轻量级数据库,适合中小规模数据持久化
提示:如果遇到动态加载内容,可以考虑加入Selenium,但本项目实测Requests已足够
2.2 整体工作流程
- 发送HTTP请求获取书单页面
- 解析HTML提取原始数据
- 数据清洗和格式化
- 存储到CSV和SQLite
- 结果验证和优化
3. 环境准备与依赖安装
3.1 基础环境配置
建议使用Python 3.8+,并创建虚拟环境:
bash复制python -m venv goodreads_scraper
source goodreads_scraper/bin/activate # Linux/Mac
goodreads_scraper\Scripts\activate # Windows
3.2 安装必要依赖
bash复制pip install requests beautifulsoup4 pandas sqlite3
4. 核心实现:请求与解析层
4.1 页面请求模块
python复制import requests
from bs4 import BeautifulSoup
def fetch_page(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
4.2 数据解析模块
书单页面的书籍信息通常包含在class为"bookTitle"和"authorName"的元素中:
python复制def parse_books(html):
soup = BeautifulSoup(html, 'html.parser')
books = []
for item in soup.select('.listImgs'):
title = item.select_one('.bookTitle').get_text(strip=True)
author = item.select_one('.authorName').get_text(strip=True)
rating = item.select_one('.minirating').get_text(strip=True)
books.append({
'title': title,
'author': author,
'rating': rating
})
return books
5. 数据清洗工具箱
5.1 评分清洗函数
Goodreads的评分文本格式通常为"4.12 · 12,345 ratings",我们需要提取出评分数字:
python复制import re
def clean_rating(rating_text):
match = re.search(r'(\d+\.\d+)', rating_text)
return float(match.group(1)) if match else None
5.2 作者信息清洗
作者信息可能包含多余的空格和换行符:
python复制def clean_author(author_text):
return ' '.join(author_text.split())
6. 数据存储实现
6.1 CSV导出
python复制import pandas as pd
def save_to_csv(books, filename):
df = pd.DataFrame(books)
df.to_csv(filename, index=False, encoding='utf-8-sig')
6.2 SQLite存储
python复制import sqlite3
def save_to_sqlite(books, db_name):
conn = sqlite3.connect(db_name)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS books
(title TEXT, author TEXT, rating REAL)''')
for book in books:
c.execute("INSERT INTO books VALUES (?, ?, ?)",
(book['title'], book['author'], book['rating']))
conn.commit()
conn.close()
7. 完整流程整合
7.1 主函数实现
python复制def main():
url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
html = fetch_page(url)
if not html:
return
raw_books = parse_books(html)
# 数据清洗
cleaned_books = []
for book in raw_books:
cleaned_books.append({
'title': book['title'],
'author': clean_author(book['author']),
'rating': clean_rating(book['rating'])
})
# 数据存储
save_to_csv(cleaned_books, 'goodreads_books.csv')
save_to_sqlite(cleaned_books, 'goodreads.db')
print(f"成功采集并存储了{len(cleaned_books)}本书籍信息")
if __name__ == "__main__":
main()
8. 常见问题与解决方案
8.1 请求被拒绝
- 现象:返回403状态码
- 解决方案:
- 完善headers,特别是User-Agent
- 添加请求延迟
- 使用代理IP(需遵守网站规则)
8.2 数据解析失败
- 现象:CSS选择器找不到元素
- 检查步骤:
- 确认页面结构是否变化
- 检查是否有动态加载内容
- 打印出HTML确认是否获取完整
8.3 数据清洗异常
- 现象:正则匹配失败
- 解决方案:
- 先打印原始文本确认格式
- 调整正则表达式
- 添加异常处理
9. 进阶优化方向
9.1 分页处理
Goodreads的书单通常有多页,可以自动遍历所有页面:
python复制def get_all_pages(base_url, pages=5):
all_books = []
for page in range(1, pages+1):
url = f"{base_url}?page={page}"
html = fetch_page(url)
if html:
all_books.extend(parse_books(html))
return all_books
9.2 异步请求
使用aiohttp可以提高采集效率:
python复制import aiohttp
import asyncio
async def fetch_page_async(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
9.3 数据质量检查
添加数据验证逻辑:
python复制def validate_book(book):
if not book['title'] or len(book['title']) > 200:
return False
if book['rating'] and (book['rating'] < 1 or book['rating'] > 5):
return False
return True
10. 项目总结
在实际开发中,我发现Goodreads的页面结构相对稳定,但有几个关键点需要注意:
- 请求频率控制:即使没有明显的反爬机制,也应合理设置请求间隔
- 数据备份:建议在清洗前后都保存原始数据
- 异常处理:网络请求和数据处理都要有完善的错误处理
这个项目虽然不大,但涵盖了爬虫开发的完整流程,从数据采集到清洗再到存储,每个环节都有值得注意的细节。通过这个实战案例,我们可以掌握如何处理半结构化文本数据,以及如何设计健壮的数据处理流程。