尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python图书爬虫实战:从requests到多线程的数据采集全流程

Python图书爬虫实战:从requests到多线程的数据采集全流程 1. 项目整体设计与思路拆解1.1 为什么选“图书爬虫”当练手项目很多人学Python爬虫第一反应就是去爬电商、爬社交平台结果被验证码、登录墙、风控系统轮番教育最后连Hello World级别的代码都没跑通就放弃了。我当年也走过这条路后来发现“图书数据”才是新手最友好的切入点。图书站点有几个天然优势数据是半结构化的标题、作者、出版社、价格、简介字段清楚页面多是服务端渲染HTML源代码里就能拿到完整内容不需要逆向JavaScript站点更新频率低不会今天改版明天重构。更关键的是图书数据足够干净适合做后续的数据分析、可视化、推荐系统爬下来不会白爬。这次的项目目标很直接全量抓取一个图书列表站点的图书信息包括书名、作者、出版社、出版日期、定价、评分和内容简介落成结构化数据文件。整体上我会用一个相对完整的小工程来做而不是一个几十行的教学脚本这样才能把异常处理、请求频率控制、数据清洗、并发提速这些真实生产环境里的问题全部暴露出来。1.2 技术选型requests还是scrapy选型这个问题网上吵了十年也没吵出标准答案。我的看法很明确第一版先别碰Scrapy用requests加BeautifulSoup把整个流程跑通等你真的遇到性能瓶颈或者需要分布式抓取海量URL的时候再上Scrapy也不迟。Scrapy是框架它会强制你按照Spider、Pipeline、Middleware的结构去组织代码学习曲线陡调试起来要同时理解Twisted异步模型。而requests是库想怎么调就怎么调出问题了print出来看对新手极其友好。图书类的站点撑死几千个页面requests加多线程完全扛得住压根用不上Scrapy的重型武器。BeautifulSoup则是解析HTML的首选API设计直观find和find_all对新手来说几乎不需要学习成本。有人会推荐lxml的XPath性能确实好但写起来可读性差调试也麻烦。我的选择是requests负责拿数据BeautifulSoup负责拆数据数据落盘用Python自带的csv和json模块全程不引额外重依赖。1.3 目标站点与数据边界怎么定爬虫项目的第一个决定性问题不是“怎么写代码”而是“爬什么”。我以前见过太多人代码写了一半才开始问“我要爬的网站长什么样”这顺序就反了。这个项目我建议选一个结构清晰、不设登录门槛的图书站点来练手。拿豆瓣读书的图书列表页来说每页展示25本书URL里的start参数控制偏移量0是第一页25是第二页规律非常直观。但要注意豆瓣对爬虫并不算友好频率稍微快一点就会弹验证页所以用它练手的话请求间隔必须拉长到3到5秒整体耗时会长不少。如果只想跑通流程选一个静态展示页面更省心。做项目之前先想清楚三件事你要哪些字段、需要多少条数据、拿到数据之后做什么。这三件事决定了页面的解析逻辑、翻页策略和存储格式也决定了你要不要上并发。别一上来就想“全都要”先定边界才能控制复杂度。2. 环境准备与核心原理2.1 Python环境与依赖安装要点工欲善其事必先利其器。环境这块我要多说两句因为我在无数个交流群里见过有人卡在第一步——装不上依赖代码还没写就放弃了。Python版本建议直接用3.10以上官方下载安装包时记得勾选“Add Python to PATH”这个选项默认不勾选不勾的话命令行里python就不是可识别命令。装完之后打开终端验证三件事python --version看版本pip --version看包管理器python -m pip install --upgrade pip把pip升到最新版。依赖只需要三个核心库。requests负责HTTP请求简单直接beautifulsoup4负责HTML解析注意安装的是bs4这个包名lxml是解析器BeautifulSoup的底层解析引擎速度比纯Python的html.parser快一个量级。安装命令是python -m pip install requests beautifulsoup4 lxml装完用pip list确认一下版本号。2.2 HTTP请求背后的完整链路爬虫的本质是模拟浏览器向服务器发起HTTP请求再把服务器返回的HTML解析成结构化数据。理解这条链路后面遇到问题才知道从哪查起。第一次请求发出去会经历DNS解析域名到IP、建立TCP连接、发送HTTP头部信息、服务器处理请求、返回响应报文这五个步骤。其中最容易出问题的是请求头服务器会检查User-Agent字段来判断请求来自浏览器还是脚本。Python的requests默认UA是python-requests/x.x.x很多站点直接看到这个就返回403拒绝访问。所以发请求之前必须伪装UA把真实的浏览器标识字符串替换成Chrome或者Edge的。人话版本这就相当于你走进一家书店门口的保安问你是什么人你说“我是扫描仪”保安当然不让你进你要装作普通顾客说“我来看看书”这才放行。UA伪装就是给爬虫“换身衣服”。服务器返回的响应报文里headers里能看到状态码、Content-Type、Set-Cookie这些元信息body里装着HTML源码。状态码200代表正常返回301和302是重定向403是拒绝访问404是页面不存在503大概率是服务器反爬策略或者过载。这些数字要刻进脑子里排查问题第一步就是看状态码。2.3 HTML结构与数据提取原理HTML的结构可以理解成一棵倒挂的树html是根节点head和body是子节点body里又挂着div、ul、li、a、span这些标签。BeautifulSoup干的事情就是把HTML字符串解析成这棵树然后允许你用选择器去树上摘“果子”。这里有个关键概念叫CSS选择器。你可以用标签名、class、id、属性值来定位元素语法和jQuery很像。比如book-title这个class使用的select(.book-title)用id定位写成select(#content)这两种方式在页面解析里覆盖了九成以上的场景。图书数据在页面里通常有稳定的结构规律每一本图书的信息都包在同一个class的容器节点里下面再按层级分布具体字段。这个“规律”就是爬虫得以工作的前提。解析的过程就是不断用select去下钻最后用get_text()把标签里的文本捞出来。需要注意get_text()会把子节点里所有文字拼在一起如果某个字段含有嵌套标签要先取下层的节点再取文本。3. 实操过程与核心环节实现3.1 请求模块带重试机制的请求函数写爬虫第一件事不是解析页面而是先把请求函数写好。我见过太多人拿requests.get裸奔去请求目标站点切片一下就是一小块。实际上一旦遇到网络波动或者反爬整个脚本就中断了前面爬的几千条数据都白干。一个稳固的请求函数至少要包含三部分请求头配置、超时控制、重试机制。请求头里User-Agent就是浏览器标识Referer字段表示从哪个页面跳转过来部分站点会校验这个字段来防止跨域请求。超时参数设成(timeout10)代表建立连接和读取响应各自最多等10秒避免某个页面卡死拖垮整个爬虫。重试机制用for循环加try except实现最多尝试三次每次失败后等待时间递增比如第一次失败等2秒第二次失败等4秒给服务器一个“缓过来”的时间。请求成功之后用resp.encoding或apparent_encoding处理编码避免中文乱码。3.2 解析模块从HTML中提取干净字段目标页面确认之后先做一次侦察把HTML拿到手在浏览器开发者工具里用CtrlF搜一个你确定会出现的字段值找到它所在的HTML代码位置然后顺着父节点往上翻理清层级关系。以典型的图书列表页为例每个图书条目通常在li标签下书名在h2标签里作者和出版社在p标签里评分存在带有class的span标签中。定位到这些位置之后按顺序提取字段。提取时注意两点一是用strip()去掉文本首尾的空白和换行符二是处理缺失值有的图书没有评分提取出来是空字符串要统一处理成None或者’无’不然存进CSV会出现错位。清洗数据这步很多人会忽略觉得差不多就行了。但数据是给人看或者拿去分析的脏数据会导致排序出错、图表失真后续做可视化时非常痛苦。我自己的习惯是字段提取完成后过一遍清洗函数把空格、全角字符、特殊标记处理掉。3.3 存储模块CSV与JSON双轨落盘数据存储的选择要看你的下游需求。CSV适合用Excel直接打开看也适合做后续的机器学习训练JSON则保留了天然的分层结构如果想做API服务或者进一步塞进数据库JSON更合适。两个都输出成本很低收益很高。CSV写入用csv模块的DictWriter把字段名列表传给fieldnames参数然后按行写入字典。这里有个坑Excel默认用ANSI编码打开CSVPython写入时用的是UTF-8直接双击打开中文会乱码。解决办法是写入时指定encodingutf-8-sig这个编码会在文件头加BOM标记Excel就能正确识别。JSON写入就简单很多所有数据收集完成后用json.dump整块写进文件spacing设置成ensure_asciiFalse这样中文会以明文形式保存而不是转码成\uXXXX。输出路径统一放在data目录下文件名包含时间戳防止重复运行把上一次的数据覆盖掉。3.4 主流程串联从第一页到最后一页主流程的逻辑就是循环翻页加解析加存储。图书列表页的URL规律通常是页码参数递增第一页start0第二页start25依此类推。先把第一页的HTML拿到找到总页数或者总条目数用总条目数除以每页数量得出总页数然后写个循环依次抓。每抓完一页打印一条日志记录当前页码、本页拿到多少条数据、累计多少条、耗时多久。日志的作用不是给人看的是排查问题用的。爬虫跑着跑着断了一看日志就知道断在哪一页就能断点续爬。页面之间加上time.sleep(random.uniform(1, 3))随机休眠把请求频率压下来。很多站点不封爬虫只是因为请求太规律了——每隔固定秒数来一次跟机器人打点一样。随机延时就是给请求加一点“人味”这也是每个爬虫工程师都应该养成的基本习惯。4. 并发设计多线程、多进程与协程选型对比4.1 三种方案的底层逻辑与适用场景爬虫爬得慢第一反应是“加并发”但并发的方式选不对速度和稳定性都无从谈起。我们先说清楚三种方案的区别再动手改代码。多线程是在一个进程里开多个线程共享内存Python的GIL锁限制了CPU密集型的并行能力但爬虫属于I/O密集型任务绝大多数时间都在等待网络响应GIL的影响很小所以多线程是爬虫提速性价比最高的方案。多进程是开多个独立的Python解释器每个进程有独立的内存空间完全没有GIL干扰适合做CPU密集型的计算任务但进程间通信开销大写起来也复杂。协程是单线程内用事件循环切换任务理论上并发能力最强开销最小但代码要写成async/await形式调试起来门槛高。4.2 怎么选一个适合自己的并发方案网上关于“爬虫并发哪个好”吵得很凶我的建议是分阶段决策。爬虫刚写出来先跑单线程确认流程正确、数据没问题。然后上多线程ThreadPoolExecutor写起来非常简单5行代码就能把爬取速度提升3到5倍而且改造成本极低。如果后续数据量真的达到十万级以上再考虑协程或者分布式。线程数不是越大越好。线程数翻倍请求速度翻倍但被封IP的风险也翻倍。图书站点比较温和同样线程数设成4到8就足够了。线程太多会导致连接数占满反而触发服务器的防抖机制。4.3 ThreadPoolExecutor并发改造实操用concurrent.futures.ThreadPoolExecutor做并发改造核心逻辑就三步定义任务函数、创建线程池、提交任务并收集结果。先定义一个fetch_book_page(page)函数这个函数内部完成“发请求、解析、清洗、存储”全流程页与页之间没有依赖天然可以并行。然后在主函数里创建ThreadPoolExecutor用max_workers指定线程数用map方法把页码列表批量提交给线程池。from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_book_page(page): # 实际爬取一页的逻辑 url fhttps://example.com/books?start{page * 25} html fetch_with_retry(url) books parse_book_list(html) save_books(books) return len(books) pages [0, 25, 50, 75, 100] with ThreadPoolExecutor(max_workers5) as executor: futures [executor.submit(fetch_book_page, page) for page in pages] for future in as_completed(futures): print(本页抓取数量:, future.result())as_completed方法会按照任务完成的先后顺序返回结果哪个线程先跑完就先处理哪个。这里有个优化点如果某页失败单个任务抛出的异常不会中断其他线程需要在fetch_book_page内部捕获异常并返回0或者None保证整个批量任务能跑完。4.4 并发下的频率控制与数据一致性并发改造做完最容易忽略的就是“礼貌抓取”。开了5个线程就相当于5个人同时访问站点这个速率对目标服务器是有真实压力的。线程池里每个线程的任务函数都要带上随机延时让整体请求节奏错落有致。数据一致性方面多个线程同时写同一个CSV文件会出问题两个线程同时打开文件句柄写入会造成内容交错。解决方案是给写文件加线程锁或者让每个线程把数据写入各自独立的临时文件最后再合并。实际工作中我更喜欢后者——线程之间完全隔离避免锁带来的性能损耗。个人经验是单线程跑通的代码做并发改造速度提升立竿见影但务必做小批量试跑比如先爬5页确认数据没有重复、没有遗漏再全量跑。不要一上来就开20个线程去冲封号封IP是小事数据污染了才叫得不偿失。5. 常见问题与排查技巧实录5.1 请求被拒绝与反爬策略应对问到最多的问题就是“爬到一半开始报403或者验证码”。这通常是请求频率过快导致的IP风控目标服务器识别到异常流量后主动拒绝服务。处理方法分三个层级先把请求间隔拉大随机延时放宽到3到8秒再把UA和请求头补全模拟真实浏览器的完整请求链如果还不行就需要检查自己的出口IP是不是被标记了换个网络环境再跑。注意千万不要去研究破解验证码或者强行绕过风控这既不符合技术伦理也容易把自己搞进黑名单。正规站点的数据用合规的方式控制频率慢慢爬大多数情况下是能拿到完整数据的。爬虫的边界感很重要技术能做什么是一回事应该做什么是另一回事。5.2 编码乱码问题爬取中文站点时乱码是最常见的问题。原因在于服务器返回的响应头里声明的字符集和实际内容的字符集对不上。requests库在解析响应时按响应头里的Content-Type字段来解码如果这个字段声明的是ISO-8859-1而页面实际是UTF-8那中文就会变成一堆“锟斤拷”。解决方法是用resp.apparent_encoding这个属性会从页面二进制内容里自动检测字符集准确率很高。检测到之后手动把resp.encoding设置成检测值再访问resp.text就能正确显示中文。调试的时候先用print打印前200个字符看到中文正常再继续解析。5.3 解析结果为空或字段错位HTML结构解析为空99%的原因是选择器写错了。标签的class属性在页面更新后改了名或者页面结构嵌套层级变化都会让选择器匹配不到节点。遇到这种问题回到浏览器开发者工具里重新检查目标元素的实际HTML代码别靠记忆猜选择器。还需要警惕一种情况页面里有一部分内容是JavaScript动态渲染的HTML源代码里根本没有。怎么判断在浏览器里右键查看源代码CtrlF搜索你要的数据如果源代码里找不到说明是前端渲染。这种情况requests拿到的HTML里没有数据需要改用Playwright这类带浏览器的工具但图书站通常不会这么复杂。字段错位的问题往往出在“先抓容器再拿子节点”的逻辑上。如果你在某个条目的容器里取了第一个a标签的文本当书名恰好其他字段也用了类似写法匹配顺序对不上就错位了。我的建议是每提取一个字段就打印一条到控制台肉眼检查前3条记录确认正确再放量跑。5.4 异常处理与断电续爬爬虫跑到一半断掉重新跑又从头开始这体验太痛苦了。断点续爬的核心是“记录进度”。记住两个原则每成功解析完一页就把这页的数据立刻写盘不要攒到最后一次性保存同时把当前已完成的页码记录到一个progress.txt文件里下次启动时读取这个文件从断点处继续。def load_progress(): try: with open(progress.txt, r) as f: return int(f.read().strip()) except (FileNotFoundError, ValueError): return 0 def save_progress(page): with open(progress.txt, w) as f: f.write(str(page))这样一个几百行的小脚本就具备了容灾能力。即使中途断网、断电、脚本崩溃重启后从断点继续跑不用重头再来。这个思路在大型爬虫里叫断点续爬在工程里叫checkpoint机制本质是一样的。5.5 常见问题速查表问题现象可能原因排查与解决返回403UA被识别为爬虫更换完整浏览器UA补全请求头返回503请求频率过高加大随机延时降低并发线程数中文乱码字符集检测错误用resp.apparent_encoding重置编码解析结果为空选择器失效或JS渲染重新检查页面结构确认数据在源码中数据错位容器内字段顺序不匹配逐字段打印验证前几条记录CSV用Excel打开乱码编码问题写入时指定encodingutf-8-sig请求超时网络不稳定设置timeout参数加重试机制6. 数据后续利用与项目扩展方向图书数据爬下来其实只是完成了“数据获取”这一步。我之前做过一个个人书库项目把爬下来的图书数据全部存入SQLite数据库做了一个简单的Web应用可以根据书名、作者、标签多维筛选自己的藏书还能用统计图表看出版年份趋势、出版社分布、评分中位数这些指标。这些分析做出来之后数据才真正有了价值。项目的扩展方向也有很多。比如给爬虫加一个定时任务每周自动更新图书评分数据观察评分的动态变化或者把存储从CSV迁移到MySQL、PostgreSQL支撑更大的数据量再进一步可以做图书推荐基于豆瓣标签和评分的协同过滤给自己推荐“可能想读”的书。如果你对可视化界面感兴趣可以用Flask搭一个简单的网页把爬虫结果通过HTTP接口暴露出来前端用表格展示数据。这个做法的好处是不用每次爬完都去翻CSV文件直接在浏览器里就能查。再进阶一步用PyQt写一个桌面端工具把爬虫、展示、导出集成在一个窗口里体验上会更完整。说到底爬虫是手段不是目的。数据拿到手里怎么清洗、怎么分析、怎么用起来才是更值钱的部分。技术框架会变但“拿数据、清数据、用数据”这条链路在任何一个数据驱动的项目里都是通用的。7. 合规意识与实际使用经验最后我特别想聊一聊合规。身边不少朋友学了爬虫之后第一反应是想爬这个爬那个拿到数据就当自己资产了。殊不知数据合规这事在国内越来越严格个人隐私数据、平台商业数据爬取和使用都有明确的法律边界。做技术学习没问题但要学会守住底线。我自己实操中的经验是先看三样东西站点的robots.txt文件里面声明了哪些路径允许爬虫访问目标站点有没有登录后才能看的数据有的话说明平台默认不公开数据本身是否涉及个人信息或者版权内容涉及的话就不要碰。图书数据这种公开的、非个人的、非商业机密的信息爬下来做个人学习和分析使用相对安全。但即使如此请求频率我也控制得很保守远低于普通用户的浏览频率。这不是小题大做而是作为从业者应当有的共识——爬虫技术是访问别人服务器的手段用得好是采集公开信息用不好就是给别人的服务器制造压力。学爬虫越久越会明白一个道理控制自己比控制代码难得多。代码写得再好如果不懂取舍和边界早晚会碰壁。那些长期靠爬虫技术稳定干活的人不是因为他们技术多硬而是因为他们知道什么能爬、什么不能爬并把“不越界”当成默认配置。希望你从这个图书爬虫项目开始不仅学会技术也能养成这种判断力。
返回列表