侧边栏壁纸
博主头像
桃花依旧笑春风 博主等级

人生应该是旷野

  • 累计撰写 13 篇文章
  • 累计创建 6 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

🚀 打造高效的 App Store 内购信息爬虫:Node.js 实现指南

老乔
2025-04-28 / 0 评论 / 0 点赞 / 10 阅读 / 0 字

想快速获取 App Store 应用的内购详情?本文带你一步步搭建一个免费、稳定的爬虫方案,基于 Node.js 和 Puppeteer,轻松应对反爬策略,还能通过 Cloudflare Worker 实现全球加速!✨


🌟 为什么要自己搭建爬虫?

在开发或研究 App Store 应用时,获取内购信息(如价格、订阅详情)是关键需求。然而,苹果官方 API 限制重重,第三方服务费用高昂且额度有限。自建爬虫不仅免费,还能完全掌控数据抓取逻辑,灵活应对频繁查询和反爬挑战。让我们探索如何用 Node.js 实现高效、稳定的内购信息抓取!


📊 获取 App Store 内购信息的渠道对比

以下是四种常见渠道的详细对比,帮你快速选择最适合的方案:

特性

渠道一:苹果官方接口

渠道二:开源 Node.js 库

渠道三:直接爬取网页

渠道四:第三方爬虫 API

推荐指数

🚫 不推荐

⭐ 部分推荐 (需扩展)

✅ 强烈推荐

⚖️ 酌情使用

内购详情获取

❌ 仅限“是否含内购”标志

❌ 默认无,需二次开发

✅ 可从 HTML 提取完整内购列表

⚠️ 视服务商抓取深度而定

成本

🆓 免费

🆓 免费 (开源)

🆓 免费 (自建)

💸 有限免费额度,超限付费

实现复杂度

🟢 低 (简单 HTTP 请求)

🟢 低 (调用库函数)

🟡 中 (需处理请求、解析、反爬)

🟢 极低 (调用现成 API)

主要优点

✅ 稳定、免费、简单

✅ 免费、封装功能、内置限速

✅ 免费、可控、支持内购详情

✅ 简单、无需处理反爬、结构化输出

主要缺点

❌ 无内购列表,API 限制多

❌ 默认无内购,需扩展

⚠️ 需应对反爬、页面变动需维护

⚠️ 免费额度少、不适合高频、依赖第三方

关键技术/工具

iTunes Search API, App Store Connect API

app-store-scraper 等库

axios, cheerio, 代理 IP

Apify, SerpApi, SearchAPI

维护成本

🟢 低

🟡 中 (库更新 + 扩展维护)

🔴 高 (反爬与页面变化)

🟢 低 (服务商负责)

💡 综合建议

渠道三:直接爬取 App Store 网页 是满足“Node.js 实现、免费、频繁查询内购详情”需求的最佳选择。虽然需要处理反爬策略(如请求频率控制、模拟浏览器行为)和维护页面结构变化,但它免费、可控,且能稳定获取内购列表。

  • 渠道二 适合快速获取基础信息,内购详情需结合渠道三的爬虫逻辑。

  • 渠道四 适合低频查询或有预算的场景,免费额度难以支撑高频需求。

  • 渠道一 因无法提供内购列表,不推荐.

推荐方案:基于 Node.js 实现渠道三,结合反爬策略,打造稳定高效的内购爬虫!🚀


🛠 方法一:一键部署

想快速上手?运行以下命令,自动完成环境配置和爬虫部署:

 bash <(curl -fsSL https://raw.githubusercontent.com/qww2014/fetchIAP/refs/heads/main/deploy-fetchiap.sh)

💡 提示:一键 deployment 适合初学者,但建议了解逐步搭建流程以便灵活调整!


🛠 方法二:逐步搭建爬虫服务

以下是详细的逐步搭建指南,带你从零打造一个基于 Node.js 和 Puppeteer 的 App Store 内购爬虫,并通过 Cloudflare Worker 实现全球加速。

📌 步骤 1:搭建 VPS 版 Puppeteer API 服务

1. 准备环境

在你的 VPS 上执行以下命令,安装 Node.js 和必要工具:

 sudo apt update
 sudo apt install -y nodejs npm
 # 注意:后续步骤需要 pnpm
 sudo npm install -g pnpm
1.1 检查 Node.js 版本

确保 Node.js 版本 ≥ 18。若低于此版本,升级到最新 LTS 版本:

 sudo npm install -g n
 sudo n lts
 ​
 n # 选择更换 Node 版本
 node -v # 确认版本
 ​
 # 如果版本未更新,刷新 shell 缓存
 hash -r

2. 安装 Puppeteer 和 Express

创建项目目录并安装依赖:

 mkdir -p /opt/fetchIAP-server
 cd /opt/fetchIAP-server
 pnpm init
 pnpm add puppeteer express
 ​
 # 生产环境建议安装 pm2
 npm add pm2 -g
 ​
 # 安装浏览器(根据架构选择)
 npx puppeteer browsers install chrome # x86
 apt install -y chromium # ARM

3. 创建 server.js

/opt/fetchIAP-server 目录下创建 server.js

 nano server.js

粘贴以下代码:

 /*
  * @Author: Lao Qiao
  * @Date: 2025-04-28
  * @FilePath: /fetchIAP-multi/server.js
  * 小美出品,必属精品 ✨
  */
 ​
 const express = require('express');
 const { fetchIAP } = require('./fetchIAP');
 ​
 const app = express();
 const port = 3000;
 const TIMEOUT_PER_COUNTRY = 30000; // 每个国家超时时间(ms)
 ​
 ​
 app.use(express.json());
 ​
 // 健康检查接口
 app.get('/', (req, res) => {
   res.send('✨ FetchIAP Server 正常运行中!');
 });
 ​
 // 单国家查询,附带超时保护
 const fetchIAPWithTimeout = (params, timeoutMs = 30000) => {
   return Promise.race([
     fetchIAP(params),
     new Promise((_, reject) =>
       setTimeout(() => reject(new Error('抓取超时')), timeoutMs)
     ),
   ]);
 };
 ​
 app.post('/iap', async (req, res) => {
   const { appId, countries = [], slug = '' } = req.body;
 ​
   if (!appId || !Array.isArray(countries) || countries.length === 0) {
     return res.status(400).json({ success: false, error: '请求必须包含 appId 和 countries 列表!' });
   }
 ​
   const isValidCountryCode = (code) => /^[a-z]{2}$/i.test(code);
 ​
   const invalidCountries = countries.filter(c => !isValidCountryCode(c));
   if (invalidCountries.length > 0) {
     return res.status(400).json({ success: false, error: `国家代码格式错误:${invalidCountries.join(', ')}` });
   }
   const results = {};
 ​
   try {
     for (const country of countries) {
       console.log(`✨ 查询 ${country.toUpperCase()}...`);
 ​
       try {
         const items = await fetchIAPWithTimeout({ appId, country, slug }, TIMEOUT_PER_COUNTRY);
         results[country] = items;
       } catch (err) {
         console.error(`⚠️ 查询 ${country.toUpperCase()} 失败:${err.message}`);
         results[country] = { error: err.message };
       }
     }
 ​
     res.json({ success: true, data: results });
   } catch (err) {
     console.error('❌ 总体查询失败:', err);
     res.status(500).json({ success: false, error: '服务器内部错误', details: err.message });
   }
 });
 ​
 // 启动服务器
 app.listen(port, () => {
   console.log(`🚀 FetchIAP Server 已启动,监听端口 ${port}`);
 });

保存并退出:

 Ctrl + O, 回车, Ctrl + X

4. 创建 fetchIAP.js

在同一目录下创建 fetchIAP.js

 nano fetchIAP.js

粘贴以下 code:

 /*
  * @Author: Lao Qiao
  * @Date: 2025-04-28
  * 小美出品,必属精品 ✨
  */
 ​
 const puppeteer = require('puppeteer');
 ​
 const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
 ​
 const purchaseLabelMap = {
   us: 'In-App Purchases',
   cn: 'App 内购买项目',
   jp: 'アプリ内課金有り',
   kr: '앱 내 구입',
   fr: 'Achats intégrés',
   de: 'In‑App‑Käufe',
   it: 'Acquisti In-App',
   es: 'Compras dentro de la app',
   ru: 'Встроенные покупки',
 };
 ​
 async function autoScrollUntil(page, selector, timeout = 10000) {
   const start = Date.now();
   while ((Date.now() - start) < timeout) {
     const found = await page.evaluate(sel => !!document.querySelector(sel), selector);
     if (found) break;
     await page.evaluate(() => window.scrollBy(0, window.innerHeight / 2));
     await sleep(100);
   }
 }
 ​
 async function fetchIAP({ appId, country = 'us', slug = '' }) {
   const url = slug
     ? `https://apps.apple.com/${country}/app/${slug}/id${appId}`
     : `https://apps.apple.com/${country}/app/id${appId}`;
 ​
   const browser = await puppeteer.launch({
     headless: 'new',
     args: ['--no-sandbox', '--disable-setuid-sandbox'],
   });
   const page = await browser.newPage();
 ​
   try {
     await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });
     await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36');
     await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
 ​
     await autoScrollUntil(page, 'dt.information-list__item__term');
     await sleep(500);
 ​
     const purchaseLabel = purchaseLabelMap[country.toLowerCase()] || 'In-App Purchases';
 ​
     const items = await page.evaluate(label => {
       const sections = Array.from(document.querySelectorAll('dt.information-list__item__term'));
       let matchedSection = null;
 ​
       for (const dt of sections) {
         if (dt.textContent.trim() === label) {
           matchedSection = dt.closest('.information-list__item');
           break;
         }
       }
 ​
       if (!matchedSection) return [];
 ​
       const results = [];
       matchedSection.querySelectorAll('li.list-with-numbers__item').forEach(li => {
         const name = li.querySelector('.list-with-numbers__item__title')?.textContent.trim();
         const price = li.querySelector('.list-with-numbers__item__price')?.textContent.trim();
         if (name && price) results.push({ name, price });
       });
       return results;
     }, purchaseLabel);
 ​
     return items;
   } finally {
     await browser.close();
   }
 }
 ​
 module.exports = { fetchIAP };

保存并退出。

5. 启动服务器

开发环境直接运行:

 node server.js

生产环境使用 pm2 运行并设置开机自启:

 pm2 start server.js --name fetchIAP-server
 pm2 save
 pm2 startup

现在,服务器通过 POST http://your-vps-ip:3000/iap 接受查询请求,返回 JSON 格式的内购数据!


📌 步骤 2:部署 Cloudflare Worker 作为 API 代理

为提升访问速度并隐藏 VPS IP,部署一个 Cloudflare Worker 作为透明代理。

1. 登录 Cloudflare

访问 Cloudflare 仪表板,进入 Workers 页面。

2. 创建 Worker

创建一个新的 Worker,粘贴以下 code:

 export default {
   async fetch(request, env, ctx) {
     const url = new URL(request.url);
     const targetUrl = 'http://your-vps-ip:3000' + url.pathname; // 注意!替换为你的 VPS 公网 IP
 ​
     const modifiedRequest = new Request(targetUrl, {
       method: request.method,
       headers: request.headers,
       body: request.body,
     });
 ​
     return fetch(modifiedRequest);
   },
 };

💡 Worker 作用:客户端请求 Worker,Worker 转发到 VPS,VPS 返回结果后再通过 Worker 返回客户端。全球访问加速,VPS IP 完全隐藏!


🎯 最终效果:快速查询内购信息

查询示例

发送以下请求到 Worker:

POST https://your-worker-subdomain.workers.dev/iap
Content-Type: application/json
{
  "appId": "6448311069",
  "slug": "chatgpt",
  "countries": ["us", "jp", "kr"]
}

返回结果

{
  "success": true,
  "data": {
    "us": {
      "success": true,
      "time_ms": 3452,
      "data": [
        { "name": "ChatGPT Plus", "price": "$19.99" },
        { "name": "ChatGPT Pro", "price": "$200.00" }
      ]
    },
    "jp": {
      "success": false,
      "time_ms": 30020,
      "error": "抓取超时"
    },
    "kr": {
      "success": true,
      "time_ms": 2750,
      "data": [
        { "name": "ChatGPT Plus", "price": "₩29,000" },
        { "name": "ChatGPT Pro", "price": "₩299,000" }
      ]
    }
  }
}

工作流程

  1. 客户端请求 Worker。

  2. Worker 转发请求到 VPS 上的 Puppeteer 爬虫。

  3. Puppeteer 启动浏览器,抓取指定国家/地区的内购数据。

  4. 数据通过 Worker 返回客户端。

优势

  • 全球访问速度快,延迟低。

  • VPS IP 隐藏,安全性高。

  • 免费、可控、支持多国家内购查询。


🛠 故障排除:缺少 Chrome 依赖库

如果服务器提示缺少 Chrome 依赖的共享库,运行以下命令安装:

sudo apt update
sudo apt install -y \
  libnss3 \
  libatk-bridge2.0-0 \
  libcups2 \
  libx11-xcb1 \
  libxcomposite1 \
  libxdamage1 \
  libxrandr2 \
  libasound2 \
  libpangocairo-1.0-0 \
  libgtk-3-0

然后重启服务:

pm2 reload fetchIAP-server

🎉 总结

通过本教程,你已学会如何用 Node.js 和 Puppeteer 搭建一个高效的 App Store 内购信息爬虫,并通过 Cloudflare Worker 实现全球加速。无论是开发者还是数据分析师,这个方案都能帮你快速获取内购详情,助力项目成功!

接下来做什么?

  • 测试你的爬虫,优化请求频率以规避反爬限制。

  • 扩展功能,比如批量查询多个应用。

  • 分享你的成果,加入技术社区讨论!🚀

💬 有问题? 在评论区留言,我会尽快解答!

0

评论区