1. 项目背景与核心价值
Books to Scrape 是一个专门为爬虫学习设计的沙盒网站,它模拟了真实电商网站的结构和反爬机制,但不会触发法律风险。这个项目看似简单,实际上涵盖了爬虫工程师日常工作中的完整链路:从页面请求、数据解析到结构化存储。
我选择这个案例教学,是因为它完美呈现了三个关键学习点:
- 如何处理分页数据(该网站有50页图书列表)
- 如何提取嵌套的多级页面信息(列表页→详情页)
- 如何应对常见的反爬策略(该网站故意设置了请求频率限制)
2. 技术方案设计
2.1 工具选型解析
采用 Requests + BeautifulSoup 组合而非 Scrapy,原因有三:
- 轻量级架构更适合教学演示
- 避免Scrapy的学习曲线干扰核心逻辑
- 该网站没有动态渲染内容,无需Selenium
import requests from bs4 import BeautifulSoup import csv import time2.2 爬取策略设计
采用广度优先策略:
- 先遍历所有列表页获取图书链接
- 再并发请求详情页提取数据
- 最后统一写入CSV文件
注意:网站明确要求请求间隔≥3秒,我们在代码中会设置time.sleep(3.5)
3. 核心代码实现
3.1 分页URL生成器
该网站分页规则很有代表性:
首页:http://books.toscrape.com/ 第n页:http://books.toscrape.com/catalogue/page-n.htmldef generate_page_urls(): base_url = "http://books.toscrape.com/catalogue/page-{}.html" for page in range(1, 51): # 共50页 yield base_url.format(page)3.2 详情页数据提取
每本书需要采集的字段包括:
- 书名
- 价格(含货币符号处理)
- 库存状态
- 星级评分(转换数字)
- 产品描述
- 分类标签
def parse_book_detail(url): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # 提取核心字段 title = soup.select_one('h1').text price = float(soup.select_one('.price_color').text[1:]) # 去掉£符号 stock = 'In stock' in soup.select_one('.availability').text # 星级转换逻辑 star_class = soup.select_one('.star-rating')['class'][1] star_map = {'One':1, 'Two':2, 'Three':3, 'Four':4, 'Five':5} rating = star_map[star_class] return { 'title': title, 'price': price, 'stock': stock, 'rating': rating, 'url': url }4. 完整爬取流程
4.1 主控程序架构
def main(): with open('books.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=['title','price','stock','rating','url']) writer.writeheader() for page_url in generate_page_urls(): book_links = get_book_links(page_url) for link in book_links: book_data = parse_book_detail(link) writer.writerow(book_data) time.sleep(3.5) # 遵守爬取间隔4.2 异常处理机制
必须处理的三种异常情况:
- 网络请求超时(增加重试机制)
- 页面结构变化(添加字段存在性校验)
- 编码问题(统一UTF-8处理)
def safe_request(url, retry=3): for i in range(retry): try: response = requests.get(url, timeout=10) response.raise_for_status() return response except Exception as e: if i == retry - 1: raise time.sleep(2)5. 高级技巧与优化
5.1 并发控制实现
虽然该网站要求串行访问,但实际项目中可以这样优化:
from concurrent.futures import ThreadPoolExecutor def batch_crawl(links): with ThreadPoolExecutor(max_workers=3) as executor: results = list(executor.map(parse_book_detail, links)) return results5.2 数据质量检查
导出前建议进行数据校验:
- 检查必填字段缺失率
- 验证价格数值范围
- 去重处理(基于URL)
def validate_data(books): for book in books: assert book['price'] > 0, f"Invalid price: {book['title']}" assert 1 <= book['rating'] <= 5, f"Invalid rating: {book['title']}"6. 实战经验分享
6.1 反爬应对策略
该网站设置的几个典型反爬机制:
- User-Agent检测(需设置常见浏览器UA)
- 请求频率限制(严格遵守3秒间隔)
- 异常流量检测(避免短时间内大量请求)
推荐使用真实浏览器头:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }6.2 数据存储优化
CSV虽然简单,但大数据量时建议:
- 分块写入(每100条flush一次)
- 使用csv.DictWriter确保字段一致性
- 添加爬取时间戳作为批次标识
def write_chunk(writer, chunk): for item in chunk: item['crawl_time'] = datetime.now().isoformat() writer.writerow(item)7. 项目扩展方向
这个基础项目可以深化为:
- 价格监控系统(定期爬取比价)
- 图书推荐引擎(基于评分/价格分析)
- 自动化测试平台(验证网站稳定性)
例如构建价格趋势分析:
import pandas as pd def analyze_price_trend(): df = pd.read_csv('books.csv') df['crawl_time'] = pd.to_datetime(df['crawl_time']) return df.groupby('title')['price'].plot(title='Price Trend')8. 法律合规提醒
即使对沙盒网站也需注意:
- 严格遵守robots.txt规定
- 控制请求频率不影响服务器
- 数据仅用于学习研究
- 商业用途需获得授权
建议在代码中添加合规声明:
""" This crawler is for educational purpose only. Data will not be used for any commercial activities. Request interval is set to 3.5s as required. """