Python爬虫实战:BeautifulSoup抓取豆瓣图书Top250并导出Excel

发布时间:2026/10/14 2:10:43

Python爬虫实战:BeautifulSoup抓取豆瓣图书Top250并导出Excel 2. 项目启动前的准备工作2.1 环境搭建与依赖安装开工之前先说结论这个项目不需要任何重型工具一个干净的Python 3环境就够了。建议使用3.8以上版本下面的库是标准组合。pip install requests beautifulsoup4 pandas openpyxlrequests负责发送HTTP请求拿到网页源码beautifulsoup4负责解析HTML定位图书信息pandas负责数据整理和导出Excelopenpyxl是pandas写Excel时需要的底层引擎如果安装过程中遇到网速问题可以临时切换镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 pandas openpyxl我个人建议用venv或conda建一个独立环境不要跟系统环境混在一起。爬虫项目依赖版本变动快独立的虚拟环境能避免很多莫名其妙的冲突。2.2 为什么不走接口、不改动模拟登录很多人一上手就想找豆瓣的隐藏接口觉得直接拿JSON比解析HTML高级。但豆瓣图书Top250的页面恰好是服务端渲染的所有数据都实实在在地写在HTML源码里不需要登录不需要计算签名不需要处理加密参数直接用requests就能拿到完整内容。对于入门项目来说这正是刻意选择的价值——把注意力放在爬虫最核心的“请求-解析-存储”链路上。你至少要能看懂普通请求和HTML解析的流程后面遇到真正需要逆向接口的站点时才知道问题到底出在哪个环节。一上来就上Selenium无头浏览器反而掩盖了爬虫的基本功。另外从维护角度看HTML页面的结构在豆瓣这种老牌站点上相对稳定而接口经常伴随前端改版一起变动解析HTML的存活时间往往更长。3. 请求构造与网页结构拆解3.1 请求头模拟豆瓣的反爬策略不算苛刻但会校验User-Agent。如果你用requests默认的python-requests/2.x去请求大概率直接吃一个403或者418。模拟正常浏览器请求至少需要带上UA和语言偏好headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, }解释一下这里面的逻辑。User-Agent本质上是告诉服务器“我是谁”服务器首先用它做第一道基础身份识别。Accept-Language代表“我偏好用什么语言”带上中文以后服务器返回的HTML就是UTF-8编码的简体中文内容后续解析时不容易出现中文乱码。3.2 URL构造与分页规律豆瓣图书Top250的URL非常直观https://book.douban.com/top250?start0 https://book.douban.com/top250?start25 https://book.douban.com/top250?start50start就是偏移量从0开始每页25条到225结束一共10页。这个规律一旦摸清翻页逻辑就变得极其简单不需要去解析“下一页”按钮的链接。我第一次写的时候还想着去找翻页的href属性后来发现完全没必要。直接for循环生成10个URL就行代码更短逻辑也更清晰。3.3 页面HTML结构拆解拿到网页源码后先不要急着写解析代码先花五分钟看一下结构。豆瓣图书Top250页面的每条图书记录都包裹在一个tr classitem节点里核心信息分布如下tr classitem td a hrefhttps://book.douban.com/subject/... img src... / /a /td td valigntop div classpl2 a href... title百年孤独百年孤独/a p classpl[哥伦比亚] 加西亚·马尔克斯 / 范晔 / 南海出版公司 / 2011-6 / 39.50元/p div classstar clearfix span classrating_nums9.3/span span classpl( 12345人评价 )/span /div p classquote span classinq魔幻现实主义文学的代表作……/span /p /div /td /tr这里有四个关键字段值得注意书名完整信息在a标签的title属性里页面上展示的书名有时会因过长而被截断作者、出版社、出版年、价格全部挤在p classpl这一行里中间用斜杠分隔评分在span classrating_nums里评价人数在span classpl里格式是( 12345人评价 )简介摘要在p classquote里如果没有内容这个节点可能不存在注意评价人数那里的span classpl和下面作者信息里的p classpl是重名的选择器写法要区分清楚别被类名骗了。4. 核心解析流程与字段提取解析阶段的目标是逐条提取图书信息整理成一个结构化字段集合书名、作者/译者、出版社、出版年、价格、评分、评价人数、简介。下面是一段可以直接用的解析函数import re import requests from bs4 import BeautifulSoup def parse_item(item): title_tag item.select_one(div.pl2 a) if not title_tag: return None title title_tag.get(title, ).strip() author_info item.select_one(p.pl) author_text author_info.get_text().strip() if author_info else rating_tag item.select_one(span.rating_nums) rating rating_tag.get_text().strip() if rating_tag else comment_tag item.select_one(div.star span.pl) comment_text comment_tag.get_text() if comment_tag else comment_match re.search(r(\d), comment_text) comment_count comment_match.group(1) if comment_match else 0 quote_tag item.select_one(p.quote span.inq) intro quote_tag.get_text().strip() if quote_tag else 无简介 return { 书名: title, 作者信息: author_text, 评分: rating, 评价人数: comment_count, 简介: intro, }从这段代码可以读出几个比较细的经验第一取书名时用get(title)而不是get_text()。页面展示的书名可能只有一部分但title属性里永远是完整的。这是爬虫解析中非常典型的“显示文本与属性值不一致”陷阱。第二作者信息那一长串文本用get_text()后要自己拆不过先留着原始字符串就好清洗时再处理不要在一个函数里塞太多逻辑。第三简介节点是可能不存在的。很多图书没有名人推荐语p.quote就整个消失了。如果直接调用select_one(...).get_text()会抛AttributeError导致整个程序中断。所以必须先判空再用空字符串兜底。只提取这五个字段已经把Top250的核心维度都覆盖了。再往细了加字段比如ISBN、页数也能从这个页面上拿到但会增加解析难度等你跑通第一遍再扩展不迟。5. 翻页循环、限速与容错处理单页解析跑通以后就要把10页连起来跑。这里最核心的工程素养是请求之间要控制频率失败要能自动重试避免因为某一个页面挂了就前功尽弃。5.1 循环请求的实现all_books [] page_errors [] for start in range(0, 226, 25): url fhttps://book.douban.com/top250?start{start} resp None for attempt in range(3): # 每页最多重试3次 try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: break except requests.exceptions.RequestException: pass time.sleep(5) if resp is None or resp.status_code ! 200: page_errors.append(start) print(f第{start // 25 1}页失败跳过) continue soup BeautifulSoup(resp.text, html.parser) items soup.select(tr.item) for item in items: book parse_item(item) if book: all_books.append(book) print(f第{start // 25 1}页抓取完成累计 {len(all_books)} 条) time.sleep(2)几个关键点range(0, 226, 25)正好生成10个地址0到225内层循环做的是失败重试最多3次每次重试前停5秒time.sleep(2)是每次请求成功之后的最低限速5.2 为什么强制限速我见过不少初学者把间隔设成0.1秒甚至去掉sleep觉得250条数据很快就能爬完。结果往往是爬到第三页就被服务器限流之后一直418最后不得不换IP重新跑。在爬虫这个场景里time.sleep(2)不是浪费而是换取稳定性的代价。250条数据10个请求就算每个间隔3秒全部跑完也不超过1分钟。对个人学习项目来说这个速度完全够用没必要去挑战服务器的风控底线。5.3 请求失败时的现场保存另一个工程技巧是不要等到全部爬完再保存。万一跑到第7页网络断了前面的努力就全没了。可以在每页成功后把all_books先序列化成JSON临时存一份import json with open(temp_books.json, w, encodingutf-8) as f: json.dump(all_books, f, ensure_asciiFalse, indent2)这样即使程序中途崩了重新启动时只要加载这个临时文件接着最后一页继续爬即可不用推倒重来。这个习惯在爬更大规模的网站时尤为重要。6. 数据清洗与Excel导出6.1 pandas处理脏数据爬到手的原始数据不能直接写进Excel。至少要做三件事评价人数字段是字符串带括号、带“人评价”字样必须提取数字并转成数值类型作者信息里有大量斜杠分隔的碎片字段需要按需求拆分简介字段可能出现空值或兜底文本import pandas as pd df pd.DataFrame(all_books) df[评分] pd.to_numeric(df[评分], errorscoerce) df[评价人数] df[评价人数].astype(int) df[作者信息] df[作者信息].str.replace(\n, ).str.strip() df df.drop_duplicates(subset[书名])关于作者信息的拆分很多人会把它拆成“作者”“译者”“出版社”“出版年”“价格”五列。这里要谨慎因为部分旧版图书的作者信息格式不统一比如有的没有译者有的没有价格。直接按斜杠split可能产生错位。我的建议是保留完整的“作者信息”字段额外的拆解放到后面需要时再做。6.2 导出到Exceloutput_path douban_book_top250.xlsx df.to_excel(output_path, indexFalse, engineopenpyxl) print(f已导出 {len(df)} 条数据到 {output_path})这一句就能生成一张干净的Excel表。pandas会自动把列名作为表头每行是一条图书数据。如果想在导出后进一步美化比如调整列宽、冻结首行、按评分降序排列可以用openpyxl二次处理基础表格。6.3 关于中文乱码用pandas搭配openpyxl导出Excel时内部是UTF-8编码Excel能正常显示一般不会乱码。如果中途改成导出CSV则要特别注意编码问题df.to_csv(douban_book_top250.csv, indexFalse, encodingutf-8-sig)CSV文件必须用utf-8-sig否则直接用Excel打开CSV时中文会变成乱码。这个坑我用记事本打开完全正常用Excel打开却乱码排查了很久才明白是编码签名的问题。7. 常见问题与排查技巧7.1 请求返回418或403先检查请求头里有没有User-Agent。如果UA正常那多半是请求频率太高被限流了。最简单的处理方式是增大time.sleep间隔从2秒加到4秒或5秒再重跑。有一点需要强调不要为了绕过风控去主动配置Cookie或者构造特殊请求头。爬这种公开静态页面故意伪装反而容易触发更严格的验证策略。7.2 爬了几页后连接超时豆瓣对连续请求会偶尔断开TCP连接报read timed out或者ConnectionError。这不是代码逻辑问题是服务端的间歇性限流。解决方案是设置timeout10并且捕获requests.exceptions.RequestException重试该页3次每次重试间隔10秒以上。如果重试3次仍然失败就把当前页记录下来先跳过继续跑后面的拿完大部分数据之后再回来单独补爬失败的页面。这种“失败页重爬”策略比全局中断再重新开始要高效得多。7.3 解析出来全是空列表先用print(len(resp.text))看返回内容的长度。如果只有几百字节说明你拿到的不是图书列表页而是验证页或错误提示页。这时候要检查你的UA和请求频率。再看resp.encoding。有时候服务器响应头没有明确声明字符编码requests会默认按ISO-8859-1解码导致中文字符乱码。解决办法是拿到响应后强制声明编码resp.encoding utf-8注意这个操作要在resp.text被访问之前设置否则会拿不到正确解码后的结果。7.4 评价人数提取出来带括号评价人数的原始文本是( 12345人评价 )带空格、带括号、带中文。直接强转int会报错得先用正则或者字符串替换把数字提出来count int(re.search(r(\d), comment_text).group(1))更稳妥的写法是提取之前先判断是否匹配成功避免None.group()报错。7.5 Excel里出现了重复书名Top250按理说不应该重复但你在写解析函数时如果选择器写宽了可能把同一个条目解析出多条。比如select(div.pl2 a)选中了同节点下的多个链接就会出现重复。Add一个drop_duplicates(subset[书名])操作既能排查数据问题也能保证最终导出数据的干净。8. 扩展方向与个人体会这个项目跑完之后整个“请求-解析-存储”链路就算完整走通了一遍。基于这套代码后面可以做的扩展有很多把评价人数和评分两列做散点图观察“口碑与人气”的关系拆分作者字段统计哪些出版社上榜次数最多把Excel导出改成写入MySQL或SQLite换成某个不同的图书榜单站点只需要改CSS选择器和字段映射其余代码几乎复用加一个增量爬取逻辑定期重跑对比榜单变化我个人在做完这个项目后最大的感受是爬虫最复杂的部分往往不是请求代码本身而是数据的稳定获取和清洗。刚开始可能觉得BeautifulSoup提取字段很繁琐跑得多了就会发现写一个健壮的解析函数做好判空和异常兜底比拼命追求代码简洁要重要得多。最后再叮嘱一句关于使用边界的事。爬虫本身是中性工具个人学习场景下爬取公开数据没什么问题但要注意三点控制请求频率别给对方服务器造成压力只爬公开页面不碰需要登录才能访问的受限信息爬下来的数据不要用于商业用途。这些边界守住爬虫之路才会走得长远。
延伸阅读

更多相关文章

2026/10/14 2:10:43

泊马度胺治疗多发性骨髓瘤的副作用管理与临床实践

1. 泊马度胺治疗中的关键副作用解析泊马度胺作为第三代免疫调节药物,在多发性骨髓瘤治疗中展现出显著疗效,但周围神经病变和中性粒细胞减少这两大副作用常常困扰着临床使用。我在血液科十年实践中发现,约65%长期用药患者会出现不同程度的周围…

2026/10/14 2:05:42

手写MyBatis第85弹:组合模式在SqlNode设计中的精妙应用

组合模式与动态SQL的完美结合 在软件设计模式中,组合模式(Composite Pattern)是一种将对象组合成树形结构以表示"部分-整体"层次结构的设计模式。MyBatis动态SQL的实现正是组合模式的经典应用,而MixedSqlNode作为这个模…

2026/10/14 3:15:46

电子零部件生产追溯系统技术实现:破解全链路溯源与合规痛点

电子零部件行业因 SKU 密度高、精度要求严、下游客户合规溯源要求明确的特性,基于全链路数据打通的生产追溯系统,是当前解决质量管控效率低、合规校验难、客诉响应慢等问题的核心技术路径。 一、电子零部件生产追溯的核心痛点是什么? 据工信…

2026/10/14 3:15:46

生产环境Agent编排:以失败兜底为核心的设计实践

先说结论:把“不失败”当成生产环境里 Agent 编排的目标,是我见过最多项目翻车的根源。我自己做过的模拟项目X,一个面向终端用户的客服问答 Agent,从第一版开始就没指望大模型、第三方接口、甚至是编排器自身能一直稳定。每个环节…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑