@xiaohhhh1/canvas-agent 0.4.23 → 0.4.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -112,11 +112,18 @@ export declare class FastMossIntegration {
|
|
|
112
112
|
private useLatestPage;
|
|
113
113
|
private openProductRanking;
|
|
114
114
|
private openVideoLearningPage;
|
|
115
|
+
private readVideoLearningItems;
|
|
116
|
+
private clickVideoLearningTab;
|
|
117
|
+
private clickVideoLearningNextPage;
|
|
118
|
+
private waitForVideoLearningNavigation;
|
|
115
119
|
}
|
|
116
120
|
export declare function fastMossSalesRankUrl(market?: string): string;
|
|
117
121
|
export declare function fastMossRankingUrl(source: FastMossRankingSource, market?: string): string;
|
|
122
|
+
export declare function fastMossVideoLearningRankUrl(market?: string): string;
|
|
123
|
+
export declare function fastMossAiVideoLearningRankUrl(market?: string): string;
|
|
118
124
|
export declare function mergeRankingRecord(target: Map<string, Record<string, unknown>>, row: Record<string, unknown>, source: FastMossRankingSource, position: number): void;
|
|
119
125
|
export declare function selectDetailCandidates(records: Array<Record<string, unknown>>, limit?: number): Record<string, unknown>[];
|
|
126
|
+
export declare function isTransientFastMossNavigation(error: unknown): boolean;
|
|
120
127
|
export declare function isAnalyzableVideoUrl(value: string): boolean;
|
|
121
128
|
export declare function videoLearningSourcesFromItems(items: VisibleVideoItem[], context: {
|
|
122
129
|
market: string;
|
|
@@ -15,7 +15,7 @@ const DETAIL_FETCH_CONCURRENCY = 3;
|
|
|
15
15
|
const VIDEO_LEARNING_TARGET = 36;
|
|
16
16
|
const VIDEO_COMMERCE_TARGET = 12;
|
|
17
17
|
const VIDEO_AI_TARGET_PER_DIMENSION = 6;
|
|
18
|
-
const VIDEO_AI_RANKING_PATH = "/zh/
|
|
18
|
+
const VIDEO_AI_RANKING_PATH = "/zh/media-source/top-ai-videos";
|
|
19
19
|
const RANKING_DEFINITIONS = [
|
|
20
20
|
{ source: "new", label: "新品榜", pathname: "/e-commerce/newProducts" },
|
|
21
21
|
{ source: "hot", label: "热推榜", pathname: "/e-commerce/hotlist" },
|
|
@@ -273,7 +273,9 @@ export class FastMossIntegration {
|
|
|
273
273
|
throw new Error("FastMoss 会员已过期或当前账号没有视频榜单权限,请先更换账号");
|
|
274
274
|
if (!this.authenticated || !this.page || this.page.isClosed())
|
|
275
275
|
throw new Error("FastMoss 尚未登录;请在恢复的窗口中完成登录后重试");
|
|
276
|
-
|
|
276
|
+
// 视频学习只研究可迁移的创意机制,不应被某一个运营市场限制。
|
|
277
|
+
// FastMoss 使用空 region 表示“全部国家/地区”。
|
|
278
|
+
const market = normalizeLearningMarket(input.market);
|
|
277
279
|
const requestedTarget = Math.max(1, Math.min(VIDEO_LEARNING_TARGET, Number(input.target || VIDEO_LEARNING_TARGET)));
|
|
278
280
|
const sources = [];
|
|
279
281
|
const rejected = [];
|
|
@@ -283,7 +285,7 @@ export class FastMossIntegration {
|
|
|
283
285
|
return;
|
|
284
286
|
let previousSignature = "";
|
|
285
287
|
for (let pageNumber = 1; pageNumber <= 5 && sources.filter((item) => item.rankingDimension === dimension).length < limit; pageNumber += 1) {
|
|
286
|
-
const snapshot = await
|
|
288
|
+
const snapshot = await this.readVideoLearningItems();
|
|
287
289
|
const signature = JSON.stringify(snapshot.map((item) => [item.text, item.media, item.links]).slice(0, 20));
|
|
288
290
|
if (!snapshot.length || (pageNumber > 1 && signature === previousSignature))
|
|
289
291
|
break;
|
|
@@ -309,9 +311,9 @@ export class FastMossIntegration {
|
|
|
309
311
|
this.message = `正在读取视频学习样本:${dimension} ${sources.filter((item) => item.rankingDimension === dimension).length}/${limit},合计 ${sources.length}/${requestedTarget}`;
|
|
310
312
|
if (sources.filter((item) => item.rankingDimension === dimension).length >= limit)
|
|
311
313
|
break;
|
|
312
|
-
if (!await
|
|
314
|
+
if (!await this.clickVideoLearningNextPage())
|
|
313
315
|
break;
|
|
314
|
-
await this.
|
|
316
|
+
await this.waitForVideoLearningNavigation();
|
|
315
317
|
await this.inspect();
|
|
316
318
|
if (this.verificationRequired)
|
|
317
319
|
throw new Error("采集视频榜单时出现滑块验证;请手动完成后重新采集,未完成样本不会计数");
|
|
@@ -319,7 +321,7 @@ export class FastMossIntegration {
|
|
|
319
321
|
throw new Error("当前 FastMoss 账号没有继续查看视频榜单的权限");
|
|
320
322
|
}
|
|
321
323
|
};
|
|
322
|
-
await this.openVideoLearningPage(
|
|
324
|
+
await this.openVideoLearningPage(fastMossVideoLearningRankUrl(market), market === "GLOBAL" ? "全球视频商品榜" : `${market} 视频商品榜`);
|
|
323
325
|
await collect("commerce-video", Math.min(VIDEO_COMMERCE_TARGET, requestedTarget));
|
|
324
326
|
const aiDimensions = [
|
|
325
327
|
{ dimension: "high-sales", labels: ["高销量榜单", "High Sales"] },
|
|
@@ -328,18 +330,16 @@ export class FastMossIntegration {
|
|
|
328
330
|
{ dimension: "high-engagement", labels: ["高互动榜单", "High Engagement"] },
|
|
329
331
|
];
|
|
330
332
|
if (sources.length < requestedTarget) {
|
|
331
|
-
|
|
332
|
-
aiUrl.searchParams.set("region", market);
|
|
333
|
-
await this.openVideoLearningPage(aiUrl.toString(), `${market} AI 带货视频榜`);
|
|
333
|
+
await this.openVideoLearningPage(fastMossAiVideoLearningRankUrl(market), market === "GLOBAL" ? "全球 AI 带货视频榜" : `${market} AI 带货视频榜`);
|
|
334
334
|
for (const entry of aiDimensions) {
|
|
335
335
|
if (sources.length >= requestedTarget)
|
|
336
336
|
break;
|
|
337
|
-
const clicked = await
|
|
337
|
+
const clicked = await this.clickVideoLearningTab(entry.labels);
|
|
338
338
|
if (!clicked) {
|
|
339
339
|
rejected.push({ dimension: entry.dimension, reason: `没有找到${entry.labels[0]}标签` });
|
|
340
340
|
continue;
|
|
341
341
|
}
|
|
342
|
-
await this.
|
|
342
|
+
await this.waitForVideoLearningNavigation();
|
|
343
343
|
await this.inspect();
|
|
344
344
|
if (this.verificationRequired)
|
|
345
345
|
throw new Error("采集 AI 视频榜时出现滑块验证;请手动完成后重新采集");
|
|
@@ -437,10 +437,18 @@ export class FastMossIntegration {
|
|
|
437
437
|
if (!this.page || this.page.isClosed())
|
|
438
438
|
throw new Error("FastMoss 专用浏览器未打开");
|
|
439
439
|
this.message = `正在自动进入 ${label}`;
|
|
440
|
-
|
|
440
|
+
try {
|
|
441
|
+
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 60_000 });
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
// FastMoss 会在进入 AI 榜时自行二次跳转。此时 Playwright 报 ERR_ABORTED,
|
|
445
|
+
// 但浏览器实际仍在加载目标页;继续等待可见榜单,而不是把正常跳转当成失败。
|
|
446
|
+
if (!isTransientFastMossNavigation(error))
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
441
449
|
const deadline = Date.now() + PRODUCT_TABLE_TIMEOUT_MS;
|
|
442
450
|
while (Date.now() < deadline) {
|
|
443
|
-
const items = await
|
|
451
|
+
const items = await this.readVideoLearningItems();
|
|
444
452
|
if (items.length)
|
|
445
453
|
return;
|
|
446
454
|
await this.inspect();
|
|
@@ -450,10 +458,74 @@ export class FastMossIntegration {
|
|
|
450
458
|
throw new Error("当前 FastMoss 账号没有该视频榜单权限");
|
|
451
459
|
if (!this.authenticated)
|
|
452
460
|
throw new Error("FastMoss 登录已失效,请重新登录后再次采集");
|
|
453
|
-
await this.
|
|
461
|
+
await this.waitForVideoLearningNavigation(750);
|
|
454
462
|
}
|
|
455
463
|
throw new Error(`${label}加载超时;请检查网络或在专用窗口中确认页面可见`);
|
|
456
464
|
}
|
|
465
|
+
async readVideoLearningItems() {
|
|
466
|
+
let lastError;
|
|
467
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
468
|
+
this.useLatestPage();
|
|
469
|
+
if (!this.page || this.page.isClosed())
|
|
470
|
+
throw new Error("FastMoss 专用浏览器未打开");
|
|
471
|
+
try {
|
|
472
|
+
return await readVisibleVideoLearningItems(this.page);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
lastError = error;
|
|
476
|
+
if (!isTransientFastMossNavigation(error))
|
|
477
|
+
throw error;
|
|
478
|
+
await this.waitForVideoLearningNavigation();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
throw lastError instanceof Error ? lastError : new Error("FastMoss 视频榜页面跳转后没有恢复");
|
|
482
|
+
}
|
|
483
|
+
async clickVideoLearningTab(labels) {
|
|
484
|
+
let lastError;
|
|
485
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
486
|
+
this.useLatestPage();
|
|
487
|
+
if (!this.page || this.page.isClosed())
|
|
488
|
+
throw new Error("FastMoss 专用浏览器未打开");
|
|
489
|
+
try {
|
|
490
|
+
return await clickVisibleText(this.page, labels);
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
lastError = error;
|
|
494
|
+
if (!isTransientFastMossNavigation(error))
|
|
495
|
+
throw error;
|
|
496
|
+
await this.waitForVideoLearningNavigation();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
throw lastError instanceof Error ? lastError : new Error("FastMoss 视频榜标签切换失败");
|
|
500
|
+
}
|
|
501
|
+
async clickVideoLearningNextPage() {
|
|
502
|
+
let lastError;
|
|
503
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
504
|
+
this.useLatestPage();
|
|
505
|
+
if (!this.page || this.page.isClosed())
|
|
506
|
+
throw new Error("FastMoss 专用浏览器未打开");
|
|
507
|
+
try {
|
|
508
|
+
return await clickVisibleNextPage(this.page);
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
lastError = error;
|
|
512
|
+
if (!isTransientFastMossNavigation(error))
|
|
513
|
+
throw error;
|
|
514
|
+
await this.waitForVideoLearningNavigation();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
throw lastError instanceof Error ? lastError : new Error("FastMoss 视频榜翻页失败");
|
|
518
|
+
}
|
|
519
|
+
async waitForVideoLearningNavigation(delay = 900) {
|
|
520
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
521
|
+
this.useLatestPage();
|
|
522
|
+
if (!this.page || this.page.isClosed())
|
|
523
|
+
return;
|
|
524
|
+
await this.page.waitForLoadState("domcontentloaded", { timeout: 10_000 }).catch((error) => {
|
|
525
|
+
if (!isTransientFastMossNavigation(error))
|
|
526
|
+
throw error;
|
|
527
|
+
});
|
|
528
|
+
}
|
|
457
529
|
}
|
|
458
530
|
export function fastMossSalesRankUrl(market) {
|
|
459
531
|
return fastMossRankingUrl("sales", market);
|
|
@@ -461,11 +533,20 @@ export function fastMossSalesRankUrl(market) {
|
|
|
461
533
|
export function fastMossRankingUrl(source, market) {
|
|
462
534
|
const definition = RANKING_DEFINITIONS.find((item) => item.source === source) || RANKING_DEFINITIONS[0];
|
|
463
535
|
const url = new URL(definition.pathname, FASTMOSS_HOME);
|
|
464
|
-
url.searchParams.set("region", normalizeMarket(market));
|
|
536
|
+
url.searchParams.set("region", market === "GLOBAL" ? "" : normalizeMarket(market));
|
|
465
537
|
url.searchParams.set("page", "1");
|
|
466
538
|
url.searchParams.set("pagesize", String(PRODUCT_RANK_PAGE_SIZE));
|
|
467
539
|
return url.toString();
|
|
468
540
|
}
|
|
541
|
+
export function fastMossVideoLearningRankUrl(market) {
|
|
542
|
+
return fastMossRankingUrl("video", normalizeLearningMarket(market));
|
|
543
|
+
}
|
|
544
|
+
export function fastMossAiVideoLearningRankUrl(market) {
|
|
545
|
+
const normalizedMarket = normalizeLearningMarket(market);
|
|
546
|
+
const url = new URL(VIDEO_AI_RANKING_PATH, FASTMOSS_HOME);
|
|
547
|
+
url.searchParams.set("region", normalizedMarket === "GLOBAL" ? "" : normalizedMarket);
|
|
548
|
+
return url.toString();
|
|
549
|
+
}
|
|
469
550
|
export function mergeRankingRecord(target, row, source, position) {
|
|
470
551
|
const key = String(row.product_id || row.title);
|
|
471
552
|
const existing = target.get(key) || {};
|
|
@@ -493,6 +574,14 @@ function normalizeMarket(value) {
|
|
|
493
574
|
const market = String(value || "MX").trim().toUpperCase();
|
|
494
575
|
return /^[A-Z]{2}$/.test(market) ? market : "MX";
|
|
495
576
|
}
|
|
577
|
+
function normalizeLearningMarket(value) {
|
|
578
|
+
const market = String(value || "GLOBAL").trim().toUpperCase();
|
|
579
|
+
return market === "GLOBAL" || market === "ALL" || market === "全球" || market === "全部" ? "GLOBAL" : normalizeMarket(market);
|
|
580
|
+
}
|
|
581
|
+
export function isTransientFastMossNavigation(error) {
|
|
582
|
+
const message = error instanceof Error ? error.message : String(error || "");
|
|
583
|
+
return /ERR_ABORTED|Execution context was destroyed|navigation interrupted|frame was detached/i.test(message);
|
|
584
|
+
}
|
|
496
585
|
function hasReadableProductRows(snapshot) {
|
|
497
586
|
return snapshot.tables.some((table) => table.rows.some((row) => row.cells.filter(Boolean).length >= 3));
|
|
498
587
|
}
|