订单每页20,和客户详情单独提交
This commit is contained in:
@@ -74,6 +74,7 @@ export const datasets = {
|
||||
return {
|
||||
loginName: loginName.replace(/\s+/g, ''),
|
||||
accountId,
|
||||
listPageNum: context.pageNum || '',
|
||||
realName: record['UID实名认证名称'] || '',
|
||||
reportSource: record['报备来源'] || '',
|
||||
reportType: record['报备类型'] || '',
|
||||
@@ -101,7 +102,7 @@ export const datasets = {
|
||||
name: 'orders',
|
||||
url: `${config.baseUrl}/#/detail/order/~/costCenter/order`,
|
||||
heading: '订单查询',
|
||||
pageSize: 100,
|
||||
pageSize: 20,
|
||||
uniqueKey: (record) => record.orderId || record.__hash,
|
||||
normalize: (record, context) => ({
|
||||
orderId: record['订单号'] || '',
|
||||
|
||||
@@ -672,30 +672,44 @@ async function syncCustomerDetails(page) {
|
||||
await runtimeCheckpoint('同步客户详情');
|
||||
const dataset = datasets.customerDetails;
|
||||
const customersState = loadCurrentState('customers', datasets.customers.uniqueKey);
|
||||
const allAccountIds = collectValidAccountIds(customersState.records || []);
|
||||
const customerTargets = collectCustomerDetailTargets(customersState.records || []);
|
||||
|
||||
if (allAccountIds.length === 0) {
|
||||
console.log('[客户详情] 本地无有效客户 accountId,跳过');
|
||||
if (customerTargets.length === 0) {
|
||||
console.log('[客户详情] 本地无有效客户定位信息,跳过');
|
||||
return persistDataset(dataset, [], {});
|
||||
}
|
||||
|
||||
console.log(`[客户详情] 共 ${allAccountIds.length} 个客户需要获取详情`);
|
||||
console.log(`[客户详情] 共 ${customerTargets.length} 个客户需要获取详情`);
|
||||
const allDetails = [];
|
||||
const detailBaseUrl =
|
||||
'https://aps.aliyun.com/?spm=5176.12818093.top-nav.ditem-fx.785716d0LKDpKT#/detail/my_customer/~/customer/';
|
||||
let currentListPage = 0;
|
||||
|
||||
for (let index = 0; index < allAccountIds.length; index += 1) {
|
||||
await runtimeCheckpoint(`客户详情 ${index + 1}/${allAccountIds.length}`);
|
||||
const accountId = allAccountIds[index];
|
||||
console.log(`[客户详情] ${index + 1}/${allAccountIds.length} accountId=${accountId}`);
|
||||
await page.goto(datasets.customers.url, { waitUntil: 'domcontentloaded' });
|
||||
await waitUntilReady(page, datasets.customers.heading);
|
||||
await trySetPageSize(page, datasets.customers.pageSize);
|
||||
|
||||
for (let index = 0; index < customerTargets.length; index += 1) {
|
||||
await runtimeCheckpoint(`客户详情 ${index + 1}/${customerTargets.length}`);
|
||||
const target = customerTargets[index];
|
||||
console.log(`[客户详情] ${index + 1}/${customerTargets.length} accountId=${target.accountId} page=${target.pageNum}`);
|
||||
const pauseMs = randomIntBetween(1000, 3000);
|
||||
console.log(`[客户详情] 随机等待 ${pauseMs}ms 后继续`);
|
||||
await sleep(pauseMs);
|
||||
|
||||
// 先跳 about:blank 再跳详情URL(强制 SPA 完整重新加载)
|
||||
await page.goto('about:blank');
|
||||
await sleep(300);
|
||||
await page.goto(`${detailBaseUrl}${accountId}`, { waitUntil: 'domcontentloaded' });
|
||||
if (target.pageNum > 0 && currentListPage !== target.pageNum) {
|
||||
const reached = await jumpToPage(page, target.pageNum);
|
||||
if (!reached) {
|
||||
console.warn(`[客户详情] 无法跳到第 ${target.pageNum} 页,跳过 ${target.accountId}`);
|
||||
continue;
|
||||
}
|
||||
currentListPage = target.pageNum;
|
||||
await waitForTableRows(page);
|
||||
}
|
||||
|
||||
const clicked = await clickCustomerDetailFromList(page, target);
|
||||
if (!clicked) {
|
||||
console.warn(`[客户详情] 列表中未找到 accountId=${target.accountId},跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
@@ -705,16 +719,23 @@ async function syncCustomerDetails(page) {
|
||||
);
|
||||
await sleep(1000);
|
||||
} catch {
|
||||
console.warn(`[客户详情] ${accountId} 详情页加载超时,跳过`);
|
||||
console.warn(`[客户详情] ${target.accountId} 详情页加载超时,跳过`);
|
||||
await page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => null);
|
||||
await waitUntilReady(page, datasets.customers.heading).catch(() => null);
|
||||
continue;
|
||||
}
|
||||
|
||||
const detail = await extractCustomerDetail(page);
|
||||
allDetails.push({ ...detail, __context: { accountId } });
|
||||
allDetails.push({ ...detail, __context: { accountId: target.accountId } });
|
||||
if (hasDbConfig()) {
|
||||
const normalizedDetail = normalizeDatasetRecords(dataset, [{ ...detail, __context: { accountId } }], {});
|
||||
const normalizedDetail = normalizeDatasetRecords(dataset, [{ ...detail, __context: { accountId: target.accountId } }], {});
|
||||
await upsertCustomerDetails(normalizedDetail);
|
||||
}
|
||||
|
||||
await page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => null);
|
||||
await waitUntilReady(page, datasets.customers.heading).catch(() => null);
|
||||
await trySetPageSize(page, datasets.customers.pageSize).catch(() => null);
|
||||
currentListPage = target.pageNum;
|
||||
}
|
||||
|
||||
return persistDataset(dataset, dedupeByHash(allDetails), {});
|
||||
@@ -1219,7 +1240,7 @@ async function scrapePagedTable(page, dataset, context, options = {}) {
|
||||
break;
|
||||
}
|
||||
visited.add(pageKey);
|
||||
const pageRows = pageData.rows.map((row) => ({ ...row, __context: context }));
|
||||
const pageRows = pageData.rows.map((row) => ({ ...row, __context: { ...context, pageNum } }));
|
||||
pages.push(...pageRows);
|
||||
if (onPage) {
|
||||
await onPage({ pageData, pageNum, pageRows });
|
||||
@@ -1668,6 +1689,54 @@ function collectValidAccountIds(records) {
|
||||
return ids;
|
||||
}
|
||||
|
||||
function collectCustomerDetailTargets(records) {
|
||||
const targets = [];
|
||||
const seen = new Set();
|
||||
for (const record of records) {
|
||||
const accountId = String(record.accountId || '').trim();
|
||||
const loginName = String(record.loginName || '').trim();
|
||||
const pageNum = Number.parseInt(String(record.listPageNum || 0), 10) || 0;
|
||||
if (!accountId || !isValidAccountId(accountId) || pageNum <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (seen.has(accountId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(accountId);
|
||||
targets.push({ accountId, loginName, pageNum });
|
||||
}
|
||||
return targets.sort((a, b) => a.pageNum - b.pageNum);
|
||||
}
|
||||
|
||||
async function clickCustomerDetailFromList(page, target) {
|
||||
const clicked = await page.evaluate(({ accountId, loginName }) => {
|
||||
const normalize = (value) => String(value || '').replace(/\s+/g, '').trim();
|
||||
const rows = Array.from(document.querySelectorAll('table tbody tr'));
|
||||
const targetRow = rows.find((row) => {
|
||||
const text = normalize(row.innerText || row.textContent || '');
|
||||
return text.includes(accountId) || (loginName && text.includes(loginName));
|
||||
});
|
||||
if (!targetRow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const detailButton = Array.from(targetRow.querySelectorAll('button, a, span'))
|
||||
.find((node) => /详情/.test(String(node.textContent || '').trim()));
|
||||
if (!detailButton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
detailButton.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
|
||||
detailButton.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
return true;
|
||||
}, target).catch(() => false);
|
||||
|
||||
if (clicked) {
|
||||
await sleep(1200);
|
||||
}
|
||||
return clicked;
|
||||
}
|
||||
|
||||
function isValidOrderId(orderId) {
|
||||
const value = String(orderId || '').trim();
|
||||
if (!value) return false;
|
||||
|
||||
Reference in New Issue
Block a user