Yellow Pages 是网络上历史最悠久的商业目录之一,至今仍是本地商业数据的密集来源:商家名称、公开电话号码、街道地址、商家所属类别,以及指向其自身网站的链接。对于销售线索开发、市场调研或构建区域服务提供商数据集,这些公开 listing 正是你所需要的结构化信息。实际操作的难点不在于概念,而在于执行:Yellow Pages 会监控爬虫形态的流量,对简单的循环请求会很快进行限速或验证拦截。

本指南将演示如何以可靠的方式使用 Python 抓取 Yellow Pages 的商业 listing。你通过 Crawling API 获取渲染后的搜索结果页面,用 BeautifulSoup 解析每条结果以提取名称、电话、地址、类别和网站,然后遍历分页以覆盖完整的结果集。本文所有内容都限定在公开的商业目录数据范围内,文末的合法性部分不是套话,请在将其应用于真实抓取量之前务必阅读。

你将构建的内容

一个小型 Python 爬虫,接受搜索查询词和地点,通过 Crawling API 获取渲染后的 Yellow Pages 搜索结果页面,并为页面上的每家商家提取结构化记录。运行示例为"洛杉矶,加州"的"Information Technology"商家,我们从每条 listing 中提取以下字段:

  • Business name listing 的主要标识名称。
  • Phone 卡片上显示的公开联系电话。
  • Address 公开街道地址,用于任何地理分析。
  • Category 该 listing 所属的商业类别。
  • Website 当 listing 中提供时,指向商家自身网站的链接。

Yellow Pages 搜索页面的结构

Yellow Pages 搜索由两个 URL 参数驱动:查询词使用 search_terms,地点使用 geo_location_terms。提交搜索后,你会进入一个结果页面,每家商家都有一张独立的卡片。卡片包含作为标题链接的名称、各自区块中的电话和地址、类别列表,以及对于已付费或已认领 listing 的商家,还有一个外部网站链接。

结果横跨多个页面。Yellow Pages 使用 URL 中的 page 参数在页面间切换,这使得分页只需递增一个整数,而无需追踪动态的"加载更多"行为。这种可预测性让同一个解析器在你让它在一个页面上正常工作后,无需任何修改就能在每个页面上运行。

为什么直接获取会遇到困难

你可以用 requests 库访问 Yellow Pages 的搜索 URL,运气好时能拿到 HTML。问题出在批量请求时。Yellow Pages 部署了反爬虫防御:它按 IP 进行限速,对看起来是自动化的流量提供 CAPTCHA,并封锁以机器式密集模式请求页面的数据中心地址。从你的笔记本发出单个请求可能成功;从同一 IP 发出数百个请求则不会。

因此,一个真正能完成任务的爬虫需要请求被识别为来自可信 IP 的真实访客。你可以自行构建一个轮换住宅代理池及其维护配套,但维护这套技术栈本身就是主要的工作量。Crawling API 将其整合为一次调用:你发送 URL,它在服务端通过住宅 IP 路由请求并处理反爬虫层,然后返回供你解析的 HTML。

使用哪种 token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 则先在真实浏览器中渲染页面。Yellow Pages 在初始 HTML 中就提供了 listing 数据,因此这里使用普通 token 是正确的选择,且每次请求的成本更低。只有当目标开始在客户端渲染 listing 时,才需要切换到 JS token。

前提条件

首先需要准备好以下几项,每项都不会花太长时间。

Python 基础。 你应该能够运行脚本并使用 pip 安装包。如果你对选择器还不熟悉,如何使用 BeautifulSoup 的入门指南深入涵盖了解析方面的内容。

Python 3.8 或更高版本。 使用 python --version 确认。如果尚未安装,请从 python.org 或 Anaconda 等发行版进行安装。

Crawlbase 账号和 token。 注册后打开控制台,从账号文档页面复制你的普通 token。前 1,000 次请求免费,无需信用卡。请像对待密码一样保护你的 token,不要将其提交到版本控制系统。

搭建项目

创建虚拟环境以隔离依赖,然后安装爬虫所需的两个库。

bash
python --version

python -m venv yellowpages_env
source yellowpages_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,使用 yellowpages_env\Scripts\activate 替代 source 那行来激活环境。两个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过 CSS 选择器提取各个字段。

第 1 步:获取渲染后的搜索页面

首先获取一个结果页面。根据查询词和地点构建搜索 URL,导入 CrawlingAPI 类,用你的 token 初始化,然后请求该 URL。在解析前检查状态可以让失败情况显式暴露,而非悄无声息。

python
from urllib.parse import urlencode
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def build_url(query, location, page=1):
    base = "https://www.yellowpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    if response["headers"]["pc_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['pc_status']}")
    return None

if __name__ == "__main__":
    url = build_url("Information Technology", "Los Angeles, CA")
    html = crawl(url)
    print(html[:500] if html else "No HTML returned")

注意状态检查读取的是响应头中的 pc_status,这是请求的 Crawlbase 状态,与上游 HTTP 状态码有所区别。使用 python scraper.py 运行脚本,你应该看到真实的结果标记,而非验证页面。这证明在编写任何选择器之前,获取路径已经正常工作。

Crawlbase Crawling API

Yellow Pages 按 IP 进行限速,并对爬虫形态的流量发起验证挑战。Crawling API 在服务端通过轮换住宅 IP 路由每次请求,处理 CAPTCHA 和封锁,并返回解析就绪的 HTML,因此你无需自行运行代理池和重试机制。先在免费套餐上对公开搜索页面进行测试。

第 2 步:使用 BeautifulSoup 解析 listing

获得结果页面后,将其加载到 BeautifulSoup 并遍历结果卡片。每张卡片位于一个可预测的容器下,名称、电话、地址、类别和网站都对应各自的选择器。以防御性方式读取每个字段,在元素缺失时返回 None,可防止某个缺失值导致整个运行崩溃。

python
from bs4 import BeautifulSoup

def text_of(node):
    return node.get_text(strip=True) if node else None

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div.search-results.organic div.result")
    listings = []

    for card in cards:
        name = card.select_one("a.business-name")
        phone = card.select_one("div.phone")
        address = card.select_one("div.adr")
        category = card.select_one("div.categories")
        website = card.select_one("a.track-visit-website")

        listings.append({
            "name": text_of(name),
            "phone": text_of(phone),
            "address": text_of(address),
            "category": text_of(category),
            "website": website["href"] if website else None,
        })

    return listings

text_of 辅助函数查询节点,在节点不存在时返回 None,而不是对空内容调用 .get_text() 时抛出异常。这使得提取具有弹性:并非每条 listing 都有网站链接或整洁的电话区块,缺失字段应在记录中留下 None,而不是中断循环。网站从锚点的 href 读取,而非其文本,因此单独处理。

选择器会漂移

上述类名(resultbusiness-nameadrcategoriestrack-visit-website)反映当前的 Yellow Pages 标记,该标记随时可能在不通知的情况下发生变化。将这些选择器视为起始模板,而非合同。当某个字段对所有 listing 都返回 None 时,请在浏览器开发者工具中重新检查实时结果页面并更新选择器。定期维护选择器对任何生产级爬虫来说都是正常的,并不意味着出了问题。

第 3 步:整合代码

现在将获取和解析整合到一个可运行的单页脚本中。构建 URL,获取 HTML,将其传给解析器,并打印结构化记录。

python
import json
from urllib.parse import urlencode
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def build_url(query, location, page=1):
    base = "https://www.yellowpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    if response["headers"]["pc_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['pc_status']}")
    return None

def text_of(node):
    return node.get_text(strip=True) if node else None

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div.search-results.organic div.result")
    listings = []

    for card in cards:
        website = card.select_one("a.track-visit-website")
        listings.append({
            "name": text_of(card.select_one("a.business-name")),
            "phone": text_of(card.select_one("div.phone")),
            "address": text_of(card.select_one("div.adr")),
            "category": text_of(card.select_one("div.categories")),
            "website": website["href"] if website else None,
        })

    return listings

def main():
    url = build_url("Information Technology", "Los Angeles, CA")
    html = crawl(url)
    if not html:
        return
    data = extract_listings(html)
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

输出示例

使用 python scraper.py 运行完整脚本,你将获得一个整洁的结构化记录列表,可直接写入 JSON、CSV 或数据库。

json
[
  {
    "name": "L. A. Computer Works",
    "phone": "(310) 277-9799",
    "address": "2355 Westwood Blvd, Los Angeles, CA 90064",
    "category": "Computer Technical Assistance and Support Services",
    "website": "http://lacomputerworks.com"
  },
  {
    "name": "Desktop Conquest",
    "phone": "(213) 321-1869",
    "address": "Los Angeles, CA 90057",
    "category": "Computer System Designers and Consultants",
    "website": null
  }
]

没有认领网站的 listing 将返回 "website": null,这是预期行为,也正是解析器以防御性方式读取每个字段而非假设所有键都存在的原因。

第 4 步:处理多个结果页面的分页

一个页面只是演示;真实任务需要覆盖完整的结果集。由于 Yellow Pages 通过 URL 中的 page 参数暴露结果页,遍历页面只需对整数范围进行循环。同样的 build_urlextract_listings 函数无需修改即可复用,因此分页只是一个在请求之间控制节奏的外层循环。

python
import time

def scrape_all_pages(query, location, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        url = build_url(query, location, page)
        html = crawl(url)
        if not html:
            print(f"Stopping at page {page}: no HTML")
            break
        listings = extract_listings(html)
        if not listings:
            print(f"No results on page {page}; reached the end")
            break
        all_listings.extend(listings)
        print(f"Page {page}: {len(listings)} listings")
        time.sleep(2)
    return all_listings

if __name__ == "__main__":
    rows = scrape_all_pages("Information Technology", "Los Angeles, CA", max_pages=5)
    with open("yellow_pages.json", "w") as f:
        json.dump(rows, f, indent=2)
    print(f"Saved {len(rows)} listings")

两个细节使这个循环适用于生产环境。当某页没有返回 listing 时提前停止,这样你就不会在最后一页之后浪费请求;请求之间休眠两秒,使运行不会以密集突发的形式到达。根据你的抓取量调整 max_pages 和休眠时间;越慢,越不引人注意。

保持不被封锁

即使有 Crawling API 处理 IP 轮换和反爬虫层,一些习惯仍有助于保持抓取任务的顺利运行,这些习惯适用于任何目录目标。

  • 控制请求节奏。 上面的休眠不是装饰性的。紧密循环是被限速最快的方式;分散请求看起来更像是正常流量。
  • 善用轮换。 住宅 IP 池能将请求分散到许多真实用户地址,使单个 IP 不触发限速。Crawling API 为你处理这个问题;如果你自建技术栈,这是最需要做好的部分。关于更多背景,请参阅轮换 IP 地址指南
  • 读懂状态码。 如果某次运行开始返回验证挑战或错误,这是在告诉你当前的请求频率过于激进。把它当作退后重试的信号,而非可以忽略的噪音。

更完整的方法论,请参阅如何在不被封锁的情况下抓取网站以及网络抓取中如何绕过 CAPTCHA 的深度指南。当你将此扩展到许多查询词和地点时,大规模网络抓取中的模式涵盖了队列和存储方面的内容。如果你更希望通过自己的轮换代理池来路由流量,而不是使用托管 API,Smart AI Proxy 可以以直连代理端点的形式提供相同的住宅轮换能力。

抓取 Yellow Pages 是否合法?

抓取 Yellow Pages 是否被允许,取决于该网站的服务条款、你所在的司法管辖区以及你对数据的用途。这里的任何代码都无法改变这一点;它只是使技术层面得以实现。请阅读 Yellow Pages 的服务条款及其 robots.txt,并将两者视为你采集内容和采集速度的边界。

以下是几条值得遵守的原则。只采集公开的商业目录数据:无需登录即可看到的商家名称、公开电话号码、公开地址和类别。尊重网站声明的速率预期,保持合理的请求量,不要给其服务器造成压力。本指南有意将范围限定为公开界面,因为这是使工作具备可辩护性的边界。

此方法不涵盖的内容同样重要。它不触碰任何需要登录的内容,也不绕过任何身份验证或访问控制来获取受限内容;这超出了本文范围,且违反了网站条款。另外请注意,即使每个字段单独来看都是公开的,在某些司法管辖区汇总商业联系数据也可能带来单独的法律义务,因此如果你计划存储、丰富或商业化重用联系数据集,请查阅适用于你的规则,而不要假设"公开"就意味着"不受限制"。

回顾

核心要点

  • Yellow Pages 是结构化目录。 每条搜索结果都是一张包含名称、公开电话、公开地址、类别和可选网站的卡片,由 URL 中的 search_termsgeo_location_terms 参数驱动。
  • 直接获取在批量时会遇到困难。 限速、CAPTCHA 和 IP 封锁会阻止简单的循环;Crawling API 通过住宅 IP 路由请求并在一次调用中返回解析就绪的 HTML。
  • BeautifulSoup 负责提取。 将名称、电话、地址、类别和网站映射到当前选择器,以防御性方式读取每个字段,并预期这些选择器会随时间漂移。
  • 分页是对 page 参数的循环。 在各页复用同一解析器,在空页时提前停止,并在请求之间休眠以控制运行节奏。
  • 坚守公开数据。 遵守服务条款和 robots.txt,切勿触碰需要登录的内容,并记住汇总联系数据在不同司法管辖区可能带有其自身的义务。

常见问题

Yellow Pages 需要普通 token 还是 JS token?

普通 token。Yellow Pages 在初始 HTML 中就提供了 listing 数据,因此普通 token 获取即可返回可解析的标记,且每次请求的成本更低。JS token 先在真实浏览器中渲染页面,只有当目标在页面到达后才在客户端加载 listing 时才需要使用。从普通 token 开始,只有在所有字段都返回空时才切换。

如何处理 Yellow Pages 上的分页?

Yellow Pages 通过 URL 中的 page 参数暴露结果页,因此你可以对整数范围进行循环,每页构建一个 URL,并对每个 URL 运行同一解析器。当某页返回零个 listing 时停止,这标志着结果集的结束;请求之间休眠几秒,使运行不会以密集突发的形式到达。

我的选择器返回 None。是什么改变了?

几乎可以肯定是 Yellow Pages 的标记。resultbusiness-nametrack-visit-website 等类名随时可能在不通知的情况下发生变化,因此上个月还能用的选择器现在可能已经失效。请在浏览器开发者工具中重新检查实时结果页面并更新选择器。定期维护选择器对任何生产级爬虫来说都是正常的,并不意味着出了问题。

为什么有些 listing 没有网站?

并非每家商家都在 Yellow Pages 上认领或链接自己的网站,因此这些卡片上根本没有网站锚点。解析器以防御性方式读取该字段,存储 None 而不是抛出异常,因此缺失的网站在记录中留下干净的 null,循环继续进行到下一条 listing。

如何避免在抓取 Yellow Pages 时被封锁?

保持每个 IP 的请求频率低,用延迟控制请求节奏,并通过轮换住宅 IP 路由,使单个地址不触发限速。Crawling API 为你管理轮换和反爬虫层;如果你自建技术栈,这是最值得投入的部分。密切关注状态码,一旦开始看到验证挑战就立即退后重试。

可以将抓取的数据导出到 Excel 吗?

可以。爬虫产生的是字典列表,pandas 只需两行就能将其转换为电子表格:pd.DataFrame(rows).to_excel("yellow_pages.xlsx", index=False)。由于每条记录共享相同的键,列能整洁对齐,同样的结构也可以轻松导出为 CSV 或数据库表。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量