尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python进阶实战:用装饰器与生成器打造健壮爬虫
不知道你有没有过这种感觉Python基础语法倒背如流列表、字典、循环、函数天天用但一提到进阶两个字就有点心虚。尤其是想把爬虫跑起来的时候明明照着教程写完了一运行不是这里报错就是那里没数据甚至干脆“exit code 0”地安静结束连个响都没有。问题出在哪多半不是爬虫本身难而是你手里只有基础语法缺了那些真正让代码抗造的工具。这篇文章我想聊聊Python进阶用法是怎么在爬虫场景里落地的。不是给你堆一堆语法糖然后说“你看多好用”而是直接对着一个能跑通的简单爬虫一步步把装饰器、生成器、上下文管理器、异常处理这些东西嵌进去让你既看得懂原理又能直接抄作业。适合学过Python基础、但还没系统写过爬虫的朋友也适合那些写了不少脚本但总觉得代码“一跑就碎、一改就乱”的同学。1. 进阶不是炫技这几个语法糖在爬虫里到底解决什么问题很多人对Python进阶用法的误解是“写给别人看的”觉得列表推导式、装饰器这些东西除了让代码短一点没什么实际价值。但我做了这么多年脚本和数据处理负责任地告诉你这些东西在爬虫里几乎是刚需。不是好看是省事、抗错、好维护。1.1 列表推导式造参数、筛结果都顺手写爬虫最常干的几件事构造一堆URL、从请求结果里抽数据、把清洗后的数据整理成列表。这些操作如果全用for循环加append代码会膨胀得很厉害。列表推导式在这里不是花架子而是把“循环收集结果”这个高频动作压缩成一行可读性反而更高。比如要拼出前十页的分页URLbase_url https://quotes.toscrape.com/page/{}/ urls [base_url.format(i) for i in range(1, 11)]比起写三行for循环这个方式意图更直接。再比如从解析结果里筛选长度大于10的名言long_quotes [quote.text for quote in quote_tags if len(quote.text) 10]如果你用过filter加lambda会发现列表推导式在可读性上几乎完胜。当然推导式不是万能的如果循环体里面逻辑特别复杂比如还要处理异常、还要调函数那就老老实实写for循环别硬挤成一行。这也是进阶用法的第一条准则可读性优先简洁是手段不是目的。1.2 生成器处理分页URL时内存差距是实打实的生成器是进阶用法里被低估的一个。很多人知道yield关键字但没体会过它的价值。写爬虫时最容易遇到的一个场景是你要抓几千个页面提前把所有URL存在一个列表里没问题但如果URL数量到了几十万甚至上百万一个列表占掉的内存就很可观了。生成器在这里的作用是“边取边生成”不一次性把所有值放进内存。def page_urls(total_pages): for i in range(1, total_pages 1): yield fhttps://quotes.toscrape.com/page/{i}/调用的时候for url in page_urls(10): # 每次循环才生成一个URL而不是先建好10个 fetch(url)这还不是最大的价值。生成器更大的意义在于支持“懒加载”你可以把整个抓取流程串成一条流水线一个生成器负责生成URL另一个生成器负责解析页面再用一个循环消费它们。每个环节只关心当前这一条数据内存占用始终很低。刚开始你可能感觉不到差别但一旦数据规模上去这个设计能救你命。1.3 装饰器重试与超时的优雅封装爬虫最烦的是什么请求超时、连接被重置、暂时性封禁、返回了意料之外的错误状态码。如果你在每个请求函数里都写一遍try...except...retry代码会变得极其啰嗦。装饰器就是来解决这个问题的把重试逻辑抽出来作为一个“包装器”套在任意爬虫函数上。import time import functools def retry(max_times3, delay1): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): for i in range(max_times): try: return func(*args, **kwargs) except Exception as e: print(f第{i1}次请求失败: {e}) if i max_times - 1: raise time.sleep(delay) return wrapper return decorator retry(max_times3, delay2) def fetch_page(url): resp requests.get(url, timeout5) resp.raise_for_status() return resp.text这个装饰器把“失败后重试”这个横切关注点从业务逻辑里剥离出去了。你写的fetch_page只需要关心“请求并返回文本”如果失败装饰器自动帮你重试。这就是进阶用法的核心思想把跨函数的通用逻辑抽出来让核心代码专注于自己的事。2. 爬虫起步的关键准备请求、解析和存储的最小闭环聊完语法我们回到爬虫本身。一个最简单的爬虫本质上是三步请求页面、解析内容、存储结果。看起来简单但每一步都有容易踩坑的地方。2.1 先确认目标页面的“脾气”写爬虫千万别上来就写代码。先花两分钟看一眼你要抓的页面搞清楚几件事内容是静态HTML还是JavaScript动态渲染的如果是动态渲染requests拿到的HTML里通常没有你要的数据得另想办法比如找接口API。页面编码是什么常见的有utf-8、gbk、gb2312编码搞错了拿到的中文直接乱码。有没有robots.txt虽然这东西没有强制力但尊重它是爬虫的基本礼仪。对于练手我强烈推荐用http://quotes.toscrape.com这个专门给爬虫学习者设计的网站。页面结构简单数据是静态的分页逻辑也清晰非常适合作为第一个爬虫的练习场。我的示例也会用它。2.2 requests发送请求时的三类常见问题用requests请求页面最常见的三类问题第一反爬识别。很多网站会检查User-Agent默认的python-requests/xxx很容易被拒绝。解决办法是伪装成一个正常浏览器headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }第二编码乱码。requests会根据响应头猜测编码但有时候不准。稳妥的做法是拿到content字节后手动指定编码resp requests.get(url, headersheaders) resp.encoding utf-8 # 根据页面实际编码调整第三超时无响应。如果不设置timeout请求可能一直挂在那里整个程序卡住。每次请求都加上超时参数resp requests.get(url, headersheaders, timeout5)这些看着不起眼但都是我在大量实战里踩过的坑。尤其是超时这个一旦目标网站响应慢没有超时的程序就跟死机了一样非常被动。2.3 解析HTMLBeautifulSoup的基本盘解析HTML我首选BeautifulSoup4它不像正则那么难读容错性也好。日常解析最常用的几个操作from bs4 import BeautifulSoup soup BeautifulSoup(html_text, html.parser) # 找单个元素 title soup.find(h1) # 找多个元素 quotes soup.find_all(div, class_quote) # CSS选择器方式 links soup.select(div.quote span a)用find_all加class_是最常见的组合因为页面里一类元素往往是同一种样式。还有一个小技巧拿到的Tag对象还可以继续做嵌套查找一层层缩小范围。2.4 存储csv足够sqlite更香爬下来的数据存哪数据量小、字段简单存csv就够import csv with open(quotes.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([text, author]) writer.writerow([人生苦短, Python])注意编码用utf-8-sig不然Excel打开会乱码。如果要抓的数据有结构关系或者后续要做查询sqlite更合适。Python内置sqlite3模块不需要额外装东西import sqlite3 conn sqlite3.connect(quotes.db) cursor conn.cursor() cursor.execute(CREATE TABLE IF NOT EXISTS quote (text TEXT, author TEXT)) cursor.execute(INSERT INTO quote VALUES (?, ?), (人生苦短, Python)) conn.commit() conn.close()最小闭环跑通之后我们再谈优化。因为爬虫这东西先跑起来比什么都重要。3. 一个真实可跑的小爬虫名言站点抓取10分钟能复现为了不纸上谈兵我直接用一个具体例子带你完整走一遍。3.1 分析目标结构目标站点是quotes.toscrape.com。打开首页你会发现每一页有若干条名言每条名言在一个div.quote里里面有三个关键部分名言文本span.text作者small.author标签div.tags a.tag页底有Next按钮指向下一页。我们要抓的内容很简单名言文本和作者。3.2 第一版代码能跑就行先写一个不求优雅、只求跑通的第一版import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_page(url): resp requests.get(url, headersheaders, timeout5) resp.encoding utf-8 resp.raise_for_status() return resp.text def parse_page(html): soup BeautifulSoup(html, html.parser) result [] for quote in soup.find_all(div, class_quote): text quote.find(span, class_text).get_text(stripTrue) author quote.find(small, class_author).get_text(stripTrue) result.append((text, author)) return result def main(): url https://quotes.toscrape.com/page/1/ html fetch_page(url) data parse_page(html) for text, author in data: print(f{author}: {text}) if __name__ __main__: main()运行之后你会在控制台看到第一页的十条名言。整个过程很直白但问题也明显如果只想抓第一页够了想抓多页得改遇到请求失败直接崩想缓存数据没有。3.3 输出结果与排查思路第一次运行最常见的现象就是在PyCharm或VSCode里看到一句“Process finished with exit code 0”然后什么都没有。这通常不是代码报错而是你的print压根没执行或者页面结构和预期不一致。排查思路我建议按这个顺序来在fetch_page函数里print(len(html))看拿到多少字节。如果拿到的是几千字节说明页面正常加载如果是几十字节多半被重定向了。在parse_page里print(soup.prettify()[:500])直接看HTML解析结果确认类名、标签名没写错。检查resp.status_code看是不是200。如果是403或者302说明请求出了身份问题或者被重定向了。这一步排查能力比你多背十个API都值钱。因为爬虫写来写去大部分时间都花在这种“数据为什么没出来”的调试上。4. 让爬虫“抗造”起来进阶技巧的一次性集成第一版能跑但很脆。下面我们把这些进阶用法一个个加进去把它变成一个遇到失败不崩、抓多页不卡、代码结构清晰的小工具。4.1 Session上下文管理器连接复用与自动关闭requests.get每次都会重新建立连接抓几百页的时候效率不高。用requests.Session可以复用TCP连接同时配合上下文管理器确保用完关掉def fetch_page(url, sessionNone): session session or requests.Session() with session.get(url, headersheaders, timeout5) as resp: resp.encoding utf-8 resp.raise_for_status() return resp.text用with的好处是响应对象在退出代码块时会自动释放连接避免文件描述符泄露。在爬虫这种频繁建立连接的场景里这是个非常实际的优化。4.2 装饰器加持超时重试与请求限速把前面写的retry装饰器用上同时加一个简单的限速装饰器def rate_limited(seconds): import time def decorator(func): last_call [0.0] functools.wraps(func) def wrapper(*args, **kwargs): now time.time() wait seconds - (now - last_call[0]) if wait 0: time.sleep(wait) result func(*args, **kwargs) last_call[0] time.time() return result return wrapper return decorator retry(max_times3, delay2) rate_limited(0.5) def fetch_with_policy(url, sessionNone): return fetch_page(url, session)这样一个函数就同时拥有了失败重试和请求间隔控制的能力。装饰器的组合俗称装饰器堆叠是Python进阶里特别值得掌握的技能把不同的横切关注点叠在一起每个装饰器只负责一件事组合起来却是完整策略。4.3 用生成器重构分页代码之前说的page_urls生成器在这里派上用场。我们可以把整个抓取流程改成生成器流水线def all_pages(start_page1, end_page10): for page in range(start_page, end_page 1): yield fhttps://quotes.toscrape.com/page/{page}/ def scrape_quotes(): session requests.Session() results [] for url in all_pages(1, 10): html fetch_with_policy(url, session) data parse_page(html) print(f抓取 {url} 完成拿到 {len(data)} 条) results.extend(data) return results如果你不想显式指定end_page也可以用while循环找下一页按钮。但用生成器的主要好处是调用方想取多少页就取多少页代码结构一目了然。4.4 用dataclass代替字典管理数据字典用多了字段容易写错特别是在函数之间传递的时候。Python 3.7的dataclass是个非常好用的数据类型既有类的好处又不用写一堆__init__from dataclasses import dataclass dataclass class Quote: text: str author: str tags: list[str]然后把parse_page改成返回Quote对象列表def parse_page(html): soup BeautifulSoup(html, html.parser) result [] for quote in soup.find_all(div, class_quote): text quote.find(span, class_text).get_text(stripTrue) author quote.find(small, class_author).get_text(stripTrue) tags [tag.get_text(stripTrue) for tag in quote.find_all(a, class_tag)] result.append(Quote(texttext, authorauthor, tagstags)) return result好处是字段明确、有IDE补全、默认就有__repr__调试时打印出来特别清晰。这是进阶用法里最“低门槛高回报”的一个。5. 踩过的坑与仍然要小心的边界进阶用法集成完代码已经比第一版好很多了但爬虫这件事远不止写代码这么简单。我把自己实际踩过的一些坑和边界问题放在这里希望能帮你绕开。5.1 exit code 0但没结果的常见原因这个现象在PyCharm用户里特别常见。程序退出码是0意味着没有异常但你要的数据没打出来。我总结过几次原因if __name__ __main__:后面的main()忘了写程序进入但什么都不执行。parse_page里的选择器写错find_all返回空列表循环体不执行。print被缓冲了尤其在循环很多、程序正常结束前没刷新。可以在脚本开头加一句import sys; sys.stdout.reconfigure(line_bufferingTrue)让输出即时刷新。页面实际返回了空内容但请求没报错这种情况把resp.text的前几百个字符打印出来一眼就能看清。核心思路不要只盯着有没有报错要学会在关键节点打印中间结果。这是调试爬虫最基本也最有效的办法。5.2 编码与解析带来的隐形bug编码问题真的很阴。你以为设置了resp.encoding utf-8就万事大吉但有些页面是gbk你硬按utf-8解码得到的就是一堆乱码而且parse_page还能解析出“元素”只是文本是乱的。处理办法先看响应头里的charset或者直接用requests.utils.get_encoding_from_headers(resp.headers)获取推荐编码再不行就试gbk、gb2312、utf-8轮着来。还有一次我遇到页面HTML里混着nbsp;这类实体用get_text(stripTrue)都去不掉需要html.unescape()处理一下。5.3 爬虫的礼貌与合规限速、robots、识别声明这里必须多说一句。爬虫是个技术活但也是个“尊重他人”的活。我在实际工作中一直坚持几个习惯限速即使不写rate_limited装饰器也要保证请求间隔不要过密。服务器不是你一个人的跑轮。识别声明把User-Agent伪装成浏览器没错但如果站点需要身份验证或明确禁止爬取那就别硬来。robots.txt虽然很多网站不强制执行但作为专业习惯抓取前看一眼总有好处。数据边界不要抓取个人信息、受版权保护的内容更不要用于任何违法违规用途。爬虫最大的风险不在技术而在滥用。一个技术上非常完美的爬虫如果被用于恶意刷接口、抓取私密数据、干扰业务运行那它就是个坏工具。我希望你学这些技术是出于学习、自动化、数据整理的正向目的。另外如果你要长期维护一个爬虫我建议用logging替代print把日志级别分开方便线上查问题用tenacity库替代手写重试装饰器它内置了指数退避和条件重试比手写的更完善数据量大了以后引入concurrent.futures.ThreadPoolExecutor做并发请求但并发一定要配合限速和重试策略否则很容易触发封禁。说到底Python进阶用法和爬虫是相辅相成的爬虫场景逼着你用上装饰器、生成器、上下文管理器、异常处理、类型注解而这些进阶用法又反过来让你的爬虫更健壮、更优雅。我自己的体会是不要追求一步到位写出“完美代码”先把第一版跑通再一步步把进阶技巧装进去这个过程本身才是最有价值的学习。现在你可以打开编辑器对着那个名言站点亲手把这段代码敲一遍——相信我踩过一次坑之后这些东西就会变成你自己的。
RELATED

相关推荐

微信小程序上传图片视频到后台存储完整方案与避坑指南

微信小程序上传图片视频到后台存储完整方案与避坑指南

简介:这是一份面向微信小程序开发者的前后端完整示例,核心解决图片与视频上传至后台存储的常见需求,适合刚接触小程序上传功能的初中级开发者作为参考模板。前端demo包含照片、视频两个独立上传入口,覆盖了页面布局、事件绑定与提…

📅 2026/9/9 9:26:06
GBVS算法程序详解:无需训练的视觉显著性检测图模型实现

GBVS算法程序详解:无需训练的视觉显著性检测图模型实现

简介:这份MATLAB实现的GBVS(全局二值可见性)图像显著性检测程序,面向计算机视觉研究者与算法学习者,可快速预测图像中最吸引人眼球的显著区域,适用于物体识别、图像分割、视频摘要等场景。资源共150个文件&…

📅 2026/9/9 9:26:06
RFID资产盘点实战:从标签选型到天线部署的工程细节

RFID资产盘点实战:从标签选型到天线部署的工程细节

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/9 9:21:05
MORE NEWS

更多资讯

📰

C++20 ranges投影函数的内联优化与编译期排序实践

C20把std::ranges带到标准库之后,写算法的姿势发生了一次不小的变化:迭代器对变成了范围,算法可以直接接受一个范围参数,还多了一个叫“投影函数”的东西。很多朋友第一次看到std::ranges::sort(vec, {}, &Person::age)这种写…

📰

开放科学实战指南:从理念到落地的完整工作流

算起来,我做科研的头几年,基本都耗在“重复造轮子”和“找不着北”上。实验方案是最新的,但数据整理方式却是二十年前的;论文发出去,审稿人问的原始数据,我自己都要翻半天文件夹。那时候我就想,…

📰

深入Ext2底层:Block Group与inode机制全解析

1. 为什么要钻进 Ext2 的底层很多人在 Linux 上工作了几年,天天ls、rm、cat,却不一定清楚这些命令背后,文件系统到底在玩什么花样。我当初也有这个困惑:文件明明存在磁盘上,怎么一断电就没了?为什么删除一个…

📰

ReactOS 0.3.15 源码编译实战:从环境准备到虚拟机启动

简介:ReactOS-0.3.15-REL-src.zip是一份基于ReactOS 0.3.15版的完整源代码压缩包,面向对Windows内核机制感兴趣的系统开发者、驱动研究者和开源操作系统学习者。ReactOS以兼容Windows应用与驱动为目标,此版本源码可用于剖析内核对象管理、进程…

📰

platform-tools.zip 报错无法定位程序输入点?adb 与 fastboot 配置实战指南

简介:这份工具包是面向安卓开发与测试人员的平台工具集合,重点解决调试过程中常见的 ADB 服务版本与客户端版本不一致、找不到设备或模拟器等问题。压缩包体积仅 1.74MB,共包含 22 个文件,其中既有安卓调试桥、fastboot、SQLite 命…

📰

文件加密工具深度盘点:从BitLocker到age的选型与实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬