fuadmefleh/Shared-Claude-Chats · 언어 Python 3 · 규모 대화 1,525개 · 메시지 12,052개 · 약 33 MB · 익스포터 6개(~3,700줄) · 의존성 curl_cffi·requests 둘뿐 · TrendShift Daily 트렌딩(2026 신규))
Shared-Claude-Chats는 "다섯 개 AI 챗봇의 공개 공유 링크를 넣으면, 모두 동일한 형식의 깨끗한 마크다운 대화록으로 뽑아 주는 스크래퍼 모음 + 그 결과물 아카이브"다. 이름은 Claude만 들어가지만 실제로는 Claude·Grok·Kimi·Qwen·DeepSeek 5종 + 구글 AI Studio(수동)까지 6개 소스를 다룬다. 링크를 한 줄씩 텍스트 파일에 넣고 스크립트를 돌리면, 대화 한 개가 마크다운 파일 하나(제목.md)로 저장된다.
같은 "대화 공유"라도 서비스마다 문 잠그는 방식이 다르다. Claude·Grok은 문지기가 손님의 걸음걸이(TLS 지문)를 보고 진짜 브라우저인지 판별하고, DeepSeek은 자바스크립트 퍼즐을 풀어야 통과시키며, Kimi는 아예 문 앞 게시판에 답을 붙여 둔다(페이지에 JSON이 박혀 있음).
이 레포는 각 문에 맞는 열쇠를 따로 깎아 만든 열쇠 꾸러미다. 그리고 어느 문으로 들어가든, 꺼내 오는 물건(마크다운 대화록)의 포장 규격은 똑같다 — 그래서 1,500개를 한 폴더에 나란히 둘 수 있다.
claude.ai/share/<uuid>). 로그인 없이 누구나 볼 수 있는 공개 스냅샷이다. 이 레포는 오직 이 공개 링크만 다룬다 — 비공개·삭제된 대화는 접근 불가(“not found”). 즉 인증을 우회하는 게 아니라, 이미 공개된 것을 읽는 도구다.*_share_export.py 다섯 개가 각각 한 서비스를 담당한다. "긁는다(scrape)"고 하면 보통 HTML을 파싱하는 걸 떠올리지만, 여기선 대부분 그 페이지가 몰래 부르는 JSON API를 직접 부른다.AI 대화를 백업·인용·연구하려는 수요는 큰데, 각 서비스가 공식 내보내기(export)를 잘 안 준다. 그래서 "공유 링크를 어떻게 긁느냐"는 개발자 사이 단골 질문이다. 이 레포가 떠오른 이유는 그 질문에 대해 서비스별로 정확히 어디를 어떻게 두드리는지를, 실제로 도는 코드 + 놀랄 만큼 상세한 README로 공개했기 때문이다. 단순 도구가 아니라 리버스 엔지니어링 사례집으로 읽힌다.
두 번째 강점은 결과의 일관성이다. 6개 소스가 전부 같은 마크다운 뼈대(메타데이터 헤더 + ---로 구분된 사람/어시스턴트 턴)로 정규화된다. 그래서 이 코퍼스를 그대로 데이터셋·검색·인용에 쓸 수 있다. 세 번째는 재현성 — 링크 목록 파일이 함께 공개돼 있어 누구나 같은 아카이브를 다시 만들 수 있다.
| 항목 | 이 레포 | 브라우저 확장/복붙 | 공식 데이터 내보내기 |
|---|---|---|---|
| 커버 범위 | 5개 서비스 + AI Studio, 단일 규격 | 보통 1개, 서비스별 제각각 | 서비스마다 있거나 없음 |
| 자동화 | 링크 목록 → 배치 실행 | 대화마다 수동 | 계정 전체 덤프(선택적 필터 어려움) |
| 출력 형식 | 깨끗한 마크다운(턴 구분·메타 헤더) | HTML/서식 깨짐 흔함 | JSON(가공 필요) |
| 재실행 | 매니페스트로 이미 받은 건 건너뜀 | 중복 관리 수동 | 매번 전체 |
| 배울 점 | 서비스별 관문 돌파 기법 | 적음 | 없음(블랙박스) |
다국어 통역사 팀. 다섯 명의 통역사(익스포터)가 각각 다른 언어(서비스)를 맡지만, 번역 결과는 모두 같은 표준 서식의 회의록(마크다운)으로 제출한다. 새 언어가 필요하면 통역사 한 명을 더 고용(스크립트 추가)하면 되고, 나머지는 그대로다. 서비스가 하나 바뀌어도 담당 통역사만 고치면 된다 — 관심사 분리가 코드 구조에 그대로 새겨져 있다.
스크래핑은 윤리·법적으로 민감한 영역이다. 이 레포는 두 선을 지킨다: (1) 공개 공유 링크만 접근하고 인증 우회 경로는 만들지 않으며, (2) 커밋 전에 모든 새 대화를 NSFW·개인정보(PII) 기준으로 검수해 걸리는 건 banned.txt에 넣고, 이미 올라간 건 git 히스토리까지 재작성해 지운다. 그래도 "타인의 대화를 모으는" 성격이 있으니, 학습·백업 목적으로 참고하되 무단 재배포·상업적 이용은 주의해야 한다.
놀랍도록 얇다. 프레임워크도, 무거운 라이브러리도 없다. requirements.txt는 딱 두 줄 — curl_cffi와 requests. 나머지는 전부 파이썬 표준 라이브러리(re, json, argparse, pathlib, tempfile, unicodedata, datetime)로 짰다. "의존성을 최소로, 대신 각 서비스의 실제 동작을 깊이 이해"가 이 프로젝트의 철학이다.
requests는 파이썬 특유의 TLS 지문을 남겨 Cloudflare에 막힌다. curl_cffi는 실제 크롬의 TLS 지문을 흉내(impersonate) 내서 통과한다(impersonate="chrome").response.json()으로 끝.window.HYDRATION_INIT_STATE = {…}를 정규식으로 뽑아 json.loads.re로 순수 id만 뽑는다(서비스마다 id 형태가 다름 — UUID vs 18자 영숫자).export_io.pyatomic_write_text): 임시 파일에 쓰고 fsync 후 os.replace로 교체 — 도중에 죽어도 반쪽짜리 파일이 남지 않는다.resolve_within): 매니페스트가 가리키는 경로가 지정 폴더 밖으로 새는지 검사(디렉토리 트래버설 차단)..<provider>-share-export.json 하나. 이미 받은 대화를 기록해 재실행 시 건너뛴다(멱등성).이 레포의 진짜 내용은 "서비스 A의 공유 데이터는 정확히 어디에 있고, 그 앞에 무슨 문이 있으며, 어떻게 여느냐"를 다섯 번 반복한 것이다. 전체 파이프라인은 서비스가 달라도 모양이 같다:
두 서비스의 공유 페이지(/share/<uuid>)는 빈 SPA 껍데기다 — HTML엔 대화가 없고, 자바스크립트가 나중에 JSON API를 불러 채운다. 그래서 익스포터는 그 JSON API를 직접 호출한다(Claude는 /api/chat_snapshots/<uuid>). 문제는 그 API가 Cloudflare 뒤에 있고, Cloudflare가 헤더가 아니라 TLS 지문으로 봇을 거른다는 것. 해결책이 curl_cffi다:
# scripts/claude_share_export.py — 크롬 TLS 지문으로 위장해 호출
from curl_cffi import requests as cffi_requests
IMPERSONATE = "chrome"
response = cffi_requests.get(
API_URL.format(uuid=uuid),
impersonate=IMPERSONATE, # ← 핵심: 크롬인 척
timeout=TIMEOUT,
headers={"Accept": "*/*", "Referer": SHARE_URL.format(uuid=uuid)},
)
흥미로운 디테일: HTTP 403이 두 가지 다른 뜻을 갖는다. Cloudflare 차단(HTML 응답)일 수도, 비공개 공유(JSON permission_error)일 수도 있다. 코드는 응답이 JSON인지 보고 둘을 구분해, "공유가 비공개다"를 "TLS 문제"로 잘못 보고하지 않는다 — 안 그러면 사용자가 애먼 curl_cffi만 업그레이드하며 헤맨다.
Kimi는 반대다. 공유 페이지가 서버 렌더링이라 대화 JSON이 이미 HTML 안 <script>에 들어 있다. 별도 API 호출도, Cloudflare 게이트도 없어 평범한 requests로 페이지를 받아 정규식으로 상태를 뽑는다:
# scripts/kimi_share_export.py
HYDRATION_RE = re.compile(
r"window\.HYDRATION_INIT_STATE\s*=\s*(.*?)</script>", re.DOTALL
)
match = HYDRATION_RE.search(response.text)
blob = normalise_hydration_literals(match.group(1)) # JS 리터럴 → JSON
payload = json.loads(blob) # queryKey == "share" 항목에서 대화 추출
Qwen도 SPA 껍데기지만, API(/api/v2/chats/share/<uuid>)가 진짜 공개라 토큰 없이 열린다. 함정은 삭제·비공개 대화도 HTTP 200을 준다는 것 — 실패 신호가 상태 코드가 아니라 JSON 본문의 "success": false에 있다. 상태 코드만 믿으면 빈 대화를 "성공"으로 착각한다.
가장 교육적인 사례. DeepSeek 공유 페이지는 AWS WAF의 자바스크립트 챌린지 뒤에 있다 — challenge.js를 실행 못 하는 클라이언트(curl_cffi 포함)에는 HTTP 202와 퍼즐 조각만 준다. 즉 페이지 자체는 어떤 스크립트로도 못 읽는다. 그런데 저자는 실제 브라우저로 페이지를 열고 네트워크 패널을 뒤져, 그 게이트 바깥에 있는 데이터 API를 찾아냈다:
# scripts/deepseek_share_export.py — WAF 밖의 순수 JSON API
API_URL = "https://chat.deepseek.com/api/v0/share/content"
response = requests.get(API_URL, params={"share_id": share_id}, ...) # 토큰·쿠키 불필요
data = response.json()["data"]
if data.get("biz_code"): # 200이어도 실패는 여기에
raise ExportError(f"not found -- {data.get('biz_msg')}")
return data["biz_data"]
여기에도 함정이 둘. (1) Qwen처럼 실패가 200 안 biz_code에 숨어 있고, (2) 어떤 공유는 긴 대화의 꼬리 일부만 담겨 온다 — 가장 앞 메시지의 parent_id가 페이로드에 없는 조상을 가리켜서, 순서 복원 시 "부모가 없는 메시지를 뿌리로" 취급하는 방어 로직이 들어간다.
초보 스크래핑은 HTML을 BeautifulSoup으로 긁는다. 이 레포의 교훈은 다르다 — 화면에 보이는 것과 데이터가 오는 곳은 다르다. SPA는 대부분 뒤에서 JSON API를 부르고, 그 API가 페이지보다 훨씬 읽기 쉽고 안정적이다. 브라우저 개발자도구의 Network 패널을 열어 "이 대화는 어느 요청으로 오나"를 관찰하는 것이 스크래핑의 진짜 첫걸음이다. DeepSeek의 /api/v0/share/content는 바로 그렇게 발견됐다.
<script> 안 하이드레이션 상태로 함께 심어 준다(Kimi). 이 경우 그 script 블록을 파싱한다.구조가 말하는 의도는 명확하다. 왼쪽(scripts + link_lists)은 "만드는 법", 오른쪽(*_chats)은 "만들어진 것"이다. 각 *_chats/ 폴더는 결과물이면서 동시에 자기 매니페스트를 품어 스스로 "무엇을 이미 받았는지" 안다. 그래서 링크 목록에 계속 이어붙이고 다시 돌려도 안전하다(이미 있는 건 건너뜀). 파일 이름은 대화 제목에서 만들되(slugify), 비-라틴 문자(한글 등)는 그대로 둔다 — 예: 네이버-AI-모델서비스-기획-...md.
좋은 배치 도구의 필수 성질: 몇 번을 돌려도 결과가 같고, 이미 한 일은 다시 안 한다. 이 레포는 폴더별 JSON 매니페스트에 "id → 저장한 파일 · 제목 · 메시지 수 · 시각"을 기록하고, 실행 초입에 lookup(id)로 확인한다. 심지어 파일을 하위 폴더로 옮겨 놔도 이름으로 알아채 다시 안 받는다. --force는 "메시지가 늘어난 대화만 다시" 받는 탈출구다. 크론으로 매일 돌리는 수집기의 교과서 패턴.
HTML을 긁기 전에 "이 데이터는 어느 요청으로 오나"를 개발자도구로 관찰하는 습관. SPA는 십중팔구 JSON API를 부르고, 그게 파싱하기 훨씬 쉽다. DeepSeek의 숨은 데이터 API를 찾은 방법이 바로 이것.
TLS 지문 검문(Cloudflare) → curl_cffi 위장, JS 챌린지(AWS WAF) → 게이트 밖 API 찾기, 게이트 없음 → 평범한 requests. 벽마다 뚫는 방법이 다르다는 걸 실물 코드로 배운다. 이건 튜토리얼에 잘 안 나오는 실전 지식이다.
Qwen·DeepSeek은 실패해도 HTTP 200을 준다. 진짜 신호는 success:false·biz_code처럼 응답 본문에 있다. API를 다룰 때 "200이면 OK"라는 순진한 가정이 왜 위험한지 체감하는 좋은 예.
export_io.py 40여 줄이 보석이다. 임시파일 + fsync + os.replace로 "쓰다 죽어도 원본이 반쪽 안 남는" 원자적 쓰기, 그리고 resolve().relative_to(root)로 경로 탈출(디렉토리 트래버설)을 차단하는 법. 모든 파일 도구에 필요한 기본기.
매니페스트로 중복 스킵, --force로 갱신, 링크에서 순수 id 정규화, 실패 한 건이 배치를 안 죽이게 하는 예외 처리(ExportError). "밤새 돌려도 안전한" 수집기의 골격.
6개 소스를 하나의 마크다운 규격으로 통일하되, 소스별 차이(Author/Model/thinking/tool-call)를 헤더로 표현. 그리고 검수→banned.txt→히스토리 재작성으로 데이터 위생을 관리하는 실전 사례. 데이터셋을 만드는 사람이라면 반드시 볼 부분.
os.replace로 한 번에 바꾸면, 교체는 OS가 보장하는 원자적 연산이라 항상 온전한 상태만 보인다.| 항목 | 요구 / 권장 |
|---|---|
| 런타임 | Python 3(f-string·str | None 타입 힌트 사용 → 3.10+ 권장) |
| 의존성 | pip install -r requirements.txt → curl_cffi(Claude·Grok)·requests(Kimi·Qwen·DeepSeek) |
| 기본 실행 | python3 scripts/claude_share_export.py link_lists/claudeaisharelinks.txt -o claude_chats |
| 입력 형태 | 링크 파일(-f 반복 가능) 또는 바로 링크/id를 인자로. #·빈 줄 무시. -o 기본값=현재 폴더 |
| 선택 플래그 | --include-thinking(Claude·Qwen·DeepSeek, 추론 블록) · --include-sources(Grok, 인용 웹/X) · --force(늘어난 대화 재수집) |
| AI Studio | 공개 API 없음 → 페이지 텍스트를 raw_google_dumps/에 붙여넣고 aistudio_export.py ... --model "Gemini 2.5 Pro" |
| 부하 | 가벼움 — 순수 HTTP 요청 + 텍스트 처리. GPU·DB·빌드 불필요 |
이 도구는 공개 공유 링크만 읽는다 — 비공개·삭제된 대화는 "not found"다. 그리고 각 서비스의 비공식 내부 경로에 의존하므로, 서비스가 API 경로나 하이드레이션 구조를 바꾸면 해당 익스포터가 깨질 수 있다("site may have changed" 에러). Claude 공유 API가 툴콜·검색 위젯을 This block is not supported… 자리표시자로 뭉개는 등 상류에서 이미 손실된 정보는 복구 불가다. 저장소에 LICENSE 파일이 없어(분석 시점 기준) 재사용 조건이 불명확하니, 코드·데이터를 가져다 쓸 땐 저자에게 확인하는 게 안전하다.
본인 Claude 대화를 "공유"로 만들고 링크를 복사한 뒤, pip install -r requirements.txt 후 python3 scripts/claude_share_export.py <링크> -o out을 실행하자. out/에 생긴 마크다운을 열어 메타 헤더 + --- 턴 구분 구조를 눈으로 확인하고, --include-thinking을 켰을 때 차이를 비교하라.
브라우저로 Qwen이나 DeepSeek 공유 페이지를 열고 개발자도구 Network 탭을 관찰하자. 대화 내용을 담아 오는 요청(/api/...)을 찾아, 코드의 API_URL과 대조하라. "화면과 데이터의 출처는 다르다"를 직접 확인하는 훈련.
Qwen/DeepSeek 익스포터에서 삭제된(또는 가짜) 공유 id를 넣어 보고, 응답이 HTTP 200인데도 success:false/biz_code로 실패를 알리는 걸 로그로 확인하라. 그런 다음 이 검사를 일부러 제거해 "빈 대화가 성공으로 저장되는" 버그를 재현했다가 되돌려, 방어 코드의 가치를 체감하라.
다른 챗봇(예: Mistral·Perplexity 공유)을 골라, 기존 익스포터를 템플릿 삼아 <service>_share_export.py를 새로 짜자. export_io.py의 atomic_write_text·매니페스트 패턴을 재사용하고, 그 서비스가 TLS 게이트·JS 챌린지·공개 API 중 무엇인지부터 Network 패널로 판별하라.
1,525개 마크다운을 파싱해 메시지 수 분포·언어·주제 통계를 내거나, 임베딩으로 시맨틱 검색을 붙여 보자. 헤더의 Source/Created/Messages를 구조화 데이터로 뽑아 작은 대시보드로 만들면, "정규화된 코퍼스"의 가치를 온몸으로 이해하게 된다.
| 주차 | 주제 | 무엇을 / 왜 |
|---|---|---|
| 1주차 | HTTP & 브라우저 개발자도구 | 요청/응답·상태 코드·헤더, Network 패널로 숨은 API 찾기. 스크래핑의 진짜 첫걸음. |
| 2주차 | SPA vs SSR & 하이드레이션 | 왜 어떤 페이지는 비었고 어떤 건 JSON이 박혀 있나. __NEXT_DATA__·하이드레이션 상태 파싱. |
| 3주차 | 안티봇 — TLS 지문 | JA3/JA4 지문, Cloudflare 검문, curl_cffi/curl-impersonate로 위장하는 원리와 한계. |
| 4주차 | 안티봇 — WAF & JS 챌린지 | AWS WAF 챌린지의 동작, "벽 밖 API 찾기" 전략, 언제 뚫지 말아야 하는지(윤리·법). |
| 5주차 | 견고한 API 클라이언트 | 재시도·타임아웃·본문 기반 실패 판정(success/biz_code)·부분 응답 처리. |
| 6주차 | 파일시스템 안전성 | 원자적 쓰기, fsync, 경로 탈출 방지, 유니코드 슬러그·파일명 충돌 처리. |
| 7주차 | 멱등한 배치 파이프라인 | 매니페스트·체크포인트·--force·부분 실패 격리. 크론으로 매일 도는 수집기 설계. |
| 8주차 | 데이터 위생 & 출처 관리 | PII/NSFW 스크리닝, 히스토리 재작성으로 스크럽, 라이선스·재배포 윤리. 데이터셋 만드는 사람의 책임. |
<script>에 데이터를 심어 줌(→정규식 파싱, Kimi).success:false/biz_code로 실패를 알리는 API. 상태 코드만 믿으면 오작동.os.replace로 반쪽 파일 방지. resolve().relative_to(root)로 지정 폴더 밖 쓰기 차단.--include-thinking로, Kimi는 항상 포함.README.md(서비스별 관문·형식·검수 정책이 매우 상세) · scripts/*_share_export.py · scripts/export_io.py