zhangliang 2 часов назад
Родитель
Сommit
76699272aa

+ 1 - 0
examples/content_finder/tools/douyin_search.py

@@ -91,6 +91,7 @@ async def douyin_search(
 
             summary_lines.append(f"{i}. {desc}")
             summary_lines.append(f"   ID: {aweme_id}")
+            summary_lines.append(f"   链接: https://www.douyin.com/video/{aweme_id}")
             summary_lines.append(f"   作者: {author_name} (sec_uid: {author_id})")
             summary_lines.append(f"   数据: 点赞 {digg_count:,} | 评论 {comment_count:,} | 分享 {share_count:,}")
             summary_lines.append("")

+ 30 - 26
examples/content_finder/tools/douyin_user_videos.py

@@ -67,59 +67,63 @@ async def douyin_user_videos(
         # 格式化输出摘要
         summary_lines = [f"账号 {account_id} 的作品列表"]
 
-        aweme_list = data.get("aweme_list", [])
-        has_more = data.get("has_more", False)
-        cursor_value = data.get("cursor", "")
+        data_block = data.get("data", {}) if isinstance(data.get("data"), dict) else {}
+        items = data_block.get("data", []) if isinstance(data_block.get("data"), list) else []
+        has_more = data_block.get("has_more", False)
+        cursor_value = data_block.get("next_cursor", "")
 
-        summary_lines.append(f"找到 {len(aweme_list)} 个作品" + (f",还有更多(cursor={cursor_value})" if has_more else ""))
+        summary_lines.append(f"找到 {len(items)} 个作品" + (f",还有更多(cursor={cursor_value})" if has_more else ""))
         summary_lines.append("")
 
         # 显示前5条
-        for i, aweme in enumerate(aweme_list[:5], 1):
-            aweme_id = aweme.get("aweme_id", "unknown")
-            desc = aweme.get("desc", "无标题")[:50]
+        for i, item in enumerate(items[:5], 1):
+            aweme_id = item.get("aweme_id", "unknown")
+            desc = (item.get("desc") or item.get("item_title") or "无标题")[:50]
 
-            stats = aweme.get("statistics", {})
+            author = item.get("author", {})
+            author_name = author.get("nickname", "未知作者")
+            author_id = author.get("sec_uid", "")
+
+            stats = item.get("statistics", {})
             digg_count = stats.get("digg_count", 0)
             comment_count = stats.get("comment_count", 0)
             share_count = stats.get("share_count", 0)
 
-            create_time = aweme.get("create_time", 0)
-
             summary_lines.append(f"{i}. {desc}")
             summary_lines.append(f"   ID: {aweme_id}")
+            summary_lines.append(f"   链接: https://www.douyin.com/video/{aweme_id}")
+            summary_lines.append(f"   作者: {author_name} (sec_uid: {author_id})")
             summary_lines.append(f"   数据: 点赞 {digg_count:,} | 评论 {comment_count:,} | 分享 {share_count:,}")
-            summary_lines.append(f"   发布时间: {create_time}")
             summary_lines.append("")
 
-        if len(aweme_list) > 5:
-            summary_lines.append(f"... 还有 {len(aweme_list) - 5} 个作品")
+        if len(items) > 5:
+            summary_lines.append(f"... 还有 {len(items) - 5} 条结果")
 
         return ToolResult(
             title=f"账号作品: {account_id}",
             output="\n".join(summary_lines),
-            long_term_memory=f"Fetched {len(aweme_list)} videos for account '{account_id}'",
+            long_term_memory=f"Fetched {len(items)} videos for account '{account_id}'",
             metadata={"raw_data": data}
         )
     except httpx.HTTPStatusError as e:
         return ToolResult(
             title="账号作品获取失败",
-            output="",
+            output="请求异常",
             error=f"HTTP error {e.response.status_code}: {e.response.text}",
         )
     except Exception as e:
         return ToolResult(
             title="账号作品获取失败",
-            output="",
+            output="账号作品获取失败",
             error=str(e),
         )
-# async def main():
-#     result = await douyin_user_videos(
-#         account_id="MS4wLjABAAAAPRCMGPAFM1VGcJrxRuvTXgJp0Sk95EW1DynNmbKSPg8",
-#         sort_type="最新",
-#         cursor=""
-#     )
-#     print(result.output)
-#
-# if __name__ == "__main__":
-#     asyncio.run(main())
+async def main():
+    result = await douyin_user_videos(
+        account_id="MS4wLjABAAAAPRCMGPAFM1VGcJrxRuvTXgJp0Sk95EW1DynNmbKSPg8",
+        sort_type="最新",
+        cursor=""
+    )
+    print(result.output)
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 60 - 85
examples/content_finder/tools/hotspot_profile.py

@@ -5,7 +5,7 @@
 """
 import asyncio
 import json
-from typing import Optional
+from typing import Optional, Dict, Any, List, Tuple
 
 import httpx
 import requests
@@ -77,53 +77,29 @@ async def get_account_fans_portrait(
         response.raise_for_status()
         data = response.json()
 
+        data_block = data.get("data", {}) if isinstance(data.get("data"), dict) else {}
+        portrait = data_block.get("data", {}) if isinstance(data_block.get("data"), dict) else {}
+
         # 格式化输出摘要
         summary_lines = [f"账号 {account_id} 的粉丝画像"]
+        summary_lines.append(f"画像链接:https://douhot.douyin.com/creator/detail?active_tab=creator_fans_portrait&creator_id={account_id}")
         summary_lines.append("")
-
-        # 年龄分布
-        if need_age and "age" in data:
-            summary_lines.append("【年龄分布】")
-            age_data = data["age"]
-            for item in age_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
-
-        # 性别分布
-        if need_gender and "gender" in data:
-            summary_lines.append("【性别分布】")
-            gender_data = data["gender"]
-            for item in gender_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
-
-        # 城市等级
-        if need_city_level and "city_level" in data:
-            summary_lines.append("【城市等级】")
-            city_level_data = data["city_level"]
-            for item in city_level_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
+        for k, v in portrait.items():
+            if not isinstance(v, dict):
+                continue
+            if k in ("省份", "城市"):
+                summary_lines.append(f"【{k} TOP5】分布")
+                items = _top_k(v, 5)
+            else:
+                summary_lines.append(f"【{k}】分布")
+                items = v.items()
+
+            for name, metrics in items:
+                ratio = metrics.get("percentage")
+                tgi = metrics.get("preference")
+                summary_lines.append(f"  {name}: {ratio} (偏好度: {tgi})")
             summary_lines.append("")
 
-        # 省份分布 TOP5
-        if need_province and "province" in data:
-            summary_lines.append("【省份分布 TOP5】")
-            province_data = data["province"][:5]
-            for i, item in enumerate(province_data, 1):
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {i}. {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
 
         return ToolResult(
             title=f"账号粉丝画像: {account_id}",
@@ -204,52 +180,28 @@ async def get_content_fans_portrait(
         response.raise_for_status()
         data = response.json()
 
+        data_block = data.get("data", {}) if isinstance(data.get("data"), dict) else {}
+        portrait = data_block.get("data", {}) if isinstance(data_block.get("data"), dict) else {}
+
         # 格式化输出摘要
         summary_lines = [f"内容 {content_id} 的点赞用户画像"]
+        summary_lines.append(f"画像链接:https://douhot.douyin.com/video/detail?active_tab=video_fans&video_id={content_id}")
         summary_lines.append("")
 
-        # 年龄分布
-        if need_age and "age" in data:
-            summary_lines.append("【年龄分布】")
-            age_data = data["age"]
-            for item in age_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
-
-        # 性别分布
-        if need_gender and "gender" in data:
-            summary_lines.append("【性别分布】")
-            gender_data = data["gender"]
-            for item in gender_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
-
-        # 城市等级
-        if need_city_level and "city_level" in data:
-            summary_lines.append("【城市等级】")
-            city_level_data = data["city_level"]
-            for item in city_level_data:
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
-            summary_lines.append("")
-
-        # 省份分布 TOP5
-        if need_province and "province" in data:
-            summary_lines.append("【省份分布 TOP5】")
-            province_data = data["province"][:5]
-            for i, item in enumerate(province_data, 1):
-                name = item.get("name", "")
-                ratio = item.get("ratio", 0)
-                tgi = item.get("tgi", 0)
-                summary_lines.append(f"  {i}. {name}: {ratio:.1f}% (偏好度: {tgi:.2f})")
+        for k, v in portrait.items():
+            if not isinstance(v, dict):
+                continue
+            if k in ("省份", "城市"):
+                summary_lines.append(f"【{k} TOP5】分布")
+                items = _top_k(v, 5)
+            else:
+                summary_lines.append(f"【{k}】分布")
+                items = v.items()
+
+            for name, metrics in items:
+                ratio = metrics.get("percentage")
+                tgi = metrics.get("preference")
+                summary_lines.append(f"  {name}: {ratio} (偏好度: {tgi})")
             summary_lines.append("")
 
         return ToolResult(
@@ -270,3 +222,26 @@ async def get_content_fans_portrait(
             output="",
             error=str(e),
         )
+
+
+def _top_k(items: Dict[str, Any], k: int) -> List[Tuple[str, Any]]:
+    def percent_value(entry: Tuple[str, Any]) -> float:
+        metrics = entry[1] if isinstance(entry[1], dict) else {}
+        return metrics.get("percentage")
+
+    return sorted(items.items(), key=percent_value, reverse=True)[:k]
+
+
+# async def main():
+#     # result = await get_account_fans_portrait(
+#     #     account_id="MS4wLjABAAAAXvRdWJsdPKkh9Ja3ZirxoB8pAaxNXUXs1KUe14gW0IoqDz-D-fG0xZ8c5kSfTPXx",
+#     #     need_province=True
+#     # )
+#     result = await get_content_fans_portrait(
+#         content_id="7614821787578568420"
+#         # need_province=True
+#     )
+#     print(result.output)
+#
+# if __name__ == "__main__":
+#     asyncio.run(main())