Apple App Store 收录了数百万条应用列表,每一条公开列表都带有产品团队、营销人员和分析师所关心的那类结构化细节:应用名称、开发者、星级评分、评论数量、所属类别以及价格。手动提取这些数据无法扩展到几个应用以上,因此实际可行的做法是构建一个 Python 版的 App Store 爬虫,它会获取一个列表页面,解析出你想要的字段,并交给你一条整洁的记录。
本指南将带你完整走完这个过程。你将通过 Crawling API 获取已渲染的 App Store 列表页面,使用官方的 crawlbase Python 客户端,用 BeautifulSoup 解析标记,并为每个应用提取一条结构化记录。整个演示的范围都限定在公开列表数据上,靠近文末的法律部分并非套话,请在将其指向真实体量之前仔细阅读。
你将构建什么
一个 Python 脚本,它接收一个公开的 App Store 应用 URL,通过 Crawling API 获取已渲染的 HTML,并提取一条结构化记录。我们将用一个知名的免费应用作为贯穿全文的示例,并提取以下字段:
- 应用名称列表标题,例如 "Microsoft Authenticator"。
- 开发者发布者或销售方,例如 "Microsoft Corporation"。
- 评分平均星级分数,例如 "4.8"。
- 评论数该分数背后的评分数量。
- 类别该应用所归属的 App Store 类别。
- 价格所列价格,若没有价格则为 "Free"。
为什么普通抓取在 App Store 上很吃力
如果你用一个简单的 HTTP 客户端请求一个 App Store 列表 URL,会遇到两个问题。首先,列表页面依赖 JavaScript 来填充部分内容,因此你拿回的原始 HTML 可能缺少你想要的字段。其次,Apple 会监控自动化流量:数据中心 IP 以及看起来不像真实浏览器的请求模式,在到达有用的标记之前就会遭到质询或限流。
因此,一个能工作的 App Store 爬虫需要在一个请求中具备两样东西:一个真正渲染页面的浏览器,以及一个被平台识别为真实访客的 IP。你可以用无头浏览器加上一批轮换住宅代理自己搭建这套组合,但把它们拼接起来并维持其健康运转是工作的大头。Crawling API 将两者整合到一次调用中:你把 URL 发给它,它在一个可信 IP 背后渲染页面,并返回完成的 HTML 供你解析。
Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 会先在真实浏览器中渲染页面。App Store 列表的某些元素依赖客户端渲染,因此在这里 JS token 是更稳妥的选择。如果某个字段在使用普通 token 时返回为空,切换到 JS token,渲染后的标记就会包含它。
先决条件
在编写任何代码之前,你需要准备好几样东西。它们都不会花太长时间。
基础 Python。你应该能够轻松地编写和运行一个 Python 脚本,并用 pip 安装包。如果你是这门语言的新手,官方 Python 文档和任何入门课程都能让你达到本教程所假定的水平。
Python 3.8 或更高版本。用 python --version 确认你的版本。如果你没有安装,可从 python.org 或通过像 Anaconda 这样的发行版安装它。
一个 Crawlbase 账户和 token。注册、打开你的控制台,并从账户文档页面复制你的 token。对 App Store 列表使用 JavaScript(JS)token。把 token 当作密码对待:它用于认证你的请求,所以不要把它放进版本控制。
搭建项目
创建一个虚拟环境以保持项目依赖相互隔离,然后安装爬虫所需的两个库。
python --version python -m venv appstore_env source appstore_env/bin/activate pip install crawlbase beautifulsoup4
在 Windows 上,请用 appstore_env\Scripts\activate 而不是 source 那一行来激活环境。两个依赖完成全部工作:crawlbase 是 Crawling API 的官方客户端,而 beautifulsoup4 解析返回的 HTML,让你能按 CSS 选择器提取单个字段。如果你想复习一下这个解析库,Python 中的 BeautifulSoup 指南涵盖了下面用到的选择器。
第 1 步:获取已渲染的列表
先从拿到完成的页面开始。导入 CrawlingAPI 类,用你的 token 初始化它,并请求应用 URL。在解析之前检查状态码,可以让失败响亮地暴露出来,而不是悄无声息。
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 3000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": page_url = "https://apps.apple.com/us/app/microsoft-authenticator/id983156458" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
两个等待选项有助于处理那些渲染较晚的元素。ajax_wait 告诉 API 等待异步内容加载完毕,而 page_wait 会在加载后固定等待一定的毫秒数,以便渲染较晚的部分在页面被捕获之前出现。三秒是一个合理的起点;如果字段返回为空,就把它调高。用 python scraper.py 运行脚本,你应该会看到真实的列表标记,而不是一个被剥离的空壳。这就在你编写任何一个选择器之前确认了渲染是有效的。
一个 App Store 列表需要在一次调用中,在可信 IP 背后获得一个已渲染的页面。Crawling API 接收你的 token,在真实浏览器中运行页面,在服务端轮换住宅 IP,并交给你完成的 HTML,于是你省去了自己运行一支无头浏览器集群和一个代理池的麻烦。先在免费套餐上将它指向一个公开的应用列表。
第 2 步:用 BeautifulSoup 解析应用字段
有了已渲染的 HTML,把它加载进 BeautifulSoup,并按选择器逐一提取每个字段。App Store 列表以一种可预测的结构布置核心细节,因此你可以把名称、开发者、评分、评论数、类别和价格映射到各自的选择器上。把提取逻辑封装进辅助函数,这样一个缺失的字段就不会让运行崩溃。
from bs4 import BeautifulSoup def text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def scrape_app(html): soup = BeautifulSoup(html, "html.parser") rating_text = text_of(soup, ".we-rating-count.star-rating__count") stars, reviews = (rating_text.split(" • ") + [None, None])[:2] if rating_text else (None, None) return { "name": text_of(soup, ".product-header__title"), "developer": text_of(soup, ".product-header__identity a"), "rating": stars, "review_count": reviews, "category": text_of(soup, ".information-list__item__term:-soup-contains('Category') + dd a"), "price": text_of(soup, ".app-header__list__item--price"), }
text_of 辅助函数一次性做了两件有用的事:它查询单个元素,并在元素缺失时返回 None,而不是在对一个空值调用 .get_text() 时抛出异常。这让提取在某个给定列表上缺少某个字段时仍保持健壮。App Store 上的评分字符串把星级分数和评论数都打包进一个值里,用一个圆点分隔,因此我们把它分割一次并分配给两个部分,在格式不匹配时默认为 None。
Apple 的类名(we-rating-count 标记、product-header 元素以及信息列表行)会在不发出通知的情况下变化。把上面的选择器当作一个起步模板,而不是一份契约。当某个字段返回为 None 时,在浏览器的开发者工具中重新检查实时列表并更新选择器。定期维护选择器对任何生产级爬虫来说都很正常,并不是哪里出了问题的迹象。
第 3 步:把它们整合起来
现在把获取和解析接入一个可运行的脚本。获取已渲染的 HTML,把它交给解析器,并打印这条结构化记录。
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 3000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def scrape_app(html): soup = BeautifulSoup(html, "html.parser") rating_text = text_of(soup, ".we-rating-count.star-rating__count") stars, reviews = (rating_text.split(" • ") + [None, None])[:2] if rating_text else (None, None) return { "name": text_of(soup, ".product-header__title"), "developer": text_of(soup, ".product-header__identity a"), "rating": stars, "review_count": reviews, "category": text_of(soup, ".information-list__item__term:-soup-contains('Category') + dd a"), "price": text_of(soup, ".app-header__list__item--price"), } def main(): page_url = "https://apps.apple.com/us/app/microsoft-authenticator/id983156458" html = crawl(page_url) if not html: return data = scrape_app(html) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
输出是什么样的
用 python scraper.py 运行完整脚本,你会得到该应用的一条整洁的结构化记录,可以直接写入 JSON、CSV 或数据库。
{ "name": "Microsoft Authenticator", "developer": "Microsoft Corporation", "rating": "4.8", "review_count": "339.5K Ratings", "category": "Productivity", "price": "Free" }
扩展到多个应用
一条列表只是演示;真正的任务会运行在一个应用清单上。形态保持不变:维护一份应用 URL 清单,通过 Crawling API 逐一获取每个,用同一个函数解析它,并收集这些数据行。因为每条列表都共享相同的结构,你已经写好的解析器无需改动就能适用于所有列表。在大规模下真正重要的细节是节奏:把请求拉开间隔,这样你读起来像是一股正常访客的流量,而不是一个紧凑的循环。
import time apps = [ "https://apps.apple.com/us/app/microsoft-authenticator/id983156458", "https://apps.apple.com/us/app/google-authenticator/id388497605", ] results = [] for url in apps: html = crawl(url) if html: results.append(scrape_app(html)) time.sleep(2) with open("apps.json", "w") as f: json.dump(results, f, indent=2)
请求之间的 time.sleep 调用是刻意为之的。即便 Crawling API 已为你轮换 IP,节奏也能让你的运行保持礼貌且可预测,并给每个已渲染的页面留出返回时间,而不是把请求一个叠一个地堆上去。如果你还想顺带采集每个应用的评论,同样的先获取后解析的模式可以延伸到评论区;抓取客户评论的指南更深入地涵盖了那种形态。
无需抓取的结构化应用数据
当你需要的恰好是访客所见的内容,而又没有返回它的 API 时,抓取公开列表页面是正确的工具。但对于纯粹的结构化应用元数据,Apple 提供了比解析标记更整洁、更稳定的官方渠道,当它们能满足你的需要时,你应该优先选择它们。
- iTunes Search API 和 App Store RSS feeds。Apple 的公开 iTunes Search API 以 JSON 形式返回应用详情(名称、开发者、平均评分、评分数、类别、价格等等),而 App Store RSS feeds 发布顶级免费、付费和热门应用的排名列表。对于列表层级的结构化元数据,这些是你首先应该考虑的。
- App Store Connect API。如果这些应用是你自己的,App Store Connect API 让你在自己的开发者账户下,对你的列表、销售和分析数据拥有第一方访问权,完全无需抓取。
当你需要的数据是官方 feeds 不暴露的,或者当你在跨众多第三方列表组装一个视图时,再动用爬虫。对于 iTunes Search API 和 RSS feeds 已经以 JSON 返回的一切,请先用它们。
保持不被封锁
即便渲染已被处理妥当,Apple 仍会监控形似爬虫的流量。有几个习惯能让一次运行保持健康,它们适用于任何难啃的商业目标。
- 控制请求节奏。在紧凑的循环里猛敲列表是最快被限流的方式。把请求拉开间隔并变换目标,而不是全速爬取同一条路径。
- 依靠轮换。一个住宅 IP 池把请求分散到众多真实用户地址上,因此没有任何单个地址会触发速率限制。Crawling API 为你处理这件事;如果你自建技术栈,这就是要做好的那部分。
- 读懂状态码。一次开始返回质询或错误的运行,是在告诉你当前的速率或 IP 层级已经不够了。把它当作放慢的信号,而不是可以忽略的噪声。
关于更全面的操作手册,请参阅深度文章如何在网页抓取中绕过验证码以及用 Python 抓取 JavaScript 页面的指南。如果你更愿意把自己的流量路由通过一个轮换池,而不是使用托管 API,Smart AI Proxy(也称 AI Proxy)会以一个即插即用的代理端点为你提供相同的住宅 IP 轮换。
抓取 App Store 是否合法?
抓取 App Store 是否被允许,取决于 Apple 的服务条款、你所在的司法管辖区以及你对数据的使用方式。Apple 的条款限制自动化访问,因此无论你的工具多么谨慎,抓取行为都可能违反这些条款。本文中的代码没有改变这一点,它只是让技术部分得以实现。请阅读 Apple 的服务条款和 App Store 的 robots.txt,并把两者都当作你采集范围的边界。
有几条原则值得坚守。仅采集公开的应用列表数据:任何人无需账户即可看到的应用名称、开发者、评分、评论数、类别和价格。尊重 Apple 声明的速率预期,保持请求量足够低,不要给它的服务器造成压力。本指南刻意将范围限定在公开列表页面,因为这是让工作保持在可辩护范围内的边界线。它不涵盖账户或个人数据、登录墙后的页面、任何需要登录才能访问的内容,也不涵盖任何绕过身份验证的尝试。那些在这里都属于范围之外,触及它们会违反 Apple 的条款。
对于结构化的应用数据,请优先选择 Apple 的官方渠道。App Store Connect API 让你对自己的应用拥有第一方访问权,而公开的 iTunes Search API 和 App Store RSS feeds 无需抓取任何一个页面就能以 JSON 返回应用元数据。当某个官方 feed 能满足你的需要时,它就是正确的工具;本指南中的爬虫面向的是 feeds 不暴露的公开列表数据,仅此而已。
核心要点
- 先渲染再解析。App Store 列表依赖 JavaScript,因此普通抓取可能返回不完整的标记;带 JS token 的 Crawling API 会先渲染页面。
-
渲染与可信 IP 一并到位。一次 Crawling API 调用同时处理两者;
ajax_wait和page_wait控制它等待迟到内容的时长。 - 由 BeautifulSoup 完成提取。把名称、开发者、评分、评论数、类别和价格映射到当前的选择器上,并预期那些选择器会漂移。
- 通过控制节奏循环 URL 来扩展。同一个解析器适用于每条列表,因此一个真正的任务就是一份应用链接清单外加请求之间的短暂休眠。
- 元数据优先选用官方渠道。iTunes Search API、App Store RSS feeds 和 App Store Connect API 无需抓取即可返回结构化数据;其余情况则坚守公开列表数据。
常见问题
为什么普通抓取从 App Store 返回的数据不完整?
因为 App Store 列表用 JavaScript 在客户端渲染其部分内容。一个原始的 HTTP 请求可能拿回页面外壳,却缺少评分或类别之类的字段,因为那些字段只有在页面的脚本于浏览器中运行之后才会填充。Crawling API 的 JS token 会先渲染页面,因此当 BeautifulSoup 解析它们时,这些字段已经存在。
对 App Store 我需要普通 token 还是 JS token?
使用 JS token。普通 token 获取静态 HTML,这在 App Store 上可能会让某些列表字段为空。JS token 在交回 HTML 之前先在真实浏览器中渲染页面,因此当你解析时,名称、开发者、评分、评论数、类别和价格都已存在。
Apple 有用于应用数据的官方 API 吗?
有。公开的 iTunes Search API 以 JSON 形式返回应用详情(名称、开发者、平均评分、评分数、类别、价格等等),而 App Store RSS feeds 发布顶级应用的排名列表。对于你自己的应用,App Store Connect API 让你对列表和分析数据拥有第一方访问权。对于结构化元数据,请优先选用这些官方渠道,只有当你需要 feeds 不暴露的公开列表数据时,才动用抓取。
我的选择器返回 None。发生了什么变化?
几乎可以肯定是 Apple 的标记变了。we-rating-count 标记、product-header 元素以及信息列表行会在不发出通知的情况下变化,因此上个月还能用的选择器这个月可能就失效了。在浏览器的开发者工具中重新检查一个实时列表并更新选择器。定期维护选择器对任何生产级爬虫来说都很正常。
我能从 App Store 抓取账户或个人数据吗?
不能,本指南也不涵盖这一点。账户数据、购买历史以及任何登录后才能看到的内容都不是公开数据。抓取登录墙后的内容,或绕过身份验证去触及它,在这里都属于范围之外,并且会违反 Apple 的条款。对于关于你自己应用的第一方数据,App Store Connect API 是正确的途径。
在抓取 App Store 时我如何避免被封锁?
保持你的单 IP 请求速率低,变换你的目标而不是循环同一条路径,并通过轮换住宅 IP 进行路由,这样没有任何单个地址会触发速率限制。Crawling API 为你管理轮换和一个可信 IP 池;如果你自建技术栈,那就是要投入的那部分。盯住状态码,当你开始看到质询时就放慢。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
