百度是中国占主导地位的搜索引擎,是大多数中国用户首选的搜索入口。这使其公开的搜索结果成为进行关键词研究、SEO 追踪、市场分析,或单纯希望了解 Google 并不主导的市场中排名情况的人的有价值信号源。结果页面承载着 SERP 工具在其他任何地方所需的相同结构化数据:标题、链接、摘要,以及它们出现的顺序。
本指南将展示如何用可靠的方式通过 Python 爬取百度搜索结果。你将构建一个小型、可运行的爬虫:通过 Crawling API 抓取渲染后的结果页面,用 BeautifulSoup 解析每条结果,并输出干净的结构化数据。整个教程范围仅限于任何人无需账号即可查看的公开搜索结果数据,末尾的合法性部分不是样板文字,在将此脚本对准任何真实规模的目标前,请务必阅读。
你将构建什么
一个 Python 脚本,接受一个公开的百度搜索 URL,通过 Crawling API 获取 HTML,并为页面上的每条自然结果提取结构化记录。我们将以一个示例查询作为贯穿示例,从每条结果中提取以下字段:
- 标题结果在列表中显示的标题文字。
- 链接结果指向的目标 URL。
- 摘要标题下方显示的描述或摘要文本。
- 排名结果在页面上的位置,从顶部开始计数。
为何普通请求在百度上会失败
如果你从脚本向百度结果 URL 发出裸 HTTP 请求,你很少能得到在自己浏览器中看到的那个干净页面。有两个因素对你不利。第一,百度从中国境内提供服务,并根据请求 IP 调整返回内容,因此来自境外数据中心地址的请求可能得到地区门控或部分内容。第二,百度监控自动化流量:看起来不像真实浏览器的请求会被质询、收到验证页,或在到达列表内容之前就被拦截。
因此,一个有效的百度爬虫在一次请求中需要两样东西:一个平台识别为真实访客的 IP,以及在页面依赖脚本时能够渲染它的浏览器。你可以自行组装:使用无头浏览器加上轮换住宅代理池,但保持这些组件健康运行占据了大部分工作量。Crawling API 将两者整合进一次调用:你发送 URL,它从受信任的住宅 IP 抓取并在需要时渲染,然后返回完整的 HTML 供你解析。
百度对地理位置的敏感程度超过大多数西方目标。来自住宅 IP 的请求看起来像普通访客,而境外数据中心地址则会立即暴露身份。Crawling API 在服务端轮换住宅地址,因此你无需自行采购和维护这个池。你可以从最多 20,000 次免费请求开始,无需信用卡。
前置条件
在编写任何代码之前,你需要准备好以下几样东西。它们都不需要太长时间。
Python 基础。你应该能够编写和运行 Python 脚本,并用 pip 安装包。如果 BeautifulSoup 对你来说是新的,我们的Python BeautifulSoup 使用指南涵盖了本教程所需的解析基础。
Python 3.8 或更高版本。用 python --version 确认你的版本。如果没有,从 python.org 或 Anaconda 等发行版安装。
Crawlbase 账号和 token。注册后,打开控制台,从账号文档页复制你的请求 token。最多 20,000 次免费请求:注册赠送 1,000 次,完成引导步骤即可获得更多。像对待密码一样对待这个 token:它用于验证你的请求,所以不要提交到版本控制中。
搭建项目
创建一个虚拟环境,使项目依赖保持隔离,然后安装爬虫所需的两个库。
python --version python -m venv baidu_env source baidu_env/bin/activate pip install requests beautifulsoup4
在 Windows 上,用 baidu_env\Scripts\activate 替换 source 行来激活环境。两个依赖项各司其职:requests 向 Crawling API 发送 HTTP 调用,beautifulsoup4 通过 CSS 选择器解析返回的 HTML,让你可以提取各个字段。
第一步:通过 Crawling API 抓取页面
首先获取 HTML。编写一个小型 crawl() 函数,将目标 URL 带着你的 token 发送给 Crawling API,检查底层页面是否以 200 状态返回,并返回 HTML 响应体。在解析之前检查状态码,可以让失败信息清晰可见而不是悄悄忽略。
import json import requests API_TOKEN = "YOUR_CRAWLBASE_TOKEN" # replace with your token API_ENDPOINT = "https://api.crawlbase.com/" def crawl(url): params = {"token": API_TOKEN, "url": url} response = requests.get(API_ENDPOINT, params=params) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] if __name__ == "__main__": url = "https://www.baidu.com/s?ie=utf-8&wd=%E8%8B%B9%E6%9E%9C%20iPhone" html = crawl(url) print(html[:500])
API 返回一个 JSON 封装体,所以你用 json.loads 加载响应并读取两个字段:original_status 是百度自身返回的状态码,body 是页面 HTML。对 original_status 的守卫意味着地区门控或拦截会以异常的形式浮出水面,而不是将垃圾数据喂给解析器。示例查询是"苹果 iPhone",在 wd 参数中进行 URL 编码,这是百度传递搜索词的方式。用 python crawling.py 运行脚本,你应该能在前 500 个字符中看到真实的结果标记,这确认了在你编写任何选择器之前抓取就已经生效。
那个 original_status 检查之所以总能读到 200,是因为请求首先就以真实访客身份到达了百度。Crawling API 从正确地区的轮换住宅 IP 抓取页面,在需要浏览器时渲染它,并将完整的 HTML 交给你,省去了自行运行无头浏览器集群和采购住宅代理池的麻烦。先在免费套餐上对一个公开结果 URL 进行测试。
第二步:用 BeautifulSoup 解析结果
拿到 HTML 后,将其加载到 BeautifulSoup 中,通过选择器遍历各条结果。百度将每条自然结果包装在一个标题块中,并从其中的锚点读取目标链接。在浏览器开发者工具中检查线上页面(右键单击,然后选择"检查")以确认当前的类名;以下选择器与撰写本文时的布局匹配。
from bs4 import BeautifulSoup def scrape_html(html): soup = BeautifulSoup(html, "html.parser") page_title = soup.title.string if soup.title else None search_input = soup.find("input", {"name": "wd"}) search_query = search_input.get("value", "") if search_input else "" results = [] for position, block in enumerate(soup.select("div.title-box_4YBsj"), start=1): heading = block.select_one("h3.t") link = block.select_one("a[href]") snippet = block.find_next("div", class_="content-right_2s-H4") if not heading or not link: continue results.append({ "position": position, "title": heading.get_text(strip=True), "url": link["href"], "snippet": snippet.get_text(strip=True) if snippet else None, }) return { "pageTitle": page_title, "searchQuery": search_query, "results": results, }
选择器 div.title-box_4YBsj 是百度用于每条结果标题块的包装器,标题在 h3.t 标签中,目标地址在其中的锚点里。从锚点的 href 读取链接可以将 URL 与标题分开。enumerate(..., start=1) 在循环时免费给出排名,使顺序来自页面顺序而非脆弱的属性。if not heading or not link: continue 守卫跳过那些不是真正自然结果的内容,使广告和多余标记不出现在你的输出中。摘要从每个标题后面的描述容器中读取,在缺失时回退到 None。
百度的类名,如 title-box_4YBsj 和 content-right_2s-H4,携带着在百度重新部署前端时会变化的生成后缀。将上面的选择器视为起始模板,而非固定合约。当每条结果的某个字段都返回为空时,在浏览器开发者工具中重新检查线上页面并更新选择器。定期进行选择器维护对任何生产级爬虫来说都是正常的,不是出了什么问题的迹象。
第三步:整合在一起
现在将抓取和解析整合成一个可运行的脚本。爬取渲染后的结果页,将 HTML 交给解析器,并将结构化输出写入 JSON。设置 ensure_ascii=False 可以让中文字符在文件中保持可读,而不是被转义为 \u 序列。
import json import requests from bs4 import BeautifulSoup API_TOKEN = "YOUR_CRAWLBASE_TOKEN" API_ENDPOINT = "https://api.crawlbase.com/" def crawl(url): params = {"token": API_TOKEN, "url": url} response = requests.get(API_ENDPOINT, params=params) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] def scrape_html(html): soup = BeautifulSoup(html, "html.parser") page_title = soup.title.string if soup.title else None search_input = soup.find("input", {"name": "wd"}) search_query = search_input.get("value", "") if search_input else "" results = [] for position, block in enumerate(soup.select("div.title-box_4YBsj"), start=1): heading = block.select_one("h3.t") link = block.select_one("a[href]") snippet = block.find_next("div", class_="content-right_2s-H4") if not heading or not link: continue results.append({ "position": position, "title": heading.get_text(strip=True), "url": link["href"], "snippet": snippet.get_text(strip=True) if snippet else None, }) return {"pageTitle": page_title, "searchQuery": search_query, "results": results} def main(): url = "https://www.baidu.com/s?ie=utf-8&wd=%E8%8B%B9%E6%9E%9C%20iPhone" html = crawl(url) data = scrape_html(html) with open("baidu_results.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"Saved {len(data['results'])} results") if __name__ == "__main__": main()
用 python main.py 运行完整脚本。它抓取"苹果 iPhone"的结果页面,为每条自然列表提取一条记录,并将所有内容写入 baidu_results.json。你只需要这两个函数:在 URL 中替换查询词,解析器会处理返回的任何内容。
输出示例
你会得到一个干净的结构化对象,包含页面标题、回显的搜索查询以及一个有序的结果列表,可以直接写入 JSON、CSV 或数据库。
{ "pageTitle": "苹果 iPhone_百度搜索", "searchQuery": "苹果 iPhone", "results": [ { "position": 1, "title": "Apple (中国大陆) - 官方网站", "url": "http://www.baidu.com/link?url=abc123", "snippet": "探索 iPhone、iPad、Mac 等 Apple 产品的全新阵容。" }, { "position": 2, "title": "iPhone - 维基百科", "url": "http://www.baidu.com/link?url=def456", "snippet": "iPhone 是苹果公司设计和销售的智能手机系列。" } ] }
请注意,结果 URL 以 baidu.com/link?url=... 重定向链接的形式返回,而非最终目标。这是百度处理出站点击的方式。如果你需要真实目标,可以用单独的请求跟随每个重定向,但请谨慎操作并保持低频率,避免将流量成倍增加到百度上。
跨页面和查询词扩展
单个查询的单页只是演示;真实任务需要跨多个搜索词深入到更多结果页。百度用 pn 查询参数分页,该参数是以 10 为倍数的偏移量:pn=10 是第二页,pn=20 是第三页,以此类推。形式保持不变:构建每个 URL,通过 Crawling API 抓取,用同一个函数解析。保持长时间运行健康的唯一习惯是控制节奏,所以请求之间要暂停而不是在紧密循环中触发。
import time from urllib.parse import quote query = "苹果 iPhone" encoded = quote(query) all_results = [] for page in range(3): offset = page * 10 url = f"https://www.baidu.com/s?ie=utf-8&wd={encoded}&pn={offset}" html = crawl(url) all_results.extend(scrape_html(html)["results"]) time.sleep(3) print(f"Collected {len(all_results)} results across 3 pages")
Crawlbase 默认每秒最多处理 20 个请求,对于一个自我调速的爬虫来说有充足的余量;如果你确实需要更多,支持团队可以提高上限。API 返回的任何 5XX 响应均不计费,所以重试被拦截或不可用的 URL 不会产生额外费用。如果你更倾向于通过轮换池路由自己的流量而不使用托管 API,Smart AI Proxy(也称为 AI Proxy)可以作为直连代理端点提供相同的住宅 IP 轮换。
保持不被封锁
即使受信任的 IP 已经处理好,百度仍然会监视爬虫特征的流量,而且由于其运营所在地,其检查比大多数平台更为严格。以下几个习惯能保持运行健康。
- 控制请求节奏。在紧密循环中轰炸结果页是最快被质询的方式。将请求分散开来,分散查询词而非全速翻页单一词条。
- 依赖轮换。住宅 IP 池将请求分散到众多真实用户地址上,使单个地址不会触发限制。Crawling API 为你处理这一点;如果你自己搭建技术栈,这是需要做好的部分。
- 读取状态码。开始返回挑战或验证页面的运行在告诉你当前的频率或 IP 层级已不再足够。将其视为退出信号,而非需要忽略的噪音。
- 字段为空时重新检查。百度会定期更改其标记。如果结果解析停止工作,在开发者工具中打开线上页面并更新选择器。
更广泛的策略手册,请参阅如何在不被封锁的情况下抓取网站和关于如何在网络爬取中绕过验证码的深入分析。如果你需要的某个百度页面依赖脚本渲染,我们关于爬取 JavaScript 网站的指南解释了渲染为何重要以及如何开启。
爬取百度合法吗?
爬取百度是否被允许,取决于百度的服务条款、你所在的司法管辖区以及你对数据的用途。百度的条款对自动访问有所限制,因此无论你的工具多么谨慎,爬取都可能与这些条款相抵触。这里的任何代码都不能改变这一点;它只是让技术部分能够运行。请阅读百度的条款及其 robots.txt,并将两者视为你收集内容的边界。
几条值得坚守的原则。只收集公开搜索结果数据:任何人在结果页面上无需账号即可看到的标题、链接、摘要和排名。保持请求量足够低,避免给百度服务器造成压力,并以适度的节奏进行爬取而非全速运行。百度没有为此类访问提供广泛可用的官方 SERP API,因此没有经过认可的高容量端点可以依赖,这更是应该在规模上保持适度、尊重站点明确规则的理由。
本指南故意将范围限定在公开搜索结果页面,因为这是保持工作可辩护性的界限。它不涵盖登录后的任何内容、账号或个人数据,或从链接目标中提取的受版权保护的媒体内容。仅限公开 SERP 数据。如果你的项目需要的不止于此,官方数据协议才是正确路径,而非更聪明的爬虫。
核心要点
- 百度对地理位置敏感。来自境外数据中心 IP 的请求会得到不同的页面或被拦截,因此你需要受信任的住宅地址才能看到真实结果。
- Crawling API 在真实 IP 后面抓取。发送 URL,它在服务端轮换住宅 IP,在需要时渲染,并返回完整的 HTML 供你解析。
-
BeautifulSoup 负责提取。选择每个
div.title-box_4YBsj,然后从中读取标题、链接、摘要和排名,并预期带后缀的类名会漂移。 -
用 pn 偏移量分页。以 10 为倍数增加
pn,深入更多结果,并在各页之间通过 sleep 控制请求节奏。 - 坚守公开数据。遵守百度的服务条款和 robots.txt,由于没有开放的官方 SERP API,保持低请求量,绝不触碰账号或个人数据。
常见问题
为什么在百度上普通请求会失败或返回错误页面?
百度从中国境内提供服务,并根据请求 IP 调整返回内容,所以来自境外数据中心地址的请求可能得到地区门控、部分内容或验证页面,而不是你在自己浏览器中看到的结果。它还会标记看起来不像真实浏览器的流量。通过 Crawling API 抓取(它使用轮换住宅 IP)可以让请求看起来像普通访客,因此你能得到真实的结果页面。
我可以用 Python 爬取百度搜索结果吗?
可以。使用 requests 和 BeautifulSoup,你可以抓取结果页并提取标题、链接、摘要和排名。Crawling API 充当桥梁,让你的请求从受信任的 IP 到达百度,使请求能够顺畅处理而不被拦截。更广泛的 Python 入门,请参阅我们关于用 Python 抓取网站的指南。
我可以从百度结果页提取哪些字段?
本教程从每条自然结果中提取四个字段:标题、目标链接、显示的摘要以及页面上的排名。你还可以从 wd 输入中获取页面标题和回显的搜索查询。坚守公开搜索结果数据,避免任何需要登录的内容。
爬取百度需要 JavaScript 渲染吗?
通常主要结果不需要,所以本指南中的基本抓取就足够了。如果你遇到需要浏览器填充内容的页面,Crawling API 提供 JavaScript 渲染选项,以真实浏览器的方式抓取页面。我们关于用 Python 爬取 JavaScript 页面的指南说明了何时这是必要的。
如何翻页获取更多百度结果?
使用 pn 查询参数,它是以 10 为倍数的偏移量:pn=10 是第二页,pn=20 是第三页,以此类推。用偏移量构建每个页面 URL,通过 Crawling API 抓取,用同一个函数解析,并在请求之间暂停几秒,使爬取保持适度而非过于激进。
我的选择器返回空值,发生了什么变化?
几乎可以肯定是百度的标记变了。类名如 title-box_4YBsj 携带在百度重新部署前端时会变化的生成后缀,所以上个月有效的选择器可能现在已经失效。在浏览器开发者工具中重新检查线上结果页并更新选择器。定期进行选择器维护对任何生产级爬虫来说都是正常的。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

