Skip to content

Instantly share code, notes, and snippets.

@listly-io
Last active November 26, 2025 16:05
Show Gist options
  • Select an option

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

Select an option

Save listly-io/9b9781d82aedb7bade5a10b1f22e848a to your computer and use it in GitHub Desktop.
oliveyoung_review_2025-11-27
// ========== 설정값 ==========
const MAX_ITEM_COUNT = 100; // 수집 목표 개수
const SCROLL_AMOUNT = 500; // 스크롤 px
const SCROLL_DELAY = 1000; // 스크롤 후 대기 시간 (ms)
const ITEM_SELECTOR = 'oy-review-review-item';
const DATE_SELECTOR = 'span.date';
const NAME_SELECTOR = 'div.name';
const CONTENT_SELECTOR = 'div.content';
// ============================
// 수집된 아이템을 저장할 Set (중복 방지)
const collectedItems = new Map();
// Shadow DOM을 재귀적으로 탐색하여 특정 셀렉터를 찾는 함수
function findInShadowDOM(root, selector) {
// 현재 레벨에서 찾기
let elements = root.querySelectorAll(selector);
if (elements.length > 0) {
return Array.from(elements);
}
// Shadow root가 있는 모든 요소 탐색
const allElements = root.querySelectorAll('*');
let found = [];
for (let element of allElements) {
if (element.shadowRoot) {
const result = findInShadowDOM(element.shadowRoot, selector);
found = found.concat(result);
}
}
return found;
}
// 특정 요소 내부에서 셀렉터를 찾는 함수 (Shadow DOM 포함)
function findInElement(element, selector) {
// 먼저 일반 DOM에서 찾기
let result = element.querySelector(selector);
if (result) return result;
// Shadow root 탐색
if (element.shadowRoot) {
result = findInShadowDOM(element.shadowRoot, selector);
if (result.length > 0) return result[0];
}
// 자식 요소들의 shadow root 탐색
const children = element.querySelectorAll('*');
for (let child of children) {
if (child.shadowRoot) {
result = findInShadowDOM(child.shadowRoot, selector);
if (result.length > 0) return result[0];
}
}
return null;
}
// 아이템에서 데이터 추출
function extractItemData(item) {
const dateElement = findInElement(item, DATE_SELECTOR);
const nameElement = findInElement(item, NAME_SELECTOR);
const contentElement = findInElement(item, CONTENT_SELECTOR);
return {
date: dateElement ? dateElement.textContent.trim() : '',
name: nameElement ? nameElement.textContent.trim() : '',
content: contentElement ? contentElement.textContent.trim() : ''
};
}
// 고유 ID 생성 (날짜 + 이름 + 내용 일부)
function generateItemId(data) {
return `${data.date}_${data.name}_${data.content.substring(0, 50)}`;
}
// 현재 페이지의 아이템 수집
function collectCurrentItems() {
const items = findInShadowDOM(document.body, ITEM_SELECTOR);
let newCount = 0;
items.forEach(item => {
const data = extractItemData(item);
const id = generateItemId(data);
if (!collectedItems.has(id) && data.date && data.name && data.content) {
collectedItems.set(id, data);
newCount++;
}
});
return newCount;
}
// 상태 표시 div 생성
function createStatusDiv() {
const statusDiv = document.createElement('div');
statusDiv.id = 'review-collector-status';
statusDiv.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 14px;
z-index: 10000;
min-width: 200px;
`;
document.body.appendChild(statusDiv);
return statusDiv;
}
// 상태 업데이트
function updateStatus(statusDiv, message) {
statusDiv.innerHTML = `
<div><strong>리뷰 수집기</strong></div>
<div>수집된 아이템: ${collectedItems.size} / ${MAX_ITEM_COUNT}</div>
<div>${message}</div>
`;
}
// 수집된 아이템을 div에 표시
function displayCollectedItems(statusDiv) {
statusDiv.innerHTML = `
<div><strong>수집 완료!</strong></div>
<div>총 ${collectedItems.size}개 수집</div>
`;
collectedItems.forEach((data, id) => {
const itemDiv = document.createElement('div');
itemDiv.style.display = 'none';
itemDiv.className = 'collected-review-item';
const dateSpan = document.createElement('span');
dateSpan.className = 'review-date';
dateSpan.textContent = data.date;
const nameSpan = document.createElement('span');
nameSpan.className = 'review-name';
nameSpan.textContent = data.name;
const contentDiv = document.createElement('div');
contentDiv.className = 'review-content';
contentDiv.textContent = data.content;
itemDiv.appendChild(dateSpan);
itemDiv.appendChild(nameSpan);
itemDiv.appendChild(contentDiv);
statusDiv.appendChild(itemDiv);
});
}
// 메인 수집 함수
async function startCollection() {
const statusDiv = createStatusDiv();
updateStatus(statusDiv, '수집 시작...');
// 초기 수집
collectCurrentItems();
updateStatus(statusDiv, '스크롤 중...');
while (collectedItems.size < MAX_ITEM_COUNT) {
// 스크롤
window.scrollBy(0, SCROLL_AMOUNT);
updateStatus(statusDiv, '스크롤 중...');
// 대기
await new Promise(resolve => setTimeout(resolve, SCROLL_DELAY));
// 새 아이템 수집
const newCount = collectCurrentItems();
updateStatus(statusDiv, `새 아이템 ${newCount}개 발견`);
// 더 이상 스크롤할 곳이 없는지 체크
if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
console.log('페이지 끝에 도달했습니다.');
break;
}
}
// 결과 표시
displayCollectedItems(statusDiv);
console.log('수집 완료:', collectedItems);
return Array.from(collectedItems.values());
}
// 실행
startCollection();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment