-
-
Save listly-io/b7bee9c4654614239841ddcdbfc80ce9 to your computer and use it in GitHub Desktop.
amoremall_review_2026-06-15
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.amoremall.com/kr/ko/product/detail?onlineProdSn=50165&onlineProdCode=111170101128 | |
| // | |
| // [사용법] | |
| // 1. 상품 리뷰 페이지에서 이 스크립트를 콘솔에 붙여넣기 → 자동 수집 시작 | |
| // 2. 수집 완료 후, 화면 우측 하단 패널에서 "수집된 리뷰: N" 박스를 Listly Parts로 선택하여 추출 | |
| // | |
| // ============================================================ | |
| // ▶ 사용자 설정 영역 (필요에 따라 수정해주세요) | |
| // ============================================================ | |
| const MAX_PAGE_COUNT = 10; // 수집할 페이지 수 (1 = 현재 페이지 / "더 보기" 클릭 횟수 + 1) | |
| const WAIT_AFTER_CLICK = 5000; // 다음 페이지("더 보기") 클릭 후 대기 시간 (ms) | |
| const WAIT_BEFORE_CLICK = 1000; // 스크롤 후 클릭 전 대기 시간 (ms) | |
| const REVIEW_TAB_SELECTOR = 'button.tab'; // 리뷰 탭 버튼 셀렉터 (텍스트 "리뷰" 로 식별) | |
| const REVIEW_LIST_SELECTOR = '.prdReview .reviewCard'; // 개별 리뷰 항목 셀렉터 | |
| const PAGINATION_SELECTOR = '.prdReview'; // 페이지네이션(더 보기 버튼이 속한) 영역 셀렉터 | |
| const NEXT_BTN_SELECTOR = `${PAGINATION_SELECTOR} > button.btnOutlined`; // 다음("더 보기") 버튼 셀렉터 | |
| const CONTAINER_ID = '__review_collector'; // 수집 컨테이너 ID | |
| // ======================== 데이터 추출 ==================================== | |
| (async () => { | |
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| // 0. 리뷰 탭 활성화 | |
| // 탭이 비활성 상태면 .reviewCard | |
| const ensureReviewTab = async () => { | |
| const tabs = Array.from(document.querySelectorAll(REVIEW_TAB_SELECTOR)); | |
| const reviewTab = tabs.find((t) => /리뷰/.test(t.innerText)); | |
| if (reviewTab && !reviewTab.classList.contains('on')) { | |
| reviewTab.click(); | |
| } | |
| // 리뷰 카드가 실제로 렌더링될 때까지 대기 (최대 10초) | |
| for (let i = 0; i < 20; i++) { | |
| if (document.querySelector(REVIEW_LIST_SELECTOR)) break; | |
| await sleep(500); | |
| } | |
| }; | |
| await ensureReviewTab(); | |
| // 1. 수집 컨테이너 생성 (이미 있으면 재사용) | |
| 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); | |
| } | |
| // 수집 저장 (숨김 영역): 컨테이너 내부에 display:none 인 래퍼 | |
| let store = container.querySelector('#__review_store'); | |
| if (!store) { | |
| store = document.createElement('div'); | |
| store.id = '__review_store'; | |
| store.style.display = 'none'; | |
| container.appendChild(store); | |
| } | |
| // 카운트 표시 라벨 | |
| 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 = `수집된 리뷰: ${count}개`; | |
| }; | |
| // 2. 현재 페이지 리뷰 수집 함수 | |
| // (아모레몰은 "더 보기" 클릭 시 기존 리스트에 누적되므로, 이미 수집한 항목은 건너뜀) | |
| const collectCurrentPage = () => { | |
| const reviews = document.querySelectorAll( | |
| `${REVIEW_LIST_SELECTOR}:not(#${CONTAINER_ID} *)` | |
| ); | |
| let added = 0; | |
| reviews.forEach((el) => { | |
| if (el.dataset.__collected) return; // 이미 수집한 리뷰는 건너뜀 | |
| el.dataset.__collected = '1'; | |
| const clone = el.cloneNode(true); | |
| store.appendChild(clone); | |
| added++; | |
| }); | |
| console.log(`[Scraper] ${added}개 리뷰 수집 (누적 ${store.children.length}개)`); | |
| updateLabel(); | |
| }; | |
| // 3. 다음 페이지("더 보기") 버튼 클릭 함수 | |
| // 반환: true = 클릭 성공, false = 버튼 없음/비활성 | |
| const goNextPage = async () => { | |
| const btn = document.querySelector(NEXT_BTN_SELECTOR); | |
| if (!btn || btn.disabled || btn.offsetParent === null) return false; | |
| // 페이지네이션("더 보기" 버튼) 영역으로 스크롤 | |
| const paginationArea = document.querySelector(PAGINATION_SELECTOR); | |
| if (paginationArea) { | |
| btn.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| } | |
| await sleep(WAIT_BEFORE_CLICK); | |
| // 다시 한번 버튼 상태 확인 (스크롤 중 상태 변경 가능) | |
| const btnAgain = document.querySelector(NEXT_BTN_SELECTOR); | |
| if (!btnAgain || btnAgain.disabled || btnAgain.offsetParent === null) return false; | |
| btnAgain.click(); | |
| return true; | |
| }; | |
| // 4. 메인 루프 | |
| console.log(`[Scraper] 수집 시작 — 최대 ${MAX_PAGE_COUNT}페이지`); | |
| for (let page = 1; page <= MAX_PAGE_COUNT; page++) { | |
| // 현재 페이지 수집 | |
| collectCurrentPage(); | |
| console.log(`[Scraper] ${page}/${MAX_PAGE_COUNT} 페이지 수집 완료`); | |
| // 마지막 페이지면 종료 | |
| if (page === MAX_PAGE_COUNT) break; | |
| // 다음 페이지로 이동 | |
| const moved = await goNextPage(); | |
| if (!moved) { | |
| console.log('[Scraper] 다음 페이지 없음 — 수집 종료'); | |
| break; | |
| } | |
| // 페이지 로딩 대기 | |
| await sleep(WAIT_AFTER_CLICK); | |
| } | |
| console.log(`[Scraper] 수집 완료! 총 ${store.children.length}개 리뷰`); | |
| updateLabel(); | |
| // ▶ 나중에 수집된 HTML을 꺼내려면 콘솔에서 아래 ID 확인 | |
| // document.getElementById('__review_store').innerHTML | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment