前言
“想建个私人书单,却要在豆瓣上一页页复制粘贴?” “看到好书想记录,但手动整理书名、作者、评分、简介太耗时?” “听说爬虫很酷,但怕封IP、怕写代码太难、怕法律风险?”
别担心!今天我们将通过一个经典且实用的项目——爬取豆瓣读书TOP250,带你从零开始掌握Python爬虫的核心技能。
为什么选豆瓣TOP250?
- 结构清晰:HTML结构规范,非常适合新手练习解析。
- 数据价值高:涵盖书名、作者、评分、评价人数、简介等核心信息,是制作书单的完美素材。
- 反爬适中:有一定的反爬机制(如User-Agent检查),能学到真实的对抗技巧,但又不会像大厂那样难如登天。
本实战你将学到:
- 🕷️ 请求伪装:如何构造Headers,让服务器以为你是真人浏览器。
- 🔍 数据提取:使用 BeautifulSoup 和 CSS选择器 精准定位目标数据。
- 🛡️ 反爬策略:添加延时、随机User-Agent,优雅地避免被封禁。
- 📊 数据持久化:使用 pandas 将清洗后的数据一键导出为专业的Excel表格。
- ⚖️ 合规指南:爬虫的道德与法律边界,做守法的开发者。
哪怕你是编程小白,跟着本文步骤,30分钟内也能拥有自己的自动化书单收集器!
一、环境准备:工欲善其事
在开始之前,我们需要安装几个核心库。请确保你已安装 Python 3.8+。
1. 安装依赖
打开终端(CMD或Terminal),运行以下命令:
pip install requests beautifulsoup4 pandas openpyxl
- requests: 发送HTTP请求,获取网页内容。
- beautifulsoup4: 解析HTML,提取数据的神器。
- pandas: 数据处理与Excel导出。
- openpyxl: pandas导出Excel所需的引擎。
2. 项目结构
创建一个文件夹 douban_spider,内部结构如下:
douban_spider/
├── main.py # 主程序代码
├── books.xlsx # 生成的结果文件(运行后产生)
└── README.md # 说明文档
二、核心代码实战:四步走战略
第一步:分析网页结构与反爬机制
打开浏览器(推荐Chrome),访问 豆瓣读书TOP250。
- 豆瓣会检查 User-Agent。如果缺失或是Python默认的 python-requests,直接返回418错误(“I’m a teapot”)。
- 对策:我们需要伪造一个浏览器的User-Agent。
- 每本书在一个 <tr class="item"> 标签中。
- 书名:<div class="pl2"> 下的 <a> 标签。
- 作者/出版社/价格:<p class="pl"> 标签。
- 评分:<span class="rating_nums">。
- 简介:<span class="inq">(注意:部分书可能没有简介)。
- 图片链接:<img> 的 src 属性。
第二步:编写爬虫核心逻辑 (main.py)
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
# 1. 配置请求头 (伪装成浏览器)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Referer": "https://book.douban.com/"
}
def get_book_data(url):
"""发送请求并获取单页数据"""
try:
response = requests.get(url, headers=HEADERS, timeout=10)
response.raise_for_status()
# 豆瓣网页编码通常是utf-8,但有时需要手动指定
response.encoding = 'utf-8'
return response.text
except requests.RequestException as e:
print(f"请求失败: {e}")
return None
def parse_html(html):
"""解析HTML,提取书籍信息"""
soup = BeautifulSoup(html, 'html.parser')
books = []
# 定位所有书籍条目
items = soup.find_all('tr', class_='item')
for item in items:
try:
# — 提取书名 (处理换行符) —
title_div = item.find('div', class_='pl2')
full_title = title_div.find('a')['title'] if title_div.find('a').get('title') else title_div.find('a').get_text(strip=True)
# 有些书名包含副标题,用换行符分隔,这里简单处理
title = full_title.replace('\\n', '').replace('/', ' ').strip()
# — 提取作者/出版社/年份/价格 —
info_p = item.find('p', class_='pl')
info_text = info_p.get_text(strip=True) if info_p else ""
# 简单分割,实际可根据 '/' 进一步结构化
author_pub = info_text
# — 提取评分 —
rating_span = item.find('span', class_='rating_nums')
rating = rating_span.get_text(strip=True) if rating_span else "无评分"
# — 提取评价人数 —
votes_span = item.find('span', text=lambda t: t and '人评价' in t)
votes = votes_span.get_text(strip=True).replace('人评价', '') if votes_span else "0"
# — 提取简介 —
quote_span = item.find('span', class_='inq')
summary = quote_span.get_text(strip=True) if quote_span else "无简介"
# — 提取封面图链接 —
img_tag = item.find('img')
cover_url = img_tag['src'] if img_tag else ""
books.append({
"书名": title,
"作者/出版信息": author_pub,
"评分": rating,
"评价人数": votes,
"简介": summary,
"封面链接": cover_url
})
except Exception as e:
print(f"解析单条数据出错: {e}")
continue
return books
def main():
all_books = []
base_url = "https://book.douban.com/top250?start={}"
print("🚀 开始爬取豆瓣读书TOP250…")
# TOP250每页25本,共10页 (0, 25, 50 … 225)
for page in range(10):
start_num = page * 25
url = base_url.format(start_num)
print(f"正在爬取第 {page + 1} 页: {url}")
html = get_book_data(url)
if html:
data = parse_html(html)
all_books.extend(data)
print(f"✅ 第 {page + 1} 页抓取成功,共 {len(data)} 本书")
else:
print(f"❌ 第 {page + 1} 页抓取失败,跳过")
# 🛡️ 关键反爬策略:随机延时
# 模拟人类阅读速度,避免请求过快触发风控
sleep_time = random.uniform(1.5, 3.5)
time.sleep(sleep_time)
# 3. 保存数据到Excel
if all_books:
df = pd.DataFrame(all_books)
file_name = "豆瓣读书TOP250书单.xlsx"
# 调整列顺序
df = df[["书名", "作者/出版信息", "评分", "评价人数", "简介", "封面链接"]]
# 导出Excel
df.to_excel(file_name, index=False)
print(f"🎉 成功!数据已保存至 {file_name},共 {len(df)} 条记录。")
else:
print("💥 未获取到任何数据,请检查网络或代码。")
if __name__ == "__main__":
main()
三、关键点深度解析
1. 为什么需要 User-Agent?
服务器通过 User-Agent 判断客户端身份。如果是Python默认的 python-requests/x.x.x,豆瓣会直接拒绝服务(返回418状态码)。
- 解决:复制浏览器的UA字符串放入 HEADERS 字典。
- 进阶:可以建立一个UA池,每次请求随机选择一个,进一步降低风险。
2. 数据清洗的细节
- 书名处理:豆瓣的书名有时包含换行符 \\n 或副标题分隔符 /,代码中使用了 .replace() 进行清理,保证Excel整洁。
- 缺失值处理:不是所有书都有“简介”或“评分”,代码中做了 if … else "无简介" 的判断,防止程序报错崩溃。
3. 反爬核心:时间延时 (time.sleep)
这是新手最容易忽略的一点。
- 错误做法:不加延时,瞬间发送10个请求。结果:IP被暂时封禁。
- 正确做法:time.sleep(random.uniform(1.5, 3.5))。
- 模拟人类翻页的不确定性。
- 给服务器喘息时间,体现“礼貌爬虫”原则。
4. 为什么用 Pandas 而不是 csv?
- csv 处理中文编码容易乱码(GBk vs UTF-8)。
- pandas 的 to_excel 直接生成 .xlsx 格式,自动处理编码,且支持格式化(虽然本例未展示,但扩展性强)。
四、运行结果展示
运行脚本后,你将得到一个名为 豆瓣读书TOP250书单.xlsx 的文件。
| 百年孤独 | [哥伦比亚] 加西亚·马尔克斯 / 2011-6-1 / 39.50元 | 9.3 | 582109 | 魔幻现实主义文学巅峰之作… | https://img1.doubanio.com/… |
| 活着 | 余华 / 2012-8-1 / 20.00元 | 9.4 | 903211 | 讲述一个人一生的故事,关于苦难与生存… | https://img2.doubanio.com/… |
| … | … | … | … | … | … |
你可以直接用Excel筛选“评分>9.0”的书籍,或者根据“作者”排序,轻松制作你的年度阅读计划!
五、常见问题与避坑指南 (FAQ)
❌ 问题1:运行报错 418 Client Error: I'm a teapot
- 原因:User-Agent 缺失或被识别为爬虫。
- 解决:检查 HEADERS 是否正确赋值。尝试更换一个最新的浏览器UA字符串。
❌ 问题2:中文乱码
- 原因:Excel打开CSV时的编码问题,或爬取时解码错误。
- 解决:本代码直接使用 pandas.to_excel,避免了CSV乱码问题。如果必须存CSV,请指定 encoding='utf-8-sig'。
❌ 问题3:数据提取为空
- 原因:豆瓣网页结构微调,导致CSS选择器失效。
- 解决:重新F12检查元素类名(class name)是否变化(如 pl2 变成了 pl3),更新 find 或 select 的参数。
❌ 问题4:IP被封禁
- 现象:连续请求后,所有请求都返回错误。
- 解决:
- 增加 sleep 时间(如 5-10秒)。
- 暂停程序,换个网络环境(如切换手机热点)再试。
- 进阶:使用代理IP池(本项目不需要,仅针对大规模爬取)。
六、⚠️ 重要:爬虫的道德与法律边界
技术无罪,但使用需有度。 在编写和运行爬虫时,请务必遵守以下原则:
- 访问 https://www.douban.com/robots.txt。
- 豆瓣允许部分爬取,但禁止高频访问和商业化利用。本教程仅限个人学习研究。
- 不要对服务器造成压力。本代码中的 sleep 是必须的,不要删除。
- ✅ 允许:个人书单管理、数据分析练习、学术研究。
- ❌ 禁止:将数据用于商业售卖、搭建镜像网站、公开传播大量数据。
- 只爬取公开数据,严禁尝试获取用户隐私信息。
声明:本教程代码仅供学习交流使用。请勿用于任何非法用途。如因滥用代码导致的法律纠纷,作者不承担任何责任。
七、进阶挑战:你能做得更好!
如果你已经跑通了代码,不妨尝试以下挑战,提升技能树:
- 利用 cover_url,使用 requests 下载图片保存到本地文件夹,建立可视化书库。
- 目前只爬了列表页。尝试进入每本书的详情页,爬取更详细的“目录”、“书评摘要”、“标签”。
- 使用 matplotlib 或 pyecharts,绘制“高分书籍出版社分布图”或“评分与评价人数散点图”。
- 结合 smtplib,每周自动抓取新书榜,发送邮件推荐给你自己。
总结
通过这个实战,你不仅获得了一份珍贵的豆瓣TOP250书单,更重要的是掌握了:
- HTTP请求与响应的本质。
- HTML解析的核心技巧。
- 反爬虫的基本应对策略。
- 数据清洗与存储的完整流程。
爬虫是通往数据世界的一把钥匙。现在,钥匙在你手中,去探索更多有趣的数据吧!



