# Japanese price parsing: yen, width and tax labels

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

[Home](https://ipvolt.com/index.md) / [Blog](https://ipvolt.com/blog.md) / Japanese price parsing: yen, width and tax labels

Category: Analysis
Published: 2026-10-06
Updated: 2026-10-06
Author: ipvolt team
Reading time: 6 minutes
Tags: proxies, troubleshooting

Parse Japanese price fields without losing currency or tax basis. Run a tested Python fixture for fullwidth yen, mixed prices and deliberate review cases.

A Japanese price parser should return an amount together with currency evidence, original text and tax basis. Select one offer field first, guard Unicode normalization, and hold ambiguous inputs. A `parsed` field still needs checks before it becomes an accepted monitoring record.

The downloadable Python example makes that boundary explicit. Its **36 synthetic offline cases produced 36 expected classifications: 11 parsed fields, 25 review outcomes and zero fixture failures**. No Japanese storefront or proxy was tested. The useful result is a reproducible set of decisions, including when the parser declines to choose a price.

## A display can contain two legitimate amounts

Japan's National Tax Agency gives this example in its total-price display guidance:

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

The explicitly tax-inclusive amount is 11,000 yen, although 10,000 appears first. The NTA's examples permit displays containing both inclusive and exclusive figures. Taking the first number would lose that distinction. [NTA No. 6902, display examples](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shohi/6902.htm).

Removing every nondigit is worse: applied to this string, Python's `re.sub(r"\D", "", text)` produces `1000011000`. The supplied parser returns `review` with reason `unsupported_or_multiple_price_text`. It does not choose an amount from a multi-price container.

Resolve that upstream: identify the intended amount and its label within the selected offer, preserving the source context. If your extractor cannot distinguish the amounts, keep the record under review. A general display obligation does not prove the tax basis of an arbitrary extracted field.

## Require currency context before parsing

The `¥` character does not establish Japanese yen. Unicode specifies U+00A5 for both Japanese yen and Chinese yuan, independent of glyph styling. Obtain explicit currency context for the same offer before supplying `JPY`. [Unicode 17, “Yen and Yuan”](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-22/).

The function accepts only three keys, with `text` limited to a string of 1–200 characters. Here is a **synthetic fixture input**, whose evidence reference is deliberately a fixture label:

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

For that input, the recorded result is:

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

In an adapter, call `parse_price(record)` from the downloaded module with those three keys. Keep product identity, URL, timestamp and other monitoring context in an outer record; extra keys in this function's input return `invalid_record`.

**The evidence reference is a caller assertion.** The function requires a nonempty string but neither retrieves nor authenticates it. An author probe using a deliberately fabricated reference still returned `parsed`. Supplying `JPY` because the page has a yen-shaped glyph would therefore defeat the intended contract even if the parser passes. Check the underlying currency evidence separately and preserve conflicts.

## Guard normalization before it changes the field

Fullwidth digits can represent ordinary decimal digits, but compatibility normalization also changes other characters. Unicode's NFKC form can erase distinctions beyond width; Python exposes it through `unicodedata.normalize`. [Unicode compatibility digits](https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-22/), [normalization guidance](https://unicode.org/faq/normalization.html), [Python unicodedata](https://docs.python.org/3/library/unicodedata.html).

This parser checks each character before normalizing the whole field. It permits changes for fullwidth ASCII U+FF01–U+FF5E, ideographic space U+3000 and fullwidth yen U+FFE5. Other characters that change under NFKC go to review. The grammar is checked after normalization; an allowed width conversion does not itself make an input a price.

That order matters for `¥①２９８（税込）` and `¥¹298（税込）`. Blind compatibility folding can turn the circled or superscript character into an ordinary digit. The fixture holds both as `unsupported_compatibility_character`. It also holds ASCII and fullwidth backslashes; a character's font appearance does not make it a yen sign.

Preserve `original` alongside `normalized`, as the successful record does above. This guarded policy is specific to the downloaded parser. It is not a prescription to apply NFKC indiscriminately to product names, identifiers or arbitrary page text.

## Preserve tax basis without calculating tax

The parser accepts one whole-yen expression: `¥1298`, `1298円` or `JPY 1298`, with optional correctly grouped commas. A supported trailing tax label can appear in parentheses or after whitespace. It maps `税込` and `税込価格` to `included`; `税抜`, `税抜き` and `税別` to `excluded`. No label produces `unspecified`.

Selected outcomes from the executed fixture are below. Except where stated, the input has `currency: "JPY"` and the synthetic evidence reference shown above.

| Input field | Result | Consequence for a later comparison |
| --- | --- | --- |
| `￥１，２９８（税込）` | `parsed`: 1298, `included` | Keep the inclusive basis with the amount. |
| `1,180円（税抜）` | `parsed`: 1180, `excluded` | Do not silently compare it as the same basis as an inclusive amount. |
| `1,298円` | `parsed`: 1298, `unspecified` | If the job requires known tax basis, that requirement remains unresolved. |
| `10,000円（税込価格11,000円）` | `review` | Select and evidence the intended field before parsing. |
| `12,98円（税込）` | `review` | The grouping does not meet this grammar. |
| `¥①２９８（税込）` | `review` | An unsupported compatibility change is held before normalization. |
| `¥1298（税込）`, currency missing or `CNY` | `review` | A familiar glyph cannot supply the required JPY context. |

There is **no tax-rate calculation, currency conversion or cents multiplier**. `amount_yen` is an integer under this parser's whole-yen input contract; it does not define the units used by every API. Do not derive a missing gross or net value from the nearby number.

The grammar also holds ranges, instalments, points, multiple prices, negatives, decimals, Kanji-number units, prefixed tax labels and leading zeros. Some may be valid store presentations outside this scope. A `review` outcome means the input is unresolved under this grammar, not that the merchant's display is invalid.

## Run the same offline fixture

Download [parse_price.py](https://ipvolt.com/downloads/japanese-price-parsing/parse_price.py) and [fixtures.json](https://ipvolt.com/downloads/japanese-price-parsing/fixtures.json) into one directory. The script uses Python's standard library; the [README](https://ipvolt.com/downloads/japanese-price-parsing/README.md) gives the Python 3.10-or-newer input contract and limits. Run:

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

The command makes no network requests. Compare `replay.json` with the published [results.json](https://ipvolt.com/downloads/japanese-price-parsing/results.json). A matching run reports `cases: 36`, `passed: 36` and `failed: 0`. The 25 review outcomes are expected decisions, so they do not count as fixture failures.

The fixture checks each declared expected field and retains complete actual outputs. Exit status 0 means the fixture expectations matched; status 1 means at least one did not; status 2 reports an invalid or unreadable fixture. None is a storefront acceptance signal.

The original [environment record](https://ipvolt.com/downloads/japanese-price-parsing/environment.json) documents Python 3.14.7 with Unicode database 16.0.0. The author replay on Python 3.14.8, also using Unicode database 16.0.0, produced the same complete result JSON. Citing Unicode 17 documentation does not mean the executable used a Unicode 17 database.

## Decide whether a parsed field is usable

Before accepting a monitoring record, bind the field to the exact product and variant, retain the source and observation time, validate currency evidence and resolve the price basis your comparison requires. Check quantity, membership, sale conditions and delivery context when the job depends on them. Those are decisions outside this parser.

For example, the fixture's 1298-yen inclusive field and 1180-yen exclusive field both parse. That does not make their numeric difference a measured price change. An unlabelled field can also parse while remaining unsuitable for a comparison that requires inclusive prices.

The separate [price and currency validation case](/blog/product-price-currency-validation) addresses conflicting offer and storefront signals. If your collection also requires a Japanese exit or named mobile access, use the [Japan proxy evaluation guide](/guides/japan-proxies) for that evidence. A local parsing result supplies no evidence about an exit's country, network or availability.

*Method: agent-assisted source research and synthesis, with executed synthetic fixtures and the separately described limit probe on 6 October 2026. These cases demonstrate the stated parser behavior; they are not a sample of Japanese retailers or an accuracy benchmark. No storefront, proxy or tax calculation was tested.*

[Notify me when access opens](https://ipvolt.com/blog/japanese-price-parsing#waitlist-blog-end). One email when access opens. Nothing else.

## Sources

- [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)

## Know when ipvolt access opens.

ipvolt is in development. Leave your email and we’ll notify you once when access opens.

Consent: One email when access opens. Nothing else.

[Notify me](https://ipvolt.com/blog/japanese-price-parsing#waitlist-blog-end). Use the email form on this page to join the interest list.

[Privacy](https://ipvolt.com/privacy)

## Related posts

- [SOCKS5 vs SOCKS5h: What 6 Clients Actually Send (Tested)](https://ipvolt.com/blog/socks5-vs-socks5h.md) (Comparison, Oct 4, 2026, 7 min read): socks5:// does not mean local DNS in every client. We logged what curl, Requests, HTTPX, aiohttp, Playwright and Node send a SOCKS5 proxy for each scheme.
- [CONNECT tunnel failed, response 403: Why AI Agents Hit It](https://ipvolt.com/blog/connect-tunnel-failed-403.md) (Analysis, Oct 2, 2026, 8 min read): Your agent's curl fails with CONNECT tunnel failed, response 403 while other hosts work. The proxy refused the tunnel. How to tell who blocked it, and the fix.
- [Betting odds API vs scraping: a cost worksheet](https://ipvolt.com/blog/betting-odds-api-vs-scraping.md) (Comparison, Sep 28, 2026, 7 min read): Compare odds APIs and authorized scraping by coverage, freshness and modeled collection cost. Use an editable worksheet with clear workload assumptions.

## Related guides

- [Japan proxies: IP location and mobile network checks](https://ipvolt.com/guides/japan-proxies.md): Evaluate Japan proxies with separate IP-location, egress and mobile-access evidence. Work through au/UQ branding and Rakuten roaming before accepting a sample.
- [Python Requests proxy: proxies dict, auth, SOCKS5](https://ipvolt.com/guides/python-requests-proxy.md): Configure a proxy in Python Requests: the proxies dictionary, Session defaults, credentials, SOCKS5 via requests[socks], environment variables and ProxyError.
- [Rotating vs sticky proxies: plan for session continuity](https://ipvolt.com/guides/rotating-vs-sticky-proxies.md): Choose rotation around the smallest complete workflow, distinguish exit affinity from cookies, and test what happens when a sticky session ends early.

## About ipvolt

Technical analysis from the ipvolt team.

ipvolt access is not open yet.
