mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 15:22:59 +08:00
fix: ignore hidden Amazon sponsored products
This commit is contained in:
parent
3f34ed8341
commit
2ad58b6f9c
@ -78,6 +78,19 @@ const html = `
|
|||||||
<a href="/dp/B000000010"><img src="https://example.com/hidden-aria.jpg" /></a>
|
<a href="/dp/B000000010"><img src="https://example.com/hidden-aria.jpg" /></a>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="featured-from-amazon-brands">
|
||||||
|
<span>Featured from Amazon brands</span>
|
||||||
|
<article data-asin="B000000011">
|
||||||
|
<h3
|
||||||
|
aria-label="Sponsored Ad - hidden brand-banner product"
|
||||||
|
style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0)"
|
||||||
|
>Hidden brand-banner product</h3>
|
||||||
|
<a href="/sspa/click?url=%2Fdp%2FB000000011">
|
||||||
|
<img src="https://example.com/visible-brand-hero.jpg" />
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside id="rightResultsATF" data-component-type="sponsored-display">
|
<aside id="rightResultsATF" data-component-type="sponsored-display">
|
||||||
@ -127,6 +140,7 @@ async function main() {
|
|||||||
assert.equal(items.filter((item) => item.asin === 'B000000008').length, 1);
|
assert.equal(items.filter((item) => item.asin === 'B000000008').length, 1);
|
||||||
assert.equal(byAsin.has('B000000009'), false);
|
assert.equal(byAsin.has('B000000009'), false);
|
||||||
assert.equal(byAsin.has('B000000010'), false);
|
assert.equal(byAsin.has('B000000010'), false);
|
||||||
|
assert.equal(byAsin.has('B000000011'), false);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
items.map((item) => item.pageRank),
|
items.map((item) => item.pageRank),
|
||||||
[1, 2, 3, 4, 5, 6, 7, 8],
|
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
|||||||
@ -42,7 +42,12 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
style.visibility === 'hidden' ||
|
style.visibility === 'hidden' ||
|
||||||
style.visibility === 'collapse' ||
|
style.visibility === 'collapse' ||
|
||||||
style.opacity === '0' ||
|
style.opacity === '0' ||
|
||||||
style.getPropertyValue('content-visibility') === 'hidden')
|
style.getPropertyValue('content-visibility') === 'hidden' ||
|
||||||
|
(style.clip !== 'auto' && style.clip !== '') ||
|
||||||
|
(style.clipPath !== 'none' && style.clipPath !== '') ||
|
||||||
|
(style.overflow === 'hidden' &&
|
||||||
|
Number.parseFloat(style.width) <= 1 &&
|
||||||
|
Number.parseFloat(style.height) <= 1))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -85,10 +90,11 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
|
|
||||||
const hasSponsoredDisclosure = (node: Element) => {
|
const hasSponsoredDisclosure = (node: Element) => {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
node,
|
...(node.matches('[aria-label], [title], span') ? [node] : []),
|
||||||
...Array.from(node.querySelectorAll('[aria-label], [title], span, a')),
|
...Array.from(node.querySelectorAll('[aria-label], [title], span')),
|
||||||
];
|
].filter((candidate): candidate is HTMLElement => candidate instanceof HTMLElement);
|
||||||
return candidates.some((candidate) => {
|
return candidates.some((candidate) => {
|
||||||
|
if (!isVisible(candidate)) return false;
|
||||||
const accessibleLabels = [
|
const accessibleLabels = [
|
||||||
candidate.getAttribute('aria-label'),
|
candidate.getAttribute('aria-label'),
|
||||||
candidate.getAttribute('title'),
|
candidate.getAttribute('title'),
|
||||||
@ -98,21 +104,10 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return /^(Sponsored|赞助)(?:\b|\s|$)/i.test(text(candidate));
|
return candidate.matches('span') && /^(Sponsored|赞助)(?:\b|\s|$)/i.test(text(candidate));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSponsoredLink = (node: Element) =>
|
|
||||||
Array.from(node.querySelectorAll<HTMLAnchorElement>('a[href]')).some((anchor) => {
|
|
||||||
const href = decodeRepeatedly(anchor.href).toLowerCase();
|
|
||||||
return (
|
|
||||||
href.includes('/sspa/click') ||
|
|
||||||
href.includes('/gp/slredirect') ||
|
|
||||||
href.includes('adid=') ||
|
|
||||||
href.includes('qualifier=')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const metadata = (node: Element) =>
|
const metadata = (node: Element) =>
|
||||||
[
|
[
|
||||||
node.getAttribute('data-component-type'),
|
node.getAttribute('data-component-type'),
|
||||||
@ -176,13 +171,7 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
'aside, #rightResultsATF, .s-right-column, [data-cel-widget*="right"], [data-component-type*="sidebar"]',
|
'aside, #rightResultsATF, .s-right-column, [data-cel-widget*="right"], [data-component-type*="sidebar"]',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const sponsored =
|
const sponsored = hasSponsoredDisclosure(card) || hasSponsoredDisclosure(module);
|
||||||
hasSponsoredDisclosure(card) ||
|
|
||||||
hasSponsoredDisclosure(module) ||
|
|
||||||
hasSponsoredLink(card) ||
|
|
||||||
hasSponsoredLink(module) ||
|
|
||||||
hasExplicitAdMetadata(card) ||
|
|
||||||
hasExplicitAdMetadata(module);
|
|
||||||
|
|
||||||
if (!sponsored) {
|
if (!sponsored) {
|
||||||
return {
|
return {
|
||||||
@ -205,7 +194,7 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
adFormat: module.querySelector('video') ? ('video' as const) : ('standard' as const),
|
adFormat: module.querySelector('video') ? ('video' as const) : ('standard' as const),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (hasSponsoredDisclosure(card) || hasSponsoredLink(card)) {
|
if (hasSponsoredDisclosure(card)) {
|
||||||
return {
|
return {
|
||||||
adType: 'sponsoredProducts' as const,
|
adType: 'sponsoredProducts' as const,
|
||||||
placement: 'middleOfSearch' as const,
|
placement: 'middleOfSearch' as const,
|
||||||
@ -249,6 +238,11 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
});
|
});
|
||||||
|
|
||||||
const cards = Array.from(candidateSet)
|
const cards = Array.from(candidateSet)
|
||||||
|
.filter((card) => {
|
||||||
|
const titleNode = card.querySelector<HTMLElement>('h2[aria-label], h2, h3[aria-label], h3');
|
||||||
|
const imageNode = card.querySelector<HTMLImageElement>('img.s-image, img[src]');
|
||||||
|
return Boolean(titleNode && imageNode && isVisible(titleNode) && isVisible(imageNode));
|
||||||
|
})
|
||||||
.filter(
|
.filter(
|
||||||
(card, index, all) =>
|
(card, index, all) =>
|
||||||
!all.some(
|
!all.some(
|
||||||
@ -284,6 +278,7 @@ export async function collectAmazonSearchPageData(_: SearchPageCollectorPayload)
|
|||||||
|
|
||||||
const titleNode = card.querySelector<HTMLElement>('h2[aria-label], h2, h3[aria-label], h3');
|
const titleNode = card.querySelector<HTMLElement>('h2[aria-label], h2, h3[aria-label], h3');
|
||||||
const imageNode = card.querySelector<HTMLImageElement>('img.s-image, img[src]');
|
const imageNode = card.querySelector<HTMLImageElement>('img.s-image, img[src]');
|
||||||
|
if (!titleNode || !imageNode || !isVisible(titleNode) || !isVisible(imageNode)) return items;
|
||||||
const title =
|
const title =
|
||||||
titleNode?.getAttribute('aria-label')?.trim() ||
|
titleNode?.getAttribute('aria-label')?.trim() ||
|
||||||
text(titleNode) ||
|
text(titleNode) ||
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user