PostHog 数据仓库 AppFollow 数据源:应用商店评论与评分同步连接器全解析
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
本文以 PostHog 仓库中 AppFollow 数据源的用户文档(posthog_com_doc.md)为主体,结合其连接器源码(settings.py、appfollow.py、source.py)与测试用例,完整讲解如何把 AppFollow 的应用商店数据(评论、评分历史、ASO 排名与关键词)同步进 PostHog 数据仓库:从 API token 的申请与计费/限流模型,到 9 张表的端点配置、按应用扇出(fan-out)的发现链、增量与快照两种同步策略,以及常见故障的排查方法。
一、连接器定位与适用场景
AppFollow 是聚合 App Store 与 Google Play 数据的应用分析、评论管理与应用商店优化(ASO)平台。PostHog 的 AppFollow 连接器(源码中标注为 Alpha 阶段)会把账户下被跟踪的应用、其评论、评分历史,以及 ASO 数据(品类排名、关键词排名、版本发布历史、评论统计)拉取进 PostHog Data warehouse。同步落地之后,这些数据可以与产品分析(product analytics)做关联查询、构建洞察(insights),并持续监控评论情绪变化。
连接器的用户文档 front matter 声明了其能力范围(availability: { free: full, selfServe: full, enterprise: full },sourceId: Appfollow,beta: true)。在 source.py 中,AppfollowSource注册了以下元信息:
- 数据源类别为
DataWarehouseSourceCategory.ANALYTICS,发布状态为ReleaseStatus.ALPHA; - 仅支持 API 版本
("v2",),默认版本"v2"; - 配置字段只有一个:
api_key(标签为 "API token",类型PASSWORD,必填,标记secret=True),生成的配置类即 generated_configs/appfollow.py 中的AppfollowSourceConfig(api_key: str)。
二、前置条件:API token、积分计费与限流
使用本数据源需要一个开启了 API 访问的 AppFollow 账户,且只有账户的Account Owner 或 Admin角色能生成 API token(在 AppFollow 的 API management 页面生成)。token 通过请求头X-AppFollow-API-Token完成全部请求的鉴权。
计费与限流模型是配置本数据源时必须理解的约束(来自文档与 api_inventory.md 的交叉印证):
- 积分(credit)计费:AppFollow 对 API 调用按积分余额扣费,每次请求消耗 1–100 积分。其中 reviews 请求每次 10 积分;ratings history 每次 10 积分,且还存在每 30 天一次的持续性扣费。这就是为什么
reviews默认开启而同步,而ratings_history、rankings、keywords等设为按需开启(opt-in)。 - 速率限制:每个 token 每小时 1000 次请求,每个账户每小时 10000 次请求。
- 首次全量回填代价高:
reviews与ratings_history表首次同步需要拉取全量历史,会消耗可观的积分;日常增量同步之后才只拉新增/变更行。
这些约束直接体现在源码里:限流(HTTP 429)与 5xx 被视为可重试错误,_fetch通过 tenacity 做最多 5 次、指数退避加抖动的重试(appfollow.py 中stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=1, max=30));而 401(token 无效)、402(积分耗尽)、403(无权限)被映射为不可重试错误,见下文"故障排查"。
三、添加数据源
按标准的数据仓库数据源接入流程操作即可(文档中该段引用了通用的SourceSetupIntro片段)。唯一的配置输入就是在 AppFollow API management 页面生成的 API token,粘贴进 "API token" 输入框。
连通性校验在 source.py 的validate_credentials中实现:它调用 appfollow.py 的check_credentials,用 token 请求一次成本最低(1 积分)且任何合法 token 都能访问的/account/apps端点作为真实性探活检查,再按状态码判定:
200:token 有效;403:由于 AppFollow 是全账户级单一 token,403 仍能证明 token 真实存在,因此判定有效;401:token 无效("Invalid AppFollow API token");402:积分耗尽("Your AppFollow account is out of API credits");- 网络异常(返回
None):"Could not reach AppFollow"。
测试用例 test_appfollow_source.py 的test_validate_credentials对上述六种状态码逐一断言,锁定了这一判定逻辑。
四、支持的表与端点配置
文档的 "Supported tables" 一节由<SourceTables />片段从连接器自身的静态目录渲染(lists_tables_without_credentials = True,即不需要凭据即可列出全部 9 张表,方便公开文档渲染,测试test_lists_tables_without_credentials保证了这一点)。目录的完整定义在 settings.py 的APPFOLLOW_ENDPOINTS中,各表对应的 API 端点(base URLhttps://api.appfollow.io/api/v2)、主键、默认同步状态与增量能力如下:
| 表名 | 端点路径 | 请求形态 | 主键 | 增量字段 | 默认同步 |
|---|---|---|---|---|---|
app_collections | /account/apps | 单次请求(kind=list,行在apps键下) | id | 无(全量刷新) | 是 |
app_lists | /account/apps/app?apps_id=<id> | 按 collection 扇出(kind=apps,行在apps_app键下) | app_collection_id, app_id | 无(全量刷新) | 是 |
users | /account/users | 单次请求(行在响应根) | id | 无(全量刷新) | 否 |
reviews | /reviews?ext_id=&from=&to=&page= | 按应用扇出,page/pages_count分页 | ext_id, review_id | updated(经服务端last_modified过滤) | 是 |
ratings_history | /meta/ratings/history?ext_id=&store=&from=&to=&offset=&limit= | 按应用扇出,offset/limit 分页 | ext_id, store, date | date(经服务端from过滤) | 否 |
rankings | /meta/rankings?ext_id=&date= | 按应用扇出,单请求 | ext_id, country, device, genre_id, date | 无(每日快照) | 否 |
keywords | /aso/keywords?ext_id=&date=&page= | 按应用扇出,1-indexedpage分页 | ext_id, country, device, date, keyword | 无(每日快照) | 否 |
app_versions | /meta/versions?ext_id=&country=&page= | 按应用扇出,page分页,需country | ext_id, country, version | 无(全量刷新) | 否 |
reviews_stats | /reviews/stats?ext_id=&from=&to= | 按应用扇出,单请求 | ext_id, date | date(经服务端from过滤) | 否 |
每行数据都会按月做日期分区(partition_mode="datetime"、partition_format="month",见 appfollow.py 的appfollow_source),分区键取各配置的partition_key(如created、date)。每张表的用途与列含义见 canonical_descriptions.py,例如reviews表包含review_id、content、rating、date、updated(增量游标)、app_version、locale等列,ratings_history表包含avg_rating与stars1–stars5分布列。
默认只开三张表的原因
文档特别指出:除app_collections、app_lists、users之外的所有表都是按应用逐一查询的——查询参数使用该应用的商店ext_id,而这些ext_id需要遍历app_collections及其下的app_lists才能发现。因为这类按应用扇出的请求都要消耗积分,所以默认只开启app_collections、app_lists、reviews三张表,其余表如需使用请在表选择器(table picker)中手动启用。测试test_should_sync_defaults精确断言了这一默认开/关分布。
五、应用发现链:从 collection 到 ext_id
AppFollow 的数据模型是"以应用为中心"的:绝大多数端点都要求传入某个应用的商店ext_id才能查询。从 appfollow.py 的_iter_collections→_iter_collection_apps可以看到发现链:
/account/apps -> collections(工作区,含 id、title、countries) /account/apps/app?apps_id=<id> -> 每个 collection 下的 apps(ext_id、store、app_id)_iter_collection_apps在遍历时会为每个 app 行打上app_collection_id与collection_name(取自 collection 的title_normalized或title),并在ext_id/store缺失时从嵌套的app对象中提升出来,保证后续扇出与复合主键能可靠依赖这两个字段。_iter_app_targets再按端点需求去重:reviews只按ext_id去重,ratings_history按ext_id + store去重,按国家限定的 ASO 端点按ext_id + country去重——同一个应用可能出现在多个 collection 中,按端点实际变化的维度去重可以避免重复请求(也就避免重复付费)。
app_versions 的国家解析
文档中提到的细节在源码中同样可查:app_versions(/meta/versions)必须传一个商店国家参数,而 app 行并不总是携带国家信息。_resolve_country的解析顺序为:app 自身的country→ 嵌套app.country→ collection 的default_country→ collectioncountries列表的首项 → 兜底us(常量FALLBACK_COUNTRY)。若同一应用被两个不同国家的 collection 跟踪,则会按国家各拉取一次,因为版本记录本身按国家区分。
六、同步模式:增量、全量与每日快照
文档 "Sync modes" 一节的规则与 settings.py 中每个端点配置的incremental_fields、time_mode一一对应:
reviews:按每条评论的 last-modified 时间戳(updated字段)增量同步。服务端过滤器是last_modified,from/to是必填参数(过滤的是评论发布date),因此每次运行都把窗口从DEFAULT_START_DATE开到今天,再让last_modified承担增量工作。首次回填窗口起点是"2008-01-01"——早于 App Store(2008)与 Google Play(2012)上线时间,确保不漏任何真实评论。增量游标还会被_clamp_future_value_to_now钳制到"当前时间":如果某条记录的日期在未来,游标越过 now 后,后续每次同步都会用未来时间戳过滤,查询变成空操作、表会静默冻结;钳制让同步可以自愈。ratings_history:type=total返回每天一条带日期的快照,from日期参数即增量游标——历史快照不会变化,所以from=水位线是安全的。reviews_stats:from/to限定统计范围,同样以from=水位线做增量。rankings与keywords:文档称之为"值得记住的例外"。AppFollow 每次只能返回单日的排名/关键词位置,没有范围查询能力;而且每天每应用一次请求要 10 积分,所以这两张表按"今日快照"方式全量同步:请求显式传date=今天,并由_stamp_fanout_row把该日期盖到每一行上,保证主键与分区键在响应 schema 未公开的情况下依然完整。它们的历史是靠持续同步逐日累积出来的,而不是回填出来的。app_versions:端点完全没有任何日期参数,全量刷新,表本身较小。
分页方面:keywords与app_versions用裸的 1-indexedpage翻页,既不公布总页数也不公布总条数,只能以"空页"为终止条件,因此源码设了MAX_PAGES_PER_APP = 100的上限,触顶时打 warning 日志("reached the 100 page cap ... later pages were not fetched"),防止异常端点无限消耗积分;rankings与reviews_stats完全不分页,单应用单请求。
七、断点续传与响应解析
所有扇出型端点(reviews、ratings、app_fanout)都实现了基于ResumableSourceManager的可恢复同步:
- 书签用稳定的
ext_id而非位置索引记录当前处理到哪个应用,崩溃重试后即使应用在两次运行之间被增删,也不会续传错位;书签对应的应用若已不存在,则从头开始,靠主键合并(merge)去重补拉的行(_resume_slice的注释与实现)。 - 每翻一页才
save_state,且在 yield 之后保存——这样崩溃后重拉的是当前页而不是跳过它,重拉的行由[ext_id, review_id]等复合主键去重。 - 因为每次运行都重开完整增量窗口(reviews 用
last_modified、ratings 用from)并依赖 merge 去重,所以页面返回顺序不影响正确性,sort_mode设为"asc"只是让水位线能按批推进。
响应解析方面,_extract_rows接受"候选键元组":AppFollow 对所有 v2 端点公开的是空 200 schema,因此 ASO 与统计类端点(rankings、keywords、app_versions、reviews_stats)的响应包裹键是最佳猜测,配置里传多个候选键(如data_key=("ranks", "rankings")),并回退到"响应根即行列表"。猜错只会得到空表,而不会得到错误的表。
八、故障排查
文档列出的两类故障在源码中有精确的映射:
- Invalid API token(401):token 错误或已被吊销。请在 AppFollow API management 页面重新生成并重新连接。
- Out of API credits(402):账户积分余额已耗尽。等待余额重置或升级套餐后重试同步。
source.py 的get_non_retryable_errors把这三类客户端错误映射成稳定的、带可操作提示的报错文案(匹配的是稳定的状态文本与 base host,而不是每次请求的路径/查询参数):
| HTTP 状态 | 触发原因 | 给出的提示 |
|---|---|---|
| 401 | token 无效/被吊销 | "Your AppFollow API token is invalid. Generate a new token on the API management page in your AppFollow account, then reconnect." |
| 402 | 积分耗尽 | "Your AppFollow account is out of API credits. Wait for your credit balance to reset or upgrade your plan, then retry the sync." |
| 403 | token 无权限 | "Your AppFollow API token does not have access to this data. Check the token's permissions, then reconnect." |
测试test_non_retryable_errors_match_auth_and_credit_failures断言 401/402/403 的错误串能被匹配,而test_non_retryable_errors_ignore_retryable_and_unrelated反向断言 429(限流)、500(服务端错误)以及其他域名的 401(如 stripe)都不会被误判为不可重试——即限流会按前文所述自动重试,权限类错误则直接失败并提示。
九、实现验证与延伸阅读
整个连接器的行为都有测试锁定(test_appfollow_source.py):get_schemas覆盖全部端点、每张表的增量能力与增量字段、默认同步开关、复合主键的表内唯一性、凭据校验状态码判定。行级抓取逻辑另有 test_appfollow.py 覆盖。端点清单的完整核对记录(含"哪些响应字段是未公开的猜测")保存在 api_inventory.md,其中明确标注了验证边界:所有路径与查询参数都对齐过公开的 v2 OpenAPI 定义,而响应字段结构是基于官方文档、开源 Airbytesource-appfollow连接器与产品 UI 重建的,未经真实 token 在线验证。
需要留意的前提:该文档是 posthog.com 站点/docs/cdp/sources/appfollow页面的源文件(文件头注释说明了迁移计划),连接器处于 Alpha/Beta 状态;rankings、keywords的历史数据需要持续同步逐日累积;首次启用reviews/ratings_history前请先评估 AppFollow 积分余额。
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考