# 日本价格解析：日元、全角字符与税込/税抜标签

Source: https://ipvolt.com/zh/blog/japanese-price-parsing
Markdown: https://ipvolt.com/zh/blog/japanese-price-parsing.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [博客](https://ipvolt.com/zh/blog.md) / 日本价格解析：日元、全角字符与税込/税抜标签

分析
发布于: 2026-10-06
更新于: 2026-10-06
作者： ipvolt team
阅读约 2 分钟

解析日本价格字段，同时不丢失币种和计税基础。运行一套经过测试的 Python 夹具，覆盖全角日元、混合价格以及刻意设计的待复核用例。

日本价格解析器应当在返回金额的同时，一并返回币种证据、原始文本和计税基础。先选定一个报价字段，对 Unicode 规范化加以防护，并搁置含糊的输入。一个 `parsed` 字段在成为被接受的监控记录之前，仍然需要经过检查。

可下载的 Python 示例把这条边界讲得很明确。它的 **36 个合成离线用例产生了 36 个预期分类：11 个已解析字段、25 个待复核结果，夹具失败为零**。没有测试过任何日本店铺或代理。有用的结果是一组可复现的决策，其中也包括解析器何时拒绝选定价格。

## 一个价格展示可以包含两个合法金额

日本国税厅在其关于总价标示的指引中给出了这个例子：

```text
10,000円（税込価格11,000円）
```

明确含税的金额是 11,000 日元，尽管 10,000 出现在前面。国税厅的示例允许同时包含含税和不含税数字的价格展示。取第一个数字就会丢失这一区别。[国税厅 No. 6902，标示示例](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shohi/6902.htm)。

删除所有非数字字符更糟：把 Python 的 `re.sub(r"\D", "", text)` 应用到这个字符串上，会得到 `1000011000`。随附的解析器返回 `review`，原因为 `unsupported_or_multiple_price_text`。它不会从包含多个价格的容器中选定一个金额。

应在上游解决这个问题：在所选报价中识别出目标金额及其标签，并保留来源上下文。如果你的提取器无法区分这些金额，就让这条记录保持待复核状态。一项一般性的标示义务并不能证明任意一个提取字段的计税基础。

## 解析之前先要求币种上下文

`¥` 字符并不能证明币种是日元。Unicode 规定 U+00A5 同时用于日元和人民币元，与字形样式无关。在提供 `JPY` 之前，先为同一报价取得明确的币种上下文。[Unicode 17，“Yen and Yuan”](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-22/)。

该函数只接受三个键，其中 `text` 限定为 1–200 个字符的字符串。下面是一个**合成的夹具输入**，其证据引用有意写成了一个夹具标签：

```json
{
  "text": "￥１，２９８（税込）",
  "currency": "JPY",
  "currency_evidence": "fixture.selected_offer.priceCurrency"
}
```

对于这个输入，记录下来的结果是：

```json
{
  "original": "￥１，２９８（税込）",
  "status": "parsed",
  "normalized": "¥1,298(税込)",
  "amount_yen": 1298,
  "currency": "JPY",
  "currency_evidence": "fixture.selected_offer.priceCurrency",
  "tax_basis": "included",
  "tax_label": "税込"
}
```

在适配器中，用这三个键调用所下载模块中的 `parse_price(record)`。把商品标识、URL、时间戳和其他监控上下文放在外层记录中；该函数的输入中如果有多余的键，会返回 `invalid_record`。

**证据引用是调用方的一项断言。** 该函数要求一个非空字符串，但既不获取它，也不验证它的真实性。作者用一个刻意伪造的引用做了一次探测，仍然得到了 `parsed`。因此，仅因页面上有一个形似日元符号的字形就提供 `JPY`，即使解析器通过，也会违背预期的约定。请单独核查底层的币种证据，并保留冲突。

## 在规范化改变字段之前加以防护

全角数字可以表示普通的十进制数字，但兼容性规范化也会改变其他字符。Unicode 的 NFKC 形式可能抹去宽度之外的区别；Python 通过 `unicodedata.normalize` 提供它。[Unicode 兼容数字](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-22/)、[规范化指引](https://unicode.org/faq/normalization.html)、[Python unicodedata](https://docs.python.org/3/library/unicodedata.html)。

这个解析器在规范化整个字段之前，会先逐个检查字符。它允许以下字符发生变化：全角 ASCII U+FF01–U+FF5E、表意空格 U+3000 和全角日元符号 U+FFE5。其他在 NFKC 下会改变的字符一律转入复核。语法在规范化之后检查；允许的宽度转换本身并不会让一个输入成为价格。

这个顺序对 `¥①２９８（税込）` 和 `¥¹298（税込）` 很重要。盲目的兼容性折叠可能把带圈字符或上标字符变成普通数字。夹具把两者都以 `unsupported_compatibility_character` 搁置。它也搁置 ASCII 和全角反斜杠；一个字符在字体中的外观并不会让它成为日元符号。

像上面那条成功的记录一样，把 `original` 与 `normalized` 一起保留。这种带防护的策略专属于所下载的解析器。它并不是建议对商品名称、标识符或任意页面文本不加区分地应用 NFKC。

## 保留计税基础，但不计算税额

解析器接受一个整日元表达式：`¥1298`、`1298円` 或 `JPY 1298`，可以带分组正确的逗号。受支持的尾随税务标签可以出现在括号中，或者跟在空白之后。它把 `税込` 和 `税込価格` 映射为 `included`；把 `税抜`、`税抜き` 和 `税別` 映射为 `excluded`。没有标签时产生 `unspecified`。

下面是从实际执行的夹具中选出的部分结果。除非另有说明，输入都带有 `currency: "JPY"` 和上面所示的合成证据引用。

| 输入字段 | 结果 | 对后续比较的影响 |
| --- | --- | --- |
| `￥１，２９８（税込）` | `parsed`：1298，`included` | 把含税基础与金额一起保留。 |
| `1,180円（税抜）` | `parsed`：1180，`excluded` | 不要悄悄把它当作与含税金额相同的基础来比较。 |
| `1,298円` | `parsed`：1298，`unspecified` | 如果任务要求已知的计税基础，这项要求仍未解决。 |
| `10,000円（税込価格11,000円）` | `review` | 解析之前先选定目标字段，并为其提供证据。 |
| `12,98円（税込）` | `review` | 这种分组不符合本语法。 |
| `¥①２９８（税込）` | `review` | 不受支持的兼容性变化会在规范化之前被搁置。 |
| `¥1298（税込）`，币种缺失或为 `CNY` | `review` | 一个熟悉的字形无法提供所需的 JPY 上下文。 |

这里**没有税率计算、货币换算或以分为单位的乘数**。在这个解析器的整日元输入约定下，`amount_yen` 是一个整数；它并不规定每个 API 所使用的单位。不要根据附近的数字推算出缺失的总额或净额。

该语法还会搁置价格区间、分期付款、积分、多个价格、负数、小数、汉字数字单位、前置税务标签和前导零。其中一些可能是本范围之外的有效店铺展示方式。`review` 结果表示该输入在本语法下尚未解决，而不是说商家的展示无效。

## 运行同一个离线夹具

把 [parse_price.py](https://ipvolt.com/downloads/japanese-price-parsing/parse_price.py) 和 [fixtures.json](https://ipvolt.com/downloads/japanese-price-parsing/fixtures.json) 下载到同一个目录。脚本使用 Python 标准库；[README](https://ipvolt.com/downloads/japanese-price-parsing/README.md) 给出了适用于 Python 3.10 或更新版本的输入约定和限制。运行：

```sh
python3 parse_price.py fixtures.json > replay.json
```

该命令不发出任何网络请求。把 `replay.json` 与已发布的 [results.json](https://ipvolt.com/downloads/japanese-price-parsing/results.json) 进行比较。结果一致的运行会报告 `cases: 36`、`passed: 36` 和 `failed: 0`。那 25 个待复核结果是预期中的决策，因此不算作夹具失败。

夹具会检查每个声明的预期字段，并保留完整的实际输出。退出状态 0 表示夹具的预期全部相符；状态 1 表示至少有一项不符；状态 2 表示夹具无效或无法读取。这些状态都不是店铺层面的验收信号。

原始的[环境记录](https://ipvolt.com/downloads/japanese-price-parsing/environment.json)记载了 Python 3.14.7 和 Unicode 数据库 16.0.0。作者在 Python 3.14.8 上的重放同样使用 Unicode 数据库 16.0.0，生成了完全相同的结果 JSON。引用 Unicode 17 的文档并不意味着可执行程序使用了 Unicode 17 数据库。

## 判断已解析的字段是否可用

在接受一条监控记录之前，把该字段绑定到确切的商品和变体，保留来源和观测时间，验证币种证据，并确定你的比较所需的价格基准。当任务依赖数量、会员资格、促销条件和配送上下文时，也要检查它们。这些决策都在这个解析器之外。

例如，夹具中 1298 日元的含税字段和 1180 日元的不含税字段都能解析。但这并不会让它们之间的数值差成为测得的价格变化。一个没有标签的字段同样可以解析，却仍然不适合用于要求含税价格的比较。

另一篇[价格与币种验证案例](/zh/blog/product-price-currency-validation)讨论的是相互冲突的报价信号和店铺信号。如果你的采集还需要日本出口或指定的移动接入，请使用[日本代理评估指南](/zh/guides/japan-proxies)获取这方面的证据。本地解析结果不提供任何关于出口的国家、网络或可用性的证据。

*方法：智能体辅助的来源研究与综合，并于 2026 年 10 月 6 日执行了合成夹具以及上文另行说明的限制探测。这些用例展示的是所述的解析器行为；它们不是日本零售商的样本，也不是准确率基准。没有测试任何店铺、代理或税额计算。*

[访问开放时通知我](/zh/blog/japanese-price-parsing#waitlist-blog-end)。访问开放时发送一封邮件。仅此而已。

## 参考来源

- [No.6902 「総額表示」の義務付け](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shohi/6902.htm)
- [Unicode 17.0.0, Chapter 22: Symbols](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-22/)
- [FAQ: Normalization](https://unicode.org/faq/normalization.html)
- [Python unicodedata](https://docs.python.org/3/library/unicodedata.html)

## 第一时间了解 ipvolt 开放体验。

顺便一提

ipvolt 仍在开发中。留下邮箱，开放体验时我们只会通知你一次。

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

[申请抢先体验](https://ipvolt.com/zh/blog/japanese-price-parsing#waitlist-blog-end)

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


## 相关文章

- [SOCKS5 与 SOCKS5h 的区别：6 个客户端实测](https://ipvolt.com/zh/blog/socks5-vs-socks5h.md) (对比, 2026年10月4日, 阅读约 3 分钟): socks5:// 并不是在每个客户端里都表示本地 DNS。我们记录了 curl、Requests、HTTPX、aiohttp、Playwright 和 Node 在每种协议写法下发给 SOCKS5 代理的地址。
- [CONNECT tunnel failed 403：智能体为何遇到](https://ipvolt.com/zh/blog/connect-tunnel-failed-403.md) (分析, 2026年10月2日, 阅读约 3 分钟): 智能体的 curl 报出 CONNECT tunnel failed, response 403，而其他主机都正常。这是代理拒绝建立隧道。本文说明如何判断是谁拦截了它，以及该怎么修。
- [博彩赔率 API 与抓取对比：成本工作表](https://ipvolt.com/zh/blog/betting-odds-api-vs-scraping.md) (对比, 2026年9月28日, 阅读约 2 分钟): 用一份可编辑的工作表，按覆盖范围、数据新鲜度和建模采集成本比较博彩赔率 API 与经授权的网页抓取，并清楚写明全部工作量假设和计算方法。

## 相关指南

- [日本代理IP：位置与 au、UQ、Rakuten 运营商核查](https://ipvolt.com/zh/guides/japan-proxies.md): 评估日本代理IP时，把 IP 位置、出口和移动接入证据分开核查；在接受样本之前，先理清 au/UQ 的品牌关系和 Rakuten 的漫游情况。
- [Python Requests 代理配置：认证与 SOCKS5](https://ipvolt.com/zh/guides/python-requests-proxy.md): 在 Python Requests 中配置代理：proxies 字典、Session 默认值、凭据、通过 requests[socks] 使用 SOCKS5、环境变量与 ProxyError。
- [轮换代理与粘性代理：为会话连续性做好规划](https://ipvolt.com/zh/guides/rotating-vs-sticky-proxies.md): 围绕最小的完整工作流来选择轮换策略，区分出口 IP 绑定与 Cookie 状态，并测试粘性会话提前结束时你的应用应当如何处理。

## 关于 ipvolt

来自 ipvolt 团队的技术分析。

ipvolt 尚未开放使用。

[阅读英文原文](https://ipvolt.com/blog/japanese-price-parsing.md)
