|
|
@@ -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())
|