用 API 量化独立开发 App 的 ASO 效果
开头
我个人发布了两款 iOS / Android 应用。一个是「ClassicTimer」,拨盘式厨房计时器;另一个是「かえたお」,用来管理毛巾更换。
应用上线几周后,我最在意的事情变成了:它们真的被用户看到了吗?在浏览器里看 App Store Connect(下文简称 ASC)和 Google Play Console 的管理后台,数字当然能看到。但要追踪“昨天和今天哪里不一样”“那次 ASO 调整到底有没有效果”,就很费劲。
于是我自己做了一个很朴素的分析系统:每天调用两个商店的 API,然后吐出一份 Markdown 报告。结果是,我可以定量比较 ASO 改动前后的表现。比如刷新关键词和分类之后,搜索展示量增长了一个数量级,但 PV 率和点击率反而下降了。数字把下一步该做什么也指了出来。
本文里出现的所有数字都是虚构示例值。它们反映的是实际趋势,但不是具体业绩数据。阅读时可以重点看方法和解读方式。
为什么只看管理后台还不够
ASC 和 Play Console 都有自己的界面,也算好看。但从独立开发者视角用起来,很快会碰到这些限制:
-
过去的记录留不住。今天的数字能看到,但如果要和两周前放在一起比,每次都得手动导出。 -
很难横向比较。iOS、Android,还有多个 App 的数据,想放到一张表里看并不轻松。 -
很难自动处理。比如“这一天更新了元数据,前后 7 天的 impression / tap / DL 发生了什么变化”,只靠后台界面回答这个问题,基本是苦差事。
如果这些东西都能“每天生成 Markdown,然后 commit 到 git”,那就可以用 git diff 直接看差异。这就是我一开始的动机。
整体架构
我面对的是 4 个数据源。
|
|
|
|
|---|---|---|
|
|
/v1/salesReports |
|
|
|
/v1/analyticsReportRequests |
|
|
|
|
|
|
|
|
|
它们各自都有坑,下面按顺序说。
ASC Sales Reports:3 天延迟和 1F / 7F
调用 /v1/salesReports,并设置 reportType=SALES,会返回一份日粒度的 gzip TSV。对免费 App 来说,Product Type Identifier 有下面两个含义:
-
1F:免费 App 的新下载 -
7F:App 内更新
很容易忽略的一点是,它基本有 3 天延迟。用今天的日期去请求会得到 404。所以我在工具里把默认目标日期设成 today - 3,如果这样还是 404,就平稳跳过。
ASC Analytics Reports:ONGOING 请求方式的坑
Apple 的 Analytics Reports 不是想拿就能直接拿。它需要走 3 步:
-
用 POST /v1/analyticsReportRequests创建一个accessType: ONGOING的请求。每个 App 只需要创建一次。 -
最多等待 48 小时,让各种报表实例生成出来。 -
拿到请求 ID 后,以后每天都用同一个 ID 下载报表。
这个请求 ID 会一直用下去,所以必须存进配置里。
apps:
-name:ClassicTimer
platforms:
ios:
apple_id:"6760642418"
analytics_request_id:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
报表种类很多,但如果要做 ASO 改善前后的对比,最先用到的是 App Store Discovery and Engagement Standard。它在一份报表里按天、来源、地区给出 Impression / Page view / Tap,做汇总也足够完整。
Google Play Console:通过 GCS bucket 读取,以及 UTF-16 的坑
Google 这边会把 Play Console 的统计报表自动导出到 GCS bucket,名字类似 pubsite_prod_XXXXXX。准备好服务账号之后,就可以用 google-cloud-storage 客户端直接读取。
我踩到的一个小坑是,部分 CSV 会以 UTF-16 返回。直接按 utf-8 打开就会乱码。
import csv
from io import StringIO
raw = blob.download_as_bytes()
# 有 UTF-16 BOM 就按 UTF-16,没有就按 UTF-8
if raw.startswith(b"\xff\xfe") or raw.startswith(b"\xfe\xff"):
text = raw.decode("utf-16")
else:
text = raw.decode("utf-8")
reader = csv.DictReader(StringIO(text))
不同种类报表的路径规则,比如 stats/installs/、stats/store_performance/,不读文档也不容易猜出来。不过一旦整理好,后面就能稳定跑。
Apple Search Ads Basic:没有 API,只能手动导出 CSV
这里最难受。
Apple Search Ads Basic 没有 API。CSV 可以导出,但文件里只有整个期间的合计。它不能拆成每天的数据,所以“按天减掉广告安装量,算纯自然量”这种本来很自然的处理就做不了。
没办法,我只能先按整个期间评估,再按期间内天数做比例分摊。这是很勉强的做法。Apple Search Ads Advanced 好像有 API,但对 Basic 用户来说,那是另一个世界。
# evaluate_organic.py 的分摊逻辑节选
ads_pre_installs = round(total_installs * pre_days / total_days)
ads_post_installs = total_installs - ads_pre_installs
只要还在用 Basic 投放,就没办法真正按天分离自然下载量。这是独立开发者搭建 ASO 分析系统时,一开始就应该接受的限制。
日报长什么样
所有数据都会汇总输出到 reports/YYYY-MM-DD.md。大概长这样。数值是虚构示例值。
# App Analytics Report - 2026-04-17
### ClassicTimer (iOS)
#### Sales Reports
- 下载: **3**
#### Analytics Reports
- 展示量: **1,150**
- 产品页浏览: **5**
- 按来源展示量: App Store search: 1150
- 按地区展示量: JP: 520, KR: 430, TW: 50, HK: 48, US: 15
### ClassicTimer (Android)(数据: 2026-04-15)
- 每日安装: **1**
- 活跃安装: **42**
- 商店访问者: **8**
- 商店获取量: **1**
ASC Analytics 有 3 天延迟,Play Console 有 2 天延迟。两边延迟不一样,所以我会在报告里写清楚“数据日期”。比如文件名是 2026-04-17.md,但 Android 这边其实是 4 月 15 日的数据。
之后把这些报告加进 Git,就能留下差异历史。用 git log -p 看“和两周前相比怎么样”,意外地很舒服,有点像在看 ASO 改善 PR 下面的评审意见。
ASO 改善实验
2026 年 4 月 8 日,我一次性修改了 ClassicTimer 的元数据。
分类修改
-
修改前:Utilities -
修改后:Food & Drink
我的假设是,“拨盘式”更多是在厨房计时器这个场景里被使用。Utilities 里各种 App 太多,靠分类被发现的机会也比较低。
关键词刷新
Fastlane 的 metadata/ja/keywords.txt 原本是“用泛化名词去覆盖更多搜索”,我把它改成了“用用途词和特征词命中更具体的需求”。
-
修改前类似 タイマー,キッチンタイマー,料理,ダイヤル,クラシック,キッチン,...,以泛化名词罗列为主。 -
修改后类似 ダイヤルタイマー,ゼンマイタイマー,料理タイマー,クッキングタイマー,ポモドーロ,...,优先使用“用途 × 特征”的复合词。
关键词有 100 字符限制,所以要挑那些看起来有搜索量,同时头部竞品又没有强到完全打不过的用途词。英语和韩语 locale 也按同样思路刷新。
副标题修改
我把“クラシックなダイヤル式タイマー”这类特征名词堆叠,改成了“回すだけ。ゼンマイ式の心地よいタイマー”这种体验表达。前者说的是“它是什么”,后者说的是“它用起来有什么感觉”。
Fastlane 联动的运用
元数据散落在各个 App 仓库里,所以我写了一个简单脚本,以中央的 aso.yaml 为准,再推送到各处。
python3 src/update_metadata.py classictimer --dry-run # 确认差异
python3 src/update_metadata.py classictimer # 写入 + 执行 upload_metadata
只要改一个中央文件,就能一路跑到 Fastlane 元数据和 upload_metadata。关键词历史也能在同一份 git log 里追,这一点很舒服。
怎么读结果
我用分析脚本 analyze_aso.py 计算了改善前后的日均值。下面这张表是虚构示例值,模拟的是实际趋势。变化率本身的精度不重要,重点看“展示量上了一个数量级,但比例下降”这个形状。
|
|
|
|
|
|---|---|---|---|
|
|
|
|
+1,900% |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-60% |
|
|
|
|
-64% |
这些数字可以这样读:
-
展示量爆了。分类和关键词刷新之后,进入搜索结果的母数增加了一个数量级。 -
PV 率和 Tap 率下降了。新关键词确实让它被搜到了,但用户没有从搜索结果页点进详情页。 -
下载量增长了,但没有像展示量那样增长。母数变大,同时 CVR 下降,最后大概稳定在 4 倍。
从这里能看出的下一步很明确:胜负会在“搜索结果里的第一张截图”和“图标”上决定。关键词能带来曝光,但如果第一张图不能让人觉得“这就是我想要的东西”,PV 率就回不来。
这个结论,光在管理后台里模模糊糊看数字肯定说不出来。正因为我看到了 PV 率的变化,才判断下一步应该改截图。
区分自然量和广告量
我还用 Apple Search Ads Basic 试着投过一点广告,所以也需要评估纯自然量。
python3 src/evaluate_organic.py report_pre.csv report_post.csv
里面的逻辑就是简单相减。
自然下载量 = ASC 总下载量 - Apple Ads 广告下载量
自然占比 = 自然下载量 / ASC 总下载量
Basic 拿不到按天数据,所以只能把期间合计按天数分摊。结果大概是这样。这里也是虚构示例值。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
也就是说,ASO 改善之后,它已经更接近“停掉广告也能靠自然量维持下载”的状态。如果不做这个拆分,就会变成“这个月下载涨了,但不知道是因为广告加大了,还是 ASO 起作用了”。
踩坑总结
|
|
|
|---|---|
|
|
apps.yaml,长期复用 |
|
|
today - 3,404 就平稳跳过 |
|
|
|
|
|
|
|
|
aso.yaml 写入各仓库,再自动执行 upload_metadata |
|
|
|
总结
-
个人开发也值得自己调用 API,搭一个分析系统。相比盯着管理后台里的数字看,用 git diff 看时间线,判断会快很多。 -
ASC Analytics、ASC Sales、Play Console(GCS)、Apple Ads Basic 这 4 条路径各有各的坑,第一次整理会有点磨人。 -
ASO 改善的效果很容易表现成两段:展示量上升,但 PV 率和 Tap 率下降。下一步要改的是截图和图标。 -
如果没有区分自然量和广告量,就没法判断改善到底有没有生效。
盯着管理后台看,然后隐约觉得“好像有点火了”,和用数字比较前后变化,然后知道下一步该做什么,是完全不同的体验。独立开发者本来就缺判断材料,所以给自己做一块仪表盘,这个投入带来的回报比我想象中大。
两款 App 现在都已经公开。如果你愿意,也可以在真机上看一眼。
来源
原文标题:個人開発アプリの ASO 効果を API で定量化する
作者:Naaw
发布平台:Zenn
发布时间:2026-04-20 22:44:33 JST
原文链接:https://zenn.dev/nawa_10half/articles/solo-dev-aso-analytics-api
相关链接
ClassicTimer: https://10half.jp/classictimer.html
かえたお: https://10half.jp/kaetao.html
推荐阅读:

夜雨聆风