mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 07:13:06 +08:00
fix: derive Amazon result page from URL
This commit is contained in:
parent
1d60213a33
commit
3f34ed8341
@ -1,32 +1,86 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { collectAmazonNextPageUrl } from '../src/page-worker/web-injectors/amazon-search-pagination';
|
||||
import {
|
||||
collectAmazonCurrentPage,
|
||||
collectAmazonNextPageUrl,
|
||||
} from '../src/page-worker/web-injectors/amazon-search-pagination';
|
||||
|
||||
const test = async (name: string, html: string, expected: string | null) => {
|
||||
const dom = new JSDOM(html, { url: 'https://www.amazon.com/s?k=security+camera&page=1' });
|
||||
const withDom = async <T>(url: string, html: string, callback: () => T | Promise<T>) => {
|
||||
const dom = new JSDOM(html, { url });
|
||||
const previousDocument = globalThis.document;
|
||||
const previousWindow = globalThis.window;
|
||||
Object.assign(globalThis, { document: dom.window.document, window: dom.window });
|
||||
try {
|
||||
const actual = await collectAmazonNextPageUrl();
|
||||
if (actual !== expected) throw new Error(`${name}: expected ${expected}, received ${actual}`);
|
||||
return await callback();
|
||||
} finally {
|
||||
Object.assign(globalThis, { document: previousDocument, window: previousWindow });
|
||||
dom.window.close();
|
||||
}
|
||||
};
|
||||
|
||||
const testNextPage = async (name: string, html: string, expected: string | null) => {
|
||||
const actual = await withDom(
|
||||
'https://www.amazon.com/s?k=security+camera&page=1',
|
||||
html,
|
||||
collectAmazonNextPageUrl,
|
||||
);
|
||||
if (actual !== expected) throw new Error(`${name}: expected ${expected}, received ${actual}`);
|
||||
};
|
||||
|
||||
const testCurrentPage = async (name: string, url: string, html: string, expected: number) => {
|
||||
const actual = await withDom(url, html, collectAmazonCurrentPage);
|
||||
if (actual !== expected) throw new Error(`${name}: expected ${expected}, received ${actual}`);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await test(
|
||||
await testNextPage(
|
||||
'active next page',
|
||||
'<a class="s-pagination-next" href="/s?k=security+camera&page=2">Next</a>',
|
||||
'https://www.amazon.com/s?k=security+camera&page=2',
|
||||
);
|
||||
await test(
|
||||
await testNextPage(
|
||||
'disabled next page',
|
||||
'<span class="s-pagination-next s-pagination-disabled">Next</span>',
|
||||
null,
|
||||
);
|
||||
await test('missing next page', '<main>No pagination</main>', null);
|
||||
await testNextPage('missing next page', '<main>No pagination</main>', null);
|
||||
await testCurrentPage(
|
||||
'URL page wins over stale pagination selection',
|
||||
'https://www.amazon.com/s?k=home+security&page=11&ref=sr_pg_11',
|
||||
'<span class="s-pagination-item s-pagination-selected">12</span>',
|
||||
11,
|
||||
);
|
||||
for (const page of [1, 2, 5, 10, 20]) {
|
||||
await testCurrentPage(
|
||||
`URL page ${page}`,
|
||||
`https://www.amazon.com/s?k=home+security&page=${page}&ref=sr_pg_${page}`,
|
||||
`<span class="s-pagination-item s-pagination-selected">${page + 1}</span>`,
|
||||
page,
|
||||
);
|
||||
}
|
||||
await testCurrentPage(
|
||||
'invalid URL page uses ref fallback',
|
||||
'https://www.amazon.com/s?k=home+security&page=invalid&ref=sr_pg_9',
|
||||
'<span class="s-pagination-item s-pagination-selected">10</span>',
|
||||
9,
|
||||
);
|
||||
await testCurrentPage(
|
||||
'ref page fallback',
|
||||
'https://www.amazon.com/s?k=home+security&ref=sr_pg_7',
|
||||
'<main>No selected pagination item</main>',
|
||||
7,
|
||||
);
|
||||
await testCurrentPage(
|
||||
'selected pagination fallback',
|
||||
'https://www.amazon.com/s?k=home+security',
|
||||
'<span class="s-pagination-item s-pagination-selected">5</span>',
|
||||
5,
|
||||
);
|
||||
await testCurrentPage(
|
||||
'first page fallback',
|
||||
'https://www.amazon.com/s?k=home+security',
|
||||
'<main>No pagination</main>',
|
||||
1,
|
||||
);
|
||||
console.log('Amazon search pagination tests passed.');
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,27 @@
|
||||
/**
|
||||
* Resolves the page being collected. Amazon's selected pagination element can
|
||||
* point at the next page while the current results are still rendered, so the
|
||||
* document URL is the authoritative source whenever it carries a page number.
|
||||
*/
|
||||
export async function collectAmazonCurrentPage(): Promise<number> {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
const parsePage = (value: string | null | undefined) => {
|
||||
const page = Number.parseInt(value || '', 10);
|
||||
return Number.isInteger(page) && page > 0 ? page : null;
|
||||
};
|
||||
|
||||
const urlPage = parsePage(currentUrl.searchParams.get('page'));
|
||||
if (urlPage) return urlPage;
|
||||
|
||||
const refPage = parsePage(
|
||||
/(?:^|_)pg_(\d+)(?:_|$)/.exec(currentUrl.searchParams.get('ref') || '')?.[1],
|
||||
);
|
||||
if (refPage) return refPage;
|
||||
|
||||
const selected = document.querySelector<HTMLElement>('.s-pagination-item.s-pagination-selected');
|
||||
return parsePage(selected?.textContent?.trim()) || 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next search-page URL without navigating from the injected script.
|
||||
* Navigation must happen only after browser.scripting.executeScript has returned,
|
||||
|
||||
@ -2,12 +2,9 @@ import { BaseInjector } from './base';
|
||||
import { collectAmazonSearchPageData, type AmazonSearchPagePattern } from './amazon-search-parser';
|
||||
import { collectAmazonDetailImageUrls } from './amazon-detail-images';
|
||||
import { waitForAmazonDetailPageReady } from './amazon-detail-page-ready';
|
||||
import { collectAmazonNextPageUrl } from './amazon-search-pagination';
|
||||
import { collectAmazonCurrentPage, collectAmazonNextPageUrl } from './amazon-search-pagination';
|
||||
import { waitForAmazonSearchPageReady } from './amazon-search-page-ready';
|
||||
import {
|
||||
collectAmazonNextReviewPageUrl,
|
||||
waitForAmazonReviewPageReady,
|
||||
} from './amazon-review-page';
|
||||
import { collectAmazonNextReviewPageUrl, waitForAmazonReviewPageReady } from './amazon-review-page';
|
||||
|
||||
export class AmazonSearchPageInjector extends BaseInjector {
|
||||
public waitForPageLoaded() {
|
||||
@ -38,10 +35,7 @@ export class AmazonSearchPageInjector extends BaseInjector {
|
||||
}
|
||||
|
||||
public async getCurrentPage() {
|
||||
return this.run(async () => {
|
||||
const node = document.querySelector<HTMLElement>('.s-pagination-item.s-pagination-selected');
|
||||
return node ? Number(node.innerText) : 1;
|
||||
});
|
||||
return this.run(collectAmazonCurrentPage);
|
||||
}
|
||||
|
||||
public async getNextPageUrl() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user