mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 07:13:06 +08:00
fix: classify sponsored ads across new Amazon templates
This commit is contained in:
parent
4c26b108a7
commit
3c0e0e4c33
@ -1127,6 +1127,80 @@ async function main() {
|
||||
false,
|
||||
);
|
||||
|
||||
const unknownTemplatesDom = new JSDOM(
|
||||
`
|
||||
<main id="search">
|
||||
<div class="s-main-slot">
|
||||
<article data-component-type="s-search-result" data-asin="B0GENORG01">
|
||||
<h2>Generic organic product</h2>
|
||||
<a href="/dp/B0GENORG01"><img src="https://example.com/generic-organic.jpg" /></a>
|
||||
</article>
|
||||
|
||||
<section class="future-layout-alpha" data-ad-details='{"adType":"sponsoredProducts"}'>
|
||||
<h2>A heading that is not Shop by type</h2>
|
||||
<span aria-label="Sponsored">Sponsored</span>
|
||||
<div class="future-card-one">
|
||||
<h3>Future SP one</h3>
|
||||
<a href="https://redirect.amazon.com/x/c/new1?url=%2Fdp%2FB0GENSP001">
|
||||
<img src="https://example.com/generic-sp-1.jpg" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="future-card-two">
|
||||
<h3>Future SP two</h3>
|
||||
<a href="https://direct.amazon.com/x/c/new2/https://www.amazon.com/dp/B0GENSP002">
|
||||
<img src="https://example.com/generic-sp-2.jpg" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="future-layout-beta" data-component-type="sponsored-brand-vNext">
|
||||
<span title="Sponsored">Sponsored</span>
|
||||
<a href="/stores/future-brand"><span class="brand-logo">Future Brand</span></a>
|
||||
<h2 class="future-copy">Future Brand collection</h2>
|
||||
<div data-asin="B0GENSB001">
|
||||
<h3>Future SB product</h3>
|
||||
<a href="/dp/B0GENSB001"><img src="https://example.com/generic-sb.jpg" /></a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="future-layout-gamma" data-ad-details='{"adType":"displayAd"}'>
|
||||
<span aria-label="Sponsored">Sponsored</span>
|
||||
<div data-asin="B0GENSD001">
|
||||
<h3>Future SD product</h3>
|
||||
<a href="/dp/B0GENSD001"><img src="https://example.com/generic-sd.jpg" /></a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
`,
|
||||
{ url: 'https://www.amazon.com/s?k=future-layout' },
|
||||
);
|
||||
Object.assign(globalThis, {
|
||||
document: unknownTemplatesDom.window.document,
|
||||
Node: unknownTemplatesDom.window.Node,
|
||||
Element: unknownTemplatesDom.window.Element,
|
||||
HTMLElement: unknownTemplatesDom.window.HTMLElement,
|
||||
HTMLAnchorElement: unknownTemplatesDom.window.HTMLAnchorElement,
|
||||
HTMLImageElement: unknownTemplatesDom.window.HTMLImageElement,
|
||||
});
|
||||
const unknownTemplates = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
|
||||
const unknownTemplatesByAsin = new Map(unknownTemplates.map((item) => [item.asin, item]));
|
||||
assert.equal(unknownTemplatesByAsin.get('B0GENORG01')?.adType, 'organic');
|
||||
assert.equal(unknownTemplatesByAsin.get('B0GENSP001')?.adType, 'sponsoredProducts');
|
||||
assert.equal(unknownTemplatesByAsin.get('B0GENSP002')?.adType, 'sponsoredProducts');
|
||||
assert.equal(
|
||||
unknownTemplatesByAsin.get('B0GENSP001')?.placementId,
|
||||
unknownTemplatesByAsin.get('B0GENSP002')?.placementId,
|
||||
);
|
||||
assert.equal(unknownTemplatesByAsin.get('B0GENSB001')?.adType, 'sponsoredBrands');
|
||||
assert.equal(unknownTemplatesByAsin.get('B0GENSD001')?.adType, 'sponsoredDisplay');
|
||||
assert.ok(
|
||||
['B0GENSP001', 'B0GENSP002', 'B0GENSB001', 'B0GENSD001'].every((asin) =>
|
||||
unknownTemplatesByAsin.has(asin),
|
||||
),
|
||||
'Visible SP/SB/SD products must be collected even when their wrapper classes are unknown',
|
||||
);
|
||||
|
||||
console.log('Amazon search parser fixtures passed.');
|
||||
}
|
||||
|
||||
|
||||
@ -239,8 +239,8 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
||||
},
|
||||
);
|
||||
|
||||
const hasThematicSpSignal = (node: Element) => {
|
||||
const blob = [
|
||||
const thematicMetadata = (node: Element) =>
|
||||
[
|
||||
metadata(node),
|
||||
node.getAttribute('cel_widget_id') || '',
|
||||
node.getAttribute('data-multi-ad-feedback-form-trigger') || '',
|
||||
@ -248,16 +248,19 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
const hasDirectThematicSpSignal = (node: Element) => {
|
||||
const blob = thematicMetadata(node);
|
||||
if (/brand-footer|multi-brand-creative|brands-related|desktop-hsa-3psl/.test(blob)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
/featured.?asins.?list|sponsoredproductsloom|sp_search_thematic|loom-desktop|influencer|shop.?by.?type|shop_by_type/.test(
|
||||
blob,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /featured.?asins.?list|sponsored.?products|sp_search_thematic|loom-desktop|influencer|shop.?by.?type|shop_by_type/.test(
|
||||
blob,
|
||||
);
|
||||
};
|
||||
|
||||
const hasThematicSpSignal = (node: Element) => {
|
||||
if (hasDirectThematicSpSignal(node)) return true;
|
||||
return hasInfluencerThematicCopy(node) || hasShopByTypeCopy(node);
|
||||
};
|
||||
|
||||
@ -348,7 +351,7 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
||||
adFormat: 'standard' as const,
|
||||
};
|
||||
}
|
||||
if (isSearchResultCard(card)) {
|
||||
if (isSearchResultCard(card) || isSponsoredProductsThematic(module)) {
|
||||
return {
|
||||
adType: 'sponsoredProducts' as const,
|
||||
placement: 'middleOfSearch' as const,
|
||||
@ -466,6 +469,58 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Amazon frequently changes the wrapper/template names used for ads. Discover a
|
||||
* sponsored unit from its visible disclosure first, then find the closest
|
||||
* ancestor that actually owns one or more product links. This keeps template
|
||||
* names and marketing headings out of the SP/SB/SD classification contract.
|
||||
*/
|
||||
const findSponsoredProductScope = (disclosure: HTMLElement) => {
|
||||
for (
|
||||
let current = disclosure.parentElement;
|
||||
current && !isMainResultsBoundary(current);
|
||||
current = current.parentElement
|
||||
) {
|
||||
if (
|
||||
isVisible(current) &&
|
||||
(hasExplicitAdMetadata(current) || hasDirectThematicSpSignal(current)) &&
|
||||
hasCollectableProductLink(current) &&
|
||||
current.querySelector('img[src], img[data-src], img[srcset]')
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
const searchResult = disclosure.closest<HTMLElement>('[data-component-type="s-search-result"]');
|
||||
if (searchResult) return searchResult;
|
||||
|
||||
const asinHost = closestAsinHost(disclosure);
|
||||
if (asinHost) return asinHost;
|
||||
|
||||
for (
|
||||
let current = disclosure.parentElement;
|
||||
current && !isMainResultsBoundary(current);
|
||||
current = current.parentElement
|
||||
) {
|
||||
if (
|
||||
isVisible(current) &&
|
||||
hasCollectableProductLink(current) &&
|
||||
current.querySelector('img[src], img[data-src], img[srcset]')
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
sponsoredDisclosureCandidates(document.body).forEach((disclosure) => {
|
||||
if (!isSponsoredDisclosure(disclosure)) return;
|
||||
const scope = findSponsoredProductScope(disclosure);
|
||||
if (!scope) return;
|
||||
collectThematicModuleCards(scope);
|
||||
addCandidate(scope);
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLElement>(
|
||||
'.s-widget-container, [class*="celwidget"], [class*="s-widget"], [class*="shop-by-type"], [cel_widget_id*="SHOP_BY_TYPE" i], [cel_widget_id*="shop-by-type" i]',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user