ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2026年Python爬虫实战:豆瓣读书TOP250抓取与Excel书单制作避坑指南

2026年Python爬虫实战:豆瓣读书TOP250抓取与Excel书单制作避坑指南 2026年还有必要写一个Python爬虫去爬豆瓣读书TOP250并做成Excel书单吗这个问题如果放在三年前答案几乎是“照着抄就行”但放到现在老教程给我的反馈大多是“403”“验证码”“解析出来全是None”。这个项目看起来简单却恰好把网络请求、页面解析、数据清洗、文件导出这几个Python入门必备技能串在了一起而且做完之后你能立刻获得一份可以打开的Excel书单正反馈非常强。这篇文章就是为你准备的2026年避坑版从页面分析到完整代码从Excel美化到问题排查全部用我这两周实测踩过坑的最新细节来写。1. 内容整体设计与思路拆解1.1 先看清楚豆瓣读书TOP250这块“试验田”长什么样豆瓣读书TOP250的地址是 https://book.douban.com/top250 第一眼看上去就是一个规规矩矩的表格页面。每一页显示25本书翻页参数是?start0、?start25一直到?start225一共10页合起来正好250本。这个分页规律对初学者极其友好不需要分析复杂的异步接口只需要在URL上传一个偏移量。真正值得关注的是页面里的每一项数据分布。每一本书都放在一个tr classitem里面封面图在左边的td右边的td包含书名、评分、评价人数和一句话简介。我们需要抓的三个核心字段书名在div.pl2下的a标签里评分在span.rating_nums里简介在span.inq里。这个结构在2026年依然稳定但网上的老代码为什么失效主要原因是很多教程还停留在用正则表达式硬抠书名遇到豆瓣偶尔插入的换行和HTML实体就崩了。用BeautifulSoup的CSS选择器会稳得多这也是我坚持用解析库而不是正则的原因。还有一点必须说清楚不是每本书都有一句话简介。页面上的“译者”“出版社”等字段会变但span.inq是可有可无的。这个缺失值如果你不提前处理写进Excel之后就会出现要么报错、要么单元格显示None的尴尬局面。下面我会专门讲清洗方案。1.2 为什么选HTML解析而不是所谓的隐藏接口很多人打开浏览器开发者工具会发现豆瓣读书其实有一个返回JSON的数据接口于是想直接调接口拿数据又快又方便。我的建议是新手不要碰这种东西。理由有三条第一这些接口没有公开文档参数和返回结构随时可能变你今天调通了明天可能就换字段名。HTML页面虽然也会改版但改动频率和幅度远小于内部接口。第二接口请求通常会带上更严格的校验参数一旦校验逻辑变化排查难度比解析HTML高一个量级。第三我们做这个项目的目的是练爬虫基本功而不是走捷径。学会解析HTML以后面对任何没有接口的网站都能通用。另外这类公开数据的爬取我建议先看一眼目标网站的robots规则并在合理频率下只抓取公开信息不要尝试翻越任何权限边界。豆瓣读书TOP250本身就是一个对外展示的榜单页抓取公开榜单内容用于个人学习是合理的但依旧要控制速度。1.3 技术选型requests、BeautifulSoup、openpyxl为什么够用这个项目我用的是最经典的组合requests发请求、BeautifulSouplxml解析页面、openpyxl生成Excel。可能有读者会问为什么不用Selenium不用Scrapy理由很直接。Selenium要启动一个完整浏览器内存开销大运行速度慢而且在豆瓣的检测下无头浏览器反而更容易露出马脚TOP250是服务端渲染的静态HTML根本不需要执行JavaScript用requests就足够了。Scrapy功能强大但对于一个只有250条数据的教学项目来说学习曲线太陡调度器、管道、中间件这些概念会让新手分心。Excel写入这一块pandas的确一行代码就能导出但导出之后列宽、居中、字体、自动筛选这些细节很难精细控制。openpyxl是直接操作xlsx单元格的库能做到表格“精美”而不是“能打开就行”。所以这次我用openpyxl并且会在第3章把样式代码完全展开。2. 核心细节解析与实操要点2.1 请求头与节奏控制让服务器觉得“这是个人”爬虫的第一个坑几乎都在请求头上。2026年豆瓣的反爬策略早已不是简单看User-Agent但User-Agent依然是最基础的身份证。我实测发现如果完全不带UA去请求豆瓣会直接返回403带了一个伪装的浏览器UA之后成功率会大幅提升。除了UA我还会设置Accept、Accept-Language、Referer三个字段。Referer填成https://book.douban.com/模拟从豆瓣首页点进来的正常访问路径这个细节很多教程不会提但对降低被误判的概率有帮助。要注意的是这里不需要登录Cookie公开页面未登录状态下就能正常抓取带登录态反而可能触发更严格的风控。请求节奏同样关键。我在每次请求后强制time.sleep(random.uniform(1.5, 2.5))也就是让间隔在1.5秒到2.5秒之间随机浮动。为什么不能固定2秒因为肉眼可见的固定间隔对反爬系统来说也是一种特征。10页抓下来也就二十多秒对个人项目完全可接受。注意如果页面返回的是验证码页面正确做法是立即停止爬取休息一段时间而不是去尝试识别或绕开验证码。个人学习项目没必要和风控系统硬碰硬。2.2 四个解析节点抓住页面里的书名、评分和简介我用BeautifulSoup解析时核心选择器是这几个soup.select(tr.item) # 每一本书的容器 item.select_one(div.pl2 a) # 书名标签 item.select_one(span.rating_nums) # 评分 item.select_one(span.inq) # 一句话简介这里有几个容易出错的细节。第一div.pl2 a里的书名文本在HTML源码中经常被空格、换行包裹直接用a.text会得到一堆空白字符所以必须用.get_text(stripTrue)。第二书名标签的title属性通常更完整有的教程用正则去取title但我实测用a_tag.get_text(stripTrue)已经能拿到《红楼梦》《活着》这种干净的书名因此不必依赖title。第三span.inq不是每个tr.item里都存在如果直接调用item.select_one(span.inq).get_text()遇到缺失项会直接报AttributeError。我的写法是quote_tag item.select_one(p.quote span.inq) intro quote_tag.get_text(stripTrue) if quote_tag else 这种带if判断的写法能把缺失简介的书籍自动存成空字符串而不是让整个爬虫崩掉。评分同理先判断节点是否存在再转成浮点数如果评分缺失就保留空字符串避免后续写入Excel时报错。2.3 数据清洗别让一个缺失值毁掉你的书单很多人辛辛苦苦爬完数据一写入Excel就出问题核心原因是没做清洗。我的习惯是先把原始数据整理成统一的元组格式(书名, 评分, 简介)评分一定是字符串或浮点数简介一定不能是None。清洗时要留意三点书名两侧的空白字符全部去掉避免Excel里出现看似一样但排序不同的脏数据。评分字段建议保留一位小数不要直接从页面拿一个字符串就塞进单元格最好用float(rating)转一下方便之后排序或做数据可视化。简介里如果出现特殊符号比如破折号、引号直接保留即可openpyxl对Unicode的兼容性很好一般不会乱码。真正会乱码的地方是你用命令窗口打印简介的时候那多半是终端编码问题不代表数据本身坏了。还有一个我踩过的坑如果请求豆瓣时返回的不是正常页面而是一个提示页面那么解析出来的书籍列表可能是空的但程序不会报错。所以在循环抓取时我建议每页都打印累计数量一旦发现某页数量为0就要立刻检查响应内容而不是继续傻跑。3. 实操过程与核心环节实现3.1 环境准备虚拟环境与依赖安装不管你是Windows、macOS还是Linux我都建议先建一个虚拟环境避免把依赖装到全局Python里。命令如下python -m venv venv source venv/bin/activate # Windows 下venv\Scripts\activate pip install requests beautifulsoup4 lxml openpyxl如果你还没装Python那就先去Python官网下载3.10以上的版本。2026年的最新稳定版已经是3.12/3.13系列向下兼容性很好。安装完之后可以用python --version检查一下。依赖安装完成后我们要验证关键库是否可用python -c import requests, bs4, openpyxl; print(ok)如果输出ok说明环境没问题。这里特别提醒lxml是一个解析速度更快的底层库在部分Windows环境下安装失败可以先把解析器换成html.parser应急在BeautifulSoup(html, html.parser)里改一下就行。但为了存书单这种小项目lxml和html.parser的差异不大所以遇到问题不必死磕。3.2 核心抓取代码分页循环、解析、保存一气呵成下面这段是抓取部分的核心代码我把它放在一个函数里方便调试import time import random import requests from bs4 import BeautifulSoup BASE_URL https://book.douban.com/top250 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, Referer: https://book.douban.com/, } def fetch_page(session, start): params {start: start} resp session.get(BASE_URL, paramsparams, headersHEADERS, timeout10) resp.raise_for_status() return resp.text def parse_page(html): soup BeautifulSoup(html, lxml) books [] for item in soup.select(tr.item): a_tag item.select_one(div.pl2 a) if not a_tag: continue title a_tag.get_text(stripTrue) or a_tag.get(title, ).strip() rating_tag item.select_one(span.rating_nums) rating rating_tag.get_text(stripTrue) if rating_tag else quote_tag item.select_one(span.inq) intro quote_tag.get_text(stripTrue) if quote_tag else books.append((title, rating, intro)) return books def scrape_top250(): session requests.Session() all_books [] for page in range(10): start page * 25 try: html fetch_page(session, start) books parse_page(html) all_books.extend(books) print(f第 {page 1} 页完成累计 {len(all_books)} 本) except Exception as e: print(f第 {page 1} 页出错{e}) break time.sleep(random.uniform(1.5, 2.5)) return all_books这段代码里有几个设计思路值得说一下。用requests.Session()能复用底层的TCP连接减少握手开销也更贴近浏览器的行为。timeout10是必须的否则遇到网络卡顿会一直挂着。resp.raise_for_status()会在HTTP状态码异常时直接抛出异常让我们能及时发现反爬。运行这个函数之后all_books里的每一个元素都是(书名, 评分, 简介)元组。你可以先打印前5条看看数据长什么样if __name__ __main__: data scrape_top250() for b in data[:5]: print(b)看到类似(红楼梦, 9.6, 中国古典小说巅峰之作。)这样的输出就说明抓取成功了。3.3 Excel书单美化列宽、冻结窗格、自动筛选一个不少数据抓完之后最后一个环节是生成Excel。我选择用openpyxl做三件事表头样式、内容样式、视图设置。表头用深蓝色背景加白色加粗字体内容区中等对齐简介左对齐再加上细边框看起来就像一份正经书单。生成函数的核心代码如下from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side from openpyxl.utils import get_column_letter def write_excel(data, filename豆瓣读书TOP250书单.xlsx): wb Workbook() ws wb.active ws.title TOP250 headers [序号, 书名, 评分, 一句话简介] header_font Font(name微软雅黑, size11, boldTrue, colorFFFFFF) header_fill PatternFill(start_color4472C4, end_color4472C4, fill_typesolid) center Alignment(horizontalcenter, verticalcenter) left Alignment(horizontalleft, verticalcenter, wrap_textTrue) thin Side(stylethin, colorBFBFBF) border Border(leftthin, rightthin, topthin, bottomthin) for col, h in enumerate(headers, 1): cell ws.cell(row1, columncol, valueh) cell.font header_font cell.fill header_fill cell.alignment center cell.border border for idx, (title, rating, intro) in enumerate(data, 1): row idx 1 ws.cell(rowrow, column1, valueidx).alignment center ws.cell(rowrow, column2, valuetitle).alignment left score float(rating) if rating else ws.cell(rowrow, column3, valuescore).alignment center ws.cell(rowrow, column4, valueintro).alignment left for col in range(1, 5): ws.cell(rowrow, columncol).border border widths [6, 30, 12, 60] for i, width in enumerate(widths, 1): ws.column_dimensions[get_column_letter(i)].width width ws.freeze_panes A2 max_row len(data) 1 ws.auto_filter.ref fA1:D{max_row} wb.save(filename) print(f已生成 {filename})里面两个容易被忽视的设置是ws.freeze_panes A2和ws.auto_filter.ref。前者冻结了第一行之后滚动查看250本书时表头始终可见后者给这四列加上了筛选按钮你可以直接在Excel里按评分倒序或者按书名筛选。这两个功能就是这个书单“精美”的关键来源。wrap_textTrue是给简介列准备的虽然一句话简介通常不长但万一某本书的简介超过30个字符打开Excel后就不会被截断在单元格外面。3.4 实测运行效果从控制台到双击打开的瞬间我把上面的scrape_top250()和write_excel(data)连起来跑了一遍整个流程大概三十多秒。控制台输出如下第 1 页完成累计 25 本 第 2 页完成累计 50 本 ... 第 10 页完成累计 250 本 已生成 豆瓣读书TOP250书单.xlsx双击打开生成的Excel你会看到《红楼梦》《活着》《百年孤独》这些熟悉的名字排在前面评分大多在9分上下简介列字数长短不一但每一行都有数据。因为设置了自动筛选你点一下评分列的筛选按钮选择“降序排列”整张表会按照评分从高到低重新排列非常直观。我还顺手试了试把简介列加宽到60字体用默认的等线整个文件大概只有20KB左右打开速度很快。如果你在Mac上用Numbers打开样式同样能正常渲染。4. 常见问题与排查技巧实录4.1 开局403/418先检查请求头再检查手速这是我被问得最多的问题。你一运行豆瓣直接返回403甚至418。我的排查顺序是第一确认请求头里有没有User-Agent第二确认请求频率是不是太快第三打印出resp.status_code和resp.text[:200]看豆瓣到底返回了什么。如果是403九成是请求头不够完整把第3.2节的HEADERS完整抄进去就能解决。如果是418说明风控系统已经注意到你了最有效的办法是停下来等待几分钟同时把每次请求间隔提高到3秒以上。不要去搜“怎么绕过418”作为个人学习项目等一会儿重跑是成本最低的方案。4.2 解析出来全是空三个最容易写错的选择器如果数据和请求都正常但parse_page返回空列表大概率是选择器写错了。常见的坑有三个soup.select(tr.item)写成了soup.select(.item)这样会把所有包含item类名的标签都选中而我们要的只是tr。item.select_one(div.pl2 a)写成了item.find(div, class_pl2).find(a)虽然也能跑但一旦某个div结构稍有变动就会因为中间层不存在而报错。CSS选择器更容错。span.inq没加item.select_one而是直接在整页上找soup.select_one(span.inq)这样只会取到整页第一个简介后面的书全是同一句话。如果这些都没问题那就用print(html[:500])看看页面内容是不是验证码页。豆瓣返回验证码页时页面标题会变成“豆瓣 - 进行安全验证”这时候解析任何节点都是空。4.3 Excel打开后的槽点列宽、格式和“外部数据”生成Excel之后常见的问题是列宽太窄、书名显示不全、评分变成“9.60”这种带尾数为零的样子。我的解决方案是列宽手动指定评分写入前用float(rating)转成数值这样Excel显示的就是“9.6”而不是文本“9.6”。如果你希望评分保留两位小数可以在单元格数字格式上设置cell.number_format 0.0。还有一个很多人忽略的点如果在爬虫运行过程中手动打开同一个Excel文件保存时可能会报权限错误。Windows下尤其明显因为文件被Excel进程锁住了。所以最好先关掉Excel再运行脚本或者每次保存时加上时间戳from datetime import datetime filename f豆瓣读书TOP250书单_{datetime.now():%Y%m%d_%H%M}.xlsx4.4 爬虫的安全边界个人学习项目的正确打开方式最后一条排查技巧其实与技术无关但比技术更重要。豆瓣读书TOP250是一个公开榜单抓取它用于个人学习没问题但要注意不要用高频率请求去打任何网站也不要把抓到的数据大规模公开传播。我每次跑这个项目间隔都不会低于1.5秒而且只在本地生成书单不发到公开平台。如果你在爬取过程中遇到验证码这是一个明确的信号停手休息而不是想办法破解。爬虫入门的真正价值是理解HTTP、HTML、数据结构不是跟网站的风控系统较劲。守住这个边界你的学习路径会顺畅很多。5. 写在最后这几次实操后的几点体会这个项目我前前后后跑了三遍第一遍按照2020年的老教程换来的是403和整整一下午的焦虑第二遍用了我今天写的这套代码30秒抓完看到Excel书单生成那一刻才觉得“对了”。第三遍我故意把请求间隔改成了0.5秒结果第4页就触发验证码这让我再次确认豆瓣的风控不是纸糊的但只要按照正常人的访问节奏来个人学习完全够用。我个人的建议是不要只复制代码而是把这几个关键点亲手在开发者工具里对照一遍找到tr.item看到div.pl2 a找到span.inq理解它为什么有时没有。这个项目能教给你的不只是爬虫语法更是一种“先观察页面、再写代码”的思维习惯。思维方式一旦建立以后换到其他网站、其他数据源你也能很快上手。如果你跑通了这份代码还可以试着把评分排序、生成多张工作表、加入封面图链接等功能一步步加上去。但今天先把TOP250这本书单拿在手里再说吧。
返回列表