豆瓣读书爬虫实战:requests+BeautifulSoup+pandas抓取图书信息并保存Excel

发布时间:2026/8/31 16:50:11
豆瓣读书爬虫实战:requests+BeautifulSoup+pandas抓取图书信息并保存Excel 简介这是一份面向Python初学者与数据采集爱好者的豆瓣读书网站实战爬虫项目旨在帮助用户快速掌握网页数据抓取与结构化存储的核心流程。资源通过RequestsBeautifulSoup实现图书信息书名、作者、评分、评论数等的稳定抓取并利用pandas高效导出至Excel文件兼顾可读性与后续分析需求。压缩包共5个文件含2个核心Python脚本douban_crawler.py与面向对象版本douban_crawler_oop.py、Pipfile及依赖锁文件用于环境复现以及.gitignore保障代码管理规范整体仅7KB轻量易部署。已有996人学习下载提供即开即用的完整可运行代码、清晰的模块划分与基础异常处理逻辑特别适合练手网络请求、HTML解析与Excel写入三大关键技能是入门级Web数据采集项目的典型实践范例。 豆瓣读书爬虫是一个非常经典的练手项目它不像电商平台那样有复杂的加密参数也不像某些社区那样需要频繁处理验证码难度刚好卡在“入门往上一点点”的位置。但恰恰是这一点点包含了爬虫开发里最核心的几个问题请求头伪装、HTML解析、数据清洗和持久化存储。我最早接触爬虫时就是拿豆瓣练的手这次应朋友需求重新把整个流程梳理了一遍抓取豆瓣读书的图书信息保存到Excel实测2021年6月28日仍然可用整个思路和代码放到现在也完全跑得通。1. 项目整体设计与技术选型1.1 需求拆解到底要爬什么、存什么在动笔写代码之前先把需求拆清楚。这个项目的核心目标是从豆瓣读书的榜单页面抓取图书信息保存到Excel表格里。那具体要抓哪些字段你得先问清楚自己——这是很多人容易忽略的一步。我给朋友设计的字段包括书名、作者、出版社、出版日期、价格、评分、评论人数、简介。这几个字段覆盖了选书时最关心的信息。其中书名和评分是必须有的作者和出版社用来做信息核对评论人数侧面反映书的受欢迎程度简介则是给后续筛选提供参考。这些字段在豆瓣读书的列表页和详情页里都能找到不需要逐本点进去就能拿到大部分数据效率很高。这里有个值得注意的设计决策我是从列表页也就是豆瓣读书的“新书速递”或“豆瓣高分”榜单抓数据而不是从搜索接口抓。原因很简单列表页的HTML结构非常规整每条图书信息都在固定的li classsubject-item节点下解析规则统一不容易出幺蛾子。而搜索接口虽然数据更灵活但涉及到登录态和反爬机制对新手不友好也没有必要。1.2 技术选型requests BeautifulSoup pandas为什么不选 Scrapy技术选型这个事我向来主张够用就好。这个项目的数据量级是几十本到几百本请求频率不高页面结构也不复杂完全不需要上 Scrapy 这种重型框架。具体选型如下requestsPython最常用的HTTP库处理GET请求、携带headers、处理cookie都很方便代码写起来直观适合快速实现。BeautifulSoup4HTML解析库配合lxml解析器用CSS选择器或find方法提取节点上手门槛极低。pandas数据处理和Excel导出神器一行to_excel()就能把DataFrame写入Excel文件还能处理去重、排序这些后续操作。openpyxl作为pandas写Excel的底层引擎处理.xlsx格式不需要额外安装Office。为什么不用 ScrapyScrapy的优势在于并发调度、管道处理和分布式扩展但代价是学习曲线陡峭框架的组件概念Spider、Item Pipeline、Middleware对新手来说是一道坎。而且对于这种小体量的爬虫Scrapy的并发优势根本发挥不出来反而是requests这种同步请求的方式更简单直接出了问题也容易Debug。说白了杀鸡不用牛刀选择适合项目复杂度的工具才是正确的工程思维。1.3 反爬策略预判豆瓣的底线在哪里豆瓣的反爬策略一直是爬虫圈热议的话题它的特点是有明显的“先松后紧”机制。短时间低频访问基本不会触发任何限制但只要是并发高了或者请求频率快了大概率会弹出验证码页面响应内容里会出现“检测到有异常请求”字样严重的直接封IP一段时间。针对这个情况我的策略是三个字慢、稳、真。慢每次请求间隔2-3秒用time.sleep(2)这种简单粗暴的方式控制频率绝不贪快。稳固定用一个User-Agent不搞随机切换——对于豆瓣来说频繁变化的UA反而更像爬虫行为。真把headers补全特别是Referer和Accept-Language让请求看起来像正常浏览器发出的。这里多说一句很多人喜欢用fake_useragent库随机生成UA这在小规模爬虫里其实是画蛇添足。正常用户谁会每次刷新页面就换一个浏览器UA保持稳定的UA加上固定的请求间隔这才是最接近真人浏览行为的模式。2. 核心细节解析与实操要点2.1 URL规律翻页逻辑与参数控制豆瓣读书的榜单页URL结构很规律以豆瓣高分榜为例url https://book.douban.com/top250?start{}filter这里的start参数控制翻页每页25本书。start0是第一页start25是第二页以此类推。filter参数保持为空字符串即可不需要额外传值。我这次做的略有不同抓的是豆瓣读书的“新书速递”频道它的URL是url https://book.douban.com/latest?subcat全部p{}这里p从1开始同样控制分页。每页大约20本左右的图书信息密度很高。有个小细节subcat参数控制图书分类比如文学、小说、历史等。如果要分门别类地抓书遍历这个参数就能实现。我在代码里把它写成了可配置项换分类只需要改一个字典映射不需要改核心逻辑。2.2 Headers伪装不是只有User-Agent那么简单请求头伪装是爬虫的必修课但很多新手只关心User-Agent忽略了其他几个同样重要的字段。豆瓣的服务器在做请求校验时会综合判断多个头部信息仅仅改了UA还是容易被识别出来。我项目里使用的请求头配置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, Referer: https://book.douban.com/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,zh-TW;q0.6,en;q0.4, Connection: keep-alive }为什么要带Referer因为正常用户从豆瓣首页点击进入榜单页时浏览器会自动带上这个字段服务器识别到合法的Referer会降低拦截概率。Accept-Language用于告知服务器优先返回中文内容同时也能让服务器认为你是一个中文用户逻辑上更合理。这里有一个实操心得如果你发现带上了完整headers后还是被识别可以先在浏览器里打开开发者工具F12切到Network面板找到任意一个请求把浏览器实际的请求头完整复制下来替换代码里的headers。这个方法屡试不爽能解决90%的“为什么我明明带了headers还是被反爬”的问题。2.3 数据解析BeautifulSoup的实用套路拿到了HTML文本之后就到了解析环节。BeautifulSoup的使用需要掌握三个核心操作定位节点、遍历节点、提取数据。先说定位。对于豆瓣读书列表页每个图书条目都包在li classsubject-item这个标签里。所以第一步是用find_all把所有的subject-item节点抓出来items soup.find_all(li, class_subject-item)这里注意一个坑class是Python的关键字所以BeautifulSoup里要用class_才是CSS的class属性千万别写错。拿到每个item节点后就是在这个节点的“局部范围”内继续查找数据而不是在整个页面里全局查找。这样做的目的是避免误匹配比如页面侧边栏推荐的图书书名如果也在h2标签里全局查找就会拿错数据。在局部范围内查找的代码类似title item.h2.a.get_text(stripTrue)这一行代码的意思是在当前item节点下找到第一个h2标签再往下找a标签用get_text()取出文本内容stripTrue去掉首尾空白。然后处理评分和评论人数它们分别位于span classrating_nums和span classpl标签里rating item.find(span, class_rating_nums).get_text(stripTrue) comment_num item.find(span, class_pl).get_text(stripTrue)注意评论人数的原始文本格式是(1234人评价)这样的包含括号和“人评价”三个字需要做字符串清洗用replace和正则把非数字部分去掉。这个解析逻辑在整个爬虫里只写一次循环遍历每一个item节点即可代码复用性很高。2.4 保存Excelpandas让数据落盘变得简单数据保存这个环节我强烈建议用pandas而不是自己手动写csv模块。原因有三第一pandas的to_excel()直接对接Excel格式列宽、列名、索引这些细节都自动处理好了不需要手动拼接。 第二pandas提供了drop_duplicates()、sort_values()这些数据处理方法方便对抓取结果进行二次加工。 第三pandas的DataFrame结构可以很方便地做数据筛选比如只保留评分大于8分的书一行代码就搞定。我的保存逻辑是先把每本书的数据存成一个字典加入列表循环结束后用pd.DataFrame(list)把列表转成DataFrame然后调用to_excel()写入文件。具体的代码在下一节完整展示。这里补充一个Excel导出的常见坑如果列表里有中文直接导出Excel会出现中文乱码。pandas的to_excel()默认会处理好编码问题但如果用to_csv()就需要显式指定encodingutf-8-sig。这是很多人第一次用Python写中文文档时踩过的坑我在这里提前给大家打个预防针。3. 完整代码实现与运行说明3.1 环境准备安装依赖库在写代码之前先把需要用的库装好。打开终端Windows是cmd或PowerShellmacOS/Linux是Terminal执行以下命令pip install requests beautifulsoup4 lxml pandas openpyxl这里注意几个细节lxml是BeautifulSoup的解析器解析速度比Python内置的html.parser快很多建议安装。openpyxl是pandas写.xlsx文件的引擎如果没有它to_excel()会报错。如果你用的是Python 3.9及以上版本所有库的最新版本都兼容直接安装即可。安装完成后可以用这行代码验证一下是否成功import requests, bs4, pandas print(依赖库安装成功)3.2 代码全景爬虫主程序下面是我的完整代码在2021年6月28日当天实测可用抓取了豆瓣读书“新书速递”频道前5页约100本图书的信息并保存到Excelimport requests import pandas as pd from bs4 import BeautifulSoup import time def get_html(url): 发送HTTP请求返回HTML文本 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, Referer: https://book.douban.com/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,zh-TW;q0.6,en;q0.4, Connection: keep-alive } try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() response.encoding utf-8 return response.text except requests.RequestException as e: print(f请求失败: {url}, 错误: {e}) return None def parse_book_item(item): 从单个subject-item节点提取图书信息 try: title item.h2.a.get_text(stripTrue) except AttributeError: title # 找到所有p标签中的作者/出版信息 pub_info rating comment_num pub_p item.find(div, class_pub) if pub_p: pub_info pub_p.get_text(stripTrue) rating_span item.find(span, class_rating_nums) if rating_span: rating rating_span.get_text(stripTrue) pl_span item.find(span, class_pl) if pl_span: comment_text pl_span.get_text(stripTrue) # 从(1234人评价)中提取数字 import re match re.search(r(\d), comment_text) comment_num match.group(1) if match else # 提取简介 intro_p item.find(p, class_intro) intro intro_p.get_text(stripTrue) if intro_p else return { 书名: title, 出版信息: pub_info, 评分: rating, 评论人数: comment_num, 简介: intro } def main(): base_url https://book.douban.com/latest?subcat全部p{} all_books [] for page in range(1, 6): # 抓取前5页 url base_url.format(page) print(f正在抓取第 {page} 页: {url}) html get_html(url) if html is None: print(f第 {page} 页抓取失败跳过) continue soup BeautifulSoup(html, lxml) items soup.find_all(li, class_subject-item) if not items: print(f第 {page} 页没有找到图书条目可能触发了反爬机制) break for item in items: book parse_book_item(item) if book[书名]: all_books.append(book) print(f第 {page} 页抓取完成共 {len(items)} 条数据) time.sleep(2) # 礼貌爬取避免请求过快 print(f总共抓取 {len(all_books)} 条图书信息) if all_books: df pd.DataFrame(all_books) # 按评分降序排列 df[评分] pd.to_numeric(df[评分], errorscoerce) df df.sort_values(评分, ascendingFalse) df df.reset_index(dropTrue) df.to_excel(豆瓣读书爬取结果.xlsx, indexFalse, engineopenpyxl) print(数据已保存到 豆瓣读书爬取结果.xlsx) else: print(没有抓到任何数据请检查网络或页面结构) if __name__ __main__: main()3.3 代码解读每一块在干什么这段代码分成了三个函数get_html()负责发请求拿页面parse_book_item()负责解析单条数据main()负责统筹整个流程。这种模块化的拆分思路很重要每个函数只干一件事出了问题也容易定位。get_html()里有一个容易被忽略的细节response.encoding utf-8。豆瓣页面返回的是UTF-8编码的HTML但requests有时候会根据响应头猜测编码可能猜错导致乱码。显式指定编码是最稳妥的做法这就避免了打开Excel以后全是乱码的窘境。parse_book_item()里用了一个try-except来兜底。为什么要这样因为列表页里偶尔会有一些条目缺少某个字段比如没有简介、没有评分如果直接用.find()然后调.get_text()遇到空节点就会抛出AttributeError整个程序就崩了。用try-except把这些异常捕获住字段缺失时赋空字符串保证程序能跑完整轮。main()函数里的注释写得很清楚每页抓完time.sleep(2)一共抓5页就是至少8秒的等待时间。这个等待是故意的它不是浪费时间而是为了降低被封IP的概率。我见过有的朋友把time.sleep去掉然后疯狂请求结果三页都没抓完就被限流了。记住爬虫的本质是模拟人类行为人类不可能每秒钟刷新一次页面。3.4 运行效果从命令行到Excel在命令行中运行代码python douban_spider.py正常情况下你会看到这样的输出正在抓取第 1 页: https://book.douban.com/latest?subcat全部p1 第 1 页抓取完成共 20 条数据 正在抓取第 2 页: https://book.douban.com/latest?subcat全部p2 第 2 页抓取完成共 20 条数据 ... 总共抓取 100 条图书信息 数据已保存到 豆瓣读书爬取结果.xlsx打开Excel文件会看到五个列书名、出版信息、评分、评论人数、简介。数据按评分从高到低排列方便挑选高分书。这里我把评分列做了数值转换pd.to_numeric这样在Excel里可以直接对评分列做排序和筛选操作非常方便。3.5 版本适配与日期说明这个项目在标题里标注了“2021.6.28可用”这反映了一个客观事实爬虫是有生命周期的。网站页面结构改版、反爬策略升级都可能导致代码突然失效。我实测的时候用的是以下环境版本供参考组件版本Python3.9.0requests2.25.1beautifulsoup44.9.3pandas1.2.4openpyxl3.0.7如果你的网站页面结构发生了变化比如豆瓣改版了最常见的结果是find_all找不到任何subject-item节点。这时候不要慌打开浏览器访问页面按F12查看最新结构调整一下对应的选择器就行。爬虫维护的核心能力就是跟随页面结构变化调整解析规则这是每个爬虫工程师的基本功。4. 常见问题与排查技巧实录4.1 触发反爬页面没有数据或者返回验证码这是最常遇到的问题特别是当你把请求间隔调得很短或是在同一IP下运行大量请求时。现象是程序正常跑完但最后Excel里只有寥寥几条数据或者直接打印“第X页没有找到图书条目”。排查思路把get_html()返回的HTML文本打印前500个字符看看。如果是正常的HTML标签大概率是解析规则出了问题如果出现“检测到异常请求”或者跳转到了验证码页面那就是被反爬了。被反爬了怎么办最简单的是加大time.sleep()的等待时间从2秒改成4秒或者5秒。如果已经触发了验证码需要等待一段时间10分钟到30分钟不等让IP冷却下来再继续。极少数情况下豆瓣会要求登录才能继续浏览。这时候需要在代码里添加Cookie信息在浏览器登录豆瓣后从开发者工具里复制Cookie字段拼接到headers里。这里分享一个我的经验当你发现被反爬时不要反复尝试强行请求那只会让封禁时间更长。正确的做法是立即停止程序等15分钟再跑。爬虫和反爬的博弈很多时候比的是耐心而不是技术。4.2 数据解析为空页面结构变化或选择器写错这个问题出现的频率比反爬还高。页面改版、class名变化、HTML结构调整都会导致原来写好的CSS选择器失效。排查思路打印整个HTML或者某个item节点的内容确认数据确实存在。仔细观察标签结构看看class名称是否有变化比如subject-item改成了book-item或者节点层级从h2 a变成了h3 a。使用find()和find_all()时多利用浏览器的“检查”功能确认选择器路径。这里有一个实用技巧在写解析代码之前先在浏览器里右键目标元素选择“检查”然后对着HTML面板确认一下class名和标签层级关系。磨刀不误砍柴工花2分钟确认结构比反复运行代码调试节省的时间多得多。还有一个很隐蔽的坑HTML页面里可能有多处相同class的元素比如页面侧边栏的“热门图书”板块也有rating_nums这个class。这时候就要依靠“局部查找”来避免误匹配。我代码里的做法是先用find_all(li, class_subject-item)锁定每个图书条目然后在这个item节点内部再做查找这样就不会抓到侧边栏的数据了。4.3 写进Excel后日期/数字格式异常pandas在写入Excel时有一个自动类型推断的机制如果某一列全是“2021-6-28”这种格式的字符串pandas会尝试把它转成TrueDate类型存进Excel后显示为日期格式。有时候我们明明只需要字符串原文却被自动转了类型导致显示不对。举个例子豆瓣的出版信息字段经常是“2021-6-28”如果pandas把它当成datetime处理存进Excel时会变成“2021/6/28”或者“2021-06-28”看起来虽然差不多但有时会丢失原始信息。解决方案是在to_excel()时强制指定格式或者把这一列先转成字符串类型再写入。df[出版信息] df[出版信息].astype(str) df.to_excel(豆瓣读书爬取结果.xlsx, indexFalse, engineopenpyxl)类似的还有一个坑评论人数提取出来后是字符串数字如果不做类型转换直接写入pandas它可能不会自动转成int。你在Excel里看到的是文本格式的数字没法直接做求和、排序等操作。我在代码里是把评分列做了pd.to_numeric处理评论人数列建议也做同样的操作df[评论人数] pd.to_numeric(df[评论人数], errorscoerce).fillna(0).astype(int)4.4 编码问题Excel打开是乱码这个问题在第一次用pandas导出Excel时经常碰到。如果用的是to_csv()导出的CSV文件直接用Excel打开会产生中文乱码原因是CSV默认编码不是UTF-8。解决方案是在to_csv()里指定encodingutf-8-sig。但用to_excel()就不存在这个问题xlsx的内部存储机制决定了它天然支持中文。所以我这次直接用to_excel()保存从根源上避开乱码问题。如果你有特殊需求必须导出CSV一定要记得加这个参数df.to_csv(豆瓣读书爬取结果.csv, indexFalse, encodingutf-8-sig)4.5 其他常见问题的速查表做爬虫项目的时间久了遇到问题我习惯先列一个排查清单按图索骥能节省大量时间。这里整理一份速查表问题现象可能原因解决方案程序报requests.exceptions.ConnectionError网络不稳定或者IP被封检查网络连接稍后重试增加等待时间报错NoneType object has no attribute get_text页面结构变化或字段缺失检查HTML结构在解析时增加try-except抓取速度很慢设置了较长的sleep时间这是正常的可以适当调整到1-2秒但不要过于激进保存的Excel没有数据列DataFrame为空或列名不匹配打印all_books列表确认数据是否为空某本书的简介为空原页面该字段缺失确认是否字段本身不存在而不是提取失败程序运行到一半被中断被网站限流或本地断网可以给代码加上异常重试机制5. 一些进阶玩法和优化方向基础版本跑通以后这个项目还有很多可以扩展的空间。我根据自己的经验列几个方向供有兴趣的朋友参考。第一个是分页深度控制。现在的代码写死了只抓5页如果你需要更多数据可以把range(1, 6)改成range(1, 11)抓取前10页。但注意页数越多被封的风险也越大建议同时把time.sleep调大一些。第二个是定制抓取分类。现在的代码固定用subcat全部参数你可以改成subcat文学、subcat小说、subcat历史等把不同分类的图书分别保存到不同的Excel文件里形成一个分类书单库。第三个是增量更新。如果你每周都想跑一次这个爬虫可以加入“去重”逻辑每次抓完数据后与上一周的Excel进行对比只保留新上架的书。pandas的merge或concat配合drop_duplicates就能实现代码量不大。第四个是加上详情页信息。现在抓的是列表页数据信息有限。如果你想抓书的目录、内容简介、作者简介这些更详细的信息可以构造详情页URL每本书有一个唯一ID逐本进入详情页抓取。但这会显著增加请求量建议只对需要的书做详情抓取而不是全量。第五个是定时运行。结合系统的定时任务Windows的任务计划程序或macOS的crontab你可以让这个爬虫每周自动运行一次把最新的书单汇报到Excel里形成一个自动化的书单情报站。我在实际使用中还有一个体会抓下来的数据不要只是堆在Excel里可以配合条件格式或者数据透视表做一些可视化分析。比如统计一下高分书都集中在哪些出版社哪些月份出书最多这能让你从图书数据里发现一些有趣的规律。Excel不光是数据的终点也可以成为你分析数据的起点。说到底爬虫的核心不是堆代码而是理解你要爬的网站结构尊重它的规则然后设计一套稳定的流程去提取数据。豆瓣读书这个项目麻雀虽小五脏俱全包含了请求、解析、清洗、存储的完整链路跑通一遍你对Python爬虫的基本功就扎实了。后面遇到更复杂的场景也无非是在这个框架上做加法而已。本文还有配套的精品资源点击获取