mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 15:22:59 +08:00
fix: wait for lazy Amazon ad modules
This commit is contained in:
parent
100b4501e6
commit
832ae83443
@ -12,15 +12,26 @@ const main = async () => {
|
|||||||
});
|
});
|
||||||
Object.assign(globalThis, { document: dom.window.document, window: dom.window });
|
Object.assign(globalThis, { document: dom.window.document, window: dom.window });
|
||||||
Object.assign(dom.window.HTMLElement.prototype, { scrollIntoView: () => undefined });
|
Object.assign(dom.window.HTMLElement.prototype, { scrollIntoView: () => undefined });
|
||||||
|
Object.assign(dom.window, { scrollTo: () => undefined });
|
||||||
|
|
||||||
|
dom.window.setTimeout(() => {
|
||||||
|
const sponsoredModule = dom.window.document.createElement('section');
|
||||||
|
sponsoredModule.innerHTML =
|
||||||
|
'<span aria-label="Sponsored">Sponsored</span><article data-asin="B000000002"><h3>Lazy sponsored product</h3><a href="/dp/B000000002"><img src="https://example.com/lazy.jpg"></a></article>';
|
||||||
|
dom.window.document.querySelector('main')?.append(sponsoredModule);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
await waitForAmazonSearchPageReady();
|
await waitForAmazonSearchPageReady();
|
||||||
const elapsed = Date.now() - started;
|
const elapsed = Date.now() - started;
|
||||||
dom.window.close();
|
|
||||||
|
|
||||||
if (elapsed >= 2000) {
|
if (!dom.window.document.querySelector('[data-asin="B000000002"]')) {
|
||||||
|
throw new Error('Lazy sponsored DOM was not loaded before page readiness completed');
|
||||||
|
}
|
||||||
|
if (elapsed >= 5000) {
|
||||||
throw new Error(`Ready search results were delayed by a persistent spinner (${elapsed}ms)`);
|
throw new Error(`Ready search results were delayed by a persistent spinner (${elapsed}ms)`);
|
||||||
}
|
}
|
||||||
|
dom.window.close();
|
||||||
|
|
||||||
const errorDom = new JSDOM(
|
const errorDom = new JSDOM(
|
||||||
'<main><h1>SORRY</h1><p>Something went wrong on our end.</p><p>Please go back and try again.</p></main>',
|
'<main><h1>SORRY</h1><p>Something went wrong on our end.</p><p>Please go back and try again.</p></main>',
|
||||||
|
|||||||
@ -7,6 +7,8 @@ export async function waitForAmazonSearchPageReady() {
|
|||||||
const deadline = Date.now() + 20000;
|
const deadline = Date.now() + 20000;
|
||||||
const sleep = (milliseconds: number) =>
|
const sleep = (milliseconds: number) =>
|
||||||
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
|
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
|
||||||
|
let nextButton: HTMLElement | null = null;
|
||||||
|
let pageBecameReady = false;
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
if (window.location.pathname.startsWith('/ap/signin')) {
|
if (window.location.pathname.startsWith('/ap/signin')) {
|
||||||
@ -26,21 +28,78 @@ export async function waitForAmazonSearchPageReady() {
|
|||||||
const resultCount = document.querySelectorAll(
|
const resultCount = document.querySelectorAll(
|
||||||
'[data-component-type="s-search-result"][data-asin], .puis-card-container',
|
'[data-component-type="s-search-result"][data-asin], .puis-card-container',
|
||||||
).length;
|
).length;
|
||||||
const nextButton = document.querySelector<HTMLElement>('.s-pagination-next');
|
nextButton = document.querySelector<HTMLElement>('.s-pagination-next');
|
||||||
const noResults = /no results for/i.test(bodyText);
|
const noResults = /no results for/i.test(bodyText);
|
||||||
const pageReady =
|
const pageReady =
|
||||||
document.readyState !== 'loading' &&
|
document.readyState !== 'loading' && (resultCount > 0 || nextButton !== null || noResults);
|
||||||
(resultCount > 0 || nextButton !== null || noResults);
|
|
||||||
|
|
||||||
if (pageReady) {
|
if (pageReady) {
|
||||||
nextButton?.scrollIntoView({ behavior: 'auto', block: 'center' });
|
pageBecameReady = true;
|
||||||
await sleep(750);
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.scrollBy(0, 500 + ~~(Math.random() * 500));
|
window.scrollBy(0, 500 + ~~(Math.random() * 500));
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pageBecameReady) {
|
||||||
throw new Error('Amazon 搜索结果在 20 秒内未加载完成');
|
throw new Error('Amazon 搜索结果在 20 秒内未加载完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit the full result stream instead of jumping straight to pagination. Amazon loads
|
||||||
|
// recommendation and sponsored modules when their region approaches the viewport.
|
||||||
|
for (const fraction of [0.15, 0.35, 0.55, 0.75, 0.92]) {
|
||||||
|
const documentHeight = Math.max(
|
||||||
|
document.documentElement.scrollHeight,
|
||||||
|
document.body?.scrollHeight || 0,
|
||||||
|
document.documentElement.offsetHeight,
|
||||||
|
document.body?.offsetHeight || 0,
|
||||||
|
);
|
||||||
|
const top = Math.max(0, (documentHeight - window.innerHeight) * fraction);
|
||||||
|
window.scrollTo({ top, behavior: 'auto' });
|
||||||
|
await sleep(180);
|
||||||
|
}
|
||||||
|
nextButton?.scrollIntoView({ behavior: 'auto', block: 'center' });
|
||||||
|
|
||||||
|
const relevantDomFingerprint = () => {
|
||||||
|
const root = document.querySelector('#search') || document.body;
|
||||||
|
if (!root) return '';
|
||||||
|
const asins = Array.from(root.querySelectorAll<HTMLElement>('[data-asin]'))
|
||||||
|
.map((node) => node.dataset.asin || '')
|
||||||
|
.filter((asin) => /^[A-Z0-9]{10}$/.test(asin));
|
||||||
|
const productLinks = Array.from(
|
||||||
|
root.querySelectorAll<HTMLAnchorElement>('a[href*="/dp/"], a[href*="%2Fdp%2F"]'),
|
||||||
|
).map((anchor) => anchor.href);
|
||||||
|
const sponsoredLabels = root.querySelectorAll(
|
||||||
|
'[aria-label*="Sponsored" i], [title*="Sponsored" i]',
|
||||||
|
).length;
|
||||||
|
const cards = root.querySelectorAll(
|
||||||
|
'[data-component-type="s-search-result"], .puis-card-container, .puisg-row',
|
||||||
|
).length;
|
||||||
|
return [
|
||||||
|
Array.from(new Set(asins)).sort().join(','),
|
||||||
|
Array.from(new Set(productLinks)).sort().join(','),
|
||||||
|
sponsoredLabels,
|
||||||
|
cards,
|
||||||
|
root.querySelectorAll('video').length,
|
||||||
|
root.querySelectorAll('img[src]').length,
|
||||||
|
].join('|');
|
||||||
|
};
|
||||||
|
|
||||||
|
const stabilityStartedAt = Date.now();
|
||||||
|
const minimumStabilityWait = 1500;
|
||||||
|
const stableForRequired = 1000;
|
||||||
|
const maximumStabilityWait = 3500;
|
||||||
|
let fingerprint = relevantDomFingerprint();
|
||||||
|
let stableSince = Date.now();
|
||||||
|
while (Date.now() - stabilityStartedAt < maximumStabilityWait) {
|
||||||
|
await sleep(250);
|
||||||
|
const nextFingerprint = relevantDomFingerprint();
|
||||||
|
if (nextFingerprint !== fingerprint) {
|
||||||
|
fingerprint = nextFingerprint;
|
||||||
|
stableSince = Date.now();
|
||||||
|
}
|
||||||
|
const elapsed = Date.now() - stabilityStartedAt;
|
||||||
|
if (elapsed >= minimumStabilityWait && Date.now() - stableSince >= stableForRequired) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user