Ethanfly há 1 dia atrás
pai
commit
37ba3cb601
1 ficheiros alterados com 82 adições e 13 exclusões
  1. 82 13
      server/src/services/WeixinVideoWorkStatisticsImportService.ts

+ 82 - 13
server/src/services/WeixinVideoWorkStatisticsImportService.ts

@@ -306,15 +306,60 @@ export class WeixinVideoWorkStatisticsImportService {
 
       const postListData = { list: [] as Array<{ exportId?: string; objectId?: string; [k: string]: any }> };
 
+      // 只采纳 _pageUrl 为该值的 post_list 响应(统计-作品页)。请求里可能是编码或解码形式,统一解码后比较
+      const POST_LIST_PAGE_URL_DECODED = 'https://channels.weixin.qq.com/micro/statistic/post';
+      const normalizePageUrl = (raw: string): string => {
+        if (!raw || !raw.trim()) return '';
+        try {
+          return decodeURIComponent(raw.trim());
+        } catch {
+          return raw.trim();
+        }
+      };
+      const getPageUrl = (req: { url: () => string; postData: () => string | undefined }): string => {
+        try {
+          const fromUrl = new URL(req.url()).searchParams.get('_pageUrl') ?? '';
+          if (fromUrl) return normalizePageUrl(fromUrl);
+          const postData = req.postData();
+          if (typeof postData === 'string') {
+            const p = JSON.parse(postData);
+            const raw = (p?._pageUrl ?? p?._page_url ?? '') as string;
+            return normalizePageUrl(raw);
+          }
+        } catch {
+          // ignore
+        }
+        return '';
+      };
       page.on('response', async (response) => {
-        const url = response.url();
         try {
-          if (url.includes('statistic/post_list') && response.request().method() === 'POST') {
-            const body = await response.json().catch(() => ({}));
-            if (body?.errCode === 0 && body?.data?.list) {
-              postListData.list = body.data.list;
+          const url = response.request().url();
+          if (!url.includes('statistic/post_list')) return;
+          const req = response.request();
+          const pageUrl = getPageUrl(req);
+          const reqUrl = new URL(url);
+          logger.info(
+            `[WX WorkStats] post_list 请求: _pageUrl=${reqUrl.searchParams.get('_pageUrl') ?? '(query无)'} getPageUrl=${pageUrl || '(空)'}`
+          );
+          const postData = req.postData();
+          if (typeof postData === 'string') {
+            try {
+              const payload = JSON.parse(postData);
+              const { _aid, _rid, _pageUrl: _, ...rest } = payload;
+              logger.info(`[WX WorkStats] post_list 请求体(不含 aid/rid): ${JSON.stringify(rest)}`);
+            } catch {
+              logger.info(`[WX WorkStats] post_list 请求体(原始): ${postData.slice(0, 500)}`);
             }
           }
+          if (pageUrl !== POST_LIST_PAGE_URL_DECODED) return;
+          const body = await response.json().catch(() => ({}));
+          logger.info(
+            `[WX WorkStats] post_list 返回: errCode=${body?.errCode} errMsg=${body?.errMsg} totalCount=${body?.data?.totalCount} listLength=${body?.data?.list?.length ?? 0}`
+          );
+          logger.info(`[WX WorkStats] post_list 返回完整 body: ${JSON.stringify(body, null, 2)}`);
+          if (body?.errCode === 0 && body?.data?.list) {
+            postListData.list = body.data.list;
+          }
         } catch {
           // ignore
         }
@@ -327,23 +372,45 @@ export class WeixinVideoWorkStatisticsImportService {
         throw new Error('Cookie 已过期,请重新登录');
       }
 
+      let singleVideoClicked = false;
       for (const sel of TAB_SINGLE_VIDEO_SELECTORS) {
         const baseLoc = page.locator(sel);
         if ((await baseLoc.count()) > 0) {
-          await baseLoc.nth(0).click().catch(() => undefined);
-          break;
+          try {
+            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 });
+            await baseLoc.nth(0).click({ timeout: 3000 });
+            singleVideoClicked = true;
+            logger.info(`[WX WorkStats] accountId=${account.id} 已点击「单篇视频」tab, selector=${sel}`);
+            break;
+          } catch (e) {
+            logger.warn(`[WX WorkStats] accountId=${account.id} 点击「单篇视频」失败, selector=${sel}`, e);
+          }
         }
       }
+      if (!singleVideoClicked) {
+        logger.warn(`[WX WorkStats] accountId=${account.id} 未找到或未点击成功「单篇视频」tab,可能影响 post_list 数据`);
+      }
       await page.waitForTimeout(2000);
 
       postListData.list = [];
+      let near30Clicked = false;
       for (const sel of NEAR_30_DAYS_SELECTORS) {
         const baseLoc = page.locator(sel);
         if ((await baseLoc.count()) > 0) {
-          await baseLoc.nth(0).click().catch(() => undefined);
-          break;
+          try {
+            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 });
+            await baseLoc.nth(0).click({ timeout: 3000 });
+            near30Clicked = true;
+            logger.info(`[WX WorkStats] accountId=${account.id} 已点击「近30天」, selector=${sel}`);
+            break;
+          } catch (e) {
+            logger.warn(`[WX WorkStats] accountId=${account.id} 点击「近30天」失败, selector=${sel}`, e);
+          }
         }
       }
+      if (!near30Clicked) {
+        logger.warn(`[WX WorkStats] accountId=${account.id} 未找到或未点击成功「近30天」,post_list 可能无数据`);
+      }
       await page.waitForTimeout(5000);
 
       const items = postListData.list;
@@ -421,7 +488,8 @@ export class WeixinVideoWorkStatisticsImportService {
         for (const sel of TAB_SINGLE_VIDEO_SELECTORS) {
           const baseLoc = page.locator(sel);
           if ((await baseLoc.count()) > 0) {
-            await baseLoc.nth(0).click().catch(() => undefined);
+            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 }).catch(() => undefined);
+            await baseLoc.nth(0).click({ timeout: 3000 }).catch(() => undefined);
             break;
           }
         }
@@ -429,7 +497,8 @@ export class WeixinVideoWorkStatisticsImportService {
         for (const sel of NEAR_30_DAYS_SELECTORS) {
           const baseLoc = page.locator(sel);
           if ((await baseLoc.count()) > 0) {
-            await baseLoc.nth(0).click().catch(() => undefined);
+            await baseLoc.nth(0).waitFor({ state: 'visible', timeout: 3000 }).catch(() => undefined);
+            await baseLoc.nth(0).click({ timeout: 3000 }).catch(() => undefined);
             break;
           }
         }
@@ -438,8 +507,8 @@ export class WeixinVideoWorkStatisticsImportService {
 
       for (let idx = 0; idx < items.length; idx++) {
         const item = items[idx]!;
-        const eid = (item.exportId ?? '').trim();
-        const oid = (item.objectId ?? '').trim();
+        const eid = (item.exportId ?? item.export_id ?? '').trim();
+        const oid = (item.objectId ?? item.object_id ?? '').trim();
         if (!oid) continue;
         if (processedExportIds.has(eid)) continue;