wangzhibo
2025-07-30 25087cbe79c8c4992551477d55d9db8bbea2202e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
from playwright.async_api import Page
import asyncio
from auto_media_publisher.uploaders.uploader_base import UploaderBase
 
CONTENT_UPLOAD_URL="https://creator.xiaohongshu.com/publish/publish?from=menu"
VIDEO_PUBLISH_SECCESSFULL_URL = "https://creator.xiaohongshu.com/publish/success?source=&bind_status=not_bind&__debugger__=&proxy="
                                 
 
class UploaderXiaohongshu(UploaderBase):
   
    async def set_schedule_time(self, page):
        try:
            
            scheduled_date_hour = self.scheduled_time.strftime("%Y-%m-%d %H:%M")
            await page.locator("label").filter(has_text="定时发布").locator("span").nth(1).click()
            await asyncio.sleep(1)
            await page.get_by_placeholder("选择日期和时间").click()
            await asyncio.sleep(1)
            await page.keyboard.press("Control+KeyA")
            await page.keyboard.type(str(scheduled_date_hour))
            await page.keyboard.press("Enter")
            await asyncio.sleep(1)
        except Exception as e:
            self.logger.error(f"  [exception] 正在设置发布时间错误:{e}")
            await asyncio.sleep(2)
 
    async def set_tile_and_description(self,page):
        try:
            self.logger.info("正在填充标题和话题...")
            await page.get_by_placeholder("填写标题会有更多赞哦~").click()
            await page.get_by_placeholder("填写标题会有更多赞哦~").fill(self.title[:20])
            await page.locator("#quillEditor div").click()
            description_tags = self.description + ' ' + ' '.join(f"#{tag}" for tag in self.tags)
            await page.locator("#quillEditor div").fill(description_tags)
            await page.mouse.click(0, 0)  # 页面左上角一般是空白
        except Exception as e:
            self.logger.error(f"  [exception] 正在设置 标题和描述错误:{e}")
            await asyncio.sleep(2)
    
    async def set_cover_image(self,page):
        try:# STEP 3 设置封面
            self.logger.info("正在设置 封面...")
            await page.get_by_text("设置封面", exact=True).wait_for()
            await page.get_by_text("设置封面", exact=True).click()
            img_upload_button = page.get_by_text("上传图片", exact=True)
            await img_upload_button.wait_for(state='visible')  # 确保按钮可见
 
            async with page.expect_file_chooser() as fc_info:
                await img_upload_button.click()
            file_chooser = await fc_info.value
            await file_chooser.set_files(self.path_cover)
            await asyncio.sleep(2)
            await page.get_by_role("button", name="确定").click(timeout=10000)
        except Exception as e:
                self.logger.error(f"  [exception] 正在上传视频中... 错误:{e}")
                await asyncio.sleep(2)
 
    async def upload_media_and_wait(self,page):
        try:
            upload_button = page.locator("input[class^='upload-input']")
            await upload_button.wait_for(state='visible')  # 确保按钮可见
 
            async with page.expect_file_chooser() as fc_info:
                await upload_button.click()
            file_chooser = await fc_info.value
            await file_chooser.set_files(self.path_media)
            await asyncio.sleep(2)
 
            # wait for video upload finish
            try:
                locator = page.locator('div:has-text("上传成功")')
                count = await locator.count()
                if count > 0:
                    # 判断是否至少有一个匹配的元素是可见的
                    for i in range(count):
                        if await locator.nth(i).is_visible():
                            self.logger.info("  [-] 视频上传完毕")
                            break
                        else:
                            self.logger.info("  [-] 正在上传视频中(元素存在但不可见)...")
                            await asyncio.sleep(3)
                            continue
                        break  # 跳出 while True
                else:
                    self.logger.info("  [-] 正在上传视频中...")
                    await asyncio.sleep(3)
            except Exception as e:
                self.logger.error(f"  [exception] 正在上传视频中... 错误:{e}")
                await asyncio.sleep(2)
        except Exception as e:
                self.logger.error(f"  [exception] 正在上传视频中... 错误:{e}")
                await asyncio.sleep(2)
 
    async def _upload_core(self) -> bool:
        try:
            page = await self.context.new_page()
            await page.goto(CONTENT_UPLOAD_URL, timeout=120000)
            self.logger.info('正在打开主页...')
            await page.wait_for_url(CONTENT_UPLOAD_URL, timeout=120000)
 
            await self.upload_media_and_wait(page)
 
            await self.set_tile_and_description(page)
 
            if self.path_cover:
                await self.set_cover_image(page)          
        
            # 定时任务
            if self.scheduled_time != 0:
                self.logger.info("正在设置 定时发布 时间...")
                await self.set_schedule_time(page)
 
            # 判断视频是否发布成功
            await page.get_by_role("button", name="定时发布").click()
            await asyncio.sleep(1)
            max_retry = 10
            for _ in range(max_retry):
                try:
                    current_url = page.url
                    if "success" in current_url:  # 或者更精确匹配你需要的路径片段
                        self.logger.info(f"视频发布成功,跳转URL: {current_url}")
                        break
                    else:
                        self.logger.info(f"视频正在发布中... 当前URL: {current_url}")
                        await asyncio.sleep(1)
                except Exception as e:
                    self.logger.warning(f"等待跳转异常: {e}")
                    await asyncio.sleep(1)
            else:
                self.logger.error("超过最大重试次数,发布可能失败。")
 
            # 始终保存 cookie 并关闭浏览器
            await self.context.storage_state(path=self.cookie_path)
            self.logger.info('cookie更新完毕!')
            await asyncio.sleep(3)
            return True
        except Exception as e:
            self.logger.error(f"{self.user} upload video {self.path_media} fails, error is :{e}")
            return False