Bing 是全球第二大搜索引擎,也是大量 Windows 桌面设备的默认搜索引擎,因此其公开结果页面是 Google 之外一个很有价值的参照来源。无论是关键词研究、SEO 排名追踪、市场分析,还是为模型提供真实世界的搜索数据,Bing SERP 都承载着你所需要的结构化信号:标题、链接、摘要,以及它们出现的顺序。
本指南介绍如何用可靠的方式用 Python 抓取 Bing 搜索结果。你将构建一个小型可运行爬虫,通过 Crawling API 获取渲染后的结果页面,用 BeautifulSoup 解析每条自然搜索结果,处理翻页,并将数据导出为 JSON 和 CSV。整个教程仅涉及公开搜索结果数据,即任何人无需账号即可看到的内容。靠近结尾的合法性章节在你将此爬虫指向任何真实量级流量之前,值得仔细阅读。
你将构建的内容
一个 Python 脚本,接收一个公开的 Bing 搜索 URL,通过 Crawling API 获取 HTML,并为页面上的每条自然搜索结果提取一条结构化记录。我们将使用一个示例查询作为贯穿全文的例子,并从每条结果中抽取以下字段:
- Position(排名)结果在页面上的位置,从顶部开始计数。
- Title(标题)结果的可点击标题文本,即列表中显示的内容。
- Link(链接)结果所指向的目标 URL。
- Description(描述)标题下方显示的摘要或摘录。
除每条结果的字段外,脚本还会跨越多个结果页面进行抓取,并将所有内容写入 JSON 文件和 CSV 文件,让数据可直接导入电子表格或数据库。
为什么普通请求在 Bing 上会失败
如果你用脚本向 Bing 结果 URL 发起裸 HTTP 请求,你很少能得到在自己浏览器中看到的干净页面。两个因素对你不利。首先,Bing 依赖 JavaScript 来组装结果页面的部分内容,因此原始抓取可能返回一个缺少你真正想要的列表的外壳。其次,Bing 会监控自动化流量:看起来不像真实浏览器的请求会遭到验证、被送上核查页面,或在到达结果之前被限流。
因此,一个能正常工作的 Bing 爬虫需要在单次请求中同时具备两点:平台认为是真实访客的 IP,以及当页面依赖脚本时能够渲染它的浏览器。你可以自己用无头浏览器加上轮换住宅代理池来实现,但维护这些基础设施才是大部分工作所在。Crawling API 将这两者合并为一次调用:你发送 URL,它从可信 IP 获取并在必要时渲染,返回可供你解析的完整 HTML。
Bing 混合了服务端渲染和脚本渲染的内容,并会对每个请求评分,判断其是否像浏览器行为。从住宅 IP 发起的渲染请求看起来像普通访客,会返回完整的列表;而来自数据中心的裸请求往往返回精简页面或验证挑战。Crawling API 在服务端处理这两点,你无需自己运行浏览器集群或准备代理池。从 1,000 次免费请求开始,无需信用卡。
前置条件
在编写任何代码之前,你需要准备好以下几件事。每件都不会花太长时间。
基础 Python 知识。你应该熟悉编写和运行 Python 脚本,以及使用 pip 安装包。如果你对 BeautifulSoup 不熟悉,我们的BeautifulSoup 使用指南涵盖了本教程所假设的解析基础知识。
Python 3.8 或更高版本。使用 python --version 确认你的版本。如果没有,请从 python.org 安装,或通过 Anaconda 等发行版安装。
Crawlbase 账号和 token。注册后,打开控制台,从账号文档页面复制你的请求 token。前 1,000 次请求免费,在用完之前添加账单信息还可额外解锁 9,000 次免费请求。请像对待密码一样保管好 token:它用于验证你的请求,不要将其提交到版本控制系统。
项目设置
创建虚拟环境以隔离项目依赖,然后安装爬虫所需的两个库。
python --version python -m venv bing_env source bing_env/bin/activate pip install requests beautifulsoup4
在 Windows 上,用 bing_env\Scripts\activate 替换 source 那行来激活环境。两个依赖各司其职:requests 向 Crawling API 发送 HTTP 请求,beautifulsoup4 解析返回的 HTML,让你能通过 CSS 选择器提取各个字段。
第一步:通过 Crawling API 获取页面
从获取 HTML 开始。编写一个小型 crawl() 函数,将目标 URL 连同你的 token 发送给 Crawling API,请求 JavaScript 渲染以确保完整列表加载,检查底层页面是否以 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, "javascript": "true"} 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.bing.com/search?q=samsung+s23+ultra" html = crawl(url) print(html[:500])
API 返回一个 JSON 信封,因此你用 json.loads 加载响应,并读取两个字段:original_status 是 Bing 本身返回的状态码,body 是页面 HTML。参数 javascript=true 告知 API 在返回前在真实浏览器中渲染页面,这保证了完整结果列表的存在。对 original_status 进行检查意味着封锁或验证挑战会以异常形式暴露,而不是将垃圾数据送入解析器。示例查询 "samsung s23 ultra" 通过 q 参数传递,这是 Bing 携带搜索词的方式。用 python crawling.py 运行脚本,你应该在前 500 个字符中看到真实的结果标记,这证明获取功能在你编写任何选择器之前就已经正常工作。
那个 original_status 检查每次都读到 200,是因为请求以真实访客身份抵达 Bing,且页面已完整渲染。Crawling API 从轮换的住宅 IP 发起请求,当你传入 javascript=true 时运行 JavaScript,并将完整的 HTML 交给你,让你无需自己运行无头浏览器集群或准备住宅代理池。先在免费套餐上指向一个公开结果 URL 试试。
第二步:用 BeautifulSoup 解析结果
拿到 HTML 后,将其加载到 BeautifulSoup 中,通过选择器提取每条结果。Bing 将每条自然搜索结果包裹在 li.b_algo 列表项中,标题和链接在 h2 a 锚元素内,摘要在 p.b_algoSlug 段落中。在浏览器开发者工具中检查实际页面(右键,然后"检查")以确认当前的类名;下面的选择器与撰写本文时 Bing 的布局一致。
from bs4 import BeautifulSoup def scrape_html(html): soup = BeautifulSoup(html, "html.parser") results = [] for position, block in enumerate(soup.select("li.b_algo"), start=1): link = block.select_one("h2 a") snippet = block.select_one("p.b_algoSlug") if not link: continue results.append({ "position": position, "title": link.get_text(strip=True), "url": link.get("href"), "description": snippet.get_text(strip=True) if snippet else None, }) return results
选择器 li.b_algo 是 Bing 用于每条自然结果的容器,因此遍历这些列表项可以精准获取你想要的列表,并跳过页面的其余装饰。从同一个 h2 a 锚元素中读取标题文本和 href,使得标题和目标链接保持对齐;p.b_algoSlug 则包含每个标题下方显示的描述。enumerate(..., start=1) 在遍历时免费给你排名信号,因此位次来自页面顺序而非脆弱的属性。if not link: continue 的守卫跳过了没有标题锚的块,从而将广告、视频轮播和杂乱标记排除在输出之外。当某条结果没有描述段落时,摘要会回退到 None。
Bing 会定期重新部署其前端,b_algo 和 b_algoSlug 等类名在部署时可能发生变化。请将上面的选择器视为起始模板,而非约定。当某个字段对每条结果都返回空时,在浏览器开发者工具中重新检查实际页面并更新选择器。定期维护选择器对任何生产爬虫来说都是正常的事,不代表哪里出了问题。
第三步:组合脚本
现在将获取和解析两个步骤串联成一个可运行的脚本。抓取渲染后的结果页面,将 HTML 传给解析器,打印结果,并将结构化输出写入 JSON。
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, "javascript": "true"} 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") results = [] for position, block in enumerate(soup.select("li.b_algo"), start=1): link = block.select_one("h2 a") snippet = block.select_one("p.b_algoSlug") if not link: continue results.append({ "position": position, "title": link.get_text(strip=True), "url": link.get("href"), "description": snippet.get_text(strip=True) if snippet else None, }) return results def main(): url = "https://www.bing.com/search?q=samsung+s23+ultra" html = crawl(url) results = scrape_html(html) print(json.dumps(results, indent=2, ensure_ascii=False)) with open("bing_results.json", "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) print(f"Saved {len(results)} results") if __name__ == "__main__": main()
用 python main.py 运行完整脚本。它获取 "samsung s23 ultra" 的结果页面,为每条自然结果提取一条记录,打印结果,并将所有内容写入 bing_results.json。只需这两个函数就足够了:替换 URL 中的查询词,解析器即可处理返回的任何内容。
输出结果示例
你将得到一份有序的结果对象列表,每条都包含排名、标题、链接和描述,可直接写入 JSON、CSV 或数据库。
[ { "position": 1, "title": "Samsung Galaxy S23 Ultra | Samsung US", "url": "https://www.samsung.com/us/smartphones/galaxy-s23-ultra/", "description": "Meet the latest Galaxy S23 Ultra phone, equipped with a built-in S Pen, Nightography camera, and a powerful chip for epic gaming." }, { "position": 2, "title": "Samsung Galaxy S23 Ultra - Full phone specifications", "url": "https://www.gsmarena.com/samsung_galaxy_s23_ultra-12024.php", "description": "Samsung Galaxy S23 Ultra Android smartphone. Announced Feb 2023. Features 6.8 inch display, Snapdragon 8 Gen 2 chipset, 5000 mAh battery." } ]
跨页面和多查询扩展
单个查询的单页只是演示;真正的任务会跨越多次搜索并深入结果更深处。Bing 通过 first 查询参数进行分页,它是以 10 为步长的 1-based 偏移量:first=11 是第二页,first=21 是第三页,以此类推。结构保持不变:构建每个 URL,通过 Crawling API 获取,用同一个函数解析。让长时间运行保持健康的一个关键习惯是控制节奏,所以在请求之间暂停,而不是在紧密循环中连续发射。
import time from urllib.parse import quote_plus query = "samsung s23 ultra" encoded = quote_plus(query) all_results = [] for page in range(3): first = page * 10 + 1 url = f"https://www.bing.com/search?q={encoded}&first={first}" html = crawl(url) all_results.extend(scrape_html(html)) 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 轮换功能。
将结果导出为 CSV
JSON 对代码来说很方便,但 CSV 可以直接在电子表格中打开,这是大多数团队传阅的格式。由于每条结果已经是具有相同键的扁平字典,用标准库写入 CSV 只需几行代码。
import csv def save_csv(results, path="bing_results.csv"): fields = ["position", "title", "url", "description"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(results) print(f"Wrote {len(results)} rows to {path}")
在完成分页抓取后调用 save_csv(all_results),你将得到一个 bing_results.csv,每行对应一条自然结果,顶部有一行表头。使用 DictWriter 配合显式的 fieldnames 列表可以保持列顺序稳定,newline="" 则防止在 Windows 上出现空行。
保持不被封锁
即使已有可信 IP 和渲染能力,Bing 仍会监测爬虫特征的流量。以下几个习惯能让运行保持健康。
- 控制请求节奏。在紧密循环中高频访问结果页面是最快触发验证的方式。分散请求,在查询上变换,而不是全速爬取同一词条的所有页面。
- 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,确保没有单一地址触发限制。Crawling API 为你处理这一切;如果你自己搭建方案,这是最需要做好的部分。
- 读懂状态码。当运行开始返回验证挑战或核查页面时,说明当前速率或 IP 层级已不再足够。将其视为需要回退的信号,而不是可以忽略的噪声。
- 字段为空时重新检查。Bing 会定期更改其标记。如果结果停止解析,在开发者工具中打开实际页面并更新选择器。
更宏观的操作手册请参阅如何抓取网站而不被封锁。如果你需要的 Bing 页面依赖脚本渲染,我们关于抓取 JavaScript 网站的指南解释了渲染为何重要以及如何开启它。关于各大搜索引擎页面结构的整体视角,请参阅Google、Yahoo 和 Bing 的返回内容解析。
抓取 Bing 是否合法?
抓取 Bing 是否被允许,取决于 Microsoft 的服务条款、你所在的司法管辖区,以及你对数据的使用方式。Bing 的条款对自动化访问有所限制,因此无论你的技术手段多么谨慎,抓取行为都可能与这些条款相抵触。这里的任何代码都不会改变这一点,它只是让技术层面的事情能够运作。请阅读 Bing 的条款及其 robots.txt,并将两者视为你采集内容的边界。
有几条底线值得坚守。只收集公开搜索结果数据:任何人无需账号即可在结果页面上看到的标题、链接、描述和排名。将请求量控制在不会给 Bing 服务器造成压力的范围内,控制爬取节奏而不是全速运行。如果你需要大规模搜索数据并希望走合规途径,Microsoft 通过 Azure 提供 Bing Search API,这是以编程方式查询 Bing 结果的官方、受支持方式,也是你的项目需要大量数据或保证质量时的正确选择。
本指南刻意将范围限定在公开搜索结果页面,因为这是保持工作可辩护性的界限。它不涉及登录后的任何内容、账号或个人数据,也不涉及从链接目标抓取的受版权保护的媒体内容。仅限公开 SERP 数据。如果你的项目需要超出此范围的内容,官方数据协议或 Azure Bing Search API 才是正确途径,而不是更聪明的爬虫。
核心要点
-
Bing 混合了渲染和脚本内容。裸请求可能返回精简页面,因此你需要通过 Crawling API 使用
javascript=true来获取完整列表。 - Crawling API 从真实 IP 获取内容。向它发送 URL,它在服务端轮换住宅 IP,在必要时渲染,并返回供你解析的完整 HTML。
-
BeautifulSoup 负责提取。选择每个
li.b_algo,从h2 a读取标题和链接,从p.b_algoSlug读取摘要,并预期类名会漂移。 -
用 first 偏移量翻页。以 10 为步长递增
first(1-based)来深入结果,并在页面间加入延迟控制节奏。 - 坚守公开数据。遵守 Bing 的服务条款和 robots.txt,保持低请求量,需要合规规模时使用官方 Azure Bing Search API。
常见问题
为什么普通请求在 Bing 上失败或返回错误页面?
Bing 依赖 JavaScript 来组装结果页面的部分内容,因此原始抓取可能返回一个缺少列表的外壳。它还会标记不像真实浏览器的流量,并可能以验证或核查页面回应。通过 Crawling API 使用 javascript=true 进行抓取(从轮换的住宅 IP 渲染页面)可以让请求看起来像普通访客,从而获取真实的结果页面。
我可以用 Python 抓取 Bing 搜索结果吗?
可以。使用 requests 和 BeautifulSoup,你可以获取结果页面并提取标题、链接、描述和排名。Crawling API 充当桥梁,将你的请求从可信 IP 送达 Bing 并渲染它,让请求顺畅处理而不被封锁。关于更全面的 Python 入门,请参阅我们的用 Python 抓取网站指南。
我可以从 Bing 结果页面提取哪些字段?
本教程从每条自然结果中提取四个字段:页面排名、标题、目标链接和显示的描述。标题和链接来自每个 li.b_algo 块内的 h2 a 锚元素,描述来自 p.b_algoSlug。坚守公开搜索结果数据,避免任何登录后的内容。
抓取 Bing 需要 JavaScript 渲染吗?
通常需要,因为 Bing 通过脚本填充结果页面的部分内容。这里的示例向 Crawling API 传入 javascript=true,以便页面在返回前在真实浏览器中渲染,从而保证完整列表的存在。我们的用 Python 抓取 JavaScript 页面指南介绍了何时需要渲染。
如何翻页获取更多 Bing 结果?
使用 first 查询参数,它是以 10 为步长的 1-based 偏移量:first=11 是第二页,first=21 是第三页,以此类推。用偏移量构建每个页面 URL,通过 Crawling API 获取,用同一个函数解析,并在请求之间暂停几秒,控制爬取节奏而不是连续高频访问。
抓取 Bing 与抓取 Google 有什么不同?
方法相同,只是选择器和翻页参数不同。Bing 使用 li.b_algo 块和 first 偏移量,而 Google 使用自己的结果容器和 start 偏移量。如果你也需要处理 Google,请参阅我们关于抓取 Google 搜索页面的指南,以及关于抓取 Yandex 搜索结果的等效选择器和步骤。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

