Skip to content

Instantly share code, notes, and snippets.

@listly-io
Last active June 4, 2026 21:58
Show Gist options
  • Select an option

  • Save listly-io/d78eef664d009ebc3fcc209e973dcd66 to your computer and use it in GitHub Desktop.

Select an option

Save listly-io/d78eef664d009ebc3fcc209e973dcd66 to your computer and use it in GitHub Desktop.
amazon_review_en_2026-06-04
// test page: https://www.amazon.com/Owala-FreeSip-Insulated-Stainless-BPA-Free/dp/B0BZYCJK89/ref=zg_bs_c_kitchen_d_sccl_1/138-0782883-4241845?pd_rd_w=if96h&content-id=amzn1.sym.fef9af56-6177-46e9-8710-a5293a68dd39&pf_rd_p=fef9af56-6177-46e9-8710-a5293a68dd39&pf_rd_r=D3BVKF50GMF969E11QAG&pd_rd_wg=yc1zf&pd_rd_r=2ef35c7f-0715-48e9-85cb-b626a242dde4&pd_rd_i=B0BZYCJK89&th=1
// ============================================================
// ▶ User configuration (edit as needed)
// ============================================================
const MAX_PAGE_COUNT = 10; // Number of pages to collect (1 = current page only)
const WAIT_AFTER_LOAD = 5000; // Wait time after a new page loads (ms)
const WAIT_BEFORE_NAV = 1000; // Wait time after scrolling, before navigating (ms)
const REVIEW_LIST_SELECTOR = '[data-hook="review"]'; // Individual review item selector
const PAGINATION_SELECTOR = '#reviewsMedley .a-pagination, ul.a-pagination'; // Pagination area selector
const NEXT_BTN_SELECTOR = 'li.a-last a'; // "Next page" link selector (reviews page only)
const CONTAINER_ID = '__review_collector'; // Collector container ID
// ============================================================
// ▶ Internal logic (no edits needed)
// ============================================================
(async () => {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 1. Create the collector container (reuse if it already exists)
let container = document.getElementById(CONTAINER_ID);
if (!container) {
container = document.createElement('div');
container.id = CONTAINER_ID;
Object.assign(container.style, {
position: 'fixed',
right: '200px',
bottom: '200px',
zIndex: '999999',
background: 'rgba(0, 0, 0, 0.85)',
color: '#0f0',
fontSize: '18px',
fontWeight: 'bold',
fontFamily: 'monospace',
padding: '16px 24px',
borderRadius: '12px',
border: '2px solid #0f0',
boxShadow: '0 4px 20px rgba(0,255,0,.3)',
cursor: 'default',
userSelect: 'none',
overflow: 'hidden',
});
document.body.appendChild(container);
}
// Collection store (hidden area) — a display:none wrapper inside the container
let store = container.querySelector('#__review_store');
if (!store) {
store = document.createElement('div');
store.id = '__review_store';
store.style.display = 'none';
container.appendChild(store);
}
// Count display label
let label = container.querySelector('#__review_label');
if (!label) {
label = document.createElement('div');
label.id = '__review_label';
container.appendChild(label);
}
const updateLabel = () => {
const count = store.children.length;
label.textContent = `Reviews collected: ${count}`;
};
// 2. Function to collect reviews on the current page
const collectCurrentPage = () => {
const reviews = document.querySelectorAll(
`${REVIEW_LIST_SELECTOR}:not(#${CONTAINER_ID} *)`
);
let added = 0;
reviews.forEach((el) => {
const clone = el.cloneNode(true);
store.appendChild(clone);
added++;
});
console.log(`[Scraper] Collected ${added} reviews (total: ${store.children.length})`);
updateLabel();
};
// 3. Function to go to the next page (scroll → wait → navigate)
// Returns: true = navigation triggered, false = no next page
// NOTE: Amazon advances reviews by loading a new URL (?pageNumber=N),
// not by an in-page AJAX button like Kurly. We persist the running
// total in sessionStorage so it survives the page reload.
const goNextPage = async () => {
// Scroll the pagination area into view
const paginationArea = document.querySelector(PAGINATION_SELECTOR);
if (paginationArea) {
paginationArea.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
await sleep(WAIT_BEFORE_NAV);
// Prefer the page's own "Next" link if present (reviews page)
const btn = document.querySelector(NEXT_BTN_SELECTOR);
if (btn && !btn.closest('.a-disabled') && !btn.classList.contains('a-disabled')) {
// Save progress before the page reloads
sessionStorage.setItem('__review_store_html', store.innerHTML);
btn.click();
return true;
}
// Fallback: build the next page URL manually
const url = new URL(window.location.href);
const current = parseInt(url.searchParams.get('pageNumber') || '1', 10);
// Only the dedicated reviews page supports pageNumber pagination
if (!/\/product-reviews\//.test(url.pathname)) {
return false;
}
url.searchParams.set('pageNumber', String(current + 1));
sessionStorage.setItem('__review_store_html', store.innerHTML);
window.location.href = url.toString();
return true;
};
// Restore any previously saved progress (after a page navigation)
const saved = sessionStorage.getItem('__review_store_html');
if (saved && store.children.length === 0) {
store.innerHTML = saved;
updateLabel();
}
// 4. Main loop
console.log(`[Scraper] Starting collection — up to ${MAX_PAGE_COUNT} pages`);
for (let page = 1; page <= MAX_PAGE_COUNT; page++) {
// Collect the current page
collectCurrentPage();
console.log(`[Scraper] Page ${page}/${MAX_PAGE_COUNT} collected`);
// Stop if this is the last page
if (page === MAX_PAGE_COUNT) break;
// Move to the next page
const moved = await goNextPage();
if (!moved) {
console.log('[Scraper] No next page — collection finished');
break;
}
// Wait for the page to load
await sleep(WAIT_AFTER_LOAD);
}
console.log(`[Scraper] Collection complete! Total reviews: ${store.children.length}`);
updateLabel();
// ▶ To retrieve the collected HTML later, run in the console:
// document.getElementById('__review_store').innerHTML
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment