Spaces:
Runtime error
Runtime error
| # app.py | |
| # pip install gradio==4.43.0 pandas requests unidecode | |
| import requests | |
| import re | |
| import random | |
| import gradio as gr | |
| import pandas as pd | |
| from difflib import SequenceMatcher | |
| from unidecode import unidecode | |
| import os | |
| # ----------------------------- | |
| # 工具函数 | |
| # ----------------------------- | |
| def normalize(s: str) -> str: | |
| s = s.strip().lower() | |
| s = unidecode(s) | |
| s = re.sub(r"\s+", " ", s) | |
| return s | |
| def dedup_words(words): | |
| out, seen = [], set() | |
| for w in words: | |
| k = normalize(w) | |
| if k not in seen: | |
| seen.add(k) | |
| out.append(w) | |
| return out | |
| def similarity(a: str, b: str) -> float: | |
| return SequenceMatcher(None, normalize(a), normalize(b)).ratio() | |
| # ----------------------------- | |
| # TikTok Cookie 支持 | |
| # ----------------------------- | |
| TIKTOK_COOKIE = "" | |
| def set_cookie(cookie_text): | |
| global TIKTOK_COOKIE | |
| TIKTOK_COOKIE = cookie_text | |
| return "✅ Cookie 已更新" | |
| # ----------------------------- | |
| # 1. TikTok 实时热搜抓取 | |
| # ----------------------------- | |
| def get_tiktok_trending_keywords(region="VN", query=None, topk=12): | |
| if query: | |
| url = f"https://www.tiktok.com/api/discover/search/?keyword={query}®ion={region}" | |
| else: | |
| url = f"https://www.tiktok.com/api/discover/trending/?region={region}" | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", | |
| "Referer": "https://www.tiktok.com/", | |
| } | |
| if TIKTOK_COOKIE: | |
| headers["Cookie"] = TIKTOK_COOKIE | |
| try: | |
| r = requests.get(url, headers=headers, timeout=8) | |
| r.raise_for_status() | |
| data = r.json() | |
| except Exception as e: | |
| print("请求失败:", e) | |
| return [] | |
| keywords = [] | |
| for item in data.get("data", []): | |
| title = item.get("title") or item.get("challenge_info", {}).get("title") | |
| views = item.get("viewCount") or 0 | |
| if title: | |
| keywords.append((title.strip(), views)) | |
| keywords.sort(key=lambda x: x[1], reverse=True) | |
| return [kw for kw, _ in keywords[:topk]] | |
| # ----------------------------- | |
| # 2. 热词建议 | |
| # ----------------------------- | |
| def suggest_hotwords(product_name: str, category: str, lang: str, topk: int = 12): | |
| region_map = {"zh": "CN", "fr": "FR", "en": "US", "vi": "VN"} | |
| region = region_map.get(lang, "US") | |
| return get_tiktok_trending_keywords(region=region, query=product_name, topk=topk) | |
| # ----------------------------- | |
| # 3. 标题生成 | |
| # ----------------------------- | |
| def rank_keywords(core_words, hotwords): | |
| items = [] | |
| for w in core_words: | |
| items.append((w, 1.0)) | |
| for i, w in enumerate(hotwords): | |
| items.append((w, 0.9 - i * 0.02)) | |
| uniq = {} | |
| for w, sc in items: | |
| k = normalize(w) | |
| if k not in uniq or sc > uniq[k]: | |
| uniq[k] = sc | |
| ranked = sorted([(w, sc) for w, sc in uniq.items()], key=lambda x: x[1], reverse=True) | |
| return [w for w, _ in ranked] | |
| def build_title(core, ranked, max_len): | |
| segments = dedup_words(core + ranked) | |
| title = " ".join(segments) | |
| if len(title) > max_len: | |
| title = title[:max_len] | |
| return title | |
| def generate_titles(product_name, category, lang, n_titles, hotwords_text, max_len): | |
| core_words = dedup_words(re.split(r"[,\|/;,、 ]+", product_name)) | |
| hotwords = dedup_words(re.split(r"[,\|/;,、\n]+", hotwords_text)) | |
| ranked = rank_keywords(core_words, hotwords) | |
| out = [] | |
| for i in range(n_titles): | |
| random.shuffle(ranked) | |
| title = build_title(core_words, ranked, max_len) | |
| out.append(title) | |
| df = pd.DataFrame({"序号": list(range(1, len(out) + 1)), "标题": out}) | |
| csv_bytes = df.to_csv(index=False).encode("utf-8-sig") | |
| return "\n".join(out), csv_bytes | |
| # ----------------------------- | |
| # 4. Gradio UI | |
| # ----------------------------- | |
| LANGS = [("中文", "zh"), ("Français", "fr"), ("English", "en"), ("Tiếng Việt", "vi")] | |
| def ui_hotwords(product_name, category, lang, topk): | |
| if not product_name: | |
| return "" | |
| kws = suggest_hotwords(product_name, category, lang, int(topk)) | |
| return ", ".join(kws) | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("## TikTok 商品标题优化工具(实时热搜 + Cookie 支持)") | |
| with gr.Row(): | |
| product_name = gr.Textbox(label="商品名称与关键词", placeholder="输入商品名,例如:阻力带", lines=2) | |
| category = gr.Textbox(label="类目", value="健身用品/健身器材") | |
| with gr.Row(): | |
| lang = gr.Dropdown(LANGS, value="fr", label="语言") | |
| n_titles = gr.Slider(1, 10, value=3, step=1, label="生成数量") | |
| max_len = gr.Slider(30, 120, value=80, step=1, label="最大标题长度") | |
| topk = gr.Slider(6, 30, value=12, step=1, label="热门关键词数量") | |
| hotwords_box = gr.Textbox(label="热门关键词(实时获取)", lines=2, placeholder="将自动填充") | |
| btn_refresh = gr.Button("⟳ 获取实时热门关键词") | |
| btn_generate = gr.Button("⚡ 生成标题") | |
| cookie_input = gr.Textbox(label="TikTok Cookie(可选)", placeholder="可手动粘贴 Cookie", lines=2) | |
| btn_set_cookie = gr.Button("✅ 更新 Cookie") | |
| titles_out = gr.Textbox(label="生成结果", lines=10) | |
| csv_file = gr.File(label="下载 CSV", file_count="single") | |
| product_name.change(ui_hotwords, [product_name, category, lang, topk], [hotwords_box]) | |
| lang.change(ui_hotwords, [product_name, category, lang, topk], [hotwords_box]) | |
| topk.change(ui_hotwords, [product_name, category, lang, topk], [hotwords_box]) | |
| btn_refresh.click(ui_hotwords, [product_name, category, lang, topk], [hotwords_box]) | |
| btn_generate.click( | |
| generate_titles, | |
| [product_name, category, lang, n_titles, hotwords_box, max_len], | |
| [titles_out, csv_file] | |
| ) | |
| btn_set_cookie.click(set_cookie, [cookie_input], [cookie_input]) | |
| if __name__ == "__main__": | |
| demo.launch() | |