Browse Source

抖音同步作品

Ethanfly 2 ngày trước cách đây
mục cha
commit
614d675f45

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
client/dist-electron/main.js.map


+ 0 - 6
client/src/components.d.ts

@@ -16,12 +16,8 @@ declare module 'vue' {
     ElButton: typeof import('element-plus/es')['ElButton']
     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
-    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
-    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
-    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
@@ -49,8 +45,6 @@ declare module 'vue' {
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
-    ElText: typeof import('element-plus/es')['ElText']
-    ElUpload: typeof import('element-plus/es')['ElUpload']
     Icons: typeof import('./components/icons/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

BIN
server/python/platforms/__pycache__/base.cpython-311.pyc


BIN
server/python/platforms/__pycache__/douyin.cpython-311.pyc


BIN
server/python/platforms/__pycache__/xiaohongshu.cpython-311.pyc


+ 32 - 12
server/python/platforms/douyin.py

@@ -676,15 +676,25 @@ class DouyinPublisher(BasePublisher):
         )
     
     async def get_works(self, cookies: str, page: int = 0, page_size: int = 20) -> WorksResult:
-        """获取抖音作品列表"""
+        """获取抖音作品列表
+        
+        Args:
+            cookies: Cookie 字符串或 JSON
+            page: 分页参数,首次请求传 0,后续传上一次返回的 next_page(即 API 的 max_cursor)
+            page_size: 每页数量
+        
+        Returns:
+            WorksResult: 包含 works, total, has_more, next_page(用于下一页请求)
+        """
         print(f"\n{'='*60}")
         print(f"[{self.platform_name}] 获取作品列表")
-        print(f"[{self.platform_name}] page={page}, page_size={page_size}")
+        print(f"[{self.platform_name}] cursor={page}, page_size={page_size}")
         print(f"{'='*60}")
         
         works: List[WorkItem] = []
         total = 0
         has_more = False
+        next_cursor = 0
         
         try:
             await self.init_browser()
@@ -703,10 +713,9 @@ class DouyinPublisher(BasePublisher):
             if "login" in current_url or "passport" in current_url:
                 raise Exception("Cookie 已过期,请重新登录")
             
-            # 调用作品列表 API
-            cursor = page * page_size
-            # 移除 scene=star_atlas 和 aid=1128,使用更通用的参数
-            api_url = f"https://creator.douyin.com/janus/douyin/creator/pc/work_list?status=0&device_platform=android&count={page_size}&max_cursor={cursor}&cookie_enabled=true&browser_language=zh-CN&browser_platform=Win32&browser_name=Mozilla&browser_online=true&timezone_name=Asia%2FShanghai"
+            # 调用作品列表 API:page 作为 max_cursor(首次 0,后续为上一页返回的 max_cursor)
+            max_cursor = page
+            api_url = f"https://creator.douyin.com/janus/douyin/creator/pc/work_list?status=0&device_platform=android&count={page_size}&max_cursor={max_cursor}&cookie_enabled=true&browser_language=zh-CN&browser_platform=Win32&browser_name=Mozilla&browser_online=true&timezone_name=Asia%2FShanghai"
             
             response = await self.page.evaluate(f'''
                 async () => {{
@@ -725,10 +734,19 @@ class DouyinPublisher(BasePublisher):
             if response.get('error'):
                 print(f"[{self.platform_name}] API 请求失败: {response.get('error')}", flush=True)
             
-            print(f"[{self.platform_name}] API 响应: has_more={response.get('has_more')}, aweme_list={len(response.get('aweme_list', []))}")
-            
-            aweme_list = response.get('aweme_list', [])
+            aweme_list = response.get('aweme_list', []) or []
             has_more = response.get('has_more', False)
+            next_cursor = response.get('max_cursor', 0)
+            
+            # 从第一个作品的 author.aweme_count 获取总作品数
+            if aweme_list and len(aweme_list) > 0:
+                first_aweme = aweme_list[0]
+                author_aweme_count = first_aweme.get('author', {}).get('aweme_count', 0)
+                if author_aweme_count > 0:
+                    total = author_aweme_count
+                    print(f"[{self.platform_name}] 从 author.aweme_count 获取总作品数: {total}")
+            
+            print(f"[{self.platform_name}] API 响应: has_more={has_more}, aweme_list={len(aweme_list)}, next_cursor={next_cursor}")
             
             for aweme in aweme_list:
                 aweme_id = str(aweme.get('aweme_id', ''))
@@ -769,8 +787,9 @@ class DouyinPublisher(BasePublisher):
                     share_count=int(statistics.get('share_count', 0)),
                 ))
             
-            total = len(works)
-            print(f"[{self.platform_name}] 获取到 {total} 个作品")
+            if total == 0:
+                total = len(works)
+            print(f"[{self.platform_name}] 本页获取到 {len(works)} 个作品")
             
         except Exception as e:
             import traceback
@@ -786,7 +805,8 @@ class DouyinPublisher(BasePublisher):
             platform=self.platform_name,
             works=works,
             total=total,
-            has_more=has_more
+            has_more=has_more,
+            next_page=next_cursor
         )
     
     async def get_comments(self, cookies: str, work_id: str, cursor: str = "") -> CommentsResult:

+ 5 - 2
server/src/services/HeadlessBrowserService.ts

@@ -724,8 +724,10 @@ class HeadlessBrowserService {
 
     let cursor: string | number = 0;
     const seenCursors = new Set<string>();
+    // 抖音和小红书使用 cursor 分页(next_page 作为下一页的 max_cursor),其他平台用 pageIndex
+    const useCursorPagination = platform === 'xiaohongshu' || platform === 'douyin';
     for (let pageIndex = 0; pageIndex < maxPages; pageIndex++) {
-      const pageParam = platform === 'xiaohongshu' ? cursor : pageIndex;
+      const pageParam = useCursorPagination ? cursor : pageIndex;
       logger.info(`[Python API] Fetching works page=${String(pageParam)}, page_size=${pageSize} for ${platform}`);
 
       const response = await fetch(`${PYTHON_SERVICE_URL}/works`, {
@@ -818,7 +820,7 @@ class HeadlessBrowserService {
         nextPage: result.next_page,
       });
 
-      if (platform === 'xiaohongshu') {
+      if (useCursorPagination) {
         const next = result.next_page;
         const expectedMore = declaredTotal && declaredTotal > 0 ? allWorks.length < declaredTotal : !!result.has_more;
 
@@ -828,6 +830,7 @@ class HeadlessBrowserService {
           seenCursors.add(key);
           cursor = next;
         } else {
+          if (platform === 'douyin' && (next === 0 || next === '0')) break;
           cursor = (typeof cursor === 'number' ? cursor + 1 : pageIndex + 1);
         }
 

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác