# Amazon SP-API 通知：去重与核对

Source: https://ipvolt.com/zh/guides/amazon-listing-notifications
Markdown: https://ipvolt.com/zh/guides/amazon-listing-notifications.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [指南](https://ipvolt.com/zh/guides.md) / Amazon SP-API 通知：去重与核对

集成
审校于: 2026-09-16
发布于: 2026-09-16
阅读约 6 分钟
作者： ipvolt

用经过测试的 SQLite 收件箱处理 Amazon SP-API 商品信息通知：对投递去重、重启后恢复，并拒绝已被取代的读取结果。

作者：ipvolt · 核查于 2026 年 9 月 16 日

在确认投递之前，把每条经过验证的 Amazon 商品信息通知**及其产生的工作在同一个事务中**持久化。用该通知来安排一次商品信息读取。如果在该读取期间又有另一条已接受的通知到达，让工作保持待处理状态并再次读取。这可以防止一种特定的本地竞争：较旧的进行中结果清除了较新的刷新请求。

本指南面向拥有已授权 SP-API 应用和现有 EventBridge 投递管道的开发者。集成需要你自己的经过验证的事件适配器和带认证的商品信息读取适配器。可下载的 SQLite 实验环境使用合成事件、一个核对工作进程和 Python 标准库离线运行；它实现了这两个适配器之间的持久化和完成检查。

**方法：** ipvolt 于 2026 年 9 月 16 日使用 CPython 3.14.7 和 SQLite 3.53.4 运行了演示和 12 个测试。源码目标为 Python 3.11+；该次记录的运行没有在 3.11 上演练。没有测试任何 Amazon 账户、API 请求或真实的队列确认。

## 运行本地实验环境

下载并解压[完整实验环境 ZIP](https://ipvolt.com/downloads/amazon-listing-notifications/amazon-listing-notifications.zip)。在包含 `consumer.py` 的目录中打开终端，然后运行：

```sh
python3 -B demo.py
python3 -B -m unittest -v test_consumer.py
```

无需安装任何软件包。两个命令都使用临时数据库。演示打印确定性的 JSON；将其与[记录的输出](https://ipvolt.com/downloads/amazon-listing-notifications/example-output.json)对比。

[README](https://ipvolt.com/downloads/amazon-listing-notifications/README.md) 记录了完整的契约。如果愿意，也可以分别查看 [consumer.py](https://ipvolt.com/downloads/amazon-listing-notifications/consumer.py)、[demo.py](https://ipvolt.com/downloads/amazon-listing-notifications/demo.py)、[测试套件](https://ipvolt.com/downloads/amazon-listing-notifications/test_consumer.py)和[合成夹具](https://ipvolt.com/downloads/amazon-listing-notifications/fixtures/scenario.json)。

## 在存储之前规范化可信事件

商品信息状态通知和问题通知使用 Amazon 的 **EventBridge 工作流**。SQS 队列可以作为下游规则目标；这与通过直接的 SP-API SQS 工作流订阅不同。关于目的地、订阅和投递权限，请遵循 Amazon 的 [EventBridge 设置](https://developer-docs.amazon/sp-api/docs/set-up-notifications-with-amazon-eventbridge)。

在适配器中固定原始类型和载荷版本。下面的小写名称属于本实验环境：

| Amazon 文档中的类型 | 载荷版本 | 规范化后的实验类型 |
| --- | --- | --- |
| `LISTINGS_ITEM_STATUS_CHANGE` | `1.0` | `listing_status_changed` |
| `LISTINGS_ITEM_ISSUES_CHANGE` | `2023-12-13` | `listing_issues_changed` |

Amazon 要求问题通知 `1.0` 版本的用户迁移到 `2023-12-13`。状态事件涉及创建、删除和可购买性；问题事件包含摘要，可能促使进行更完整的读取。它们并不描述所有可能的商品信息问题。[Amazon 通知类型](https://developer-docs.amazon/sp-api/docs/notification-type-values)。

两种原始载荷都允许 `MarketplaceId` 缺失。只能通过可信的、已授权的配置来解析它；否则在创建规范化记录之前先搁置该事件以待调查。绝不要静默地填入你的默认商城。实验环境要求显式的卖家、SKU 和商城。参见固定版本的[状态 schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json)和[问题 schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemIssuesChangeNotification_2023-12-13.json)。

还有一处命名不一致：该状态 schema 的枚举写的是 `LISTINGS_ITEM_STATUS_CHANGED`，而其示例和文档写的是 `LISTINGS_ITEM_STATUS_CHANGE`。任何验证例外都应保持范围窄、带版本，并由适配器测试覆盖。规范化的实验环境既不解析该信封，也不认证其 schema 符合性。

这是夹具中的第一条合成记录，原样如下：

```json
{
  "schema_version": 1,
  "source": "fixture:eventbridge:bus-A",
  "subscription": "fixture-subscription-A",
  "notification_id": "notice-001",
  "seller_id": "seller-A",
  "sku": "SKU-RED",
  "marketplace_id": "market-A",
  "notification_type": "listing_status_changed",
  "event_time": "2026-09-16T10:00:00Z",
  "payload": {"hint": "synthetic status change"}
}
```

`Store.ingest()` 以 JSON 文本形式接受此对象。适配器必须先验证来源、应用/订阅上下文、卖家授权和范围。将内部通知标识与 EventBridge 的外层事件 ID 以及任何队列接收句柄分开保存。[EventBridge 信封](https://docs.aws.amazon.com/eventbridge/latest/ref/events-structure.html)和 [SQS 删除契约](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html)描述了这些不同的传输值。

实验环境按 `(source, subscription, notification_id)` 去重。这个组合键是一项应用策略。把投递尝试计数、接收句柄和接收时间排除在规范化对象之外：它们在不同投递之间会变化。README 列出了其严格的字段、大小和 JSON 验证边界。

## 将接受与待处理工作一起提交

EventBridge 可能投递重复项，且不提供顺序保证。下游的 SQS 标准队列同样允许重复和乱序投递。[AWS 投递对比](https://docs.aws.amazon.com/decision-guides/latest/decision-guides/sns-or-sqs-or-eventbridge.html)、[SQS 标准队列](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html)。

可执行的核心分四步处理这种不确定性：

1. 开始一个 SQLite 事务。对于新标识，同时插入收件箱记录并递增 `(seller_id, sku, marketplace_id)` 的工作世代。
2. 对于已存在的标识，比较规范化后的标准内容。内容相同则返回 `duplicate` 而不递增工作世代。内容不同则抛出 `IdentityConflict` 并保留原始状态。
3. 仅在提交之后返回 `may_ack=True`。这是给你的传输适配器的一个逻辑决定；下载包中没有任何队列确认调用。验证、冲突和存储错误不会产生该决定。
4. 在重启后以及各次运行之间扫描 `dirty_keys()`。当 `generation > applied_generation` 时键为脏。捕获其世代，在事务之外执行读取，然后仅当键和世代仍然匹配时才保存结果。

每个新接受的标识都会推进其键，包括 `event_time` 较旧的事件。载荷仅保留用于标识比较，绝不会作为当前商品信息状态应用。所检查的[状态模型](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json)把 `EventTime` 定义为时间戳，而不是共享的商品信息修订号。这种设计不会仅仅因为时间戳较旧就丢弃工作。

## 观察重启与进行中读取的竞争

下面这份精简的跟踪来自记录的演示，针对 `seller-A / SKU-RED / market-A`：

| 情形 | 世代 | 已应用世代 | 脏 | 观察到的结果 |
| --- | --- | --- | --- | --- |
| 提交后进程退出 | 1 | 0 | true | 一行收件箱记录得以保留 |
| 重启后重新投递 | 1 | 0 | true | `duplicate`，`may_ack: true` |
| 初始合成读取 | 1 | 1 | false | `read-1` 已保存 |
| 较旧的事件到达 | 2 | 1 | true | 需要新的刷新 |
| 世代 2 读取期间又有事件到达 | 3 | 1 | true | `completion_applied: false` |
| 随后的读取失败 | 3 | 1 | true | 保留先前的快照 |
| 之后的刷新成功 | 3 | 3 | false | `read-3` 已保存 |

世代 2 的结果无法清除世代 3。其被拒绝的 `read-2` 文档永远不会替换 `read-1`；随后的失败也让工作保持待处理。演示结束时有五行收件箱记录、三个相互独立的干净键，以及零次外部确认。

记录的 12 个测试全部通过。它们包括提交后子进程硬退出、注入的 SQLite 故障（同时回滚收件箱和工作）、冲突的标识、错误键的结果，以及上述竞争。这确立的是本地进程重启行为，而不是断电或文件系统损坏下的持久性。

「干净」意味着某次读取已针对最新的**已持久化本地世代**被接受。它无法证明 Amazon 的响应包含了触发变更、没有其他通知被延迟，或投递是完整的。该计数器不是 Amazon 的修订号，也不是分布式的恰好一次保证。

## 在清除工作之前验证读取

你的读取适配器必须显式选择 `getListingsItem` 数据集，并在返回 `ReadResult` 之前验证 HTTP 状态、响应内容以及预期的卖家/SKU/商城含义。Amazon 的[商品信息检索教程](https://developer-docs.amazon/sp-api/docs/retrieve-details-about-a-listing)解释了哪些数据集回答哪些问题。它现在支持卖家在同一区域内使用多个商城；本实验环境刻意让每个工作键只对应一个商城。

**核心不验证真实的 Amazon 响应。** 它检查适配器声明的键、JSON 边界和本地世代。即使 `{}` 也能通过其对象形状检查。如果必需的数据缺失、响应属于别处，或请求失败，你的适配器必须抛出异常，而不是返回一个看似成功的占位结果。`refresh_once()` 随后会让工作保持脏状态，并保持先前的快照不变。

关于已接受的提交、当前报价、库存和未知观察的另一个决定，请参见 [Amazon 商品信息更新：已接受不等于已生效](/zh/blog/amazon-listing-update-reconciliation)。一次成功的存储操作无法让不完整的观察变得有意义。

## 在核心之外添加恢复机制

本示例使用一个核对工作进程。它的票据是世代检查，而不是工作进程租约。数据库无限期保留收件箱标识；删除它们会改变去重的时间范围。调度、退避、速率限制、保留策略和并发工作进程的所有权都需要额外设计。

生产管道还需要对格式错误或冲突事件的持久化处理、投递失败恢复，以及对已授权商品信息范围的定期核对。Amazon 建议使用[备用检索机制](https://developer-docs.amazon/sp-api/docs/notifications-api)；EventBridge 在其配置的策略耗尽后停止重试，并支持[死信队列](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-retry-policy.html)。清扫和 DLQ 恢复不在本实验环境范围内。它们覆盖的是收件箱看不到的故障，因为事件从未到达收件箱。

ipvolt 正在开发中。[加入早期访问名单](https://ipvolt.com/#waitlist-closing)，开放访问时收到一封邮件。仅此而已。

## 参考来源与延伸阅读

- [Set up notifications using the Amazon EventBridge workflow](https://developer-docs.amazon/sp-api/docs/set-up-notifications-with-amazon-eventbridge)
- [Notification Type Values](https://developer-docs.amazon/sp-api/docs/notification-type-values)
- [Listings Item Status Change Notification schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json)
- [Listings Item Issues Change Notification schema2023-12-13](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemIssuesChangeNotification_2023-12-13.json)
- [AWS service event metadata](https://docs.aws.amazon.com/eventbridge/latest/ref/events-structure.html)
- [DeleteMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html)
- [Amazon SQS, Amazon SNS, or Amazon EventBridge?](https://docs.aws.amazon.com/decision-guides/latest/decision-guides/sns-or-sqs-or-eventbridge.html)
- [Amazon SQS standard queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html)
- [Retrieve details about a listing for single or multiple Amazon stores](https://developer-docs.amazon/sp-api/docs/retrieve-details-about-a-listing)
- [Notifications API](https://developer-docs.amazon/sp-api/docs/notifications-api)
- [How EventBridge retries delivering events](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-retry-policy.html)

## 相关指南

- [Python Requests 代理配置：认证与 SOCKS5](https://ipvolt.com/zh/guides/python-requests-proxy.md)
- [一次一个阶段地排查代理超时](https://ipvolt.com/zh/guides/proxy-timeout-troubleshooting.md)
- [在 curl 中使用代理：-x、环境变量、SOCKS5 与认证](https://ipvolt.com/zh/guides/curl-proxy-setup.md)

## 关于 ipvolt

示例使用通用的代理设置，并附上原始技术文档链接。具体产品的行为请向你的服务商确认。ipvolt 仍在开发中。

[阅读英文原文](https://ipvolt.com/guides/amazon-listing-notifications.md)

## 了解何时开放体验。

ipvolt · 开发中

我们正在为开发者和数据团队打造代理基础设施。加入意向名单，ipvolt 就绪时第一时间收到通知。

开放体验时仅发一封通知邮件，不发送其他邮件。

[申请抢先体验](https://ipvolt.com/zh/guides/amazon-listing-notifications#waitlist-closing)

[隐私政策](https://ipvolt.com/privacy)

