Python爬虫异常处理:保证程序稳定
Python爬虫异常处理:让程序告别“一言不合就崩溃”
半夜挂着爬虫脚本去睡觉,满心以为早上能收获满满的数据,结果打开电脑一看,程序早在凌晨两点就因为一个小小的报错罢工了。这种“惊喜”想必每个写过爬虫的人都体会过。写爬虫不难,难的是让它像老黄牛一样稳定地跑完整个周期。今天我们就来聊聊,如何通过异常处理给Python爬虫穿上“防弹衣”。
网络请求阶段的“断网”危机
爬虫的第一步是发请求,这也是最容易翻车的地方。目标网站服务器打个盹,或者你的网络稍微抖一下,ConnectionError 或 Timeout 就会直接让程序崩溃。
面对这种偶发的网络波动,死磕一次是不明智的。最实用的思路是引入重试机制。你可以手写一个带重试逻辑的装饰器,或者直接拥抱 tenacity 这个库。
from tenacity import retry, stop_after_attempt, wait_fixed
@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
def fetch_data(url):
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
这里有个细节需要特别注意:一定要设置 timeout 参数。如果不设置,遇到服务器不响应也不拒绝的情况,你的程序会永远卡死在这个请求上,连重试的机会都没有。
解析阶段的“缺斤少两”
好不容易拿到了网页源码,到了用 BeautifulSoup 或 XPath 提取数据的环节,又容易栽跟头。有时候目标网站悄悄改了HTML结构,或者某个商品刚好缺货导致页面少了一块DOM节点,直接提取就会抛出 AttributeError 或 IndexError。
处理这类异常的核心原则是优雅降级与数据兜底。不要一报错就终止整个任务,而是捕获异常并赋予默认值。
try:
price = soup.select_one('.price').text
except AttributeError:
price = '未知'
logging.warning(f"未找到价格节点,URL: {url}")
通过记录日志并赋默认值,既能保证程序继续运行,又能在事后排查时知道哪些数据是缺失的,不至于拿到一堆残缺不全的脏数据。
反爬机制下的“状态码”陷阱
当你请求频率稍微快了一点,或者IP被目标网站盯上,返回的往往不是200,而是 403(禁止访问)、429(请求过多)甚至 503。很多新手习惯用 raise_for_status() 一刀切,遇到非200状态码直接报错。
针对反爬状态码,我们需要分层处理。遇到 429 时,说明被限流了,这时候最聪明的做法是主动休眠,而不是盲目重试;遇到 403 或 503,可能是触发了风控,需要切换代理IP或更新请求头。
if response.status_code == 429:
time.sleep(random.uniform(10, 20)) # 遇到限流,随机休眠
elif response.status_code in [403, 503]:
proxy = get_new_proxy() # 切换代理
把状态码判断放在 raise_for_status() 之前,能让你的爬虫在面对反爬时游刃有余,而不是像个无头苍蝇一样乱撞。
数据入库时的“最后一公里”
数据辛辛苦苦抓下来了,存进数据库时也可能出岔子。比如数据库连接断开,或者遇到了重复数据导致 DuplicateKeyError。
对于数据库连接异常,同样可以复用前面的重试机制。而对于数据重复问题,建议在SQL语句中使用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE,或者在MongoDB中使用 update_one 配合 upsert=True。在代码层面,捕获特定的数据库异常并打印警告,比直接让程序崩溃要体面得多,也能避免因为一条脏数据毁掉整批入库进度。
结语
写爬虫就像是在充满未知的水域里航行,异常处理就是你船底的压舱石。与其在程序崩溃后对着满屏的红字报错抓狂,不如在编写代码时就预判好各种可能出错的场景。把重试、兜底、日志记录这些防御性编程的习惯融入日常,你的爬虫程序才能真正实现“无人值守”,稳稳当当地把数据搬回你的硬盘里。


还没有评论,来说两句吧...