이 레포가 무엇을 하는 물건인가.
MediaCrawler의 핵심은 JS 역공학(reverse engineering)을 하지 않는다는 선택이다. 플랫폼이 아무리 복잡한 서명 알고리즘을 숨겨도, 로그인된 브라우저가 그 서명을 대신 만들어 준다. Playwright로 실제 Chrome을 제어해 로그인하고, 그 세션 쿠키·토큰을 저장해두면 이후 API 요청에서 서명이 자동으로 붙는다.
이 원리 하나로 소홍서·더우인·콰이서우·비리비리·웨이보·티에바·즈후 7개 플랫폼 크롤러를 동일한 Python 추상 클래스 위에 얹었다.
트렌딩 이유 · JS 역공학 대신 브라우저를 통째로 빌리는 영리함.
중국 SNS 플랫폼들은 API 요청에 복잡한 암호화 서명을 붙인다. 소홍서의 X-S 헤더, 더우인의 _signature 파라미터 등이 대표적이다. 이를 분석(역공학)하려면 JavaScript 난독화 해제, 알고리즘 재구현이 필요하고, 플랫폼이 업데이트할 때마다 다시 해야 한다. MediaCrawler는 이 문제를 우회한다 — 브라우저가 서명을 만들어 주니까 코드가 만들 필요가 없다.
서명 알고리즘을 직접 재구현하면, 플랫폼이 알고리즘을 바꾸는 순간 크롤러가 무용지물이 된다. 유지보수 비용이 매우 높고, 법적으로도 기술적 보호조치 우회에 해당할 수 있어 더 위험하다.
또한 API 서명 해독 자체가 매우 어렵다 — 소홍서 하나만 해도 보안팀이 수시로 업데이트한다.
Playwright로 실제 Chrome을 열고, 사용자가 한 번 QR코드로 로그인하면 세션을 저장한다. 이후 크롤러가 실행될 때 이 세션을 그대로 재사용한다. 브라우저 내부에서 JavaScript 표현식으로 서명값을 뽑아 쓰면, 플랫폼 업데이트와 무관하게 서명이 자동으로 생성된다.
결과적으로 코드 유지보수 부담이 크게 줄고, 기술 진입장벽도 낮아진다. 이 점이 ★25k를 넘게 받은 핵심 이유다.
또 다른 주목 이유는 통일된 아키텍처다. 7개 플랫폼이 모두 AbstractCrawler, AbstractLogin, AbstractStore라는 동일한 ABC(Abstract Base Class) 인터페이스를 구현한다. 새 플랫폼을 추가할 때 인터페이스만 보고 작성하면 된다. 저장 방식도 CSV·JSON·JSONL·MySQL·SQLite·MongoDB·Excel 7가지를 팩토리 패턴으로 지원한다.
| 비교 항목 | JS 역공학 크롤러 | MediaCrawler (Playwright 방식) |
|---|---|---|
| 기술 난이도 | 매우 높음 (암호화 분석) | 중간 (Python + 브라우저 자동화) |
| 유지보수 | 플랫폼 업데이트마다 재작업 | 브라우저가 서명을 처리하므로 안정적 |
| 탐지 위험 | 비정상 요청 패턴 감지 쉬움 | 실제 브라우저 핑거프린트 사용 |
| 속도 | 빠름 (HTTP 직접 요청) | 느림 (브라우저 기동 오버헤드) |
| 법적 위험 | 기술적 보호조치 우회 가능성 | 동일하게 이용약관 위반 가능성 있음 |
백엔드·브라우저 자동화·저장소·인프라 각 레이어 상세.
| 구성 요소 | 버전/설명 | 역할 |
|---|---|---|
| Python | 3.11+ | 메인 런타임. asyncio 기반 비동기 처리 |
| uv | 최신 | 초고속 Python 패키지 관리자 (pip 대체). pyproject.toml 기반 |
| Node.js | LTS | playwright install 시 브라우저 바이너리 관리에 필요 |
| 구성 요소 | 버전 | 역할 |
|---|---|---|
| Playwright | 1.45.0 | 브라우저 제어 핵심. BrowserContext, Page, CDP 연결 모두 담당 |
| stealth.min.js | 내장 (libs/) | 크롤러 감지 방지용 JS 삽입 스크립트. navigator.webdriver 숨김 |
| CDP (Chrome DevTools Protocol) | 내장 | 로컬 설치된 Chrome/Edge를 직접 제어. 더 강력한 반탐지 능력 |
| 구성 요소 | 버전 | 역할 |
|---|---|---|
| httpx | 0.28.1 | 비동기 HTTP 클라이언트. 각 플랫폼 API 직접 호출에 사용 |
| requests | 2.32.3 | 동기 HTTP. 일부 유틸리티 용도 |
| tenacity | 8.2.2 | 재시도(retry) 데코레이터. 네트워크 실패 시 자동 재시도 |
| 옵션 | 라이브러리 | 특징 |
|---|---|---|
| CSV | 내장 csv | 가장 간단. 엑셀로 바로 열 수 있음 |
| JSON / JSONL | 내장 json + aiofiles | JSONL은 줄 단위 JSON, 대용량에 적합 |
| MySQL / PostgreSQL | aiomysql 0.2.0 / asyncpg | 비동기 RDB. SQLAlchemy + Alembic 마이그레이션 |
| SQLite | aiosqlite 0.21.0 | 로컬 파일 DB. 별도 서버 불필요 |
| MongoDB | motor 3.3.0 | 비동기 NoSQL. 비정형 데이터에 유리 |
| Excel | openpyxl 3.1.2 | .xlsx 직접 생성. 비동기 flush 방식 |
| 라이브러리 | 역할 |
|---|---|
| pandas 2.2.3 | 데이터 조작 및 분석 |
| jieba 0.42.1 | 중국어 형태소 분석 (댓글 분석 시) |
| wordcloud 1.9.3 | 댓글 키워드 워드클라우드 이미지 생성 |
| matplotlib 3.9.0 | 차트·시각화 |
| Pillow 12.1.0 | 이미지 처리 |
| opencv-python | 이미지 처리 고급 기능 |
| parsel 1.9.1 | CSS/XPath 기반 HTML 파싱 (Scrapy의 파싱 라이브러리) |
| 구성 요소 | 설명 |
|---|---|
| ProxyIpPool | IP 프록시 풀 관리 클래스. 유효성 검증 포함 |
| 쾌다이리 (kuaidaili) | 유료 중국 IP 프록시 서비스 어댑터 내장 |
| 완두 HTTP (wandouhttp) | 또 다른 유료 프록시 서비스 어댑터 |
| Static Proxy | 사용자 지정 고정 프록시 URL 지원 |
| 플랫폼 | 코드명 | 주요 특징 |
|---|---|---|
| 소홍서 (Xiaohongshu) | xhs | playwright_sign.py로 JS 표현식 서명 추출. rednote.com 해외판 지원 |
| 더우인 (Douyin) | dy | 틱톡 중국판. 강력한 반봇 시스템 |
| 콰이서우 (Kuaishou) | ks | 短视频 2위 플랫폼 |
| 비리비리 (Bilibili) | bili | 동영상·댓글·크리에이터 홈 지원 |
| 웨이보 (Weibo) | wb | 중국판 트위터. 이미지·동영상 저장 지원 |
| 바이두 티에바 (Tieba) | tieba | 바이두 포럼 커뮤니티 |
| 즈후 (Zhihu) | zhihu | 중국판 Quora. 질문·답변 수집 |
브라우저 → CDP → API 가로채기 → 파서 → 저장 흐름 + 핵심 설계 패턴.
모든 플랫폼 크롤러는 AbstractCrawler를 상속받아 start(), search(), launch_browser()를 반드시 구현해야 한다. CrawlerFactory가 플랫폼 문자열(예: "xhs")을 받아 해당 클래스 인스턴스를 반환한다. 저장소도 XhsStoreFactory가 동일한 패턴으로 7가지 백엔드 중 하나를 선택한다.
# CrawlerFactory 구조 (main.py)
class CrawlerFactory:
CRAWLERS: dict[str, Type[AbstractCrawler]] = {
"xhs": XiaoHongShuCrawler,
"dy": DouYinCrawler,
"ks": KuaishouCrawler,
"bili": BilibiliCrawler,
"wb": WeiboCrawler,
"tieba": TieBaCrawler,
"zhihu": ZhihuCrawler,
}
@staticmethod
def create_crawler(platform: str) -> AbstractCrawler:
crawler_class = CrawlerFactory.CRAWLERS.get(platform)
return crawler_class()
플랫폼의 JS 서명 함수는 역공학하지 않는다. 대신 Playwright의 page.evaluate()로 브라우저 내 JS 환경에서 직접 서명 함수를 호출한다. 소홍서를 예로 들면 브라우저 안에서 실행되는 window._webmsxyw 같은 내부 함수를 그대로 불러 서명값을 뽑아낸다.
asyncio의 Semaphore로 동시 요청 수를 MAX_CONCURRENCY_NUM으로 제한한다. 크롤러가 플랫폼 서버에 과부하를 주지 않도록 CRAWLER_MAX_SLEEP_SEC 기반 랜덤 슬립도 삽입된다. tenacity의 @retry 데코레이터로 네트워크 오류를 투명하게 재시도한다.
각 폴더가 어떤 역할을 맡는가.
base/base_crawler.py는 "식당 가맹 계약서" — 모든 지점(플랫폼)이 반드시 제공해야 할 메뉴(메서드)를 규정한다.
media_platform/xhs/는 "소홍서 지점" — 실제 재료(API 엔드포인트, 서명 방식)를 써서 계약서의 메뉴를 만드는 방법을 구현한다.
store/는 "음식 포장 방식" — 같은 음식을 CSV 봉투·JSON 상자·DB 냉장고 중 어디에 넣을지 고른다.
브라우저 자동화 · 비동기 Python · 크롤링 윤리에서 배울 것.
이 레포는 Playwright의 고급 기능을 실전으로 배울 수 있는 좋은 예시다. 단순한 클릭/입력을 넘어 BrowserContext 저장과 복원, page.evaluate()로 JS 함수 직접 호출, CDP 프로토콜 연결, init_script로 감지 방지 JS 삽입까지 다룬다.
base_crawler.py의 launch_browser_with_cdp()와 tools/cdp_browser.py의 CDPBrowserManager를 함께 보면 로컬 Chrome 프로세스를 찾아 실행하고 CDP 포트로 연결하는 전 과정을 이해할 수 있다.
이 레포 전체가 async/await로 작성되어 있다. 배울 수 있는 패턴:
| 패턴 | 위치 | 설명 |
|---|---|---|
| asyncio.Semaphore | 각 플랫폼 core.py | 동시 요청 수 제한 |
| asyncio.gather() | batch 요청 처리 | 여러 코루틴 동시 실행 |
| aiofiles | store/ 저장 | 파일 I/O 비동기화 |
| aiomysql / aiosqlite | database/ | DB 쿼리 비동기화 |
| tenacity @retry | API 클라이언트 | 실패 시 자동 재시도 |
base/base_crawler.py의 ABC 설계를 보면 인터페이스 분리, 의존성 역전 원칙(DIP)을 실제로 어떻게 적용하는지 볼 수 있다. proxy/proxy_mixin.py의 Mixin 패턴도 Python 다중 상속을 활용한 기능 합성의 좋은 예다.
이 레포의 LICENSE는 "NON-COMMERCIAL LEARNING LICENSE" — 상업적 이용 명시적 금지다. 코드 자체를 학습하는 것과 실제 플랫폼에서 데이터를 수집하는 것은 별개의 법적 판단을 요한다.
실제 실행 전 반드시 확인할 사항: ① 대상 플랫폼의 robots.txt와 서비스 이용약관, ② 개인정보보호법(국내 PIPA, 유럽 GDPR), ③ 컴퓨터 자원 무단 접근 관련 형법 조항. 중국에서는 크롤러 관련 기소 사례가 다수 존재한다 (레포 내 링크 참고).
실제 플랫폼 크롤링 대신 안전하게 배우는 방법: 공개 API가 있는 서비스(Hacker News API, Reddit Public API, Wikipedia API)를 대상으로 같은 추상 클래스 구조를 직접 구현해보는 것이 좋다. 서명 로직이 없으므로 아키텍처 학습에 집중할 수 있다.
요구사항 · uv / pip 설치 · playwright install 명령.
| 항목 | 최소 사양 | 권장 / 비고 |
|---|---|---|
| OS | macOS / Linux / Windows | Linux에서 CDP 모드 사용 시 Xvfb 필요할 수 있음 |
| Python | 3.11 이상 | .python-version 파일에 명시 |
| Node.js | LTS (18+) | Playwright 브라우저 설치 시 필요 |
| RAM | 4 GB | 브라우저 + Python + DB 동시 실행 시 8 GB 권장 |
| Chrome/Edge | 최신 | CDP 모드 사용 시 로컬 설치 필수 |
| 인터넷 | 필수 | 대상 플랫폼 접근 가능한 네트워크 |
# 1. uv 설치 (없는 경우)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. 레포 클론
git clone https://github.com/NanmiCoder/MediaCrawler.git
cd MediaCrawler
# 3. 가상환경 생성 + 의존성 설치 (uv가 자동으로 Python 3.11 관리)
uv sync
# 4. Playwright 브라우저 바이너리 설치
uv run playwright install chromium
# 5. 환경변수 설정
cp .env.example .env
# .env 파일에 DB 연결, 쿠키 등 필요 항목 채우기
# Python 3.11+ 가상환경 생성
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
playwright install chromium
# config/__init__.py 또는 .env에서 설정 후:
# PLATFORM=xhs, KEYWORDS=파이썬, CRAWLER_TYPE=search
uv run python main.py
# 처음 실행 시 QR코드가 터미널에 표시됨 → 소홍서 앱으로 스캔
# 이후 세션이 저장되어 재로그인 불필요
# SAVE_DATA_OPTION=db 또는 postgres인 경우
uv run python main.py --init-db mysql
# 또는
uv run python main.py --init-db sqlite
난이도별 3~5개 실습 — 실제 플랫폼 크롤링보다 구조 이해에 집중.
base/base_crawler.py를 읽고, AbstractCrawler·AbstractLogin·AbstractStore·AbstractApiClient의 메서드 목록을 표로 정리한다. 그리고 media_platform/xhs/core.py에서 각 메서드가 어떻게 구현되었는지 대응시켜 본다.
MediaCrawler의 핵심 원리를 네이버에 적용해 본다. Playwright Python 라이브러리로 네이버에 수동 로그인한 뒤 browser_context.storage_state(path="naver_state.json")로 저장한다. 다음 실행 시 browser.new_context(storage_state="naver_state.json")로 세션을 복원하고 로그인 없이 마이페이지에 접근해본다.
MediaCrawler의 AbstractCrawler를 상속하여 Hacker News API (공개, 인증 불필요, https://hacker-news.firebaseio.com/v0/) 크롤러를 구현한다. start(), search()를 구현하고, 저장 방식은 AbstractStore를 상속한 JSON 구현체를 만든다. 결과를 JSONL 파일로 저장한다.
tools/async_file_writer.py의 워드클라우드 생성 로직을 분리해서 테스트 코드를 작성한다. 직접 만든 가짜 댓글 JSONL 파일(100개 줄)을 입력으로 주고, jieba 형태소 분석 → 불용어 제거 → wordcloud 이미지 생성까지 독립적으로 실행한다. 한국어 형태소 분석기 konlpy로 교체해 한국어 댓글 워드클라우드를 만들어 본다.
media_platform/에 reddit/ 폴더를 만들고 AbstractCrawler·AbstractLogin·AbstractApiClient·AbstractStore 4개를 모두 구현한다. Reddit의 public JSON API(https://www.reddit.com/r/python.json)를 대상으로 한다. CrawlerFactory에 "reddit": RedditCrawler를 등록하고 config/reddit_config.py를 추가한다.
이 레포를 기반으로 확장할 수 있는 주차별 학습 경로.
| 주차 | 주제 | 핵심 학습 내용 | 실습 |
|---|---|---|---|
| 1주 | Python 비동기 기초 | asyncio 이벤트 루프, async/await, coroutine vs thread 차이, asyncio.gather() | asyncio로 동시에 5개 URL 다운로드 코드 작성 |
| 2주 | Playwright 기초 | 브라우저 실행, 페이지 이동, 클릭·입력, 스크린샷, wait_for_selector() | 네이버 검색어 입력 후 결과 첫 페이지 제목 10개 추출 |
| 3주 | Playwright 고급 | BrowserContext 저장/복원, page.evaluate() JS 실행, CDP 연결, stealth 스크립트 | 로그인 상태 저장 후 재사용 실습 (본인 계정) |
| 4주 | HTTP 클라이언트 (httpx) | 비동기 GET/POST, 헤더 조작, 쿠키 관리, 프록시 설정, 세션 재사용 | 공개 REST API 10개 동시 호출 + 응답 파싱 |
| 5주 | Python OOP 설계 패턴 | ABC 추상 클래스, 팩토리 패턴, Mixin, Pydantic 모델 검증 | MediaCrawler의 AbstractCrawler를 보고 독자적인 데이터 수집 시스템 설계 |
| 6주 | 데이터 저장 (다중 백엔드) | SQLAlchemy async, Alembic 마이그레이션, aiosqlite, motor(MongoDB) | 같은 데이터를 SQLite / JSON / CSV 세 가지로 동시 저장하는 스토어 구현 |
| 7주 | 데이터 처리 · 시각화 | pandas 데이터 가공, jieba/konlpy 형태소 분석, wordcloud, matplotlib | 수집한 텍스트 데이터로 키워드 트렌드 차트 생성 |
| 8주 | 윤리·법률·보안 | robots.txt 파싱, 이용약관 검토, rate limiting 구현, IP 프록시의 법적 문제 | robots.txt를 읽고 크롤 허용 경로 자동 필터링 로직 작성 |
이 레포를 이해하는 데 필요한 핵심 개념 정리.
| 용어 | 설명 |
|---|---|
| Playwright | Microsoft의 브라우저 자동화 라이브러리. Python/JS/Java/C# 지원. asyncio 완전 호환. Selenium보다 빠르고 안정적. |
| CDP (Chrome DevTools Protocol) | Chrome/Edge 브라우저를 외부 프로그램이 제어할 수 있게 하는 프로토콜. --remote-debugging-port로 활성화. Playwright가 이를 통해 브라우저와 통신한다. |
| BrowserContext | Playwright의 "독립된 브라우저 세션". 쿠키·캐시·로컬스토리지가 격리된다. storage_state로 저장/복원 가능. |
| page.evaluate() | Playwright에서 브라우저 내 JavaScript를 직접 실행하는 메서드. 이 레포에서는 플랫폼 내부 서명 함수를 여기서 호출한다. |
| stealth.min.js | 크롤러 탐지 방지 JS 스크립트. navigator.webdriver 값 숨김, Chrome 헤드리스 판별 변수 위장 등을 수행. |
| asyncio.Semaphore | 동시 실행 가능한 코루틴 수를 제한하는 동기화 도구. 서버 과부하 방지에 사용. |
| tenacity @retry | 함수 실패 시 자동 재시도 데코레이터. 재시도 횟수·대기 시간·예외 조건을 선언적으로 설정. |
| Abstract Base Class (ABC) | Python의 추상 클래스. @abstractmethod로 표시된 메서드는 자식 클래스에서 반드시 구현해야 함. 인터페이스 역할. |
| Factory Pattern | 객체 생성을 담당하는 클래스(Factory)가 어떤 클래스의 인스턴스를 생성할지 결정하는 패턴. CrawlerFactory, XhsStoreFactory가 예시. |
| IP 프록시 풀 (Proxy Pool) | 여러 IP 주소를 번갈아 사용하여 IP 차단을 회피하는 기법. 유효성 검증 + 순환 로직 포함. |
| JSONL (JSON Lines) | 한 줄에 JSON 객체 하나. 대용량 데이터 스트리밍 저장에 유리. 마지막 줄만 추가하므로 메모리 효율적. |
| jieba | Python 중국어 형태소 분석 라이브러리. 공백이 없는 중국어 문장을 단어 단위로 분리. |
| robots.txt | 웹사이트 운영자가 크롤러에게 접근 허용/금지 경로를 명시하는 파일. 법적 강제력은 없지만 준수가 관행이자 안전책. |
| NON-COMMERCIAL LICENSE | 이 레포의 라이선스. 학습·연구 목적으로만 사용 가능하며 상업적 이용 명시 금지. MIT와 달리 사용 범위가 제한된다. |
출처 · 정리 기준일 2026-06-27 · 솔직한 caveat.
① 이용약관(ToS) 위반: 소홍서·더우인 등 모든 대상 플랫폼은 자동화 데이터 수집을 이용약관으로 금지하고 있다. 이를 어기면 계정 영구 정지 및 민사 손해배상 대상이 될 수 있다.
② robots.txt: 각 플랫폼의 /robots.txt를 확인하고 Disallow 경로는 접근하지 않는 것이 기본 예절이자 법적 리스크 감소 방법이다.
③ 개인정보보호: 수집한 데이터에 사용자 이름·프로필·연락처 등 개인정보가 포함되면 국내 개인정보보호법(PIPA), 유럽 GDPR 적용 대상이 될 수 있다. 제3자 공유·상업적 이용은 더욱 위험하다.
④ 상업적 이용 금지: 레포의 NON-COMMERCIAL LEARNING LICENSE는 영리 목적 사용을 명시적으로 금지한다. 수익 창출 서비스에 사용하면 저작권 침해다.
⑤ 중국 크롤러 법적 사례: 중국에서는 크롤러 사용으로 인한 형사 기소가 다수 발생했다. 레포 내 링크 참고: github.com/HiddenStrawberry/Crawler_Illegal_Cases_In_China