위챗 공식계정(공중호) 글을 HTML·MD·DOCX·Excel로 100% 원본 그대로 백업하는 오픈소스 도구. Nuxt 3 SPA + Nitro 프록시 + Cloudflare/Docker 배포까지 한 번에 배운다.
왜 이런 도구가 중국 개발자 커뮤니티를 점령했나.
위챗(WeChat·微信)에는 한국의 네이버 블로그 + 카카오 채널과 비슷한 "공중호(公众号)"가 있다. 중국에서 가장 큰 콘텐츠 플랫폼 중 하나다. 하지만 위챗은 모든 글이 위챗 앱 안에 갇혀 있어서, 밖에서 검색하거나 백업하기가 거의 불가능하다. 이 레포는 그 벽을 우회해 글을 통째로 내려받게 해주는 도구다.
2024~2026년 사이 중국에서 공식계정 콘텐츠의 법적 보존, 아카이빙, 마이그레이션 수요가 폭증했다. 정부 규제 변경·플랫폼 삭제 사고가 잦아지면서 "내가 쓴 글을 내 손으로 보관"하려는 흐름이 일반화됐다. 이 레포는 그 정확한 timing을 잡았다.
mp.weixin.qq.com(웹 백엔드)에서 한다. 이 백엔드 로그인 권한이 있어야 본 도구를 쓸 수 있다.unstorage로 키-값 저장소도 환경별로 갈아 끼울 수 있다. 이 레포가 어디서든 돌아가는 비결.한 문장으로 줄이면.
위챗 공중호는 마치 "메뉴판은 보여주는데 음식 포장은 안 해주는 식당"과 같다. 이 도구는 그 식당의 메뉴판(공식 글 검색 기능)을 활용해, 글 하나하나를 도시락 통(HTML/Excel/DOCX)에 담아 집까지 가져갈 수 있게 해주는 "포장 도우미"다.
설치 없이 사이트만 방문하면 된다. 서버가 아니라 사용자 자신의 브라우저가 직접 위챗에 요청한다는 설계 덕에, 운영자가 사용자 계정을 풀에 넣어 공용 크롤링하는 윤리·법적 리스크가 원천 차단된다.
트렌딩 이유 + 경쟁 우위.
중국 개발자 커뮤니티에서 폭발적으로 입소문이 났다. 위챗 백업 수요(법적 보존, 콘텐츠 아카이빙, 플랫폼 이전)가 워낙 커서 빠르게 확산. 무엇보다 HTML 스타일 재현률 100% — 이미지·CSS·외부 자원까지 함께 묶어 오프라인에서도 완벽히 원본 그대로 보이게 만든다. 단순 텍스트 추출과는 다른 차원이다.
두 번째 강점은 "브라우저에서 직접 돈다"는 점. 일반 크롤러는 서버에서 돌아가서 운영자가 모든 사용자 쿠키를 보관해야 하고, 한 IP에서 들어오는 요청이라 위챗이 쉽게 차단한다. 이 레포는 그 모든 윤리·기술 리스크를 우회한다.
| 비교 항목 | 일반 크롤러 | wechat-article-exporter |
|---|---|---|
| 설치 | Python·Node + 환경 셋업 | 웹사이트 방문만 (SPA) |
| 실행 위치 | 서버 → 계정 풀링·차단 위험 | 유저 브라우저 → 차단 위험 없음 |
| 출력 포맷 | 대부분 텍스트·JSON | HTML·JSON·Excel·TXT·MD·DOCX |
| HTML 재현률 | 이미지 깨짐·스타일 손실 | 100% — 이미지·폰트·CSS 패키징 |
| 배포 옵션 | 로컬 실행 중심 | 온라인 / Docker / Cloudflare |
| 댓글·조회수 | 거의 불가능 | credentials 추출 시 가능 |
이 설계 덕분에 운영자가 사용자 계정을 풀에 넣어 공용 크롤링하는 윤리·법적 리스크가 원천 차단된다. README의 "声明(선언)" 섹션에 명시되어 있다. 서버는 단지 CORS만 뚫어주는 얇은 프록시일 뿐.
프론트엔드 · 백엔드 · 라이브러리 · 인프라.
setup()·ref·computed 기반브라우저는 보안상 다른 도메인의 API를 직접 호출하지 못한다 (CORS 정책). 그래서 Nitro 서버가 "중간 전령"을 한다 — 사용자 요청을 받아 위챗 서버에 대신 보내고, 응답을 다시 사용자에게 전달. 이게 리버스 프록시(reverse proxy)다.
db.articles.where('date').above('2024-01-01').toArray()처럼 SQL 비슷한 문법이 가능해진다.전체 구조도 → 데이터 흐름 → 설계 패턴.
┌─────────────────────────────────────────────────────────────┐
│ 사용자 브라우저 (Chrome 등) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Nuxt 3 SPA (app.vue → pages/dashboard.vue) │ │
│ │ · Vue 3 Composition API (composables/) │ │
│ │ · AG Grid로 글 목록 표시 │ │
│ │ · Tailwind + @nuxt/ui로 UI 구성 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ IndexedDB (Dexie 래퍼) — store/v2/db.ts │ │
│ │ ├─ article (글 메타데이터) │ │
│ │ ├─ asset (에셋) │ │
│ │ ├─ comment (댓글) │ │
│ │ ├─ comment_reply (댓글 답글) │ │
│ │ ├─ debug (디버그) │ │
│ │ ├─ html (원본 HTML) │ │
│ │ ├─ info (계정 정보) │ │
│ │ ├─ metadata (메타데이터) │ │
│ │ ├─ resource (이미지·CSS 캐시) │ │
│ │ └─ resource-map (리소스 맵) │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────┬────────────────────────────┬───────────────────┘
│ │
│ ① 정적 자원 요청 │ ② 위챗 API 프록시 요청
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Cloudflare Pages / │ │ Nitro 서버 (Edge or Node) │
│ Docker (Nginx + Node) │ │ server/api/web/mp/ │
│ .output/public/* │ │ · 약 25개 엔드포인트 │
│ │ │ · proxy-request.ts │
│ │ │ · Cookie 자동 전달 │
└─────────────────────────────┘ └────────────┬────────────────┘
│ ③ 사용자 쿠키와 함께
▼
┌─────────────────────────────┐
│ 위챗 공식계정 백엔드 │
│ mp.weixin.qq.com │
│ · 글쓰기 검색 API │
│ · 글 상세 API │
│ · 댓글/조회수 API │
└─────────────────────────────┘
[1] 로그인 [2] 검색 [3] 캐싱 [4] 다운로드 큐 [5] 내보내기
───────── ──────── ──────── ────────────── ─────────────
사용자가 apis/index.ts Dexie가 p-queue가 Exporter가
위챗 백엔드 → 가 Nitro 프록시 → IndexedDB에 → 동시 5개씩 → HTML/MD/Excel
QR 로그인 호출 글 메타데이터 다운로드 /DOCX로 변환
저장 (rate limit) 후 JSZip 패키징
일반 크롤러는 서버에서 돈다. 하지만 그러면 (1) 운영자가 모든 사용자 쿠키 보관, (2) 위챗이 한 IP의 요청이라 차단 쉬움, (3) 계정 공유 윤리 문제까지 셋이 한꺼번에 터진다. 이 프로젝트는 각 사용자 브라우저가 자기 자신의 쿠키로 직접 요청하게 해서 이 셋을 모두 해결한다. 서버는 그저 CORS만 뚫어주는 얇은 프록시.
// composables/useDownloader.ts
export function useDownloader() {
const queue = new PQueue({ concurrency: 5 });
const progress = ref(0);
async function download(article) {
await queue.add(() => Downloader.fetch(article));
progress.value++;
}
return { download, progress };
}
React의 커스텀 훅과 같은 개념. UI 컴포넌트에서 const { download, progress } = useDownloader()만 부르면 다운로드 로직을 재사용할 수 있다. Nuxt 3는 composables/ 폴더 안의 함수를 자동 임포트해주기 때문에 import 문도 필요 없다.
브라우저 메모리 (Vue ref/reactive)
↓ 페이지 새로고침 시 유지 X
IndexedDB (Dexie) — store/v2/db.ts
↓ 같은 브라우저면 영구 보관
Cloudflare KV / FileSystem KV — NITRO_KV
↓ 서버 측 캐싱 (인기 검색 결과)
위챗 원본 API
요청 횟수를 줄이고 위챗의 rate limit을 피하기 위한 핵심 전략. 같은 글을 또 봐도 위챗 서버까지 가지 않고 IndexedDB에서 바로 꺼낸다.
// nuxt.config.ts
nitro: {
storage: {
kv: {
driver: process.env.NITRO_KV_DRIVER || 'memory',
base: process.env.NITRO_KV_BASE,
}
}
}
같은 코드가 환경 변수만 바꾸면 메모리·파일시스템·Cloudflare KV에서 모두 돈다. Nitro의 unstorage 추상화 덕분. 한 번 익혀두면 다양한 배포 환경을 쉽게 다룰 수 있다.
Nuxt를 처음 쓰면 자연스럽게 SSR이 켜져 있다. 그런데 위챗 데이터는 사용자 쿠키가 있어야 가져올 수 있어 서버가 미리 그릴 콘텐츠가 없다. SSR이 켜져 있으면 로그인 전엔 빈 페이지가 서버에서 그려져 내려와 hydration 에러가 폭발한다.
로그인 기반 SaaS·대시보드는 SSR을 꺼서 깔끔하게 SPA로 만드는 게 정답. 대신 SEO는 포기. 본인 프로젝트가 검색에 노출돼야 하면 SSG(Static Site Generation)나 ISR로 가야 한다.
어디에 뭐가 있는지.
wechat-article-exporter/ ├── app.vue # 최상위 Vue 컴포넌트 (라우터 진입점) ├── pages/ │ ├── index.vue # 진입점 라우트 │ ├── dashboard.vue # SPA 메인 화면 │ ├── dashboard/ # dashboard 하위 라우트 디렉토리 │ └── dev/ # 개발용 라우트 디렉토리 │ ├── apis/ # 클라이언트용 API 함수 (getArticleList 등) ├── composables/ # useDownloader, useExporter, useBatchDownload ├── components/ # Vue 재사용 컴포넌트 (글 카드, 다운로드 버튼) │ ├── server/ │ ├── api/web/mp/ # Nitro의 ~25개 프록시 엔드포인트 │ └── utils/proxy-request.ts # 위챗 API 호출의 핵심 프록시 로직 │ ├── utils/ │ └── download/ │ ├── Downloader.ts # p-queue 기반 동시 다운로드 매니저 │ └── Exporter.ts # HTML/MD/Excel/DOCX 변환·패키징 │ ├── store/ │ └── v2/db.ts # Dexie 스키마 정의 (10개 테이블) │ ├── shared/ # 클라이언트·서버 공용 유틸 (HTML 파싱) ├── types/ # AppMsgEx, AccountInfo 등 TS 타입 ├── config/ # 상수, 공개 API 엔드포인트, AG Grid 설정 ├── public/vendors/ # html-docx-js 같은 외부 JS (defer 로드) │ ├── nuxt.config.ts # 모듈·런타임·Nitro·Sentry·Umami 설정 ├── Dockerfile # node:22-alpine 빌드 → slim+Chromium 실행 ├── biome.json # Biome 포맷터 설정 (lint 비활성, 행폭 120) └── CLAUDE.md # Claude Code용 컨텍스트 가이드
| 경로 | 역할 |
|---|---|
pages/dashboard.vue | SPA 메인 화면. 거의 모든 UI 로직이 여기 또는 호출된 컴포넌트에 있음 |
server/utils/proxy-request.ts | 가장 중요한 파일. 사용자 쿠키를 위챗에 어떻게 전달하는지 핵심 로직 |
composables/useDownloader.ts | p-queue로 동시 5개 다운로드 제어. 진행률 표시까지 |
utils/download/Exporter.ts | HTML→MD·Excel·DOCX 변환. 새 포맷 추가하려면 이 파일 |
store/v2/db.ts | Dexie 10개 테이블 스키마. v1→v2 마이그레이션 흔적도 여기 |
nuxt.config.ts | SSR off, NITRO_KV, Sentry, Umami, runtimeConfig 모두 여기 |
이 레포는 Nuxt 3의 "컨벤션 over 설정" 철학을 잘 보여준다. pages/, server/api/, composables/, components/는 별도 등록 없이 폴더만 만들어두면 자동 등록된다. Next.js의 App Router와 비슷한 발상.
기술 영역마다 무엇을 가져갈지.
pages/dashboard.vue가 자동으로 /dashboard가 됨ssr: false로 클라이언트 전용 앱@nuxt/ui, @vueuse/nuxt, @sentry/nuxt를 한 줄로 통합useDownloader()를 import 없이 호출실습: 빈 Nuxt 3 프로젝트에 composables/useCounter.ts를 추가해 자동 임포트가 동작하는지 확인
defineEventHandler로 HTTP 엔드포인트 정의puppeteer를 빌드 단계에서 제외하고 런타임 설치실습: server/api/hello.ts를 만들어 /api/hello에서 JSON 반환
store/v2/의 v2는 v1에서 마이그레이션된 결과where('publishTime').above(...).toArray()실습: Dexie로 "북마크 매니저" 만들기 (URL·제목·태그·캡쳐 이미지)
USER node로 보안 강화실습: 멀티스테이지를 단일 스테이지로 바꿔봤을 때 이미지 크기가 얼마나 커지는지 비교
실습: 빈 Nuxt 3 앱을 Cloudflare Pages에 배포. wrangler login → wrangler pages deploy
배포 모드별 최소 사양.
| 모드 | 최소 사양 | 외부 의존성 | 용도 |
|---|---|---|---|
| 온라인 사용 | 최신 브라우저 | 위챗 백엔드 로그인 권한 | 가장 쉬움. 그냥 사이트 방문 |
| 로컬 개발 | Node 22 + 4GB RAM | Corepack + Yarn 1.22.22 | 개발·학습 |
| Docker 셀프호스팅 | 2 vCPU + 2GB RAM | Chromium (이미지 포함) | NAS·VPS 운영 |
| Cloudflare Pages | — | Cloudflare 계정 (무료) | 월 100k 요청까지 무료 |
| 항목 | 최소 | 권장 |
|---|---|---|
| CPU | 1 vCPU | 2 vCPU |
| RAM | 1 GB | 2 GB (Chromium 때문) |
| 디스크 | 2 GB | 5 GB (KV + 이미지) |
| OS | Docker 지원 모든 OS | Linux (시놀로지 NAS 포함) |
| 네트워크 | mp.weixin.qq.com 접근 가능 | — |
무료 플랜으로 월 100k 요청까지 가능하지만, Puppeteer가 Cloudflare에서 안 돈다. 일부 고급 기능(렌더링 검사 등)은 비활성화된다. 풀 기능을 원하면 Docker 셀프호스팅으로 가야 한다.
난이도별 실습 — Easy → Hard.
1) https://down.mptext.top 접속 2) 위챗 공식계정 백엔드에 QR로 로그인 (계정 없으면 README 스크린샷만 봐도 OK) 3) 아무 공식계정 검색 → 글 5개 선택 → HTML로 다운로드 4) 받은 zip을 풀고 index.html을 브라우저에서 열어 "원본 위챗 글과 100% 일치" 확인
배우는 것: 도구의 실제 동작, HTML 패키징의 완성도
1) Node 22 이상 설치 2) corepack enable + corepack prepare yarn@1.22.22 --activate 3) 레포 클론 후 yarn · yarn dev 4) http://localhost:3000 접속
배우는 것: Nuxt 3 개발 서버, Corepack 패키지 매니저 핀
# Node 22 이상 필수 corepack enable corepack prepare yarn@1.22.22 --activate git clone https://github.com/wechat-article/wechat-article-exporter.git cd wechat-article-exporter yarn yarn dev # → http://localhost:3000
1) Dockerfile 한 번 정독 (멀티스테이지 흐름 파악) 2) docker build --build-arg VERSION=local -t wxe:local . 3) docker run -d -p 3000:3000 -v $(pwd)/data:/app/.data --name wxe wxe:local 4) 또는 공식 이미지 ghcr.io/wechat-article/wechat-article-exporter:2.3.17
배우는 것: 멀티스테이지 빌드, Puppeteer + Chromium 패키징, 시놀로지 NAS 셀프호스팅 응용
1) utils/download/Exporter.ts 정독 2) "epub 포맷" 또는 "단일 HTML로 묶기" 같은 새 포맷 추가 3) 테스트 글 5개로 검증 4) PR 보내는 것까지 도전!
힌트: epub은 사실 zip 안에 정해진 디렉토리 구조 + XML 메타데이터를 넣은 포맷. jszip으로 충분히 가능. epub-gen 라이브러리를 써도 OK.
1) Cloudflare 계정 만들고 wrangler login 2) yarn preview가 로컬에서 도는지 확인 3) wrangler pages deploy dist로 배포 4) 대시보드에서 KV 네임스페이스 생성 + NITRO_KV_BINDING으로 연결 5) 도메인 연결까지 끝내면 본인만의 wechat-exporter 사이트 완성
배우는 것: Wrangler CLI, Cloudflare KV 바인딩, Nitro 프리셋 전환
이 프로젝트의 구조를 모방해서 "네이버 블로그 백업 도구" 또는 "브런치 다운로더"를 만들어 보세요. 위챗 부분만 다른 API로 갈아 끼우면 됩니다. 본인 포트폴리오로 GitHub에 올릴 만한 프로젝트.
배우는 것: 어댑터 패턴, CORS 우회 일반화, 본인만의 풀스택 도구 빌드
주차별로 따라가면 전체 스택 마스터.
공식 튜토리얼 클리어 → Composition API 완전 이해 → composables/·useState·useFetch 직접 써보기. 이 레포의 pages/dashboard.vue를 한 줄씩 읽으면 좋다.
Nitro 공식 문서 → defineEventHandler·setHeader·readBody → CORS와 프록시 패턴 → unstorage로 메모리/FS/KV 전환. 이 레포의 server/utils/proxy-request.ts를 다른 사이트(예: 네이버 뉴스 RSS)에 적용해보기.
IndexedDB / Dexie → Blob·File·FileReader API → file-saver·jszip으로 zip 다운로드 → html2pdf.js로 PDF 변환 → turndown으로 HTML→MD. "오프라인 노트 앱"을 만드는 게 좋은 미션.
Dockerfile 멀티스테이지 → 컨테이너 보안 (USER node) → docker-compose → Cloudflare Pages·Workers → KV / R2 / D1 → GitHub Actions로 자동 배포. 본인 사이드 프로젝트를 실제로 인터넷에 띄워보는 게 핵심.
p-queue·Bottleneck·async-pool → 재시도 전략 (exponential backoff) → Sentry로 에러 수집 → Umami로 사용자 분석 → AG Grid로 대규모 테이블 → Monaco Editor로 디버그 뷰. 이 레포의 useDownloader·useExporter가 종합 예제.
자주 마주칠 단어들.
setup()·ref·computed 기반.공식 · 학습 리소스 · 관전 포인트.
· SPA + Nitro 프록시로 CORS 우회 패턴
· IndexedDB 다층 캐싱 + p-queue 동시 제어
· Docker 멀티스테이지로 Puppeteer 포함 이미지 빌드 (시놀로지 NAS 셀프호스팅에도 직접 적용 가능)
· Cloudflare KV로 서버 비용 0원 운영
· CLAUDE.md를 잘 작성해서 AI 에이전트가 코드 베이스를 이해하기 쉽게 만든 점
· 위챗 백엔드 API는 비공식 — 위챗이 API를 바꾸면 깨질 수 있음
· SSR 끄면 SEO·초기 로딩에 손해 — 본인 프로젝트에선 신중히
· AG Grid Enterprise는 상용 라이선스 필요
· Cloudflare Pages에선 Puppeteer 기능 일부 비활성화
· 법적 회색지대 — 콘텐츠 저작권은 원작자에게 있다는 점 README에 명시
https://down.mptext.top 접속 → 위챗 백엔드 로그인 권한이 없어도 README 스크린샷으로 흐름 파악 → 100% HTML 재현률이 어떤 느낌인지 눈으로 확인.yarn dev. pages/dashboard.vue 한 줄씩 읽으면서 Composition API 패턴 흡수.store/v2/db.ts로 10개 테이블 스키마 파악 → useDownloader.ts로 동시성 제어 + IndexedDB 캐싱이 어떻게 합쳐지는지. 다층 캐싱이라는 발상을 본인 프로젝트에 옮기는 연습.docker run -d -p 3000:3000 ghcr.io/wechat-article/wechat-article-exporter:2.3.17로 즉시 띄우기. 한 발 더 가면 같은 구조로 "네이버 블로그 백업 도구"를 본인 손으로 만들어 포트폴리오에 올린다.