[데이터 수집] 페이스북 영상 조회수, Graph API 없이 1000개 추적하기: yt-dlp 및 Headless 브라우저 한계 돌파 전략

페이스북 페이지에 업로드된 수많은 동영상 및 릴스의 일일 조회수를 Meta Graph API 사용 없이 정확하게 추적해야 하는 상황에 직면하셨나요? 특히 500개에서 1,000개에 달하는 대규모 URL을 매일 처리해야 한다면, 이는 단순히 기술적인 문제를 넘어선 복잡한 아키텍처 설계가 필요한 도전입니다. 이 글에서는 개발자들이 흔히 겪는 문제점들을 분석하고, 안정적이면서도 효율적인 인하우스 데이터 수집 시스템 구축 방안을 깊이 있게 다룹니다.

1. 페이스북 영상 조회수 데이터 수집의 도전 과제

수백에서 수천 개의 페이스북 동영상 및 릴스의 일일 조회수를 추적하는 것은 쉽지 않은 일입니다. 특히 ‘자체 페이지’의 공개 URL이라는 점과 ‘Meta Graph API 사용 불가’라는 강력한 제약사항은 개발자들에게 큰 난관으로 작용합니다.

Meta Graph API 없는 데이터 수집의 필요성

Meta Graph API는 페이스북 데이터를 공식적으로 접근하는 가장 권장되는 방법이지만, 앱 승인 절차, 토큰 관리, 그리고 접근 권한 제한 등의 이유로 사용이 불가능하거나 현실적인 대안이 필요한 경우가 많습니다. 본 사례처럼 API를 사용할 수 없는 상황에서는 웹 스크래핑이 유일한 대안으로 고려될 수밖에 없습니다.

기존 시도의 한계점

  • yt-dlp / 메타데이터 스크래핑:
    • /share/p/ 형태의 게시물 링크에서 로그인 오류가 발생하여 데이터를 가져오지 못합니다.
    • 릴스(Reels)의 경우, 캐시되거나 오래된 크롤러 메트릭(예: 라이브 페이지에서 652회 재생이지만 yt-dlp는 271회로 추출)을 반환하여 정확성이 떨어집니다. 이는 동적인 JavaScript 로딩 및 사용자 인터랙션에 의해 조회수가 업데이트되는 페이스북 페이지의 특성 때문입니다.
  • Headless 브라우저 (Playwright/Selenium):
    • 대규모 스크래핑 시 봇 지문 인식, 동적 CSS 클래스 변경, 빠른 쿠키 무효화 등으로 인해 매우 취약하고 불안정합니다. 페이스북과 같은 대규모 플랫폼은 정교한 봇 감지 시스템을 운영하므로, 단순한 headless 브라우저 사용은 쉽게 차단될 수 있습니다.

2. 기존 스크래핑 방식의 한계 및 근본 원인

위에서 언급된 문제들은 페이스북의 강력한 안티-스크래핑 메커니즘과 현대 웹 페이지의 동적인 특성에서 비롯됩니다.

yt-dlp의 한계: 정적 콘텐츠 위주의 처리

yt-dlp는 주로 YouTube와 같은 비디오 플랫폼의 메타데이터 및 스트림 URL을 추출하는 데 최적화되어 있습니다. 하지만 페이스북의 동영상 조회수는 로그인 상태, 사용자 세션, 그리고 JavaScript 실행을 통해 실시간으로 업데이트되는 경우가 많습니다. yt-dlp는 이러한 동적인 상호작용을 처리하지 못하거나, 제한된 정적 HTML 분석에 의존하기 때문에 부정확하거나 오래된 데이터를 반환하게 됩니다. 특히 공유(share) 링크는 특정 세션이 필요할 수 있어 로그인 문제에 직면합니다.

Headless 브라우저의 한계: 정교한 봇 감지 우회 실패

Playwright나 Selenium과 같은 headless 브라우저는 웹 페이지를 실제 브라우저처럼 렌더링하고 JavaScript를 실행할 수 있어 동적인 콘텐츠 스크래핑에 강력합니다. 그러나 페이스북과 같은 대규모 서비스는 다음과 같은 정교한 봇 감지 기술을 활용하여 자동화된 접근을 차단합니다.

  • 봇 지문 인식 (Bot Fingerprinting): 브라우저 환경, HTTP 헤더, 네트워크 요청 패턴 등을 분석하여 일반 사용자 트래픽과 봇 트래픽을 구분합니다.
  • 동적 CSS 클래스 변경: HTML 요소의 클래스명이나 ID를 주기적으로 변경하여 스크래핑 코드가 특정 요소에 의존하기 어렵게 만듭니다.
  • 빠른 쿠키/세션 무효화: 비정상적인 접근 패턴이 감지되면 사용자 세션이나 쿠키를 빠르게 무효화하여 계속적인 접근을 차단합니다.
  • CAPTCHA 및 Challenge: 의심스러운 트래픽에 대해 CAPTCHA나 다른 보안 챌린지를 제시하여 수동 확인을 요구합니다.

결론적으로, 단순히 headless 브라우저를 사용하는 것만으로는 페이스북의 고도화된 방어 체계를 뚫고 대규모로 안정적인 데이터를 수집하기 어렵습니다.

3. 안정적인 페이스북 데이터 수집을 위한 전략적 접근

Meta Graph API 없이 페이스북에서 대규모의 정확한 동영상 조회수를 얻는 것은 극도로 어려운 일입니다. 하지만 다음 전략들을 조합하여 성공 확률을 높일 수 있습니다.

1. 고도화된 Headless 브라우저 + 스텔스 기법 활용

완전한 브라우저 렌더링 없이 조회수를 얻는 것은 거의 불가능하므로, headless 브라우저의 한계를 극복하는 데 집중해야 합니다. Playwright나 Puppeteer를 기반으로 하되, 다음 기법들을 추가합니다.

  • 프록시 로테이션 (Proxy Rotation): 주거용 프록시(Residential Proxy)나 데이터센터 프록시를 사용하여 IP 주소를 지속적으로 변경하고, 요청을 분산시켜 IP 차단을 회피합니다.
  • 사용자 에이전트 (User-Agent) 로테이션: 다양한 실제 브라우저의 User-Agent 문자열을 사용하여 봇으로 감지되는 것을 방지합니다.
  • 브라우저 지문 위장 (Browser Fingerprinting Evasion): undetected-chromedriver (Selenium용)나 puppeteer-extra-plugin-stealth (Puppeteer/Playwright용)와 같은 라이브러리를 사용하여 봇 지문 인식을 어렵게 만듭니다. 실제 사용자처럼 보이도록 브라우저 속성을 조작합니다.
  • 리소스 최적화: 이미지, 폰트, 불필요한 스크립트 등 비핵심 리소스 로드를 차단하여 페이지 로드 시간을 단축하고, 브라우저 메모리 사용량을 줄입니다. (Playwright의 page.route() 활용)
  • 사람과 유사한 행동 에뮬레이션: 스크롤, 마우스 이동, 클릭 지연 시간 등을 무작위로 추가하여 봇이 아닌 사용자처럼 보이게 합니다.

2. XHR/GraphQL 요청 스니핑 및 분석

페이지 로드 후 조회수와 같은 동적 데이터는 종종 백그라운드에서 XHR (XMLHttpRequest) 또는 GraphQL 요청을 통해 비동기적으로 로드됩니다. headless 브라우저를 사용하여 페이지를 로드한 다음, 개발자 도구의 네트워크 탭에서 이러한 요청들을 가로채(intercept) 분석함으로써, 순수 JSON 형태의 데이터를 직접 추출할 수 있습니다. 이는 “전체 브라우저 페이지 렌더링 없이” 데이터를 얻는 한 가지 방법이 될 수 있습니다. 페이스북의 복잡한 구조로 인해 이 방식도 유지보수가 어려울 수 있습니다.


const { chromium } = require('playwright');

async function getFacebookVideoViews(url) {
    let browser;
    try {
        browser = await chromium.launch({ headless: true });
        const context = await browser.newContext({
            userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36'
        });
        const page = await context.newPage();

        // 특정 리소스 차단 (옵션)
        await page.route(/\\.(png|jpg|jpeg|gif|css|woff|woff2)$/, route => route.abort());
        await page.route('**/*', async (route) => {
            const requestUrl = route.request().url();
            // Facebook GraphQL 또는 API 요청 URL 패턴을 찾아낸 후 필터링
            // 실제 페이스북 요청을 분석하여 정확한 URL 패턴을 찾아야 함
            if (requestUrl.includes('graphql') && requestUrl.includes('view_count')) {
                console.log('Intercepting GraphQL request:', requestUrl);
                // 요청을 진행하고 응답을 기다린 후 처리
                const response = await route.continue();
                if (response) {
                    const json = await response.json();
                    // 여기에서 json 데이터 내에서 조회수를 파싱
                    console.log('GraphQL Response data (simplified):', json);
                    // 실제 파싱 로직 추가 (매우 복잡할 수 있음)
                }
            } else {
                route.continue();
            }
        });

        await page.goto(url, { waitUntil: 'domcontentloaded' }); // DOM 로드 완료까지 대기

        // JavaScript 로딩 후 조회수 요소 직접 찾기 (가장 일반적인 접근)
        // 이 셀렉터는 페이스북 UI 변경에 따라 달라질 수 있으므로 지속적인 모니터링 필요
        const viewCountElement = await page.waitForSelector('span[aria-label*="views"], div[data-testid*="view_count"]', { timeout: 10000 }).catch(() => null);
        if (viewCountElement) {
            const textContent = await viewCountElement.textContent();
            console.log(`Live View Count for ${url}: ${textContent}`);
            return textContent;
        } else {
            console.log('View count element not found after waiting.');
            // GraphQL 요청 스니핑으로 얻은 데이터 처리 로직을 여기에 결합
        }

    } catch (error) {
        console.error(`Error scraping ${url}:`, error.message);
    } finally {
        if (browser) {
            await browser.close();
        }
    }
    return null;
}

(async () => {
    const videoUrl = 'https://www.facebook.com/yourpage/videos/your_video_id/'; // 실제 페이스북 비디오/릴스 URL
    const views = await getFacebookVideoViews(videoUrl);
    console.log(`Extracted Views: ${views}`);
})();

주의: 위 코드는 개념적인 예시이며, 페이스북의 동적인 웹 페이지 구조와 안티-봇 시스템으로 인해 실제 동작을 보장하지 않습니다. 특히 GraphQL 요청의 구조는 복잡하고 지속적으로 변경될 수 있어 심층적인 분석과 지속적인 유지보수가 필수적입니다.

4. 확장 가능하고 안정적인 데이터 수집 아키텍처 구축 가이드

1,000개에 달하는 URL을 매일 안정적으로 처리하기 위해서는 단순한 스크립트를 넘어선 견고한 아키텍처 설계가 필요합니다.

1. 분산 처리 및 워커 풀 (Distributed Processing & Worker Pool)

  • 큐 시스템 도입: RabbitMQ, Kafka 또는 Redis 기반의 메시지 큐를 사용하여 처리할 URL 목록을 관리합니다. 각 URL은 메시지로 발행되고, 워커(Worker)들이 이를 소비하여 처리합니다.
  • 다중 워커 인스턴스: 여러 대의 서버나 컨테이너에서 독립적인 스크래핑 워커를 실행합니다. 각 워커는 고유한 IP 주소(프록시 사용 시)와 사용자 에이전트를 가질 수 있도록 설정합니다.
  • 로드 밸런싱: 워커 간에 작업을 고르게 분배하여 특정 워커가 과부하되거나 차단될 위험을 줄입니다.

2. 지능형 프록시 관리 시스템

  • 다양한 프록시 공급원: 여러 주거용 프록시 공급업체(예: Bright Data, Oxylabs 등)를 혼합하여 사용함으로써 단일 공급원의 차단 위험을 분산합니다.
  • 프록시 헬스 체크 및 로테이션: 프록시의 활성 상태를 주기적으로 확인하고, 차단되거나 성능이 저하된 프록시는 목록에서 제외하며 새로운 프록시로 자동 교체하는 시스템을 구축합니다.
  • 세션 기반 프록시: 특정 세션 동안 동일한 IP를 유지하는 스티키 프록시(sticky proxy)를 사용하여 로그인 상태를 유지하는 데 유리하게 활용할 수 있습니다.

3. 강력한 오류 처리 및 재시도 메커니즘

  • 지수 백오프 (Exponential Backoff): 요청 실패 시 즉시 재시도하는 대신, 점진적으로 대기 시간을 늘려가며 재시도하여 서버에 대한 부담을 줄이고 차단 위험을 낮춥니다.
  • 자동 차단 감지 및 IP 변경: 특정 IP가 반복적으로 차단되거나 CAPTCHA를 자주 받는 경우, 해당 IP를 자동으로 프록시 풀에서 제외하고 다른 IP로 교체하는 로직을 구현합니다.
  • 실패한 작업 재큐잉: 일시적인 오류로 실패한 작업은 자동으로 큐에 다시 넣어 나중에 재처리하도록 합니다.

4. 모니터링 및 알림 시스템

  • 로그 관리: 모든 스크래핑 작업의 성공/실패 여부, 추출된 데이터, 발생한 오류 등을 상세히 로깅합니다.
  • 성능 모니터링: 각 워커의 처리 속도, 프록시 사용량, 에러율 등을 실시간으로 모니터링하여 병목 현상이나 문제를 조기에 감지합니다.
  • 알림 설정: 특정 임계값(예: 에러율 증가, 특정 URL에 대한 반복적인 실패)을 초과할 경우, 슬랙(Slack)이나 이메일 등으로 개발자에게 자동으로 알림을 보냅니다.

5. 지속적인 유지보수 및 적응

  • 선택자(Selector) 및 패턴 업데이트: 페이스북 웹 페이지의 HTML 구조나 API 응답 패턴은 언제든지 변경될 수 있습니다. 정기적으로 스크래핑 코드를 테스트하고, 변경 사항이 발생하면 즉시 업데이트해야 합니다.
  • AI/ML 기반 CAPTCHA 해결 (선택 사항): 수동 CAPTCHA 해결이 불가능한 대규모 시나리오에서는 2Captcha, Anti-Captcha 등 CAPTCHA 해결 서비스를 통합하는 것을 고려할 수 있습니다.

결론적으로, Meta Graph API 없이 페이스북 동영상 조회수를 대규모로 정확하게 스크래핑하는 것은 상당한 기술적 투자와 지속적인 노력이 필요한 매우 복잡한 과제입니다. 단일 스크립트로는 불가능하며, 분산되고 지능적인 아키텍처를 구축하고 끊임없이 변화하는 웹 환경에 적응하는 것이 성공의 열쇠입니다.

이러한 접근 방식은 비용과 시간이 많이 소요될 수 있으므로, 최종적으로는 비즈니스 가치와 투자 비용을 면밀히 비교하여 가장 적합한 전략을 선택하는 것이 중요합니다.

댓글 남기기