Skip to content

Instantly share code, notes, and snippets.

@listly-io
Created June 18, 2026 04:55
Show Gist options
  • Select an option

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

Select an option

Save listly-io/778081caa498828e64d8e489f492cbaa to your computer and use it in GitHub Desktop.
ohouse_review_2026-06-18
// test page: https://store.ohou.se/goods/3270412?affect_id=6&affect_type=StoreSearchResult
// ============================================================
// ▶ 사용자 설정 영역 (필요에 따라 수정하세요)
// ============================================================
const MAX_PAGE_COUNT = 10; // 수집할 페이지 수 (1 = 현재 페이지만)
const WAIT_AFTER_CLICK = 5000; // 다음 페이지 클릭 후 대기 시간 (ms)
const WAIT_BEFORE_CLICK = 1000; // 스크롤 후 클릭 전 대기 시간 (ms)
const REVIEW_ITEM_SELECTOR = '.ew8r8221'; // 개별 리뷰 항목 셀렉터 (페이지당 5개)
const PAGINATION_SELECTOR = '.e1o3bo0y0'; // 페이지네이션 영역 셀렉터 (페이지 내 2곳 존재)
const NEXT_BTN_SELECTOR = 'button:last-child'; // 페이지네이션 내 다음(▶) 버튼 셀렉터
const CONTAINER_ID = '__review_collector'; // 수집 컨테이너 ID
// ============================================================
// ▶ 로직
// ============================================================
(async () => {
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// ※ 이 사이트는 동일한 클래스의 페이지네이션이 2개(스타일링샷/리뷰) 존재하기 때문에
// 리뷰 항목을 실제로 포함하는 <section> 내부의 페이지네이션만 골라서 사용
// 리뷰 목록을 감싸는 section 반환
const getReviewSection = () => {
const item = document.querySelector(REVIEW_ITEM_SELECTOR);
if (!item) return null;
let s = item;
while (s && s.tagName !== 'SECTION') s = s.parentElement;
return s;
};
// 리뷰 전용 페이지네이션 영역 반환
const getPagination = () => {
const sec = getReviewSection();
return sec ? sec.querySelector(PAGINATION_SELECTOR) : null;
};
// 리뷰 전용 "다음" 버튼 반환
const getNextBtn = () => {
const pag = getPagination();
return pag ? pag.querySelector(NEXT_BTN_SELECTOR) : null;
};
// 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_ITEM_SELECTOR}:not(#${CONTAINER_ID} *)`
);
let added = 0;
reviews.forEach((el) => {
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 = getNextBtn();
if (!btn || btn.disabled) return false;
// 페이지네이션 영역으로 스크롤
const paginationArea = getPagination();
if (paginationArea) {
paginationArea.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
await sleep(WAIT_BEFORE_CLICK);
// 다시 한번 버튼 상태 확인 (스크롤 중 상태 변경 가능)
const btnAgain = getNextBtn();
if (!btnAgain || btnAgain.disabled) 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을 꺼내려면 콘솔에서 아래 영역 참고
// document.getElementById('__review_store').innerHTML
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment