Amazon Scraper API vs Proxy: Choose a Product Data Source
Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.
Read guideCompare Playwright response-body bytes before and after blocking presentation assets, while checking that each required record still passes.

Playwright can reduce browser response traffic by aborting presentation requests such as images, fonts, and media while leaving scripts and application programming interface (API) calls available. Compare the same target with and without the rule, then accept a run only when its required record passes validation. Playwright's response-body sizes do not equal a proxy provider's billed traffic.
Audience: Developers who can run a Node.js script and identify the fields that make a record valid.
Caution: A route rule can remove content required by the target or change an authorized workflow. Start with an owned test page and a bounded URL cohort. Keep a baseline copy of the resource policy, and remove the rule if record validation fails.
Choose the record before changing browser behavior. Specify the target URL, the selector that holds a required value, and the expected text that distinguishes a valid record from an empty page or challenge response.
Test a small, fixed URL cohort first. Keep the account, browser version, proxy route, session behavior, cookies, request pacing, and validator the same between baseline and filtered runs. Do not retry failed requests automatically.
| Resource type | Starting rule | Check before keeping the rule |
|---|---|---|
| Images | Abort when pixels do not identify the record | Confirm the identifier is not supplied only by an image or image URL |
| Fonts | Abort for text-only records | Confirm selectors and accepted page state do not depend on font loading |
| Media | Abort when video or audio is outside the record | Confirm the target does not use media playback as a required state |
| Stylesheets | Test separately; retain when layout drives visibility | Use a separate visibility validator when visible state matters; the example checks text presence only |
| JavaScript | Keep by default | A script may render the record or request the required API data |
| XMLHttpRequest (XHR) and fetch | Keep by default | Inspect authorized requests before writing a narrower rule |
Create a task-owned directory, install the pinned Playwright version, and keep the script below in bandwidth-check.cjs:
npm init -y
npm install --save-dev playwright@1.62.1
This example uses Chrome through Playwright's channel option. It makes one baseline navigation and one filtered navigation, each in a fresh browser context. The filter blocks image, font, media, and stylesheet requests; it leaves JavaScript and API calls available. Blocking service workers makes routed requests observable; this policy does not fit a target whose service worker is part of the behavior under test. The script prints JavaScript Object Notation (JSON) records for both runs.
const { chromium } = require('playwright');
const TARGET_URL = process.env.TARGET_URL;
const REQUIRED_SELECTOR = process.env.REQUIRED_SELECTOR;
const EXPECTED_TEXT = process.env.EXPECTED_TEXT;
const BLOCKED_TYPES = new Set(['image', 'font', 'media', 'stylesheet']);
const TIMEOUT_MS = 20_000;
const proxyServer = process.env.PROXY_SERVER;
if (!TARGET_URL || !REQUIRED_SELECTOR || !EXPECTED_TEXT) {
throw new Error('Set TARGET_URL, REQUIRED_SELECTOR, and EXPECTED_TEXT');
}
async function measure({ blockPresentation }) {
const launchOptions = { channel: 'chrome', headless: true };
if (process.env.TEST_DIRECT === '1' && !proxyServer) {
launchOptions.args = ['--no-proxy-server'];
}
const browser = await chromium.launch(launchOptions);
const contextOptions = { serviceWorkers: 'block' };
if (proxyServer) {
contextOptions.proxy = {
server: proxyServer,
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
};
}
const context = await browser.newContext(contextOptions);
const page = await context.newPage();
const requestSizes = [];
const networkFailures = [];
let requestCount = 0;
let abortedPresentationRequests = 0;
page.on('request', () => {
requestCount += 1;
});
page.on('requestfinished', (request) => {
requestSizes.push(request.sizes());
});
page.on('requestfailed', (request) => {
networkFailures.push(request.failure()?.errorText ?? 'unknown');
});
if (blockPresentation) {
await page.route('**/*', async (route) => {
if (BLOCKED_TYPES.has(route.request().resourceType())) {
abortedPresentationRequests += 1;
await route.abort();
return;
}
await route.continue();
});
}
let status = null;
let responseOk = false;
let finalUrl = null;
let matched = false;
let failure = null;
try {
const response = await page.goto(TARGET_URL, {
waitUntil: 'load',
timeout: TIMEOUT_MS,
});
status = response ? response.status() : null;
responseOk = response ? response.ok() : false;
finalUrl = safeUrl(page.url());
await page.waitForFunction(
({ selector, expected }) => document.querySelector(selector)?.textContent?.includes(expected),
{ selector: REQUIRED_SELECTOR, expected: EXPECTED_TEXT },
{ timeout: TIMEOUT_MS },
);
const marker = await page.locator(REQUIRED_SELECTOR).first().textContent({ timeout: TIMEOUT_MS });
matched = Boolean(marker && marker.trim().includes(EXPECTED_TEXT));
} catch (error) {
failure = error.name === 'TimeoutError' ? 'navigation_or_marker_timeout' : error.name;
finalUrl = safeUrl(page.url());
}
const sizes = await Promise.all(requestSizes);
const responseBodyBytes = sizes.reduce((total, item) => total + item.responseBodySize, 0);
await context.close();
await browser.close();
return {
mode: blockPresentation ? 'filtered' : 'baseline',
status,
finalUrl,
requests: requestCount,
completedResponseBodyBytes: responseBodyBytes,
abortedPresentationRequests,
recordAccepted: Boolean(responseOk && matched),
failure,
networkFailures,
};
}
function safeUrl(value) {
if (!value || value === 'about:blank') {
return value;
}
const url = new URL(value);
url.username = '';
url.password = '';
url.search = '';
url.hash = '';
return url.href;
}
(async () => {
const results = [];
for (const blockPresentation of [false, true]) {
results.push(await measure({ blockPresentation }));
}
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
})().catch((error) => {
process.stderr.write(`${error.name}: ${error.message}\n`);
process.exitCode = 1;
});
Set TARGET_URL, REQUIRED_SELECTOR, and EXPECTED_TEXT from the owned test page. Set PROXY_SERVER to the provider's proxy endpoint when the target requires that route. Supply PROXY_USERNAME and PROXY_PASSWORD through the approved secret store or a protected local environment; do not place credentials in the script, terminal history, or run output. Use TEST_DIRECT=1 only for a local fixture without a proxy.
Run the script once for the same accepted page and once for an authorized page that intentionally lacks the required selector or expected text. Inspect both outcomes before using the rule on a larger cohort:
node bandwidth-check.cjs
The output omits query strings and fragments from the final URL to avoid exposing tokens. Retain any non-sensitive campaign or item identifier in a restricted run record when it is needed to reproduce a result.
request.sizes() reports encoded response-body bytes along with request and response header sizes. The script sums completed response bodies only. It does not count bytes that were never received after an aborted request, browser transport overhead, or provider-billed usage. Compare this value as a browser-side measurement, then reconcile billable usage against the provider's documented accounting or invoice.
| Run | Requests started | Completed response-body bytes | Presentation requests aborted | Record accepted |
|---|---|---|---|---|
| Baseline | From the script | From request.sizes() |
0 | Required status and field pass |
| Filtered | From the script | From request.sizes() |
From the route handler | Required status and field pass |
A lower body-byte total is not a successful run by itself. Keep only a rule whose filtered run still returns the required field. A successful response without that field is rejected, as is a challenge page that lacks the record. The example blocks service workers so Playwright's route handler can observe requests; do not use that setting for a target whose service worker behavior is under test.
| Observation | Likely check | Next action |
|---|---|---|
| Baseline passes; filtered run loses the field | A blocked resource may supply or reveal the record | Remove that resource type and repeat the paired check |
| Both runs return a status but no accepted field | Target state, authorization, challenge, or selector may be wrong | Inspect the permitted page and update the validator before comparing bytes |
| Navigation times out | Route, target availability, or page load behavior | Check the route and target with one bounded direct run |
| Request failure appears after filtering | A deliberately aborted presentation request or an unrelated network failure | Compare the failure with abortedPresentationRequests; inspect only authorized request details |
Do not block JavaScript or XHR/fetch globally to chase a smaller number. A structured record may arrive through an API, and JavaScript may be required to render it. If the task is server-side extraction, use a client suited to that page instead of paying to start a browser.
The cost-per-successful-request guide explains how to divide actual run cost by distinct validated records. Use the proxy bandwidth billing guide to reconcile browser-side measurements with provider usage.
Scale your workflows with the tools you already use. Rotating or sticky sessions up to 72 hours.
Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.
Read guideChoose the right anti-detect browser proxy guide, map the shared verification steps, and keep account access separate from the network route.
Read guideOur picks for market research: ProxyLane for small, intermittent projects, plus options for regional tracking, monthly collection and managed extraction.
Read guide