Ver Fonte

百家号补数

Ethanfly há 2 dias atrás
pai
commit
a553770fbf
1 ficheiros alterados com 131 adições e 27 exclusões
  1. 131 27
      server/src/services/BaijiahaoWorkDailyStatisticsImportService.ts

+ 131 - 27
server/src/services/BaijiahaoWorkDailyStatisticsImportService.ts

@@ -6,6 +6,7 @@ import { logger } from '../utils/logger.js';
 import { WorkDayStatisticsService } from './WorkDayStatisticsService.js';
 import type { ProxyConfig } from '@media-manager/shared';
 import { AccountService } from './AccountService.js';
+import { getPythonServiceBaseUrl } from './PythonServiceConfigService.js';
 
 type PlaywrightCookie = {
   name: string;
@@ -419,6 +420,132 @@ export class BaijiahaoWorkDailyStatisticsImportService {
     return msg.includes('未登录') || msg.includes('not logged in');
   }
 
+  /**
+   * 通过 Python 服务调用百家号 articleListStatistic 接口
+   * 复用 Python 端对 Cookie 的处理和登录逻辑
+   */
+  private async fetchArticleListStatisticViaPython(
+    account: PlatformAccount,
+    params: {
+      startDay: string; // YYYYMMDD
+      endDay: string; // YYYYMMDD
+      type: BjhListType;
+      num: number;
+      count: number;
+    }
+  ): Promise<ArticleListStatisticResponse> {
+    const base = (await getPythonServiceBaseUrl()).replace(/\/$/, '');
+    const url = `${base}/baijiahao/article_stats`;
+
+    const cookie = String(account.cookieData || '').trim();
+    if (!cookie) {
+      throw new Error('百家号账号 cookie 为空,无法调用 Python article_stats');
+    }
+
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30_000);
+
+    try {
+      const res = await fetch(url, {
+        method: 'POST',
+        signal: controller.signal,
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          cookie,
+          start_day: params.startDay,
+          end_day: params.endDay,
+          type: params.type,
+          num: params.num,
+          count: params.count,
+        }),
+      });
+
+      const text = await res.text();
+      let data: any = {};
+      try {
+        data = text ? JSON.parse(text) : {};
+      } catch {
+        throw new Error(`Python article_stats 返回非 JSON 响应: http=${res.status}`);
+      }
+
+      if (!res.ok) {
+        const msg = String(data?.errmsg || data?.error || '').trim() || `HTTP ${res.status}`;
+        throw new Error(`Python article_stats 调用失败: ${msg}`);
+      }
+
+      const errno = typeof data?.errno === 'number' ? data.errno : Number(data?.errno ?? 0);
+      const errmsg = String(data?.errmsg || data?.error || '').trim() || undefined;
+      const payload: ArticleListStatisticResponse = {
+        errno,
+        errmsg,
+        data: data?.data,
+      };
+      return payload;
+    } finally {
+      clearTimeout(timeoutId);
+    }
+  }
+
+  /**
+   * 通过 Python 服务调用百家号 gettrenddata 接口
+   * 复用 Python 端对 Cookie 的处理和登录逻辑
+   */
+  private async fetchTrendDataViaPython(
+    account: PlatformAccount,
+    nid: string
+  ): Promise<GetTrendDataResponse> {
+    const base = (await getPythonServiceBaseUrl()).replace(/\/$/, '');
+    const url = `${base}/baijiahao/trend_data`;
+
+    const cookie = String(account.cookieData || '').trim();
+    if (!cookie) {
+      throw new Error('百家号账号 cookie 为空,无法调用 Python trend_data');
+    }
+
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30_000);
+
+    try {
+      const res = await fetch(url, {
+        method: 'POST',
+        signal: controller.signal,
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          cookie,
+          nid,
+        }),
+      });
+
+      const text = await res.text();
+      let data: any = {};
+      try {
+        data = text ? JSON.parse(text) : {};
+      } catch {
+        throw new Error(`Python trend_data 返回非 JSON 响应: http=${res.status}`);
+      }
+
+      if (!res.ok) {
+        const msg = String(data?.errmsg || data?.error || '').trim() || `HTTP ${res.status}`;
+        throw new Error(`Python trend_data 调用失败: ${msg}`);
+      }
+
+      const errno = typeof data?.errno === 'number' ? data.errno : Number(data?.errno ?? 0);
+      const errmsg = String(data?.errmsg || data?.error || '').trim() || undefined;
+      const payload: GetTrendDataResponse = {
+        errno,
+        errmsg,
+        data: data?.data,
+      };
+      return payload;
+    } finally {
+      clearTimeout(timeoutId);
+    }
+  }
+
   private async importAccountWorkDaily(account: PlatformAccount, isRetry = false): Promise<void> {
     const cookies = parseCookiesFromAccount(account.cookieData);
     if (!cookies.length) {
@@ -444,32 +571,14 @@ export class BaijiahaoWorkDailyStatisticsImportService {
       if (k) idMap.set(k, w.id);
     }
 
-    let context: BrowserContext | null = null;
-    let browser: Browser | null = null;
-    let shouldClose = false;
-    let token = '';
-
     try {
-      const created = await this.createContext(account, cookies);
-      context = created.context;
-      browser = created.browser;
-      shouldClose = created.shouldClose;
-      token = created.token;
-
-      if (!token) {
-        throw Object.assign(
-          new Error('未能提取百家号 token(可能未登录)'),
-          { code: 'BJH_NOT_LOGGED_IN' }
-        );
-      }
-
-      // 默认取昨天(中国时区)
+      // 默认取近 30 天(中国时区):昨天作为 end_day,往前推 29 天作为 start_day
       const now = new Date();
       const chinaNow = new Date(now.getTime() + 8 * 60 * 60 * 1000);
       const chinaYesterday = new Date(chinaNow.getTime() - 24 * 60 * 60 * 1000);
       const endDay = toYmd(chinaYesterday);
       const startDayDate = new Date(chinaYesterday);
-      startDayDate.setDate(startDayDate.getDate() - 6);
+      startDayDate.setDate(startDayDate.getDate() - 29);
       const startDay = toYmd(startDayDate);
 
       const types: BjhListType[] = ['small_video_v2', 'video', 'news'];
@@ -483,7 +592,7 @@ export class BaijiahaoWorkDailyStatisticsImportService {
         let num = 1;
         let total = 0;
         while (true) {
-          const body = await this.fetchArticleListStatisticPage(context!, token, {
+          const body = await this.fetchArticleListStatisticViaPython(account, {
             startDay,
             endDay,
             type: t,
@@ -538,7 +647,7 @@ export class BaijiahaoWorkDailyStatisticsImportService {
             const workId = idMap.get(articleId);
             if (!workId) continue;
 
-            const trend = await this.fetchTrendData(context!, token, articleId);
+            const trend = await this.fetchTrendDataViaPython(account, articleId);
             if (this.isNotLoggedInErrno(trend.errno)) {
               const err = new Error(
                 `gettrenddata errno=${trend.errno} 未登录/会话失效`
@@ -634,11 +743,6 @@ export class BaijiahaoWorkDailyStatisticsImportService {
       }
 
       throw e;
-    } finally {
-      await context?.close().catch(() => undefined);
-      if (shouldClose && browser) {
-        await browser.close().catch(() => undefined);
-      }
     }
   }
 }