| name | appstoreprice-hub |
| description | 查询 App Store 全球各地区应用价格的技能,通过 appstoreprice.org 获取数据。 使用 Minis 内置浏览器(minis-browser-use CLI)在页面上下文中直接调用网站原生签名函数, 无需 API Key、无需自行实现签名算法。支持:按名称搜索应用、查询单个 App 所有地区价格、 获取最便宜地区排行、分页浏览应用列表。 当用户提到"App Store 价格"、"哪个区最便宜"、"土耳其区价格"、"appstoreprice"、 "app 比价"、"App Store 低价区"、"订阅哪个区划算",或任何需要查询 iOS/macOS App 跨地区价格对比的场景,必须触发本技能。 |
appstoreprice-hub
查询 appstoreprice.org 的 App Store 全球价格数据。
数据来源:appstoreprice.org,由 @qingnianxiaozhe 维护的非官方比价网站,实时抓取并对比全球各地区 App Store 价格。非 Apple 官方数据,价格每日更新。
原理
网站为 Next.js App Router,通过两种方式访问数据:
- REST API(搜索/列表):需要 FNV-1a 签名头
X-Timestamp + X-Signature
- RSC 页面流(价格详情):
fetch(url, { headers: { RSC: '1' } }) 直接解析
签名函数已内嵌在页面 webpack module(当前为 22463)中,直接在网站页面上下文里复用,无需自行实现。module ID 可能随网站部署更新变化,参见故障排查。
执行方式:minis-browser-use CLI
始终使用 minis-browser-use CLI + shell 脚本执行,不要用 browser_use tool call。
好处:JS 代码从文件读取直接传给进程,不会占用 agent 上下文。
标准模板
所有步骤必须在同一个 shell_execute 调用里完成,避免跨调用的 tab 状态失效。
业务逻辑用 file_write 写入临时文件再拼接,不用 heredoc(BusyBox ash 遇到花括号/引号容易解析出错)。
minis-browser-use navigate --url "https://appstoreprice.org/zh/apps" \
&& minis-browser-use wait_for_dom_stable --timeout 8 \
&& minis-browser-use execute_js --script "$(cat /var/minis/skills/appstoreprice-hub/scripts/api.js /tmp/asp_logic.js)"
为什么合并到一个 shell_execute? minis-browser-use 的浏览器 tab 在两次 shell_execute 之间可能失效(tab 被回收),第二次调用时会报 webpackChunk_N_E not found。同一进程内串行执行可保证 tab 状态稳定。
API 速查
AppStorePriceAPI() 返回 { search, list, prices, prices_all }:
| 方法 | 参数 | 返回 |
|---|
search(query, page=1, limit=20) | 关键词 | { apps, hasMore, total } |
list(page=1, limit=20) | 页码/每页数 | { apps, hasMore, total } |
prices(appStoreId, locale='zh') | App Store ID | 第一个 tier 的价格数组,按 priceUsd 升序 |
prices_all(appStoreId, locale='zh') | App Store ID | 所有 tier 的价格数组列表(多 tier 订阅必用,如 Claude Pro/Max) |
prices / prices_all 每条:{ region, regionName, currency, price, priceUsd, priceCny }
⚠️ 多 tier 订阅(如 ChatGPT Plus/Pro、Claude Pro/Max 等)必须用 prices_all(),prices() 只返回第一个订阅档位。
常用地区代码:US 美国、TR 土耳其、NG 尼日利亚、PK 巴基斯坦、EG 埃及、AR 阿根廷、VN 越南、JP 日本、KR 韩国、CN 中国、HK 香港
典型业务逻辑
多 tier 订阅
const asp = AppStorePriceAPI();
const sr = await asp.search('Claude');
const app = sr.apps.find(a => a.developer?.includes('Anthropic'));
const tierNames = ['Claude Pro(月付)', 'Claude Max 5x(月付)', 'Claude Max 20x(月付)', 'Claude Pro(年付)'];
const allTiers = await asp.prices_all(app.appStoreId);
return allTiers.map((prices, i) => {
const sorted = [...prices].sort((a, b) => a.priceUsd - b.priceUsd);
const usPrice = prices.find(p => p.region === 'US')?.priceUsd;
return {
tier: tierNames[i] || `Tier ${i+1}`,
usPriceUsd: usPrice,
cheapestTop5: sorted.slice(0, 5).map(p => ({
...p, saveVsUS: usPrice ? Math.round((1 - p.priceUsd / usPrice) * 100) + '%' : 'N/A'
}))
};
});
最便宜 Top N
const asp = AppStorePriceAPI();
const sr = await asp.search('ChatGPT');
const app = sr.apps[0];
const all = await asp.prices(app.appStoreId);
const topN = all.sort((a, b) => a.priceUsd - b.priceUsd).slice(0, 10);
const usPrice = all.find(p => p.region === 'US')?.priceUsd;
return { appName: app.name, topN: topN.map(p => ({
...p, saveVsUS: usPrice ? Math.round((1 - p.priceUsd / usPrice) * 100) + '%' : 'N/A'
}))};
指定地区价格
const asp = AppStorePriceAPI();
const sr = await asp.search('Notion');
const all = await asp.prices(sr.apps[0].appStoreId);
return all.find(p => p.region === 'TR');
结果展示规范
用 Markdown 表格展示,含:地区(国旗 emoji + 名称)、货币、原价、USD 等值、CNY 等值。
有对比场景时标注与美区折扣:节省 = (1 - priceUsd / usPrice) * 100。
故障排查
签名函数未加载:确认已 navigate 到 appstoreprice.org 页面并 wait_for_dom_stable。
api.js 会动态扫描所有 webpack module,通过函数体含 X-Timestamp/X-Signature 字符串来定位签名函数,无需 hardcode module ID。
签名函数未找到(网站大改版):若报 "签名函数未找到",说明签名头 key 名称可能已变更。
执行以下命令检查新特征:
minis-browser-use execute_js --script "
const define=(t,d)=>{for(const k in d) Object.defineProperty(t,k,{get:d[k],enumerable:true})};
const hits=[];
for(const [,m] of self.webpackChunk_N_E){
if(!m) continue;
for(const k of Object.keys(m)){
try{
const e={};m[k]({exports:e},e,{d:define});
for(const fn of Object.values(e)){
if(typeof fn!=='function') continue;
const s=fn.toString();
if(s.includes('X-') && s.length<800) hits.push({module:k,src:s.slice(0,200)});
}
}catch(e){}
}
}
return hits.slice(0,5);
"
根据输出更新 api.js 中 _getSignFn 的特征字符串检测条件。