-
-
Save listly-io/d78eef664d009ebc3fcc209e973dcd66 to your computer and use it in GitHub Desktop.
amazon_review_en_2026-06-04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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