| |
| """ |
| SEO Stub Generator - Phiên bản đơn giản cho Static Space |
| Tạo file HTML stub cho mỗi sản phẩm để xử lý URL /san-pham/{slug}/ |
| |
| CÁCH CHẠY: |
| 1. Trên UI Space: Settings → Build → Trigger Rebuild |
| (Build sẽ tự động chạy build.sh → python3 app/run_seo_generation.py) |
| 2. Hoặc manually: python3 app/run_seo_generation.py |
| |
| YÊU CẦU: |
| - huggingface_hub (được cài trong build.sh) |
| """ |
| import json |
| import re |
| import unicodedata |
| import urllib.request |
|
|
| try: |
| from huggingface_hub import HfApi |
| except ImportError: |
| import subprocess |
| subprocess.run(["pip", "install", "huggingface_hub", "-q"]) |
| from huggingface_hub import HfApi |
|
|
| SPACE_ID = "bep40/V.AISTUDIO" |
| BASE_URL = "https://bep40-v-aistudio.static.hf.space" |
|
|
| def slugify(name): |
| s = unicodedata.normalize('NFKD', name or '').lower() |
| s = re.sub(r'[^a-z0-9]+', '-', s).strip('-') |
| return s or str(hash(name) % 100000) |
|
|
| def create_stub(p): |
| slug = p.get('slug') or slugify(p.get('name', 'product')) |
| name = str(p.get('name', 'Product'))[:200] |
| brand = str(p.get('brand', '')) |
| img = p.get('image') or f"{BASE_URL}/logo/logo_600.png" |
| desc = re.sub(r'<[^>]+>', '', str(p.get('description', '')))[:160] |
| title = f"{name} | {brand} - V.AISTUDIO" if brand else f"{name} - V.AISTUDIO" |
| |
| return f'''<!DOCTYPE html> |
| <html lang="vi"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>{name} | {brand} - V.AISTUDIO</title> |
| <meta name="description" content="{desc}"> |
| <meta name="robots" content="noindex,follow"> |
| <link rel="canonical" href="{BASE_URL}/san-pham/{slug}/"> |
| <meta property="og:type" content="product"> |
| <meta property="og:title" content="{title}"> |
| <meta property="og:description" content="{desc}"> |
| <meta property="og:image" content="{img}"> |
| <meta property="og:url" content="{BASE_URL}/san-pham/{slug}/"> |
| <meta property="og:site_name" content="V.AISTUDIO"> |
| <meta name="twitter:card" content="summary_large_image"> |
| <script>window.location.replace("/?product={slug}");</script> |
| </head> |
| <body> |
| <h1><a href="/?product={slug}">{title}</a></h1> |
| </body> |
| </html>''' |
|
|
| def main(): |
| |
| url = f"https://huggingface.co/spaces/{SPACE_ID}/resolve/main/index.html" |
| try: |
| with urllib.request.urlopen(url, timeout=30) as r: |
| html = r.read().decode('utf-8') |
| except Exception as e: |
| print(f"LỖI tải index: {e}") |
| return |
| |
| |
| match = re.search(r'var\s+products\s*=\s*(\[[\s\S]*?\]);', html) |
| if not match: |
| print("KHÔNG TÌM THẤY products array!") |
| return |
| |
| products = json.loads(match.group(1)) |
| print(f"Tìm thấy {len(products)} sản phẩm") |
| |
| api = HfApi() |
| uploaded = 0 |
| errors = 0 |
| |
| for i, p in enumerate(products): |
| slug = p.get('slug') or slugify(p.get('name', 'product')) |
| stub = create_stub(p) |
| |
| try: |
| api.upload_file( |
| path_or_fileobj=stub.encode(), |
| path_in_repo=f"san-pham/{slug}/index.html", |
| repo_id=SPACE_ID, |
| repo_type="space", |
| commit_message=f"SEO stub: {slug}" |
| ) |
| uploaded += 1 |
| if uploaded % 100 == 0: |
| print(f"✓ Đã tạo {uploaded}/{len(products)} stubs...") |
| except Exception as e: |
| errors += 1 |
| if errors < 5: |
| print(f"Lỗi {slug}: {e}") |
| |
| print(f"\n=== HOÀN THÀNH ===") |
| print(f"Thành công: {uploaded}") |
| print(f"Lỗi: {errors}") |
|
|
| if __name__ == "__main__": |
| main() |