-
-
Save listly-io/46bbee83449eb14163e42dd1c2bf1699 to your computer and use it in GitHub Desktop.
2025-10-23
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
| // https://shopee.vn/-QU%C3%80-T%E1%BA%B6NG-385K-Kem-d%C6%B0%E1%BB%A1ng-%E1%BA%A9m-ph%E1%BB%A5c-h%E1%BB%93i-v%C3%A0-ch%C4%83m-s%C3%B3c-h%C3%A0ng-r%C3%A0o-b%E1%BA%A3o-v%E1%BB%87-da-AESTURA-ATOBARRIER365-Cream-80ml-i.1122867310.24257591496?is_from_signup=true | |
| // ========== 사용자 설정 변수 ========== | |
| const MAX_PAGE_COUNT = 10; // 수집할 페이지 수 | |
| const WAIT_TIME = 5000; // 페이지 로딩 대기 시간 (밀리초) | |
| const SCROLL_WAIT_TIME = 1000; // 스크롤 후 대기 시간 (밀리초) | |
| const REVIEW_SELECTOR = 'DIV[class="shopee-product-comment-list"] > div'; // 리뷰 목록 셀렉터 | |
| const PAGINATION_SELECTOR = 'nav.shopee-page-controller'; // 페이지네이션 영역 셀렉터 | |
| // ========== 메인 코드 ========== | |
| (async function() { | |
| // 수집 컨테이너 생성 (body 마지막 자식으로 추가) | |
| const collectionContainer = document.createElement('div'); | |
| collectionContainer.id = 'review-collection-container'; | |
| collectionContainer.style.cssText = 'position: fixed; bottom: 200px; right: 10px; background: #fff; border: 2px solid #333; padding: 10px; font-size: 14px; z-index: 9999; min-width: 200px; max-height: 150px; overflow-y: auto;'; | |
| document.body.appendChild(collectionContainer); | |
| // 상태 표시 영역 | |
| const statusDiv = document.createElement('div'); | |
| statusDiv.id = 'collection-status'; | |
| collectionContainer.appendChild(statusDiv); | |
| // 수집 데이터 저장 영역 (숨김) | |
| const dataContainer = document.createElement('div'); | |
| dataContainer.id = 'collected-data-container'; | |
| dataContainer.style.display = 'none'; | |
| collectionContainer.appendChild(dataContainer); | |
| let collectedCount = 0; | |
| let totalReviewCount = 0; | |
| // 상태 표시 업데이트 함수 | |
| function updateStatus(message) { | |
| statusDiv.innerHTML = ` | |
| <div style="font-weight: bold; margin-bottom: 5px;">리뷰 수집 중...</div> | |
| <div>수집된 페이지: ${collectedCount} / ${MAX_PAGE_COUNT}</div> | |
| <div>수집된 리뷰: ${totalReviewCount}개</div> | |
| <div style="margin-top: 5px; color: #666;">${message}</div> | |
| `; | |
| } | |
| // 리뷰 수집 함수 | |
| function collectReviews() { | |
| const reviews = document.querySelectorAll(REVIEW_SELECTOR); | |
| // 각 리뷰를 dataContainer에 직접 추가 (같은 레벨의 sibling으로) | |
| reviews.forEach(review => { | |
| dataContainer.appendChild(review.cloneNode(true)); | |
| }); | |
| collectedCount++; | |
| totalReviewCount += reviews.length; | |
| console.log(`페이지 ${collectedCount} 수집 완료 (이번 페이지: ${reviews.length}개, 총: ${totalReviewCount}개)`); | |
| return reviews.length; | |
| } | |
| // 대기 함수 | |
| function wait(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // 페이지네이션 영역으로 스크롤 | |
| async function scrollToPagination() { | |
| const pagination = document.querySelector(PAGINATION_SELECTOR); | |
| if (pagination) { | |
| pagination.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| console.log('페이지네이션 영역으로 스크롤 이동'); | |
| await wait(SCROLL_WAIT_TIME); | |
| } else { | |
| console.warn('페이지네이션 영역을 찾을 수 없습니다.'); | |
| } | |
| } | |
| // 다음 페이지 버튼 찾기 및 클릭 함수 | |
| async function clickNextPage() { | |
| // 페이지네이션 영역으로 스크롤 이동 | |
| await scrollToPagination(); | |
| // 현재 활성화된 페이지 번호 찾기 (shopee-button-solid--primary 클래스) | |
| const currentPageBtn = document.querySelector('button.shopee-button-solid.shopee-button-solid--primary'); | |
| if (!currentPageBtn) { | |
| console.error('현재 페이지 버튼을 찾을 수 없습니다.'); | |
| return false; | |
| } | |
| console.log('현재 페이지:', currentPageBtn.textContent); | |
| // 다음 페이지 번호 버튼 찾기 (shopee-button-no-outline 클래스, ... 버튼 제외) | |
| let nextPageBtn = currentPageBtn.nextElementSibling; | |
| // ... 버튼은 건너뛰기 | |
| while (nextPageBtn && nextPageBtn.classList.contains('shopee-button-no-outline--non-click')) { | |
| nextPageBtn = nextPageBtn.nextElementSibling; | |
| } | |
| if (nextPageBtn && nextPageBtn.classList.contains('shopee-button-no-outline') && !nextPageBtn.disabled) { | |
| console.log('다음 페이지로 이동:', nextPageBtn.textContent); | |
| nextPageBtn.click(); | |
| return true; | |
| } else { | |
| console.log('다음 페이지가 없습니다.'); | |
| return false; | |
| } | |
| } | |
| // 메인 수집 루프 | |
| try { | |
| updateStatus('첫 번째 페이지 수집 중...'); | |
| // 첫 번째 페이지 수집 | |
| collectReviews(); | |
| updateStatus(`완료! 다음 페이지로 이동 중...`); | |
| // 나머지 페이지들 수집 | |
| for (let i = 1; i < MAX_PAGE_COUNT; i++) { | |
| // 다음 페이지로 이동 (스크롤 + 대기 포함) | |
| if (!await clickNextPage()) { | |
| updateStatus('수집 완료 (마지막 페이지 도달)'); | |
| break; | |
| } | |
| // 페이지 로딩 대기 | |
| await wait(WAIT_TIME); | |
| // 리뷰 수집 | |
| updateStatus(`페이지 ${i + 1} 수집 중...`); | |
| collectReviews(); | |
| updateStatus(`페이지 ${i + 1} 완료!`); | |
| } | |
| // 최종 결과 | |
| statusDiv.innerHTML = ` | |
| <div style="font-weight: bold; color: green; margin-bottom: 10px;">✓ 수집 완료!</div> | |
| <div>수집된 페이지: ${collectedCount}개</div> | |
| <div>수집된 리뷰: ${totalReviewCount}개</div> | |
| <div style="margin-top: 10px; font-size: 12px; color: #666;"> | |
| 수집된 데이터는 이 div 내부<br> | |
| 'collected-data-container'에 저장됨 | |
| </div> | |
| `; | |
| console.log('=== 수집 완료 ==='); | |
| console.log(`총 ${collectedCount}개 페이지, ${totalReviewCount}개 리뷰 수집 완료`); | |
| console.log('수집된 HTML은 document.querySelector("#collected-data-container") 내부에 저장됨'); | |
| } catch (error) { | |
| console.error('수집 중 오류 발생:', error); | |
| statusDiv.innerHTML = ` | |
| <div style="color: red; font-weight: bold;">오류 발생!</div> | |
| <div style="font-size: 12px;">${error.message}</div> | |
| `; | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment