douyin_user_videos.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. """
  2. 抖音账号历史作品工具(示例)
  3. 调用内部爬虫服务获取指定账号的历史作品列表。
  4. """
  5. import asyncio
  6. import logging
  7. import time
  8. from typing import Optional
  9. import requests
  10. from agent.tools import tool, ToolResult
  11. logger = logging.getLogger(__name__)
  12. # API 基础配置
  13. DOUYIN_BLOGGER_API = "http://crawapi.piaoquantv.com/crawler/dou_yin/blogger"
  14. DEFAULT_TIMEOUT = 60.0
  15. @tool(description="根据账号ID获取抖音历史作品,支持排序与游标")
  16. async def douyin_user_videos(
  17. account_id: str,
  18. sort_type: str = "最新",
  19. cursor: str = "",
  20. timeout: Optional[float] = None,
  21. ) -> ToolResult:
  22. """
  23. 抖音账号历史作品查询
  24. 获取指定抖音账号的历史作品列表,支持排序和分页。
  25. Args:
  26. account_id: 抖音账号ID(使用 author.sec_uid)
  27. sort_type: 排序方式(可选:最新/最热,默认 "最新")
  28. cursor: 分页游标,用于获取下一页结果,默认 ""
  29. timeout: 超时时间(秒),默认 60
  30. Returns:
  31. ToolResult: 包含以下内容:
  32. - output: 文本格式的作品列表摘要(显示前5条)
  33. - metadata.user_videos: 结构化的作品列表(与 search_results 格式一致)
  34. - aweme_id: 视频ID
  35. - desc: 视频描述(最多100字符)
  36. - author: 作者信息
  37. - nickname: 作者昵称
  38. - sec_uid: 作者ID(完整,约80字符)
  39. - statistics: 统计数据
  40. - digg_count: 点赞数
  41. - comment_count: 评论数
  42. - share_count: 分享数
  43. - metadata.raw_data: 原始 API 返回数据
  44. Note:
  45. - account_id 参数使用 author.sec_uid(约80字符)
  46. - 使用 cursor 参数可以获取下一页结果
  47. - 建议从 metadata.user_videos 获取结构化数据
  48. - user_videos 与 search_results 格式完全一致,可使用相同的处理逻辑
  49. """
  50. start_time = time.time()
  51. try:
  52. payload = {
  53. "account_id": account_id,
  54. "sort_type": sort_type,
  55. "cursor": cursor,
  56. }
  57. request_timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
  58. response = requests.post(
  59. DOUYIN_BLOGGER_API,
  60. json=payload,
  61. headers={"Content-Type": "application/json"},
  62. timeout=request_timeout
  63. )
  64. response.raise_for_status()
  65. data = response.json()
  66. # 格式化输出摘要
  67. summary_lines = [f"账号 {account_id} 的作品列表"]
  68. data_block = data.get("data", {}) if isinstance(data.get("data"), dict) else {}
  69. items = data_block.get("data", []) if isinstance(data_block.get("data"), list) else []
  70. has_more = data_block.get("has_more", False)
  71. cursor_value = data_block.get("next_cursor", "")
  72. summary_lines.append(f"找到 {len(items)} 个作品" + (f",还有更多(cursor={cursor_value})" if has_more else ""))
  73. summary_lines.append("")
  74. # 显示前5条
  75. for i, item in enumerate(items[:5], 1):
  76. aweme_id = item.get("aweme_id", "unknown")
  77. desc = (item.get("desc") or item.get("item_title") or "无标题")[:50]
  78. author = item.get("author", {})
  79. author_name = author.get("nickname", "未知作者")
  80. author_id = author.get("sec_uid", "")
  81. stats = item.get("statistics", {})
  82. digg_count = stats.get("digg_count", 0)
  83. comment_count = stats.get("comment_count", 0)
  84. share_count = stats.get("share_count", 0)
  85. summary_lines.append(f"{i}. {desc}")
  86. summary_lines.append(f" ID: {aweme_id}")
  87. summary_lines.append(f" 链接: https://www.douyin.com/video/{aweme_id}")
  88. summary_lines.append(f" 作者: {author_name}")
  89. summary_lines.append(f" sec_uid: {author_id}")
  90. summary_lines.append(f" 数据: 点赞 {digg_count:,} | 评论 {comment_count:,} | 分享 {share_count:,}")
  91. summary_lines.append("")
  92. if len(items) > 5:
  93. summary_lines.append(f"... 还有 {len(items) - 5} 条结果")
  94. duration_ms = int((time.time() - start_time) * 1000)
  95. logger.info(
  96. "douyin_user_videos completed",
  97. extra={
  98. "account_id": account_id,
  99. "results_count": len(items),
  100. "has_more": has_more,
  101. "cursor": cursor_value,
  102. "duration_ms": duration_ms
  103. }
  104. )
  105. return ToolResult(
  106. title=f"账号作品: {account_id}",
  107. output="\n".join(summary_lines),
  108. long_term_memory=f"Fetched {len(items)} videos for account '{account_id}'",
  109. metadata={
  110. "raw_data": data,
  111. "user_videos": [ # 结构化数据,与 search_results 保持一致
  112. {
  113. "aweme_id": item.get("aweme_id"),
  114. "desc": (item.get("desc") or item.get("item_title") or "无标题")[:100],
  115. "author": {
  116. "nickname": item.get("author", {}).get("nickname", "未知作者"),
  117. "sec_uid": item.get("author", {}).get("sec_uid", ""),
  118. },
  119. "statistics": {
  120. "digg_count": item.get("statistics", {}).get("digg_count", 0),
  121. "comment_count": item.get("statistics", {}).get("comment_count", 0),
  122. "share_count": item.get("statistics", {}).get("share_count", 0),
  123. }
  124. }
  125. for item in items
  126. ]
  127. }
  128. )
  129. except requests.exceptions.HTTPError as e:
  130. logger.error(
  131. "douyin_user_videos HTTP error",
  132. extra={
  133. "account_id": account_id,
  134. "status_code": e.response.status_code,
  135. "error": str(e)
  136. }
  137. )
  138. return ToolResult(
  139. title="账号作品获取失败",
  140. output="",
  141. error=f"HTTP {e.response.status_code}: {e.response.text}",
  142. )
  143. except requests.exceptions.Timeout:
  144. logger.error("douyin_user_videos timeout", extra={"account_id": account_id, "timeout": request_timeout})
  145. return ToolResult(
  146. title="账号作品获取失败",
  147. output="",
  148. error=f"请求超时({request_timeout}秒)",
  149. )
  150. except requests.exceptions.RequestException as e:
  151. logger.error("douyin_user_videos network error", extra={"account_id": account_id, "error": str(e)})
  152. return ToolResult(
  153. title="账号作品获取失败",
  154. output="",
  155. error=f"网络错误: {str(e)}",
  156. )
  157. except Exception as e:
  158. logger.error("douyin_user_videos unexpected error", extra={"account_id": account_id, "error": str(e)}, exc_info=True)
  159. return ToolResult(
  160. title="账号作品获取失败",
  161. output="",
  162. error=f"未知错误: {str(e)}",
  163. )
  164. async def main():
  165. result = await douyin_user_videos(
  166. account_id="MS4wLjABAAAAPRCMGPAFM1VGcJrxRuvTXgJp0Sk95EW1DynNmbKSPg8",
  167. sort_type="最新",
  168. cursor=""
  169. )
  170. print(result.output)
  171. if __name__ == "__main__":
  172. asyncio.run(main())