|
|
@@ -7,6 +7,7 @@ import { BrowserManager } from '../automation/browser.js';
|
|
|
import { logger } from '../utils/logger.js';
|
|
|
import { UserDayStatisticsService } from './UserDayStatisticsService.js';
|
|
|
import { AccountService } from './AccountService.js';
|
|
|
+import { getPythonServiceBaseUrl } from './PythonServiceConfigService.js';
|
|
|
import type { ProxyConfig } from '@media-manager/shared';
|
|
|
import { WS_EVENTS } from '@media-manager/shared';
|
|
|
import { wsManager } from '../websocket/index.js';
|
|
|
@@ -458,6 +459,74 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 通过 Python 调用 appStatisticV3(登录模式与打开后台一致:使用账号已存 Cookie)
|
|
|
+ */
|
|
|
+ private async fetchAppStatisticV3ViaPython(
|
|
|
+ account: PlatformAccount,
|
|
|
+ startDay: string,
|
|
|
+ endDay: string
|
|
|
+ ): Promise<Record<string, unknown>> {
|
|
|
+ const base = (await getPythonServiceBaseUrl()).replace(/\/$/, '');
|
|
|
+ const url = `${base}/baijiahao/app_statistic_v3`;
|
|
|
+ const cookie = String(account.cookieData || '').trim();
|
|
|
+ if (!cookie) throw new Error('百家号账号 cookie 为空,无法调用 Python app_statistic_v3');
|
|
|
+
|
|
|
+ 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: startDay, end_day: endDay }),
|
|
|
+ });
|
|
|
+ const text = await res.text();
|
|
|
+ const data = text ? (JSON.parse(text) as Record<string, unknown>) : {};
|
|
|
+ if (!res.ok) {
|
|
|
+ const msg = String(data?.errmsg || data?.error || '').trim() || `HTTP ${res.status}`;
|
|
|
+ throw new Error(`Python app_statistic_v3 调用失败: ${msg}`);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ } finally {
|
|
|
+ clearTimeout(timeoutId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过 Python 调用 getFansBasicInfo(登录模式与打开后台一致)
|
|
|
+ */
|
|
|
+ private async fetchFansBasicInfoViaPython(
|
|
|
+ account: PlatformAccount,
|
|
|
+ start: string,
|
|
|
+ end: string
|
|
|
+ ): Promise<Record<string, unknown>> {
|
|
|
+ const base = (await getPythonServiceBaseUrl()).replace(/\/$/, '');
|
|
|
+ const url = `${base}/baijiahao/fans_basic_info`;
|
|
|
+ const cookie = String(account.cookieData || '').trim();
|
|
|
+ if (!cookie) throw new Error('百家号账号 cookie 为空,无法调用 Python fans_basic_info');
|
|
|
+
|
|
|
+ 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, end }),
|
|
|
+ });
|
|
|
+ const text = await res.text();
|
|
|
+ const data = text ? (JSON.parse(text) as Record<string, unknown>) : {};
|
|
|
+ if (!res.ok) {
|
|
|
+ const msg = String(data?.errmsg || data?.error || '').trim() || `HTTP ${res.status}`;
|
|
|
+ throw new Error(`Python fans_basic_info 调用失败: ${msg}`);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ } finally {
|
|
|
+ clearTimeout(timeoutId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 统一入口:定时任务与添加账号均调用此方法,执行“内容分析-基础数据-近30天 + 粉丝 getFansBasicInfo”
|
|
|
*/
|
|
|
static async runDailyImport(): Promise<void> {
|
|
|
@@ -492,14 +561,114 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 单账号:导出 Excel → 解析 → 入库 → 删除文件
|
|
|
+ * 单账号:优先 Python+Node(登录与打开后台一致,使用账号已存 Cookie);失败则刷新重试一次,再失败则浏览器兜底
|
|
|
*/
|
|
|
async importAccountLast30Days(account: PlatformAccount, isRetry = false): Promise<void> {
|
|
|
const cookies = parseCookiesFromAccount(account.cookieData);
|
|
|
- if (!cookies.length) {
|
|
|
- throw new Error('cookieData 为空或无法解析');
|
|
|
+ if (!cookies.length) throw new Error('cookieData 为空或无法解析');
|
|
|
+
|
|
|
+ const end = new Date();
|
|
|
+ end.setHours(0, 0, 0, 0);
|
|
|
+ end.setDate(end.getDate() - 1);
|
|
|
+ const start = new Date(end);
|
|
|
+ start.setDate(start.getDate() - 29);
|
|
|
+ const fmt = (d: Date) =>
|
|
|
+ `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
|
|
+ const start_day = fmt(start);
|
|
|
+ const end_day = fmt(end);
|
|
|
+
|
|
|
+ const chinaTz = 'Asia/Shanghai';
|
|
|
+ const toChinaYMD = (date: Date): { y: number; m: number; d: number } => {
|
|
|
+ const formatter = new Intl.DateTimeFormat('en-CA', {
|
|
|
+ timeZone: chinaTz,
|
|
|
+ year: 'numeric',
|
|
|
+ month: '2-digit',
|
|
|
+ day: '2-digit',
|
|
|
+ });
|
|
|
+ const parts = formatter.formatToParts(date);
|
|
|
+ const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '0';
|
|
|
+ return {
|
|
|
+ y: parseInt(get('year'), 10),
|
|
|
+ m: parseInt(get('month'), 10),
|
|
|
+ d: parseInt(get('day'), 10),
|
|
|
+ };
|
|
|
+ };
|
|
|
+ const now = new Date();
|
|
|
+ const today = toChinaYMD(now);
|
|
|
+ const yesterdayDate = new Date(
|
|
|
+ Date.UTC(today.y, today.m - 1, today.d, 0, 0, 0, 0) - 24 * 60 * 60 * 1000
|
|
|
+ );
|
|
|
+ const startDate = new Date(yesterdayDate.getTime() - 29 * 24 * 60 * 60 * 1000);
|
|
|
+ const endYMD = toChinaYMD(yesterdayDate);
|
|
|
+ const startYMD = toChinaYMD(startDate);
|
|
|
+ const pad = (n: number) => String(n).padStart(2, '0');
|
|
|
+ const startStr = `${startYMD.y}${pad(startYMD.m)}${pad(startYMD.d)}`;
|
|
|
+ const endStr = `${endYMD.y}${pad(endYMD.m)}${pad(endYMD.d)}`;
|
|
|
+
|
|
|
+ // 优先 Python(登录与打开后台一致:仅用账号已存 Cookie,不启浏览器)
|
|
|
+ try {
|
|
|
+ const data = await this.fetchAppStatisticV3ViaPython(account, start_day, end_day);
|
|
|
+ const errno = typeof data?.errno === 'number' ? data.errno : Number(data?.errno ?? -1);
|
|
|
+ if (errno !== 0) throw new Error(data?.errmsg ? String(data.errmsg) : 'appStatisticV3 errno !== 0');
|
|
|
+ const perDay = parseBaijiahaoAppStatisticV3(data);
|
|
|
+ if (perDay.size === 0) throw new Error('appStatisticV3 解析后无数据');
|
|
|
+
|
|
|
+ let inserted = 0;
|
|
|
+ let updated = 0;
|
|
|
+ for (const v of perDay.values()) {
|
|
|
+ const { recordDate, ...patch } = v;
|
|
|
+ const r = await this.userDayStatisticsService.saveStatisticsForDate(account.id, recordDate, patch);
|
|
|
+ inserted += r.inserted;
|
|
|
+ updated += r.updated;
|
|
|
+ }
|
|
|
+ logger.info(
|
|
|
+ `[BJ Import] basic-data (via Python). accountId=${account.id} days=${perDay.size} inserted=${inserted} updated=${updated}`
|
|
|
+ );
|
|
|
+
|
|
|
+ try {
|
|
|
+ const fansBody = await this.fetchFansBasicInfoViaPython(account, startStr, endStr);
|
|
|
+ const fansErrno = (fansBody as any).errno;
|
|
|
+ if (fansErrno === 0 || fansErrno === undefined) {
|
|
|
+ const list = this.parseGetFansBasicInfoResponse(fansBody as Record<string, unknown>);
|
|
|
+ let fansUpdated = 0;
|
|
|
+ for (const { recordDate, fansCount, fansIncrease } of list) {
|
|
|
+ const r = await this.userDayStatisticsService.saveStatisticsForDate(
|
|
|
+ account.id,
|
|
|
+ recordDate,
|
|
|
+ { fansCount, fansIncrease }
|
|
|
+ );
|
|
|
+ fansUpdated += r.inserted + r.updated;
|
|
|
+ }
|
|
|
+ logger.info(`[BJ Import] Fans data (via Python). accountId=${account.id} days=${list.length} updated=${fansUpdated}`);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ logger.warn(`[BJ Import] Fans via Python failed (non-fatal). accountId=${account.id}`, e instanceof Error ? e.message : e);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ } catch (pythonError) {
|
|
|
+ logger.warn(
|
|
|
+ `[BJ Import] Python path failed, fallback to browser. accountId=${account.id}`,
|
|
|
+ pythonError instanceof Error ? pythonError.message : pythonError
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isRetry) {
|
|
|
+ try {
|
|
|
+ const accountService = new AccountService();
|
|
|
+ const refreshResult = await accountService.refreshAccount(account.userId, account.id);
|
|
|
+ if (!refreshResult.needReLogin) {
|
|
|
+ const refreshedAccount = await this.accountRepository.findOne({ where: { id: account.id } });
|
|
|
+ if (refreshedAccount) {
|
|
|
+ logger.info(`[BJ Import] Account ${account.id} refreshed, retrying import...`);
|
|
|
+ return await this.importAccountLast30Days(refreshedAccount, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (refreshError) {
|
|
|
+ logger.error(`[BJ Import] Account ${account.id} refresh failed:`, refreshError);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 浏览器兜底:原有逻辑不变
|
|
|
const { browser, shouldClose } = await createBrowserForAccount(account.proxyConfig);
|
|
|
try {
|
|
|
const statePath = await this.ensureStorageState(account, cookies);
|
|
|
@@ -513,50 +682,33 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
...(statePath ? { storageState: statePath } : {}),
|
|
|
});
|
|
|
context.setDefaultTimeout(60_000);
|
|
|
- if (!statePath) {
|
|
|
- await context.addCookies(cookies as any);
|
|
|
- }
|
|
|
+ if (!statePath) await context.addCookies(cookies as any);
|
|
|
|
|
|
const page = await context.newPage();
|
|
|
- await page.goto('https://baijiahao.baidu.com/builder/rc/analysiscontent', {
|
|
|
- waitUntil: 'domcontentloaded',
|
|
|
- });
|
|
|
+ await page.goto('https://baijiahao.baidu.com/builder/rc/analysiscontent', { waitUntil: 'domcontentloaded' });
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
if (page.url().includes('passport') || page.url().includes('login')) {
|
|
|
- // 第一次检测到登录失效时,尝试刷新账号
|
|
|
if (!isRetry) {
|
|
|
- logger.info(`[BJ Import] Login expired detected for account ${account.id}, attempting to refresh...`);
|
|
|
+ logger.info(`[BJ Import] Login expired for account ${account.id}, attempting refresh...`);
|
|
|
await context.close();
|
|
|
if (shouldClose) await browser.close();
|
|
|
-
|
|
|
try {
|
|
|
const accountService = new AccountService();
|
|
|
const refreshResult = await accountService.refreshAccount(account.userId, account.id);
|
|
|
-
|
|
|
if (refreshResult.needReLogin) {
|
|
|
- // 刷新后仍需要重新登录,走原先的失效流程
|
|
|
logger.warn(`[BJ Import] Account ${account.id} refresh failed, still needs re-login`);
|
|
|
throw new Error('未登录/需要重新登录(跳转到登录页)');
|
|
|
}
|
|
|
-
|
|
|
- // 刷新成功,重新获取账号信息并重试导入
|
|
|
- logger.info(`[BJ Import] Account ${account.id} refreshed successfully, retrying import...`);
|
|
|
const refreshedAccount = await this.accountRepository.findOne({ where: { id: account.id } });
|
|
|
- if (!refreshedAccount) {
|
|
|
- throw new Error('账号刷新后未找到');
|
|
|
- }
|
|
|
-
|
|
|
- // 递归调用,标记为重试
|
|
|
+ if (!refreshedAccount) throw new Error('账号刷新后未找到');
|
|
|
return await this.importAccountLast30Days(refreshedAccount, true);
|
|
|
} catch (refreshError) {
|
|
|
logger.error(`[BJ Import] Account ${account.id} refresh failed:`, refreshError);
|
|
|
throw new Error('未登录/需要重新登录(跳转到登录页)');
|
|
|
}
|
|
|
- } else {
|
|
|
- // 已经是重试了,不再尝试刷新
|
|
|
- throw new Error('未登录/需要重新登录(跳转到登录页)');
|
|
|
}
|
|
|
+ throw new Error('未登录/需要重新登录(跳转到登录页)');
|
|
|
}
|
|
|
|
|
|
const bodyText = (await page.textContent('body').catch(() => '')) || '';
|
|
|
@@ -574,103 +726,54 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
throw new Error('百家号数据看板暂无访问权限/暂无数据,已标记 expired 并通知用户');
|
|
|
}
|
|
|
|
|
|
- // 统一入口:数据中心 -> 内容分析 -> 基础数据
|
|
|
await page.getByText('数据中心', { exact: false }).first().click().catch(() => undefined);
|
|
|
await page.getByText('内容分析', { exact: false }).first().click().catch(() => undefined);
|
|
|
await page.getByText('基础数据', { exact: false }).first().click().catch(() => undefined);
|
|
|
-
|
|
|
- // 切换“近30天”(容错:有些账号默认就是近30天,或文案略有差异)
|
|
|
try {
|
|
|
const trigger = page.getByText(/近\d+天?/, { exact: false }).first();
|
|
|
- const hasTrigger = await trigger.count();
|
|
|
- if (hasTrigger > 0) {
|
|
|
- await trigger.click().catch(() => undefined);
|
|
|
- }
|
|
|
-
|
|
|
+ if ((await trigger.count()) > 0) await trigger.click().catch(() => undefined);
|
|
|
const thirtyDay =
|
|
|
(await page.getByText('近30天', { exact: true }).first().count()) > 0
|
|
|
? page.getByText('近30天', { exact: true }).first()
|
|
|
: page.getByText('近30日', { exact: false }).first();
|
|
|
-
|
|
|
await thirtyDay.click().catch(() => undefined);
|
|
|
-
|
|
|
- // 等页面后端刷新统计数据和日期范围(百家号这里比较慢)
|
|
|
await page.waitForTimeout(5000);
|
|
|
} catch (e) {
|
|
|
- logger.warn(
|
|
|
- `[BJ Import] Unable to explicitly switch to last 30 days, continue with default range. accountId=${account.id}`,
|
|
|
- e
|
|
|
- );
|
|
|
+ logger.warn(`[BJ Import] Unable to switch to 近30天. accountId=${account.id}`, e);
|
|
|
}
|
|
|
|
|
|
- // 优先抓取接口:/author/eco/statistics/appStatisticV3?type=all&start_day=YYYYMMDD&end_day=YYYYMMDD&stat=0&special_filter_days=30
|
|
|
- // 这样可以拿到完整 30 天数据(避免导出 Excel 只有 7 天 / GBK 乱码)
|
|
|
- const end = new Date();
|
|
|
- end.setHours(0, 0, 0, 0);
|
|
|
- end.setDate(end.getDate() - 1); // 默认取昨天
|
|
|
- const start = new Date(end);
|
|
|
- start.setDate(start.getDate() - 29);
|
|
|
- const fmt = (d: Date) =>
|
|
|
- `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
|
|
- const start_day = fmt(start);
|
|
|
- const end_day = fmt(end);
|
|
|
-
|
|
|
let perDay = new Map<string, { recordDate: Date } & Record<string, any>>();
|
|
|
let inserted = 0;
|
|
|
let updated = 0;
|
|
|
-
|
|
|
const tryFetchApi = async () => {
|
|
|
const apiUrl = `https://baijiahao.baidu.com/author/eco/statistics/appStatisticV3?type=all&start_day=${start_day}&end_day=${end_day}&stat=0&special_filter_days=30`;
|
|
|
- // 使用 browser context 的 request(带 cookie)
|
|
|
const res = await (context as any).request.get(apiUrl, {
|
|
|
- headers: {
|
|
|
- Referer: 'https://baijiahao.baidu.com/builder/rc/analysiscontent',
|
|
|
- },
|
|
|
+ headers: { Referer: 'https://baijiahao.baidu.com/builder/rc/analysiscontent' },
|
|
|
});
|
|
|
- if (!res.ok()) {
|
|
|
- throw new Error(`appStatisticV3 http ${res.status()}`);
|
|
|
- }
|
|
|
+ if (!res.ok()) throw new Error(`appStatisticV3 http ${res.status()}`);
|
|
|
const json = await res.json().catch(() => null);
|
|
|
if (!json) throw new Error('appStatisticV3 json parse failed');
|
|
|
-
|
|
|
- // 调试:BJ_IMPORT_DEBUG=1 时把接口原始返回写入文件,便于对比
|
|
|
if (process.env.BJ_IMPORT_DEBUG === '1') {
|
|
|
const debugPath = path.join(this.downloadDir, `appStatisticV3_response_${account.id}_${Date.now()}.json`);
|
|
|
await ensureDir(this.downloadDir);
|
|
|
await fs.writeFile(debugPath, JSON.stringify(json, null, 2), 'utf-8');
|
|
|
logger.info(`[BJ Import] DEBUG: appStatisticV3 原始响应已写入 ${debugPath}`);
|
|
|
}
|
|
|
-
|
|
|
- const map = parseBaijiahaoAppStatisticV3(json);
|
|
|
- logger.info(`[BJ Import] appStatisticV3 fetched. accountId=${account.id} days=${map.size} range=${start_day}-${end_day}`);
|
|
|
-
|
|
|
- // 调试:打印解析后指定日期的数据(如 2026-02-02)便于对比
|
|
|
- if (process.env.BJ_IMPORT_DEBUG === '1' && map.size > 0) {
|
|
|
- const sampleKeys = ['2026-02-02', '2026-02-01', '2026-01-16'];
|
|
|
- for (const k of sampleKeys) {
|
|
|
- const v = map.get(k);
|
|
|
- if (v) logger.info(`[BJ Import] DEBUG: 解析后 ${k} => ${JSON.stringify(v)}`);
|
|
|
- }
|
|
|
- }
|
|
|
- return map;
|
|
|
+ return parseBaijiahaoAppStatisticV3(json);
|
|
|
};
|
|
|
-
|
|
|
try {
|
|
|
perDay = await tryFetchApi();
|
|
|
} catch (e) {
|
|
|
- logger.warn(`[BJ Import] appStatisticV3 fetch failed, fallback to Excel export. accountId=${account.id}`, e);
|
|
|
+ logger.warn(`[BJ Import] appStatisticV3 failed, fallback to Excel. accountId=${account.id}`, e);
|
|
|
}
|
|
|
|
|
|
- // 兜底:如果接口抓不到,则退回导出 Excel;如果接口抓到但天数偏少,则“合并 Excel 补齐空缺”
|
|
|
let filePath: string | null = null;
|
|
|
if (perDay.size === 0) {
|
|
|
const [download] = await Promise.all([
|
|
|
page.waitForEvent('download', { timeout: 60_000 }),
|
|
|
page.getByText('导出数据', { exact: true }).first().click(),
|
|
|
]);
|
|
|
-
|
|
|
- const filename = `${account.id}_${Date.now()}_${download.suggestedFilename()}`;
|
|
|
- filePath = path.join(this.downloadDir, filename);
|
|
|
+ filePath = path.join(this.downloadDir, `${account.id}_${Date.now()}_${download.suggestedFilename()}`);
|
|
|
await download.saveAs(filePath);
|
|
|
perDay = parseBaijiahaoExcel(filePath);
|
|
|
} else if (perDay.size < 20) {
|
|
|
@@ -678,9 +781,7 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
page.waitForEvent('download', { timeout: 60_000 }),
|
|
|
page.getByText('导出数据', { exact: true }).first().click(),
|
|
|
]);
|
|
|
-
|
|
|
- const filename = `${account.id}_${Date.now()}_${download.suggestedFilename()}`;
|
|
|
- filePath = path.join(this.downloadDir, filename);
|
|
|
+ filePath = path.join(this.downloadDir, `${account.id}_${Date.now()}_${download.suggestedFilename()}`);
|
|
|
await download.saveAs(filePath);
|
|
|
const excelMap = parseBaijiahaoExcel(filePath);
|
|
|
for (const [k, v] of excelMap.entries()) {
|
|
|
@@ -691,43 +792,24 @@ export class BaijiahaoContentOverviewImportService {
|
|
|
try {
|
|
|
for (const v of perDay.values()) {
|
|
|
const { recordDate, ...patch } = v;
|
|
|
- const r = await this.userDayStatisticsService.saveStatisticsForDate(
|
|
|
- account.id,
|
|
|
- recordDate,
|
|
|
- patch
|
|
|
- );
|
|
|
+ const r = await this.userDayStatisticsService.saveStatisticsForDate(account.id, recordDate, patch);
|
|
|
inserted += r.inserted;
|
|
|
updated += r.updated;
|
|
|
}
|
|
|
-
|
|
|
- logger.info(
|
|
|
- `[BJ Import] basic-data imported. accountId=${account.id} days=${perDay.size} inserted=${inserted} updated=${updated}`
|
|
|
- );
|
|
|
+ logger.info(`[BJ Import] basic-data (browser). accountId=${account.id} days=${perDay.size} inserted=${inserted} updated=${updated}`);
|
|
|
} finally {
|
|
|
- if (filePath) {
|
|
|
- if (process.env.KEEP_BJ_XLSX === 'true') {
|
|
|
- logger.warn(`[BJ Import] KEEP_BJ_XLSX=true, keep file: ${filePath}`);
|
|
|
- } else {
|
|
|
- await fs.unlink(filePath).catch(() => undefined);
|
|
|
- }
|
|
|
- }
|
|
|
+ if (filePath && process.env.KEEP_BJ_XLSX !== 'true') await fs.unlink(filePath).catch(() => undefined);
|
|
|
}
|
|
|
|
|
|
- // 粉丝数据:直接请求 getFansBasicInfo(近30天:昨天为结束,往前推30天),不打开页面、不等待点击
|
|
|
try {
|
|
|
await this.importFansDataByApi(context, account);
|
|
|
} catch (e) {
|
|
|
- logger.warn(
|
|
|
- `[BJ Import] Fans data import failed (non-fatal). accountId=${account.id}`,
|
|
|
- e instanceof Error ? e.message : e
|
|
|
- );
|
|
|
+ logger.warn(`[BJ Import] Fans import failed (non-fatal). accountId=${account.id}`, e instanceof Error ? e.message : e);
|
|
|
}
|
|
|
|
|
|
await context.close();
|
|
|
} finally {
|
|
|
- if (shouldClose) {
|
|
|
- await browser.close().catch(() => undefined);
|
|
|
- }
|
|
|
+ if (shouldClose) await browser.close().catch(() => undefined);
|
|
|
}
|
|
|
}
|
|
|
|