mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 15:22:59 +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 { 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 withDom = async <T>(url: string, html: string, callback: () => T | Promise<T>) => {
|
||||||
const dom = new JSDOM(html, { url: 'https://www.amazon.com/s?k=security+camera&page=1' });
|
const dom = new JSDOM(html, { url });
|
||||||
const previousDocument = globalThis.document;
|
const previousDocument = globalThis.document;
|
||||||
const previousWindow = globalThis.window;
|
const previousWindow = globalThis.window;
|
||||||
Object.assign(globalThis, { document: dom.window.document, window: dom.window });
|
Object.assign(globalThis, { document: dom.window.document, window: dom.window });
|
||||||
try {
|
try {
|
||||||
const actual = await collectAmazonNextPageUrl();
|
return await callback();
|
||||||
if (actual !== expected) throw new Error(`${name}: expected ${expected}, received ${actual}`);
|
|
||||||
} finally {
|
} finally {
|
||||||
Object.assign(globalThis, { document: previousDocument, window: previousWindow });
|
Object.assign(globalThis, { document: previousDocument, window: previousWindow });
|
||||||
dom.window.close();
|
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 () => {
|
const main = async () => {
|
||||||
await test(
|
await testNextPage(
|
||||||
'active next page',
|
'active next page',
|
||||||
'<a class="s-pagination-next" href="/s?k=security+camera&page=2">Next</a>',
|
'<a class="s-pagination-next" href="/s?k=security+camera&page=2">Next</a>',
|
||||||
'https://www.amazon.com/s?k=security+camera&page=2',
|
'https://www.amazon.com/s?k=security+camera&page=2',
|
||||||
);
|
);
|
||||||
await test(
|
await testNextPage(
|
||||||
'disabled next page',
|
'disabled next page',
|
||||||
'<span class="s-pagination-next s-pagination-disabled">Next</span>',
|
'<span class="s-pagination-next s-pagination-disabled">Next</span>',
|
||||||
null,
|
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.');
|
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.
|
* Reads the next search-page URL without navigating from the injected script.
|
||||||
* Navigation must happen only after browser.scripting.executeScript has returned,
|
* 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 { collectAmazonSearchPageData, type AmazonSearchPagePattern } from './amazon-search-parser';
|
||||||
import { collectAmazonDetailImageUrls } from './amazon-detail-images';
|
import { collectAmazonDetailImageUrls } from './amazon-detail-images';
|
||||||
import { waitForAmazonDetailPageReady } from './amazon-detail-page-ready';
|
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 { waitForAmazonSearchPageReady } from './amazon-search-page-ready';
|
||||||
import {
|
import { collectAmazonNextReviewPageUrl, waitForAmazonReviewPageReady } from './amazon-review-page';
|
||||||
collectAmazonNextReviewPageUrl,
|
|
||||||
waitForAmazonReviewPageReady,
|
|
||||||
} from './amazon-review-page';
|
|
||||||
|
|
||||||
export class AmazonSearchPageInjector extends BaseInjector {
|
export class AmazonSearchPageInjector extends BaseInjector {
|
||||||
public waitForPageLoaded() {
|
public waitForPageLoaded() {
|
||||||
@ -38,10 +35,7 @@ export class AmazonSearchPageInjector extends BaseInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getCurrentPage() {
|
public async getCurrentPage() {
|
||||||
return this.run(async () => {
|
return this.run(collectAmazonCurrentPage);
|
||||||
const node = document.querySelector<HTMLElement>('.s-pagination-item.s-pagination-selected');
|
|
||||||
return node ? Number(node.innerText) : 1;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNextPageUrl() {
|
public async getNextPageUrl() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user