diff --git a/src/main/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractor.java b/src/main/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractor.java index ee88019..1783494 100644 --- a/src/main/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractor.java +++ b/src/main/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractor.java @@ -257,11 +257,12 @@ private record ResolvedPrice(String price, String currency) { /** * OG 가 이름·이미지는 주지만 가격을 JS state(window.__PRELOADED_STATE__ 등)에만 둔 SPA(예: 유니클로)를 * LLM 없이 추출하기 위한 특화 경로다 — 가격이 거대 state 깊숙이 있어 Gemini fallback 의 토큰 상한에 - * 안 맞는 사이트를 파서가 직접 건진다. OG 표준 가격 태그가 있으면 불필요한 state 파싱을 하지 않는다. + * 안 맞는 사이트를 파서가 직접 건진다. OG 가격 태그가 있으면 불필요한 state 파싱을 하지 않는다. */ private ResolvedPrice resolvePrice(Document document) { - String ogCurrency = metaContent(document, "product:price:currency"); - String ogAmount = metaContent(document, "product:price:amount"); + // 구형 OG product 네임스페이스(og:price:*)만 내보내는 몰(Shopify 계열)이 있어 표준 태그 뒤에 폴백으로 둔다. + String ogCurrency = firstMetaContent(document, "product:price:currency", "og:price:currency"); + String ogAmount = firstMetaContent(document, "product:price:amount", "og:price:amount"); if (ogAmount != null) { return new ResolvedPrice(ogAmount, ogCurrency); } @@ -374,6 +375,16 @@ private JsonNode findPricesNode(JsonNode node) { return null; } + private String firstMetaContent(Document document, String... properties) { + for (String property : properties) { + String content = metaContent(document, property); + if (content != null) { + return content; + } + } + return null; + } + private String metaContent(Document document, String property) { Element meta = document.selectFirst("meta[property=" + property + "]"); if (meta == null) { diff --git a/src/test/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractorTest.java b/src/test/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractorTest.java index 0171533..17d9de0 100644 --- a/src/test/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractorTest.java +++ b/src/test/java/com/depromeet/piki/extractor/extraction/structured/StructuredDataExtractorTest.java @@ -301,6 +301,42 @@ void extractsFromOpenGraph() { assertEquals("KRW", snapshot.currency()); } + @Test + @DisplayName("표준 가격 태그가 없고 구형 og_price 네임스페이스만 있으면 콤마 낀 가격을 뽑는다") + void extractsFromLegacyOgPriceNamespace() { + String html = """ +
+ + + + + """; + + ProductSnapshot snapshot = snapshotOrNull(extractor.extract(pageOf(html))); + + assertEquals("Stream Pants (Archive) - Charcoal", snapshot.name()); + assertEquals(680_000, snapshot.currentPrice()); + assertEquals("KRW", snapshot.currency()); + } + + @Test + @DisplayName("표준 product_price 태그와 구형 og_price 가 함께 있으면 표준을 쓴다") + void prefersStandardPriceTagOverLegacyNamespace() { + String html = """ + + + + + + + """; + + ProductSnapshot snapshot = snapshotOrNull(extractor.extract(pageOf(html))); + + assertEquals(45_000, snapshot.currentPrice()); + assertEquals("KRW", snapshot.currency()); + } + @Test @DisplayName("og 에 title 만 있고 가격이 없으면 missing_field 로 fallback 한다") void openGraphTitleOnlyFallsBackToMissingField() {