亚马逊 Movers and Shakers 数据采集实战:用 Python + Scrape API 构建实时榜单监控系统

Tags: #Python #Amazon #爬虫 #API #选品

前言

亚马逊 Movers and Shakers(MnS)榜单每小时更新一次,记录各品类内 BSR 排名涨幅最大的商品。这份数据对跨境电商选品决策的价值极高,但要做到系统化、自动化地采集,有几道技术门槛需要跨越。

本文记录了我们从自建爬虫到接入 Pangolinfo Scrape API 的完整技术演进过程,包含核心代码实现和生产环境中遇到的真实问题。

TL;DR:

  • 亚马逊 MnS 页面反爬机制强,自建爬虫稳定性差
  • Pangolinfo Scrape API 提供 MnS 解析模板,直接输出结构化 JSON
  • 本文提供完整可运行的 Python 采集 + 预警代码
  • 覆盖接入方式、字段说明、性能优化和常见报错解法

技术背景:MnS 页面的反爬挑战

亚马逊 Movers and Shakers 数据在 https://www.amazon.com/gp/movers-and-shakers/{category_id} 路径下,DOM 结构复杂且存在动态渲染。自建 requests + BeautifulSoup 方案面临以下问题:

  1. 速率限制严格:同一 IP 高频访问会触发 503/CAPTCHA,普通住宅 IP 的安全频率约为每 10 分钟 1 次
  2. User-Agent 检测:亚马逊会检测 UA 字符串,需要定期更新浏览器指纹
  3. Cookie/Session 状态依赖:部分字段(如用户地区相关的价格)需要维持有效 Session
  4. HTML 结构变动频繁:MnS 页面在 A/B 测试期间结构变化快,解析规则需要频繁维护

我们早期自建的爬虫方案在全品类规模下的可用率只有约 73%,丢数据问题在高流量节点(Prime Day 前后)尤为严重。


解决方案:接入 Pangolinfo Scrape API

Pangolinfo Scrape API 针对亚马逊主要页面类型提供了预处理好的解析模板,MnS 模板输出的标准 JSON 包含以下核心字段:

json 复制代码
{
  "asin": "B09XK3K3T3",
  "title": "PRODUCT TITLE HERE",
  "current_rank": 47,
  "previous_rank": 1847,
  "rank_gain_pct": 3826.0,
  "rank_gain_absolute": 1800,
  "category": "Kitchen & Dining",
  "category_id": "1055398",
  "price": 24.99,
  "rating": 4.3,
  "review_count": 128,
  "image_url": "https://...",
  "listing_url": "https://www.amazon.com/dp/B09XK3K3T3",
  "badge": "Best Seller",
  "timestamp": "2026-04-22T08:00:00Z"
}

完整实现代码

1. 基础采集模块

python 复制代码
"""
amazon_mns_collector.py
亚马逊 Movers and Shakers 数据采集模块
依赖: requests, python-dotenv
"""

import os
import requests
import time
import logging
from typing import List, Dict, Optional
from datetime import datetime

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s"
)
logger = logging.getLogger(__name__)

PANGOLINFO_API_KEY = os.environ.get("PANGOLINFO_API_KEY", "")
API_ENDPOINT = "https://api.pangolinfo.com/scrape"

# 亚马逊品类 ID 映射(部分常用品类)
CATEGORY_MAP = {
    "1055398": "Kitchen & Dining",
    "3375251": "Sports & Outdoors",
    "1063498": "Home Storage",
    "172282": "Electronics",
    "284507": "Tools & Home Improvement",
    "2619533011": "Health & Household",
}


def fetch_mns_page(
    category_id: str,
    locale: str = "us",
    retry: int = 3,
    retry_delay: float = 5.0
) -> Optional[List[Dict]]:
    """
    采集指定品类的 MnS 榜单数据

    Args:
        category_id: 亚马逊品类节点 ID
        locale: 站点区域(us/uk/de/jp/...)
        retry: 失败重试次数
        retry_delay: 重试间隔(秒)

    Returns:
        商品列表(含涨幅、ASIN 等字段),失败时返回 None
    """
    if not PANGOLINFO_API_KEY:
        raise ValueError("环境变量 PANGOLINFO_API_KEY 未设置")

    payload = {
        "url": f"https://www.amazon.{'co.uk' if locale == 'uk' else locale if locale != 'us' else 'com'}"
               f"/gp/movers-and-shakers/{category_id}",
        "parse_type": "movers_shakers",
        "output_format": "json",
        "locale": locale
    }
    headers = {
        "Authorization": f"Bearer {PANGOLINFO_API_KEY}",
        "Content-Type": "application/json"
    }

    for attempt in range(1, retry + 1):
        try:
            resp = requests.post(
                API_ENDPOINT, json=payload, headers=headers, timeout=45
            )
            resp.raise_for_status()
            data = resp.json()
            items = data.get("items", [])
            logger.info(
                f"品类 {category_id} ({CATEGORY_MAP.get(category_id, 'Unknown')}): "
                f"采集到 {len(items)} 条数据"
            )
            return items
        except requests.HTTPError as e:
            logger.warning(f"HTTP 错误 (尝试 {attempt}/{retry}): {e.response.status_code}")
            if e.response.status_code == 429:
                # 速率限制:等待更长时间
                time.sleep(retry_delay * 3)
            elif e.response.status_code >= 500:
                time.sleep(retry_delay)
            else:
                break  # 4xx 非 429:不重试
        except requests.Timeout:
            logger.warning(f"请求超时 (尝试 {attempt}/{retry})")
            time.sleep(retry_delay)
        except Exception as e:
            logger.error(f"未知错误: {e}")
            break

    return None

2. 预警过滤与评分模块

python 复制代码
"""
mns_alert_engine.py
涨幅预警与商业价值评分引擎
"""

from typing import List, Dict, Tuple


SPIKE_THRESHOLD = 1000.0    # 涨幅 > 1000% 触发预警
REVIEW_CAP = 300            # 评论数 < 300 视为切入窗口开放
MIN_RATING = 3.8            # 评分低于此值忽略(质量风险高)


def score_opportunity(item: Dict) -> float:
    """
    对 MnS 涨幅商品打分(0-100分)
    评分维度:涨幅强度 + 竞争壁垒低(评论少)+ 评分良好
    """
    gain = item.get("rank_gain_pct", 0)
    reviews = item.get("review_count", 9999)
    rating = item.get("rating", 0)

    # 涨幅分(最高 50 分)
    gain_score = min(50, gain / 200)

    # 竞争空间分(评论越少越高,最高 30 分)
    if reviews < 50:
        competition_score = 30
    elif reviews < 150:
        competition_score = 20
    elif reviews < 300:
        competition_score = 10
    else:
        competition_score = 0

    # 评分质量分(最高 20 分)
    rating_score = max(0, (rating - 3.0) / 2.0 * 20) if rating >= 3.0 else 0

    return round(gain_score + competition_score + rating_score, 1)


def filter_and_rank_alerts(items: List[Dict]) -> List[Tuple[float, Dict]]:
    """
    过滤预警商品并按机会分排序
    返回: [(score, item), ...] 降序排列
    """
    results = []
    for item in items:
        if item.get("rank_gain_pct", 0) < SPIKE_THRESHOLD:
            continue
        if item.get("rating", 0) < MIN_RATING and item.get("review_count", 0) > 20:
            continue
        score = score_opportunity(item)
        results.append((score, item))

    return sorted(results, key=lambda x: x[0], reverse=True)

3. 主运行入口

python 复制代码
"""
main.py
MnS 监控主程序 - 每 30 分钟扫描一次所有目标品类
"""

import time
from datetime import datetime
from amazon_mns_collector import fetch_mns_page, CATEGORY_MAP
from mns_alert_engine import filter_and_rank_alerts

SCAN_INTERVAL = 1800  # 30 分钟


def format_alert_message(score: float, item: dict) -> str:
    return (
        f"⚡ [{score:.0f}分] ASIN: {item['asin']}\n"
        f"   涨幅: +{item.get('rank_gain_pct', 0):.0f}% | "
        f"当前 BSR: #{item.get('current_rank', '?')} | "
        f"评论: {item.get('review_count', '?')} | "
        f"评分: {item.get('rating', '?')}\n"
        f"   {item.get('title', '')[:50]}"
    )


def main():
    print(f"[{datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}] "
          f"MnS 监控启动,覆盖 {len(CATEGORY_MAP)} 个品类")

    while True:
        for cat_id, cat_name in CATEGORY_MAP.items():
            items = fetch_mns_page(cat_id)
            if not items:
                print(f"  ⚠️  {cat_name}: 无数据")
                continue

            alerts = filter_and_rank_alerts(items)
            print(f"  {cat_name}: {len(items)} 条 | "
                  f"{len(alerts)} 个预警")

            for score, item in alerts[:3]:  # 每品类最多输出前 3 名预警
                print(format_alert_message(score, item))

            time.sleep(2)  # 品类间间隔

        print(f"\n  下次扫描时间: {SCAN_INTERVAL // 60} 分钟后\n")
        time.sleep(SCAN_INTERVAL)


if __name__ == "__main__":
    main()

常见问题与解决方案

Q1: 返回 items 为空列表,但没有报错?

A: 通常是因为 category_id 格式有误或该品类暂无 MnS 数据(极小的子品类)。验证方法:直接在浏览器打开对应 URL,确认页面有内容后再调试 API 调用。

Q2: 某些品类的 review_count 字段返回 null

A: 新品或评论数极少的商品(< 5条)亚马逊在 MnS 页面不展示评论数,null 是正常值,处理时需要做 or 0 的空值保护。

Q3: 如何支持非美国站点(如英国 Amazon UK)?

A: 修改 locale 参数为 uk,URL 中的域名会相应调整为 amazon.co.uk。Pangolinfo API 支持 us/uk/de/jp/ca 等主流站点。

Q4: 每次调用都采集 100 条 MnS 商品,想只拿前 20,有没有办法在 API 层面过滤?

A: 当前 API 返回完整的 Top 100,过滤逻辑需要在客户端实现。可以在 filter_and_rank_alerts 中加 items = items[:20] 切片。


性能优化建议

  1. 异步并发采集 :多品类采集可以用 asyncio + aiohttp 重写,从顺序 60 秒减少到 10 秒以内(50 个品类场景)
  2. 增量更新:用 Redis 缓存上次采集结果,只处理 ASIN 变动的部分,降低下游系统写入压力
  3. 预警去重:同一 ASIN 在连续 3 次扫描中都触发预警,只发送一次通知,避免飞书 / Slack 轰炸

参考资料


如果你在对接过程中遇到具体问题,欢迎在评论区留言,或者私信交流。

相关推荐
雷帝木木2 小时前
Python 并发编程高级技巧详解:从原理到实践
人工智能·python·深度学习·机器学习
一个天蝎座 白勺 程序猿2 小时前
AI入门踩坑实录:我换了3种语言才敢说,Python真的是入门唯一选择吗?
开发语言·人工智能·python·ai
Hui_AI7202 小时前
保险条款NLP解析与知识图谱搭建:让AI准确理解保险产品的技术方案
开发语言·人工智能·python·算法·自然语言处理·开源·开源软件
雷帝木木2 小时前
Python Web 框架对比与实战:Django vs Flask vs FastAPI
人工智能·python·深度学习·机器学习
万粉变现经纪人2 小时前
如何解决 pip install jaxlib[cuda] 报错 CUDA 版本与轮子标签不匹配 问题
人工智能·python·深度学习·tensorflow·pandas·scikit-learn·pip
杜子不疼.2 小时前
用 Python 搭建本地 AI 问答系统:避开 90% 新手都会踩的环境坑
开发语言·人工智能·python
源码之家2 小时前
计算机毕业设计源码:京东商品数据采集分析可视化系统python Django Selenium爬虫 人工智能 大数据(建议收藏)✅
人工智能·爬虫·python·信息可视化·数据分析·django·课程设计
生信小窝2 小时前
081B 基于R包-Ecospat的生态位重叠分化迁移分析与可视化制图(单物种和多物种版本)【2027】
人工智能·python·r语言
执于代码2 小时前
python 常见的框架
开发语言·python