Skip to content

Instantly share code, notes, and snippets.

@listly-io
Last active June 11, 2026 02:03
Show Gist options
  • Select an option

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

Select an option

Save listly-io/9c7ca29db8959ad4a3e7952f79e3caa0 to your computer and use it in GitHub Desktop.
shein_review_2026-06-10
// test page: https://kr.shein.com/Short-Sleeve-T-Shirt-Versatile-Short-Sleeve-Tee-Women-s-Short-Sleeve-Top-Slim-Fit-Solid-Color-Undershirt-Casual-Black-Summer-review-142262475.html?mall_code=1&goods_spu=z250419597558&local_site_query_flag=&sku=sz25041959755899894&store_code=9108550356&isLowestPriceProductOfBuyBox=NaN&cat_id=1738&sku_code=&isAppointMall=&isTrusted=true&_vts=1781142343247
// ============================================================
// ▶ User configuration (edit as needed)
// ============================================================
// Works from EITHER the product page (...-p-142262475.html) OR the
// dedicated reviews page (...-review-142262475.html).
// On the product page it auto-clicks "View more reviews" (which navigates),
// then resumes automatically on the reviews page via sessionStorage.
const MAX_PAGE_COUNT = 10; // Number of scroll-load batches (1 = current view only)
const WAIT_AFTER_LOAD = 5000; // Wait after page load / new batch (ms)
const WAIT_BEFORE_NAV = 1000; // Wait after scrolling, before checking (ms)
const REVIEW_LIST_SELECTOR = '.common-reviews-new__list-item'; // Individual review item selector
const VIEW_ALL_SELECTOR = '.common-reviews__pageViewAll'; // "View more reviews" button (product page)
const REVIEW_ID_ATTR = 'data-comment-id'; // Attribute used to de-duplicate reviews (falls back to text)
const CONTAINER_ID = '__review_collector'; // Collector container ID
// ============================================================
// ▶ Internal logic (no edits needed)
// ============================================================
(async () => {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Build a stable de-dup key for a review element.
// On the product page the id lives on the list item (REVIEW_ID_ATTR);
// on the reviews page the item has no id, so we also probe descendants
// for [data-comment-id]/[data-review-id], and finally fall back to text.
const keyFor = (el) => {
let id = el.getAttribute(REVIEW_ID_ATTR);
if (!id) {
const inner = el.querySelector('[data-comment-id], [data-review-id]');
if (inner) id = inner.getAttribute('data-comment-id') || inner.getAttribute('data-review-id');
}
return ((id || el.textContent || '')).trim().slice(0, 120);
};
// 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 = () => {
label.textContent = `Reviews collected: ${store.children.length}`;
};
// Restore any previously saved progress (after navigating from the product page)
const saved = sessionStorage.getItem('__review_store_html');
if (saved && store.children.length === 0) {
store.innerHTML = saved;
updateLabel();
}
// De-dup set, rebuilt from whatever is already in the store
const seen = new Set();
[...store.children].forEach((el) => {
const key = keyFor(el);
if (key) seen.add(key);
});
// 2. Collect reviews currently in the DOM (de-duplicated)
const collectCurrentPage = () => {
const reviews = document.querySelectorAll(
`${REVIEW_LIST_SELECTOR}:not(#${CONTAINER_ID} *)`
);
let added = 0;
reviews.forEach((el) => {
const key = keyFor(el);
if (!key || seen.has(key)) return;
seen.add(key);
store.appendChild(el.cloneNode(true));
added++;
});
console.log(`[Scraper] Collected ${added} new reviews (total: ${store.children.length})`);
updateLabel();
};
// 3a. If we're on the PRODUCT page, jump to the reviews page first.
// Clicking "View more reviews" triggers a full navigation, so we save
// progress (like the Amazon script did) and let the script re-run on load.
const onReviewsPage = /-review-\d+\.html/.test(location.pathname);
if (!onReviewsPage) {
collectCurrentPage(); // grab the 3 preview reviews shown on the product page
const viewAll = document.querySelector(VIEW_ALL_SELECTOR);
if (viewAll) {
console.log('[Scraper] On product page — opening full reviews page…');
sessionStorage.setItem('__review_store_html', store.innerHTML);
viewAll.click();
return; // page navigates; re-run this script on the reviews page to continue
}
console.log('[Scraper] No "View more reviews" button found — staying on this page.');
}
// 3b. Reviews page: load the next batch via infinite scroll.
// Returns true if more reviews appeared, false at end of list.
const goNextPage = async () => {
const before = document.querySelectorAll(REVIEW_LIST_SELECTOR).length;
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
await sleep(WAIT_BEFORE_NAV);
window.scrollTo(0, document.documentElement.scrollHeight);
await sleep(WAIT_AFTER_LOAD);
return document.querySelectorAll(REVIEW_LIST_SELECTOR).length > before;
};
// 4. Main loop
console.log(`[Scraper] Starting collection — up to ${MAX_PAGE_COUNT} batches`);
for (let page = 1; page <= MAX_PAGE_COUNT; page++) {
collectCurrentPage();
console.log(`[Scraper] Batch ${page}/${MAX_PAGE_COUNT} collected`);
if (page === MAX_PAGE_COUNT) break;
const moved = await goNextPage();
if (!moved) {
console.log('[Scraper] No more reviews loaded — collection finished');
collectCurrentPage();
break;
}
}
console.log(`[Scraper] Collection complete! Total reviews: ${store.children.length}`);
updateLabel();
sessionStorage.removeItem('__review_store_html');
// ▶ 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