#!/usr/bin/env python3 """ ルイヴィトンLP v9.9改修スクリプト 全14件の修正を自動適用 """ import re import sys # Usage: python3 apply-fixes.py input.html [output.html] if len(sys.argv) < 2: print("Usage: python3 apply-fixes.py [output.html]") print(" input.html = 元のルイヴィトンLPファイル") print(" output.html = 修正後の出力ファイル(省略時: input-v99-fixed.html)") sys.exit(1) input_path = sys.argv[1] output_override = sys.argv[2] if len(sys.argv) > 2 else None with open(input_path, 'r', encoding='utf-8') as f: html = f.read() print(f"📄 Input: {input_path} ({len(html):,} bytes)") # ============================================================ # FIX 1: data-price-key を全商品固有に変更 # ============================================================ # Build mapping: model number -> price key # We need to find each product item and fix its data-price-key based on its model number price_key_map = { 'M41108': 'lv_M41108', # スピーディ30 (already correct) 'M53152': 'lv_M53152', # アルマBB 'M43186': 'lv_M43186', # エクリプス バックパック 'M42616': 'lv_M42616', # ジッピー・ウォレット 'M40712': 'lv_M40712', # ポシェット・アクセソワール 'M41178': 'lv_M41178', # ネヴァーフルMM (モノグラム) 'M45321': 'lv_M45321', # オンザゴーPM 'M44813': 'lv_M44813', # ミュルティ・ポシェット 'M44873': 'lv_M44873', # パームスプリングス MINI 'M81266': 'lv_M81266', # ナノ・ノエ 'N41358': 'lv_N41358', # ネヴァーフルMM (ダミエ) 'N41414': 'lv_N41414', # キーポル・バンドリエール55 'N41545': 'lv_N41545', # シエナPM 'N51994': 'lv_N51994', # ジェロニモス 'N53151': 'lv_N53151', # アルマPM (ダミエ) 'N41365': 'lv_N41365', # スピーディ25 (ダミエ) 'N41028': 'lv_N41028', # ディストリクト PM 'N63209': 'lv_N63209', # ポルトフォイユ・サラ 'N48074': 'lv_N48074', # アマゾン (SPオーダー) 'N41280': 'lv_N41280', # トータリーPM 'M40932': 'lv_M40932', # ネヴァーフルMM (エピ) 'M50280': 'lv_M50280', # ツイストMM 'M30056': 'lv_M30056', # ジッピー・ウォレット (タイガ) 'M40862': 'lv_M40862', # アルマBB (エピ) 'M30501': 'lv_M30501', # ポルトフォイユ・ブラザ 'M42972': 'lv_M42972', # キーポル 45 (エピ) 'M44028': 'lv_M44028', # ノエ (エピ) 'M33416': 'lv_M33416', # アントン・ブリーフ 'M41312': 'lv_M41312', # クリュニーBB 'M30233': 'lv_M30233', # アウトドア・メッセンジャー 'M44925': 'lv_M44925', # オンザゴー GM 'M54350': 'lv_M54350', # ヒナPM 'M94634': 'lv_M94634', # カプシーヌ BB 'M41053': 'lv_M41053', # モンテーニュBB 'M44829': 'lv_M44829', # ネオアルマBB 'M80478': 'lv_M80478', # プティット・サックプラ 'M64060': 'lv_M64060', # ヴィクトリーヌ 'M51223': 'lv_M51223', # バビロン・チェーン BB 'M51395': 'lv_M51395', # ロックミー・エヴァー 'M93175': 'lv_M93175', # ステラPM 'M61248': 'lv_M61248', # ポルトフォイユ・カプシーヌ 'QA050Z': 'lv_QA050Z', # タンブール ホライゾン V2 'M78656': 'lv_M78656', # バンドー・モノグラム 'M69317': 'lv_M69317', # ポルト クレ・イリュストレ 'M6334F': 'lv_M6334F', # ブラスレ・コンフィデンシャル 'M72432': 'lv_M72432', # エシャルプ・ロゴマニア 'M30271': 'lv_M30271', # コインカード・ホルダー 'Z1165E': 'lv_Z1165E', # 1.1 ミリオネア 'R20005': 'lv_R20005', # アジェンダ PM 'M60067': 'lv_M60067', # ジッピー・コインパース } # Strategy: find each product-item block, extract model number from it, fix price-key def fix_price_keys(html_content): """Fix data-price-key for each product item based on its model number.""" # Split into product items parts = html_content.split('data-category="') if len(parts) <= 1: return html_content result = parts[0] for i, part in enumerate(parts[1:], 1): # Find model number in this product block (from the span with model number) model_match = re.search(r'rounded">([A-Z0-9]+)', part) if model_match: model_num = model_match.group(1) if model_num in price_key_map: correct_key = price_key_map[model_num] # Replace any lv_M41108 with correct key in this block # But only up to the next product-item (or end) next_item = part.find('data-category="', 10) if next_item == -1: next_item = len(part) block = part[:next_item] rest = part[next_item:] block = block.replace('data-price-key="lv_M41108"', f'data-price-key="{correct_key}"') part = block + rest result += 'data-category="' + part return result html = fix_price_keys(html) print(f"[FIX 1] data-price-key: Fixed {sum(1 for m in price_key_map if m != 'M41108')} product keys") # ============================================================ # FIX 2: H1 を sr-only に + aria-hidden 視覚見出し # ============================================================ old_h1 = '''

銀座の鑑定士が査定する
ルイヴィトン
高価買取

2026年最新相場で毎日更新中

''' new_h1 = '''

ルイヴィトン買取|銀座の専門鑑定士が2026年最新相場で査定|エニティ銀座

''' if old_h1 in html: html = html.replace(old_h1, new_h1) print("[FIX 2] H1 sr-only: Applied") else: print("[FIX 2] H1 sr-only: WARNING - pattern not found, manual fix needed") # ============================================================ # FIX 3: animate-pulse 削除(v9.9禁止) # ============================================================ count_pulse = html.count('animate-pulse') html = html.replace(' animate-pulse', '') print(f"[FIX 3] animate-pulse: Removed {count_pulse} instances") # ============================================================ # FIX 4: HTML 閉じタグ不整合修正(参考買取価格セクション) # ============================================================ old_broken = '''

※当サイトでは、最新の国際相場をシステムで自動取得し、常にリアルタイムの最高買取価格を表示しています。

※相場は日々変動します。正確な金額はLINE査定・メール査定でご確認ください。

''' new_fixed = '''

※当サイトでは、最新の国際相場をシステムで自動取得し、常にリアルタイムの最高買取価格を表示しています。

※相場は日々変動します。正確な金額はLINE査定・メール査定でご確認ください。

''' if old_broken in html: html = html.replace(old_broken, new_fixed) print("[FIX 4] Broken : Fixed") else: print("[FIX 4] Broken : WARNING - pattern not found") # ============================================================ # FIX 5-A: llmo-summary ブロック追加(main直後) # ============================================================ llmo_summary = ''' ''' html = html.replace('
\n', '
\n' + llmo_summary, 1) print("[FIX 5-A] llmo-summary: Inserted after
") # ============================================================ # FIX 5-B: 基礎知識セクション → dl/dt/dd 構造に置換 # ============================================================ old_knowledge = '''

ルイヴィトン買取の基礎知識

Knowledge Base

ルイヴィトンの買取相場はどのくらい?

''' new_knowledge_start = '''

ルイヴィトン買取の基礎知識

Knowledge Base

ルイヴィトンの買取相場はどのくらい?
''' if old_knowledge in html: html = html.replace(old_knowledge, new_knowledge_start) print("[FIX 5-B] Knowledge section dl/dt/dd: Started replacement") else: print("[FIX 5-B] WARNING - knowledge section start not found") # Replace article -> div, h3 -> dt, p -> dd for remaining knowledge articles # Article 1 body html = html.replace( '''

ルイヴィトンはブランド品の中でもリセールバリュー(再販価値)が極めて高いブランドです。2026年現在、定番モデルの買取率は定価の30〜60%が相場で、特にネヴァーフルMM(モノグラム)は15〜25万円前後、オンザゴーGMは20〜30万円前後、カプシーヌBBは30〜45万円前後で取引されています。草間彌生コラボや村上隆コラボなどの限定品は定価を超えるプレミア価格になることもあります。相場は国際市場の需給バランスや為替レートの影響を受けるため日々変動しますが、モノグラムやダミエの定番ラインは比較的安定した価格帯を維持しています。

銀座でルイヴィトンを売るメリットとは?

''', '''
ルイヴィトンはブランド品の中でもリセールバリュー(再販価値)が極めて高いブランドです。2026年現在、定番モデルの買取率は定価の30〜60%が相場で、特にネヴァーフルMM(モノグラム)は15〜25万円前後、オンザゴーGMは20〜30万円前後、カプシーヌBBは30〜45万円前後で取引されています。草間彌生コラボや村上隆コラボなどの限定品は定価を超えるプレミア価格になることもあります。相場は国際市場の需給バランスや為替レートの影響を受けるため日々変動しますが、モノグラムやダミエの定番ラインは比較的安定した価格帯を維持しています。
銀座でルイヴィトンを売るメリットとは?
''' ) # Article 2 body html = html.replace( '''

銀座はインバウンド需要と国内富裕層の購買力が集中する日本最大級のラグジュアリーマーケットです。海外バイヤーやリセラーが銀座に集中しているため、地方の買取店よりも高い価格での売却が期待できます。エニティ銀座は銀座一丁目駅から徒歩2分、銀座1-3-6 銀座べラメンテ901に店舗を構え、来店買取のほか全国対応の宅配買取・出張買取にも対応しています。つまり、銀座の相場水準を全国どこにいても享受できる仕組みです。

状態が悪くても売れる?ベタつき・焼け・傷について

''', '''
銀座はインバウンド需要と国内富裕層の購買力が集中する日本最大級のラグジュアリーマーケットです。海外バイヤーやリセラーが銀座に集中しているため、地方の買取店よりも高い価格での売却が期待できます。エニティ銀座は銀座一丁目駅から徒歩2分、銀座1-3-6 銀座べラメンテ901に店舗を構え、来店買取のほか全国対応の宅配買取・出張買取にも対応しています。つまり、銀座の相場水準を全国どこにいても享受できる仕組みです。
状態が悪くても売れる?ベタつき・焼け・傷について
''' ) # Article 3 body html = html.replace( '''

結論から言えば、ルイヴィトンは状態が悪くても売れます。日本特有の高湿度環境により、内張り(ライニング)が加水分解を起こしてベタつく現象はモノグラムやダミエのバッグで非常に多く見られますが、リペア前提やパーツ取りとしての需要があるため、買取不可になることはほとんどありません。ヌメ革(バケッタレザー)の日焼けや雨シミ、ファスナーの不具合、イニシャル刻印(ホットスタンピング)が入ったお品物も買取対象です。ただし、状態によって買取価格は変動するため、まず写真で無料査定を受けることをおすすめします。

ルイヴィトン買取で失敗しないためのポイント

''', '''
結論から言えば、ルイヴィトンは状態が悪くても売れます。日本特有の高湿度環境により、内張り(ライニング)が加水分解を起こしてベタつく現象はモノグラムやダミエのバッグで非常に多く見られますが、リペア前提やパーツ取りとしての需要があるため、買取不可になることはほとんどありません。ヌメ革(バケッタレザー)の日焼けや雨シミ、ファスナーの不具合、イニシャル刻印(ホットスタンピング)が入ったお品物も買取対象です。ただし、状態によって買取価格は変動するため、まず写真で無料査定を受けることをおすすめします。
ルイヴィトン買取で失敗しないためのポイント
''' ) # Article 4 closing html = html.replace( '''

高く売るために重要なのは、複数店での査定比較、付属品(箱・保存袋・カデナ・購入レシート)の揃え、クリーニングの実施、そして相場が高いタイミングでの売却です。ルイヴィトンは新品定価が定期的に値上げされるため、定価改定直後はリセール相場も連動して上昇する傾向があります。また、2021年以降のICチップ搭載モデルは従来のデートコード刻印よりも真贋判定が容易なため、買取店側のリスクが低くなり、高値がつきやすい傾向にあります。

''', '''
高く売るために重要なのは、複数店での査定比較、付属品(箱・保存袋・カデナ・購入レシート)の揃え、クリーニングの実施、そして相場が高いタイミングでの売却です。ルイヴィトンは新品定価が定期的に値上げされるため、定価改定直後はリセール相場も連動して上昇する傾向があります。また、2021年以降のICチップ搭載モデルは従来のデートコード刻印よりも真贋判定が容易なため、買取店側のリスクが低くなり、高値がつきやすい傾向にあります。
''' ) print("[FIX 5-B] Knowledge dl/dt/dd: All 4 articles converted") # ============================================================ # FIX 5-C/D + 6 + 7: JSON-LD 全面置換 # ============================================================ # Find and replace the entire JSON-LD block json_ld_start = html.find('', json_ld_start) + len('') new_json_ld = '''''' if json_ld_start != -1: html = html[:json_ld_start] + new_json_ld + html[json_ld_end:] print("[FIX 5-C/D + 6] JSON-LD: Full replacement (Person @id, speakable, LocalBusiness @id, sameAs)") else: print("[FIX 5-C/D + 6] WARNING - JSON-LD not found") # ============================================================ # FIX 7: meta author 修正 # ============================================================ html = html.replace( '', '' ) print("[FIX 7] meta author: Simplified") # ============================================================ # FIX 8: OGP / Twitter Card 追加 # ============================================================ ogp_block = ''' ''' # Only insert if not already present if 'og:title' not in html: html = html.replace( '', '' + ogp_block ) print("[FIX 8] OGP/Twitter Card: Inserted") else: print("[FIX 8] OGP/Twitter Card: Already present") # ============================================================ # FIX 9: hreflang 追加 # ============================================================ if 'hreflang' not in html: html = html.replace( '', '\n ' ) print("[FIX 9] hreflang: Inserted") else: print("[FIX 9] hreflang: Already present") # ============================================================ # FIX 10: sr-only CSS class 追加 # ============================================================ sr_only_css = ''' /* sr-only(v9.9仕様:H1 + llmo-summary用) */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } ''' if '.sr-only' not in html: html = html.replace( '.font-serif-jp { font-family:', sr_only_css + '\n .font-serif-jp { font-family:' ) print("[FIX 10] .sr-only CSS: Inserted") else: print("[FIX 10] .sr-only CSS: Already present") # ============================================================ # FIX 11: フィルターボタン ARIA + JS連動 # ============================================================ # Add role="tablist" to filter container html = html.replace( 'class="flex flex-wrap justify-center gap-2 mb-8" id="line-filters"', 'class="flex flex-wrap justify-center gap-2 mb-8" id="line-filters" role="tablist" aria-label="カテゴリフィルター"' ) # Add role="tab" and aria-selected to filter buttons html = html.replace( 'class="filter-btn bg-[#C59846] text-white px-4 py-2 rounded-full text-sm font-bold shadow transition duration-300" data-filter="all"', 'class="filter-btn bg-[#C59846] text-white px-4 py-2 rounded-full text-sm font-bold shadow transition duration-300" data-filter="all" role="tab" aria-selected="true"' ) for cat in ['monogram', 'damier', 'epi', 'empreinte', 'others']: html = html.replace( f'data-filter="{cat}">', f'data-filter="{cat}" role="tab" aria-selected="false">' ) # Update JS to toggle aria-selected old_filter_js = ''' filterBtns.forEach(b => { b.classList.remove('bg-[#C59846]', 'text-white'); b.classList.add('bg-gray-100', 'text-gray-600'); }); this.classList.remove('bg-gray-100', 'text-gray-600'); this.classList.add('bg-[#C59846]', 'text-white');''' new_filter_js = ''' filterBtns.forEach(b => { b.classList.remove('bg-[#C59846]', 'text-white'); b.classList.add('bg-gray-100', 'text-gray-600'); b.setAttribute('aria-selected', 'false'); }); this.classList.remove('bg-gray-100', 'text-gray-600'); this.classList.add('bg-[#C59846]', 'text-white'); this.setAttribute('aria-selected', 'true');''' html = html.replace(old_filter_js, new_filter_js) print("[FIX 11] Filter ARIA: Applied (role=tablist, aria-selected, JS sync)") # ============================================================ # FIX 12: LocalBusiness重複のスタンドアロン削除 # (JSON-LD全面置換で対応済み — Service内に@id参照のみ残す) # ============================================================ # Already handled by JSON-LD replacement in FIX 5-C/D + 6 # The old standalone "エニティ銀座|ルイヴィトン買取専門" block is gone print("[FIX 12] LocalBusiness dedup: Handled by JSON-LD replacement") # ============================================================ # Write output # ============================================================ output_path = output_override or input_path.replace('.html', '-v99-fixed.html') with open(output_path, 'w', encoding='utf-8') as f: f.write(html) print(f"\n✅ All fixes applied. Output: {output_path}") print(f" File size: {len(html):,} bytes") print(f"\n📋 Next steps:") print(f" 1. prices.json に全49キーが存在するか確認") print(f" 2. Google Rich Results Test で構造化データを検証") print(f" 3. Lighthouse でアクセシビリティスコアを確認")