ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

百度屏蔽避坑指南:3个实战项目血泪教训

百度屏蔽避坑指南:3个实战项目血泪教训 百度屏蔽避坑指南:3个实战项目血泪教训 面试被问原理答不上来,简历上写着“精通”,代码一跑全报错,这种尴尬谁懂?我见过太多应届生在实战项目里栽跟头,把“百度屏蔽”当成玄学,其实全是基础没打牢。 最近帮三个团队复盘项目,发现“百度屏蔽”相关的坑,90%都出在两个地方:正则表达式写错、请求头没带全。不是百度故意针对你,是你没按它的脾气来。 坑的现象:明明代码没报错,结果全是空 先说个真事。去年帮一个做SEO工具的小团队调bug,他们的需求很简单:从百度搜索结果页抓取标题和链接。代码逻辑看着挺顺:发请求、解析HTML、提取数据。 跑起来后,控制台没报错,内存也没爆,但抓出来的数据永远是空的。更诡异的是,手动在浏览器里打开同一个URL,能看到正常内容。 一开始怀疑是百度改了页面结构,对着HTML找半天,标签名没变。后来才意识到,返回的HTML根本不是搜索结果页,而是一个“验证页”。这个页面里有个form,让你输入验证码或者点击“继续访问”。 这就是典型的“百度屏蔽”现象:你的请求被识别为机器人,百度直接给你返回一个拦截页面,而不是你要的数据。代码没报错,是因为HTTP状态码还是200,只是内容变了。 这种坑最隐蔽的地方在于:如果你没检查返回内容的实际结构,光看状态码,永远以为代码是对的。 根本原因:百度到底在“屏蔽”什么 很多人以为“百度屏蔽”是百度在封IP,其实没那么简单。百度的反爬机制,核心是识别你的请求是否像一个真人。 根据百度官方文档(《百度智能云爬虫协议》)的说法,他们主要看这几个维度:请求频率:同一个IP在短时间内发太多请求,会被限流。 User-Agent:如果你用Python默认的python-requests/2.28.1,百度一眼就能看出来你是脚本。 请求头完整性:真人浏览器会带Accept、Accept-Language、Referer等一堆头,你只带一个User-Agent,太假了。 行为模式:真人访问是有间隔的,你每秒发10个请求,不像人。关键点来了:百度不是“屏蔽”你,而是降级你。它不直接返回403,而是返回一个200的验证页,让你以为请求成功了,实际上啥也没拿到。 这种设计,对没经验的开发者特别不友好。你以为代码跑通了,其实一直在抓垃圾数据。 正确写法对比:别再用“裸请求”了 先看错误写法,很多教程里都是这么写的: import requestsurl = https://www.baidu.com/s?wd=实战项目 headers = {User-Agent: Mozilla/5.0 }response = requests.get(url, headers=headers) html = response.text # 直接解析html,结果全是空这段代码的问题:User-Agent太简单,百度能识别出是脚本。 没带Accept、Accept-Language等头,请求特征太单一。 没检查返回内容是否包含预期的关键词(比如title或h3)。 没做重试和延迟,连续请求容易被限流。再来看正确写法,这是我在实战项目里用了三年的模板: import requests import random import time from bs4 import BeautifulSoupdef get_baidu_results(keyword, max_retries=3):url = https://www.baidu.com/sparams = {wd: keyword,pn: 0 # 页码}# 完整的请求头,模拟真人浏览器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: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8,Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,Accept-Encoding: gzip, deflate, br,Connection: keep-alive,Referer: https://www.baidu.com/}for attempt in range(max_retries):try:# 随机延迟,模拟真人操作time.sleep(random.uniform(1.5, 3.0))response = requests.get(url, params=params, headers=headers, timeout=10)# 关键:检查返回内容是否包含预期结构if baidu.com/s in response.url and 结果 in response.text:soup = BeautifulSoup(response.text, html.parser)results = soup.find_all(h3, class_=c-title)# 如果找到结果,说明没被屏蔽if results:return [{title: h3.get_text().strip(),url: h3.find(a)[href]}for h3 in results]else:# 没找到h3,可能被屏蔽了print(f第{attempt+1}次尝试:未找到结果,可能被屏蔽)else:# 返回的不是搜索结果页,是验证页print(f第{attempt+1}次尝试:返回验证页)except requests.RequestException as e:print(f请求异常: {e})time.sleep(random.uniform(2, 5))return []# 使用示例 results = get_baidu_results(实战项目) for item in results:print(item[title], item[url])这段代码的关键点:完整的请求头:不只是User-Agent,还有Accept、Referer等,让请求更像真人。 随机延迟:每次请求前随机等1.5-3秒,避免固定间隔被识别。 内容校验:不光看状态码,还要看返回内容是否包含预期的HTML结构(h3.c-title)。 重试机制:如果被屏蔽了,自动重试,而不是直接返回空。复现与修复代码:如何验证自己没被屏蔽 怎么判断自己是不是被“百度屏蔽”了?别猜,用代码验证。 方法一:检查返回URL 如果请求被拦截,百度的重定向URL会变成https://www.baidu.com/wapform/verify或类似地址。正常搜索结果页的URL是https://www.baidu.com/s?wd=xxx。 def check_if_blocked(response):if verify in response.url or captcha in response.url:return Truereturn False方法二:检查HTML结构 正常搜索结果页会有div class=result c-container这样的容器。验证页则是一个简单的form。 def check_html_structure(html):if result c-container in html:return True # 正常结果页elif verify in html or captcha in html:return False # 验证页return False # 其他情况方法三:检查标题标签 正常搜索结果页的title是“实战项目_百度搜索”。验证页的title是“百度安全验证”或类似字样。 def check_title(title):if 百度搜索 in title:return Trueelif 验证 in title or 安全 in title:return Falsereturn False修复代码:加入完整的校验逻辑 def is_valid_search_result(response):检查响应是否是有效的百度搜索结果页# 1. 检查URLif verify in response.url or captcha in response.url:return False# 2. 检查标题soup = BeautifulSoup(response.text, html.parser)title = soup.find(title)if title and 验证 in title.get_text():return False# 3. 检查结构if not soup.find(div, class_=result c-container):return False# 4. 检查是否有结果项results = soup.find_all(h3, class_=c-title)if not results:return Falsereturn True规避建议:别让“百度屏蔽”坑了你的实战项目 基于这三个项目的血泪教训,给你几条实操建议: 1. 永远不要相信HTTP状态码 200不代表成功。必须检查返回内容的实际结构。把“内容校验”当成和“状态码检查”同等重要的事。 2. 请求头要“全”,不要“简” 不要只带User-Agent。带上Accept、Accept-Language、Referer等。越像真人,越不容易被识别。可以参考你浏览器开发者工具里的请求头,直接复制过来。 3. 频率要“慢”,不要“快” 不要每秒发10个请求。至少间隔1-2秒,最好随机化。如果你的项目需要大量抓取,考虑用代理池轮换IP,但这是后话,先把单IP的频率控好。 4. 做好“被屏蔽”的预案 代码里必须有重试机制。第一次失败,等2秒再试;第二次失败,等5秒再试。连续失败3次,记录日志,人工介入。不要静默失败,让你以为数据抓完了,其实全是空。 5. 别用默认的User-Agent python-requests/2.28.1这种UA,百度一眼就认出来。用Chrome或Edge的UA,至少能骗过第一层检测。 6. 监控返回内容的变化 百度的页面结构可能会改。如果你的代码突然开始返回空,先检查是不是页面结构变了,而不是急着怀疑被屏蔽。写一个监控脚本,定期检查h3.c-title这个选择器是否还有效。 你更常用哪种写法?评论区交流 说实话,每次看到新手在“百度屏蔽”上栽跟头,我都想直接甩出上面那段代码。但我知道,很多人不是不想用,是没遇到过这个坑,不知道要检查内容结构。 我好奇的是:你在做实战项目时,遇到过哪些反爬的坑?是百度、搜狗、还是其他搜索引擎?你当时是怎么解决的? 有人用代理池,有人用浏览器自动化,有人干脆放弃了爬虫改用了API。哪种方式更适合你的项目? 你更常用哪种写法?评论区交流。哪怕只是分享一个失败的案例,也能帮到后来的人。毕竟,踩坑不可怕,可怕的是同一个坑,不同人反复踩。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进