1. 为什么选择豆瓣图书Top250作为爬虫实战项目
豆瓣图书Top250榜单是一个非常适合爬虫初学者进阶练习的优质目标源。这个榜单收录了豆瓣用户评分最高的250本图书,数据具有以下特点:
- 页面结构清晰:榜单页面采用标准HTML表格布局,图书信息以规整的
<tr>标签排列 - 反爬措施温和:相比电商平台,豆瓣对爬虫的容忍度较高(但仍需遵守robots.txt规则)
- 数据字段丰富:每本书包含书名、评分、评价人数、作者、出版社、出版年份等结构化数据
- 实战价值高:爬取结果可直接用于数据分析、推荐系统等下游应用
重要提示:虽然豆瓣的反爬相对宽松,但仍需遵循以下道德准则:
- 严格遵守豆瓣robots.txt规定(https://www.douban.com/robots.txt)
- 请求间隔建议设置在3-5秒以上
- 不要使用多线程/分布式爬取
- 每日请求量控制在100次以内
1.1 技术栈选型分析
本方案采用Python作为开发语言,主要依赖以下库:
python复制# 核心依赖库
import requests # HTTP请求
from bs4 import BeautifulSoup # HTML解析
import pandas as pd # 数据处理
import sqlite3 # 数据库操作
import time # 请求间隔控制
选择这些库的原因是:
requests比urllib更简洁易用,社区支持更好BeautifulSoup的HTML解析API对新手更友好pandas可以无缝处理表格数据并导出CSVsqlite3是Python内置库,无需额外安装
2. 环境准备与项目初始化
2.1 Python环境配置
推荐使用Python 3.8+版本,这是目前最稳定的Python发行版。通过以下命令检查版本:
bash复制python --version
# 或
python3 --version
如果尚未安装Python,可以从官网下载安装包:
- Windows用户:选择"Add Python to PATH"选项
- Mac用户:建议通过Homebrew安装
- Linux用户:通常已预装Python3
2.2 依赖库安装
创建虚拟环境是Python项目的最佳实践:
bash复制# 创建虚拟环境
python -m venv douban_env
# 激活环境
# Windows
douban_env\Scripts\activate
# Mac/Linux
source douban_env/bin/activate
# 安装依赖
pip install requests beautifulsoup4 pandas
2.3 项目目录结构
建议采用如下目录结构:
code复制douban_top250/
├── spiders/
│ ├── __init__.py
│ └── douban_spider.py # 爬虫主逻辑
├── outputs/ # 存放爬取结果
├── utils/ # 工具函数
│ └── headers.py # 请求头配置
└── requirements.txt # 依赖清单
3. 爬虫核心实现
3.1 页面请求与反反爬策略
豆瓣会对频繁请求进行限制,因此需要配置合理的请求头并控制请求频率:
python复制# utils/headers.py
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://book.douban.com/'
}
# 在爬虫中使用
import requests
from utils.headers import HEADERS
def get_page(url):
time.sleep(5) # 重要:每次请求间隔至少5秒
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
return response.text
else:
print(f'请求失败,状态码:{response.status_code}')
return None
3.2 HTML解析与数据提取
豆瓣Top250页面采用分页设计,每页显示25本书,共10页。我们需要先解析每本书的详情页链接,再进入详情页提取完整信息。
python复制from bs4 import BeautifulSoup
def parse_index(html):
soup = BeautifulSoup(html, 'html.parser')
book_items = soup.find_all('tr', class_='item')
books = []
for item in book_items:
title_tag = item.find('div', class_='pl2').a
title = title_tag.get_text(strip=True)
link = title_tag['href']
books.append({
'title': title,
'link': link
})
return books
3.3 详情页信息提取
进入每本书的详情页后,我们需要提取以下字段:
python复制def parse_detail(html):
soup = BeautifulSoup(html, 'html.parser')
# 提取评分和评价人数
rating_num = soup.find('strong', class_='ll rating_num').get_text(strip=True)
rating_people = soup.find('a', class_='rating_people').span.get_text(strip=True)
# 提取出版信息
pub_info = soup.find('div', id='info').get_text('|', strip=True)
pub_info = [i.strip() for i in pub_info.split('|') if i.strip()]
# 提取作者、出版社等信息
info_map = {}
for info in pub_info:
if ':' in info:
key, value = info.split(':', 1)
info_map[key.strip()] = value.strip()
return {
'rating': rating_num,
'rating_people': rating_people,
'author': info_map.get('作者', ''),
'publisher': info_map.get('出版社', ''),
'pub_date': info_map.get('出版年', ''),
'price': info_map.get('定价', ''),
'isbn': info_map.get('ISBN', '')
}
4. 数据存储方案
4.1 CSV导出实现
使用pandas可以方便地将数据导出为CSV:
python复制import pandas as pd
def save_to_csv(books, filename='douban_top250.csv'):
df = pd.DataFrame(books)
df.to_csv(filename, index=False, encoding='utf_8_sig') # 使用utf_8_sig解决Excel中文乱码
4.2 SQLite数据库存储
对于更复杂的数据分析需求,SQLite是轻量级的好选择:
python复制import sqlite3
def init_db(db_name='douban.db'):
conn = sqlite3.connect(db_name)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS books
(title TEXT, rating REAL, rating_people INTEGER,
author TEXT, publisher TEXT, pub_date TEXT,
price TEXT, isbn TEXT PRIMARY KEY)''')
conn.commit()
return conn
def save_to_db(conn, book):
c = conn.cursor()
try:
c.execute('''INSERT INTO books VALUES
(:title, :rating, :rating_people, :author,
:publisher, :pub_date, :price, :isbn)''', book)
conn.commit()
except sqlite3.IntegrityError:
print(f'书籍 {book["title"]} 已存在,跳过')
5. 完整爬虫流程实现
将上述模块组合成完整爬虫:
python复制import time
from utils.headers import HEADERS
def main():
# 初始化数据库
conn = init_db()
all_books = []
base_url = 'https://book.douban.com/top250?start='
for i in range(0, 250, 25): # 每页25本,共10页
url = base_url + str(i)
print(f'正在抓取: {url}')
html = get_page(url)
if not html:
continue
book_links = parse_index(html)
for book in book_links:
detail_html = get_page(book['link'])
if not detail_html:
continue
detail = parse_detail(detail_html)
complete_book = {**book, **detail}
all_books.append(complete_book)
save_to_db(conn, complete_book)
time.sleep(5) # 详情页请求间隔
# 保存CSV
save_to_csv(all_books)
conn.close()
print('爬取完成!共获取{}本书籍信息'.format(len(all_books)))
6. 生产级优化建议
6.1 异常处理增强
生产环境需要更健壮的异常处理:
python复制def safe_request(url, retry=3):
for i in range(retry):
try:
response = requests.get(url, headers=HEADERS, timeout=10)
if response.status_code == 200:
return response.text
elif response.status_code == 403:
raise Exception('触发反爬,请检查请求头或降低频率')
except Exception as e:
print(f'请求失败({i+1}/{retry}): {str(e)}')
time.sleep(5 * (i + 1)) # 指数退避
return None
6.2 日志记录
添加日志记录便于问题排查:
python复制import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('douban_spider.log'),
logging.StreamHandler()
]
)
# 使用示例
logging.info(f'开始抓取页面: {url}')
try:
# 爬取逻辑
except Exception as e:
logging.error(f'抓取失败: {str(e)}')
6.3 增量爬取
通过记录已爬取的ISBN实现增量爬取:
python复制def get_existing_isbns(conn):
c = conn.cursor()
c.execute("SELECT isbn FROM books")
return {row[0] for row in c.fetchall()}
# 在主逻辑中
existing_isbns = get_existing_isbns(conn)
if detail['isbn'] not in existing_isbns:
save_to_db(conn, complete_book)
7. 常见问题与解决方案
7.1 请求被拒绝(403错误)
可能原因:
- User-Agent被识别为爬虫
- 请求频率过高
解决方案:
- 轮换多个User-Agent
- 增加随机延迟:
time.sleep(random.uniform(3, 8)) - 使用代理IP(需谨慎,可能违反豆瓣使用条款)
7.2 数据提取不完整
可能原因:
- 页面结构发生变化
- 某些字段在某些书中不存在
解决方案:
- 使用更健壮的CSS选择器
- 添加默认值处理:
python复制rating_num = soup.find('strong', class_='ll rating_num').get_text(strip=True) if soup.find('strong', class_='ll rating_num') else '0.0'
7.3 数据库写入冲突
可能原因:
- 同一本书被多次爬取
- ISBN重复(极少数情况)
解决方案:
- 使用INSERT OR IGNORE语法:
python复制c.execute('''INSERT OR IGNORE INTO books VALUES
(:title, :rating, :rating_people, :author,
:publisher, :pub_date, :price, :isbn)''', book)
8. 项目扩展方向
这个基础爬虫可以进一步扩展为:
- 数据可视化:使用matplotlib或pyecharts绘制评分分布、作者作品数量等图表
- 情感分析:爬取书评进行情感分析
- 推荐系统:基于图书标签构建简单的推荐系统
- Web应用:使用Flask/Django构建图书查询网站
例如,一个简单的评分分布分析:
python复制import matplotlib.pyplot as plt
def analyze_ratings(df):
df['rating'] = df['rating'].astype(float)
df['rating_people'] = df['rating_people'].str.extract('(\d+)')[0].astype(int)
plt.figure(figsize=(10, 6))
df['rating'].hist(bins=20)
plt.title('豆瓣Top250评分分布')
plt.xlabel('评分')
plt.ylabel('书籍数量')
plt.savefig('rating_dist.png')
