Browse Source

内容: 三张整页截图(首页/笔记/账号概览) + 评论通知改简洁文本 + 字段顺序修正

Sisyphus Agent 1 month ago
parent
commit
5ceca2187d

+ 5 - 8
运营文案/xhs_bot_listener.py

@@ -178,7 +178,7 @@ def _handle_data_command(message_id: str, feishu: FeishuChat):
         from xhs_daily_report import (
         from xhs_daily_report import (
             collect_overview,
             collect_overview,
             collect_notes,
             collect_notes,
-            screenshot_current,
+            capture_three_screenshots,
             CREATOR_HOME,
             CREATOR_HOME,
             NOTES_MANAGE_URL,
             NOTES_MANAGE_URL,
         )
         )
@@ -201,12 +201,9 @@ def _handle_data_command(message_id: str, feishu: FeishuChat):
         time.sleep(4)
         time.sleep(4)
         notes = collect_notes(publisher, 10)
         notes = collect_notes(publisher, 10)
 
 
-        shot_path = os.path.join(SCREENSHOT_DIR, f"xhs_bot_{int(time.time())}.jpg")
-        try:
-            screenshot_current(publisher, shot_path)
-        except Exception as e:
-            _log(f"[WARN] 截图失败: {e}")
-            shot_path = None
+        _log("[截图] 三张整页截图(首页/笔记管理/账号概览)...")
+        shot_paths = capture_three_screenshots(publisher, SCREENSHOT_DIR, prefix="xhs_bot")
+        _log(f"  截图数量: {len(shot_paths)}")
         publisher.disconnect()
         publisher.disconnect()
         publisher = None
         publisher = None
 
 
@@ -214,7 +211,7 @@ def _handle_data_command(message_id: str, feishu: FeishuChat):
         try:
         try:
             sys.path.insert(0, OPERATION_DIR)
             sys.path.insert(0, OPERATION_DIR)
             from xhs_feishu import send_daily_report
             from xhs_feishu import send_daily_report
-            send_daily_report(CONFIG_PATH, date, overview, notes, screenshot_path=shot_path)
+            send_daily_report(CONFIG_PATH, date, overview, notes, screenshot_paths=shot_paths)
             _log("[完成] 日报已推送")
             _log("[完成] 日报已推送")
         except Exception as e:
         except Exception as e:
             _log(f"[推送失败] {e}")
             _log(f"[推送失败] {e}")

+ 12 - 3
运营文案/xhs_feishu.py

@@ -172,6 +172,7 @@ def send_daily_report(
     overview: dict,
     overview: dict,
     notes: list,
     notes: list,
     screenshot_path: str = None,
     screenshot_path: str = None,
+    screenshot_paths: list = None,
 ):
 ):
     """发送小红书创作者中心日报到飞书群。
     """发送小红书创作者中心日报到飞书群。
 
 
@@ -180,7 +181,8 @@ def send_daily_report(
         date: 报告日期 YYYY-MM-DD
         date: 报告日期 YYYY-MM-DD
         overview: 账号总览 dict
         overview: 账号总览 dict
         notes: 笔记列表 list[dict]
         notes: 笔记列表 list[dict]
-        screenshot_path: 截图文件路径(可选,存在则作为图片消息附发)
+        screenshot_path: 单张截图文件路径(可选,向后兼容)
+        screenshot_paths: 多张截图文件路径列表(可选,优先使用)
     """
     """
     with open(config_path, "r", encoding="utf-8") as f:
     with open(config_path, "r", encoding="utf-8") as f:
         cfg = json.load(f)
         cfg = json.load(f)
@@ -193,9 +195,16 @@ def send_daily_report(
     card_json = _build_card_json(date, overview, notes)
     card_json = _build_card_json(date, overview, notes)
     data = _send_message(token, cfg, "interactive", card_json)
     data = _send_message(token, cfg, "interactive", card_json)
 
 
-    if screenshot_path and os.path.exists(screenshot_path):
+    # 收集要附发的截图
+    shot_list = []
+    if screenshot_paths:
+        shot_list = [p for p in screenshot_paths if p and os.path.exists(p)]
+    elif screenshot_path and os.path.exists(screenshot_path):
+        shot_list = [screenshot_path]
+
+    for shot in shot_list:
         try:
         try:
-            image_key = _upload_image(token, screenshot_path)
+            image_key = _upload_image(token, shot)
             _send_message(
             _send_message(
                 token, cfg, "image",
                 token, cfg, "image",
                 json.dumps({"image_key": image_key}),
                 json.dumps({"image_key": image_key}),

+ 50 - 26
运营文案/xhs_unreplied_comments.py

@@ -7,11 +7,17 @@ import re
 import sys
 import sys
 import time
 import time
 
 
-sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+import requests
+
+# 仅在交互式运行时包装 stdout;由计划任务调用时保持原生流
+if not sys.stdout.isatty():
+    try:
+        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
+    except Exception:
+        pass
 
 
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-XHS_DIR = os.path.dirname(SCRIPT_DIR)
-OPERATION_DIR = os.path.dirname(XHS_DIR)
+OPERATION_DIR = SCRIPT_DIR  # 本脚本位于 运营文案/ 目录,配置文件也在这
 CONFIG_PATH = os.path.join(OPERATION_DIR, "xhs_daily_config.json")
 CONFIG_PATH = os.path.join(OPERATION_DIR, "xhs_daily_config.json")
 
 
 XHS_SKILLS_PATH = r"C:\code\XiaohongshuSkills\scripts"
 XHS_SKILLS_PATH = r"C:\code\XiaohongshuSkills\scripts"
@@ -19,7 +25,7 @@ sys.path.insert(0, XHS_SKILLS_PATH)
 sys.path.insert(0, OPERATION_DIR)
 sys.path.insert(0, OPERATION_DIR)
 
 
 from cdp_publish import XiaohongshuPublisher
 from cdp_publish import XiaohongshuPublisher
-from xhs_feishu import send_daily_report
+from xhs_feishu import _get_token, FEISHU_MSG_URL
 
 
 
 
 def _load_config():
 def _load_config():
@@ -28,7 +34,10 @@ def _load_config():
 
 
 
 
 def extract_comment_counts(publisher, limit=10):
 def extract_comment_counts(publisher, limit=10):
-    """从笔记管理页提取每篇笔记的评论数"""
+    """从笔记管理页提取每篇笔记的评论数。
+
+    页面数字顺序:阅读 / 评论 / 点赞 / 收藏 / 分享
+    """
     publisher._navigate("https://creator.xiaohongshu.com/new/note-manager")
     publisher._navigate("https://creator.xiaohongshu.com/new/note-manager")
     time.sleep(6)
     time.sleep(6)
 
 
@@ -39,23 +48,24 @@ def extract_comment_counts(publisher, limit=10):
     i = 0
     i = 0
     while i < len(lines) and len(notes) < limit:
     while i < len(lines) and len(notes) < limit:
         ln = lines[i]
         ln = lines[i]
-        # 匹配日期格式
+
         if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$", ln) and i + 5 < len(lines):
         if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$", ln) and i + 5 < len(lines):
             title = lines[i - 1].strip() if i >= 1 else ""
             title = lines[i - 1].strip() if i >= 1 else ""
-            # 后面5个数字:阅读/点赞/收藏/评论/分享
+
             nums = []
             nums = []
             j = i + 1
             j = i + 1
             while j < len(lines) and len(nums) < 5 and lines[j].isdigit():
             while j < len(lines) and len(nums) < 5 and lines[j].isdigit():
                 nums.append(int(lines[j]))
                 nums.append(int(lines[j]))
                 j += 1
                 j += 1
             if len(nums) >= 5 and title:
             if len(nums) >= 5 and title:
+                # 页面顺序:阅读 / 评论 / 点赞 / 收藏 / 分享
                 notes.append({
                 notes.append({
                     "title": title,
                     "title": title,
                     "publish_date": ln[:10],
                     "publish_date": ln[:10],
                     "read_count": nums[0],
                     "read_count": nums[0],
-                    "like_count": nums[1],
-                    "collect_count": nums[2],
-                    "comment_count": nums[3],
+                    "comment_count": nums[1],
+                    "like_count": nums[2],
+                    "collect_count": nums[3],
                     "share_count": nums[4],
                     "share_count": nums[4],
                 })
                 })
             i = j if j > i + 1 else i + 1
             i = j if j > i + 1 else i + 1
@@ -81,29 +91,43 @@ def check_comments():
         noted_with_comments = [n for n in notes if n["comment_count"] > 0]
         noted_with_comments = [n for n in notes if n["comment_count"] > 0]
 
 
         if not noted_with_comments:
         if not noted_with_comments:
-            print("没有发现新评论")
+            print("没有发现有评论的笔记")
             return
             return
 
 
-        # 构建推送消息
         date = time.strftime("%Y-%m-%d")
         date = time.strftime("%Y-%m-%d")
-        overview = {
-            "fans_count": 0,
-            "follow_count": 0,
-            "interact_count": 0,
-            "period_7d": {},
-            "period_30d": {},
-        }
-        # 只推送评论笔记列表
-        notes_for_card = noted_with_comments
 
 
         print(f"发现 {len(noted_with_comments)} 篇有评论的笔记")
         print(f"发现 {len(noted_with_comments)} 篇有评论的笔记")
+        lines = []
         for n in noted_with_comments:
         for n in noted_with_comments:
-            print(f"  - {n['title']}: {n['comment_count']} 条评论")
-
-        # 发送飞书消息
+            lines.append(f"  · 《{n['title']}》({n['publish_date']}):{n['comment_count']} 条评论")
+        msg = (
+            "📬 小红书评论提醒(" + date + ")\n\n"
+            + "\n".join(lines)
+            + "\n\n注:当前无法检测“未回复”状态,"
+            "请前往创作者中心查看具体评论回复情况。"
+        )
+        print(msg)
+
+        # 发送简洁文本通知(不发完整日报卡片,避免与日报重复)
         try:
         try:
-            send_daily_report(CONFIG_PATH, date, overview, notes_for_card)
-            print("评论通知已推送")
+            cfg = _load_config()
+            token = _get_token(cfg["feishu_app_id"], cfg["feishu_app_secret"])
+            url = f"{FEISHU_MSG_URL}?receive_id_type=chat_id"
+            r = requests.post(
+                url,
+                headers={"Authorization": f"Bearer {token}"},
+                json={
+                    "receive_id": cfg["feishu_chat_id"],
+                    "msg_type": "text",
+                    "content": json.dumps({"text": msg}, ensure_ascii=False),
+                },
+                timeout=20,
+            )
+            d = r.json()
+            if d.get("code") == 0:
+                print(f"[OK] 评论通知已推送")
+            else:
+                print(f"[WARN] 推送失败: {d.get('msg')}")
         except Exception as e:
         except Exception as e:
             print(f"推送失败: {e}")
             print(f"推送失败: {e}")
 
 

+ 48 - 12
运营文案/小红书发布/_过程脚本/xhs_daily_report.py

@@ -214,11 +214,17 @@ def collect_notes(publisher, limit=10):
     return notes[:limit]
     return notes[:limit]
 
 
 
 
-def screenshot_current(publisher, output_path):
-    """用 CDP 截取当前页面(JPEG)。"""
+def screenshot_current(publisher, output_path, full_page: bool = True):
+    """用 CDP 截取当前页面(JPEG),默认整页截图。
+
+    full_page=True 时用 captureBeyondViewport 截取整个可滚动页面。
+    """
     os.makedirs(os.path.dirname(output_path), exist_ok=True)
     os.makedirs(os.path.dirname(output_path), exist_ok=True)
-    result = publisher._send("Page.captureScreenshot", {"format": "jpeg", "quality": 60})
-    # CDP 对 captureScreenshot 返回 {"data": "<base64>"}(_send 已剥掉 result 包装)
+    params = {"format": "jpeg", "quality": 60}
+    if full_page:
+        params["captureBeyondViewport"] = True
+    result = publisher._send("Page.captureScreenshot", params)
+
     data = result.get("data") or (result.get("result") or {}).get("data")
     data = result.get("data") or (result.get("result") or {}).get("data")
     if data:
     if data:
         img_data = base64.b64decode(data)
         img_data = base64.b64decode(data)
@@ -229,6 +235,38 @@ def screenshot_current(publisher, output_path):
         raise RuntimeError(f"截图失败: {result}")
         raise RuntimeError(f"截图失败: {result}")
 
 
 
 
+# 账号概览页 URL(创作者中心数据看板)
+ACCOUNT_OVERVIEW_URL = "https://creator.xiaohongshu.com/statistics/account/v2"
+
+
+def capture_three_screenshots(publisher, shot_dir, prefix="xhs_bot"):
+    """依次截取三张整页截图:首页 / 笔记管理 / 账号概览。
+
+    Returns:
+        list[str]: 三张截图的文件路径(按顺序:首页, 笔记管理, 账号概览)
+    """
+    os.makedirs(shot_dir, exist_ok=True)
+    ts = int(time.time())
+    shots = []
+
+    targets = [
+        (CREATOR_HOME, f"{prefix}_{ts}_home.jpg"),
+        (NOTES_MANAGE_URL, f"{prefix}_{ts}_notes.jpg"),
+        (ACCOUNT_OVERVIEW_URL, f"{prefix}_{ts}_overview.jpg"),
+    ]
+    for url, fname in targets:
+        try:
+            publisher._navigate(url)
+            time.sleep(5)
+            path = os.path.join(shot_dir, fname)
+            screenshot_current(publisher, path, full_page=True)
+            shots.append(path)
+        except Exception as e:
+            _log(f"[WARN] 截图失败 {url}: {e}")
+
+    return shots
+
+
 def append_daily_record(date, overview, notes):
 def append_daily_record(date, overview, notes):
     """追加当日快照到本地 JSON 记录(轻量,不依赖旧 CSV 结构)。"""
     """追加当日快照到本地 JSON 记录(轻量,不依赖旧 CSV 结构)。"""
     os.makedirs(RECORD_DIR, exist_ok=True)
     os.makedirs(RECORD_DIR, exist_ok=True)
@@ -291,21 +329,19 @@ def main():
         notes = collect_notes(publisher, args.limit)
         notes = collect_notes(publisher, args.limit)
         _log(f"  采集到 {len(notes)} 篇笔记")
         _log(f"  采集到 {len(notes)} 篇笔记")
 
 
-        # 说明:新版创作者中心已无汇总"未回复评论"独立页面,评论数据
-        # 体现在每篇笔记的 comment_count 中;如需每篇未回复详情需进单篇详情页。
 
 
-        _log("截图(笔记管理页)...")
-        shot_path = os.path.join(SCREENSHOT_DIR, f"xhs_daily_{date}.jpg")
-        screenshot_current(publisher, shot_path)
+        _log("截图(首页/笔记管理/账号概览 三张整页)...")
+        shot_paths = capture_three_screenshots(publisher, SCREENSHOT_DIR, prefix=f"xhs_daily_{date}")
+        _log(f"  截图 {len(shot_paths)} 张")
+
 
 
-        # 5. 本地记录
         append_daily_record(date, overview, notes)
         append_daily_record(date, overview, notes)
 
 
-        # 6. 飞书推送
+
         _log("推送到飞书...")
         _log("推送到飞书...")
         sys.path.insert(0, OPERATION_DIR)
         sys.path.insert(0, OPERATION_DIR)
         from xhs_feishu import send_daily_report
         from xhs_feishu import send_daily_report
-        send_daily_report(CONFIG_PATH, date, overview, notes, screenshot_path=shot_path)
+        send_daily_report(CONFIG_PATH, date, overview, notes, screenshot_paths=shot_paths)
         _log("✅ 日报流程完成")
         _log("✅ 日报流程完成")
 
 
     except Exception as e:
     except Exception as e: