Reddit 최종 결정 반영(2026-07-29) · 코드 반영 전

Social Fetcher URL Structure Audit — 2026-07-17
관련 코드: social-fetcher.router.ts(classifyReddit) · fetchers/reddit.fetcher.ts
관련 데이터: samples/reddit.json · samples/unknown.json(커버리지 갭 후보)
✅ 최종 결정 (수기 audit 반영)
원칙: sourceKey는 unique & immutable & linkable. Reddit은 username과 subreddit name이 영구 불변이고 삭제해도 재사용되지 않아(재할당 없음) 이름 자체를 키로 써도 안전 — TikTok(30일마다 변경+타인 재할당)·X(변경 가능)와 정반대. 따라서 audit의 username/name 키 채택은 원칙 위반이 아니라 플랫폼 특성에 맞는 판단.
조인 키
객체비고
Creator (user)usernameReddit은 username 변경 불가(대소문자조차 불가). 계정 삭제 시에도 해당 username은 재사용 불가 → 충돌 없음. audit "username == authorName → content와 연결" 그대로 성립 — post 응답의 authorName이 곧 username이라 추가 조회 없이 링크됨.
Content (post)parsedId + authorName (creator 링크)parsedId(base36 post id)가 dedup 키. venue의 id/parsedId는 audit이 제거 지시했으나 content의 parsedId는 키라 제거 대상이 아님 — 객체가 달라 같은 필드명이 다르게 처리됨.
Venue (subreddit)name (subreddit slug)subreddit 이름은 생성 후 변경 불가 → 키로 안전. audit "name (id 안써도 됨)" 채택, id/parsedId(t5_2th52 / 2th52)는 제거.
sourceType 통합 맵
최종 타입포함 패턴
reddit/r/{sub}, /r/{sub}/search, /r/{sub}/new — key=subreddit(소문자). 기존 제안 유지
reddit_post/r/{sub}/comments/{postId}/{slug}, redd.it/{postId} — key=postId. redd.it은 차단되지 않음(301 정상)이고 URL에 이미 postId가 있어 해석 불필요
reddit_share/r/{sub}/s/{shareId} — key=shareId 유지. audit의 조건문("post에 대한 share면 post로, 안되면 shareId로")이 실측상 "안된다"로 판정
reddit_user/user/{name}, /u/{name} — key=username. 현재 fetcher가 URL 정규식 sniffing으로 분기하던 것을 타입으로 분리. 기존 제안 유지
구조 결정 — 필드 단위 key/keep/remove로 표현할 수 없는 결정
항목내용
share 링크는 해석하지 않고 shareId 유지 — Reddit이 우리 IP를 전면 차단audit: "만약 share가 기본적으로 post에 대한 share라면 type post로 설정하고 key는 post id로. 안된다면 어쩔 수 없이 shareId로". 실측 결과 "안된다": /r/{sub}/s/{shareId} HEAD = 403인데, /r/wallstreetbets/(일반 subreddit)·/r/vinted/comments/{id}/(일반 post)·old.reddit.com 전부 403 → share 전용 문제가 아니라 Reddit이 우리 인프라 IP를 전면 차단(문서 §3의 about.json 100% 403과 동일 원인). TikTok shortlink처럼 무료 HEAD로 푸는 길이 막혀 있음. 비대칭 발견: redd.it/{postId}는 차단되지 않고 301 정상 응답(→ reddit.com/comments/{postId}). 다만 redd.it은 URL에 이미 postId가 있어 리다이렉트가 애초에 불필요. → share 해석은 유료 Apify actor 경유만 남으며, 그 전까지 reddit_share+shareId 유지. 후속 과제.
stickied / distinguished — 무엇인지 확인 후 audit 지시대로 제거audit이 "이게 뭔지 체크하고 제거"로 유보한 항목. stickied = 모더레이터가 subreddit 상단에 고정한 글(공지·메가스레드). distinguished = 작성자가 모더레이터/관리자 자격으로 작성했음을 표시하는 태그. 둘 다 "공식성"의 약한 보조 신호이고(문서 판정: context), 토큰 홍보 글이 고정돼 있으면 "운영진 관여"로 해석할 여지는 있으나 단독 매매 신호로는 약함 → audit 지시대로 remove. postType/isSelf/isVideo도 함께 제거(문서 판정 noise와 일치).
이 문서에서 쓰는 pill 범례
관측 source-data에서 실제 발견됨 웹리서치/실측 curl 또는 검색으로 보완(이 데이터셋엔 미관측) 정상 fetch 성공/의도대로 동작 차단·버그·갭 실패했거나 고쳐야 할 것 redirect/mutable 유지 / 신규 타입 fixed / mutable / 미선언
목차   1a. 플랫폼 개념 글로서리 · 1b. URL 패턴 · 2. sourceType/sourceKey 확정 · 3. Fetch 테스트 + 에러 원인(3.5 Apify 실측) · 4. 필드 통합 (변동성·파싱·판정) · 다음 액션

1a. 플랫폼 개념 글로서리

Reddit을 이해하는 데 필요한 최소 도메인 개념. URL 패턴이나 필드 설명에서 이 용어들을 그대로 참조한다.

개념설명
Subreddit (r/<name>)특정 주제로 묶인 커뮤니티 공간. 이 audit의 reddit sourceType은 항상 이 단위로 데이터를 가져온다(§3).
Post / Submissionsubreddit에 올라온 글(텍스트/링크/이미지). /comments/<postId>/ URL이 이 포스트를 직접 가리키는 permalink.
Comment 딥링크포스트 URL 뒤에 댓글 ID가 하나 더 붙으면(/comments/<postId>/<slug>/<commentId>/) 특정 댓글 하나를 가리킨다. 관측 데이터엔 이 형태 없음 — postId까지만 있는 포스트 permalink만 나왔음.
Redditor (User)계정. /user/<name> 또는 /u/<name>으로 프로필 접근.
Share link모바일 앱 "공유" 버튼이 생성하는 /s/<shareId> 링크. shareId는 공유 이벤트별 추적용 opaque 토큰 — 원본 포스트의 postId와 무관하며 redirect로만 resolve 가능(§1b, §3).
Karma / SubscribersReddit의 평판·구독 시스템. subreddit은 구독자 수(subscribers), 유저는 karma로 활성도를 표시 — 둘 다 시간에 따라 계속 변하는 값(mutable).
NSFW (over18)성인 컨텐츠 플래그. subreddit/포스트 단위로 설정.

1b. URL 패턴

reddit.com/r/{subreddit}/comments/{postId}/{slug}/
subreddit (커뮤니티)   postId (base36, 포스트 고유 식별자)   slug는 제목 요약(파싱에 안 씀)
URL 패턴의미ID 후보출처
reddit.com/r/{sub}subreddit 홈subreddit관측 16건source-data
reddit.com/r/{sub}/comments/{postId}/{slug}포스트 permalinkpostId관측 106건source-data
reddit.com/r/{sub}/s/{shareId}모바일 공유 링크shareId → redirect 후 postId관측 10건source-data + 실측(curl)
reddit.com/user/{name}, /u/{name}유저 프로필. /u//user/의 shorthandusername관측 18건(/user/만)source-data
reddit.com/r/{sub}/search?q=...subreddit 내부 검색 결과(콘텐츠 아님)subreddit(브라우징 컨텍스트)관측 7건source-data
reddit.com/r/{sub}/newsubreddit "new" 리스팅(콘텐츠 아님)subreddit(브라우징 컨텍스트)관측 4건source-data
redd.it/{postId}Reddit 공식 숏링크. subreddit 세그먼트 없음postId미관측실측(curl): redd.it/eorhm → 301 → reddit.com/comments/eorhm
old.reddit.com/...구버전 UI, 콘텐츠는 동일(경로 규칙 동일)미관측실측(curl) — 정상 서빙, 단 about.json은 차단(§3)
new.reddit.com, np.reddit.com, m.reddit.com신버전 UI / no-participation(브리게이딩 방지 공유) / 모바일(경로 규칙 동일 추정)미관측웹검색 — Reddit 운영 도메인 확인(medium confidence, 원문 미확인)

postId·shareId 둘 다 "포스트"를 가리키지만 shareId는 postId와 무관한 별도 토큰이라 리다이렉트 없이는 실제 포스트를 알 수 없다 — §1a "Share link" 참고.

2. sourceType / sourceKey 확정 👤 판단 반영

router 리뷰(대화)에서 합의한 4-way 분리 + redd.it 반영. 설계문서(social-fetcher-implementation-plan.html) 원안은 sub/postId였는데 구현이 subreddit-only로 축소돼 있었음.

sourceType대상 패턴sourceKey근거사용자 결정
reddit유지
/r/{sub}, /r/{sub}/search, /r/{sub}/new
subreddit(소문자)콘텐츠가 아닌 subreddit 컨텍스트 — 현재 로직 그대로✅ 기존 제안 유지(확정). key=subreddit 소문자.
reddit_post신규
/r/{sub}/comments/{postId}/{slug}, redd.it/{postId}
postId설계문서 원래 의도 복원 — post-level fetch 경로(§3) 필요✅ 신규 타입 확정. redd.it/{postId}도 여기 편입 — 실측상 redd.it은 차단되지 않고(301 정상) URL에 이미 postId가 있어 리다이렉트 해석도 불필요.
reddit_share신규
/r/{sub}/s/{shareId}
shareId(raw)router는 순수함수 유지(네트워크 없이 shareId만 추출) — 진짜 postId는 fetcher가 redirect resolve로 확보key=shareId 유지 확정. audit 조건문("post에 대한 share면 post로, 안되면 shareId로")이 실측상 "안된다"로 판정 — /s/ 뿐 아니라 일반 subreddit·post·old.reddit 전부 403이라 Reddit이 우리 IP를 전면 차단(§3 about.json 403과 동일 원인). 유료 Apify 경유 해석은 후속 과제.
reddit_user신규
/user/{name}, /u/{name}
username(소문자)지금은 fetcher가 URL 문자열을 정규식으로 sniffing해서 분기 — X/Telegram처럼 타입 자체로 분리✅ 신규 타입 확정. key=username — Reddit username은 변경 불가라 그대로 canonical 조인키.
커버리지 갭(이번 스코프 밖, 후속 이슈로만 기록)old.reddit.com / new.reddit.com / np.reddit.com / m.reddit.comclassify()의 host 체크가 host === 'reddit.com'만 봐서 전부 website로 오분류됨. redd.it은 host 체크 대상이라 reddit으로는 분류되지만, 위 4-way 분리 전까지는 postId를 subreddit으로 착각해서 /r/{postId}/about.json을 호출하는 버그 상태.

3. Fetch 테스트 + 에러 원인

3.1 실측 (curl, RedditFetcher와 동일 엔드포인트/헤더)

GET https://www.reddit.com/r/smallstreetbets/about.json  (UA: social-fetcher/1.0)
→ HTTP 403, content-type: text/html   (JSON 아님 — 차단 페이지)

GET https://www.reddit.com/user/ElegantCombination43/about.json  (UA: social-fetcher/1.0)
→ HTTP 403, content-type: text/html

GET https://www.reddit.com/r/smallstreetbets/about.json  (UA: 브라우저 Chrome UA로 교체)
→ HTTP 403, content-type: text/html   (UA 문제 아님을 확인)

GET https://old.reddit.com/r/smallstreetbets/about.json  (UA: 브라우저)
→ HTTP 403, content-type: text/html   (old.reddit.com도 동일 차단)

GET https://redd.it/eorhm
→ HTTP 301 → https://www.reddit.com/comments/eorhm   (리다이렉트 자체는 차단 안 됨)

GET https://www.reddit.com/r/TheMatpatEffect/s/RT8o3wyoRD
→ HTTP 301 → .../comments/1u3vkxd/the_original_hopium_needle_guy_image/?share_id=...   (리다이렉트 정상)

GET https://www.reddit.com/r/TheMatpatEffect/s/RT8o3wyoRD.json
→ HTTP 307 → https://www.reddit.com/r/TheMatpatEffect/   (post 데이터 없이 subreddit root로 튕김 — .json 트릭이 공유링크엔 안 먹음)

3.2 원인 분석

about.json 계열은 100% 403fetched.reddit[](161건) 전량 status: 'error'와 정확히 일치. User-Agent를 바꿔도, old.reddit.com으로 바꿔도 동일하게 차단됨 → UA/파싱 문제가 아니라 이 환경(서버 egress IP)에 대한 Reddit의 봇 차단(WAF/anti-scraping). 다만 이 403은 비인증 www.reddit.com 도메인 대상이라는 점이 중요 — 인증된 oauth.reddit.com API 서버까지 막는지는 별개 문제(§3.3 참고, 미확인).

3.3 해결 옵션 (결정 필요, 이번 audit에서 바로 고르지 않음)

공식 API 상태 업데이트(웹 리서치, 2026-07-17) — Reddit이 2025년 말 "Responsible Builder Policy"를 도입하면서 reddit.com/prefs/apps의 self-service 앱 등록(즉시 무료 발급)이 닫혔다. 이제는 승인 신청을 거쳐야 client_id/secret이 나오는 구조로 바뀐 것으로 보인다. OAuth2 메커니즘 자체(client_credentials grant, 로그인 유저 없이 서버가 바로 토큰 발급)와 무료 티어(비상업 용도 OAuth 클라이언트당 분당 100건)는 여전히 존재해서 우리 볼륨(하루 수백~수천 건)엔 충분하지만, 등록이 가능한지 자체가 게이트가 됐다. 신청 흐름 상세는 §3.4.
확신도 — 정책 존재: 중간(1차 문서 직접 확인 못 함, 2차 출처 다수 일치) · 인증된 oauth.reddit.com까지 WAF가 막는지: 미확인(우리가 실측한 403은 비인증 도메인 대상)
옵션설명트레이드오프
A. Reddit 공식 OAuth APIscript 타입 앱 등록 → client_id/secret으로 인증된 요청. 단, 신청/승인 절차 필요(§3.4)승인만 나면 credential 관리 정도로 끝, rate limit(100 QPM) 내에서 안정적. 승인 자체가 불확실(거절/무응답 흔하다는 2차 보고)
A-1. Devvit조사 결과 부적합 — 기각 Reddit이 "Devvit으로 안 되면 수동신청" 순서로 안내하길래 확인했으나, Devvit은 특정 subreddit에 설치되어 Reddit 쪽 이벤트로 실행되는 인터랙티브 앱 플랫폼 — 우리처럼 "외부 이벤트로 임의 subreddit을 온디맨드 조회"하는 구조 자체를 지원 안 함. 토큰도 Devvit 내부에서만 쓰이고 외부로 노출되지 않음. 게다가 Devvit을 거쳐도 결국 A와 동일한 Responsible Builder Policy 승인 게이트를 만남사용 불가로 결론 — 더 조사 불필요
B. 프록시/주거용 IP 사용요청을 프록시 경유로 우회(비인증 www.reddit.com 경로 유지)비용 발생, 별도 인프라, 승인 절차 없이 바로 시도 가능
C. Known limitation으로 문서화, 방치reddit 신호는 fetch 불가 상태 유지, errors[]로만 계속 기록구현 비용 0, 대신 reddit 관련 signal(subscriber growth 등)은 영구히 확보 불가
D. Apify (harshmaur~reddit-scraper)채택 — 실측 완료, 4개 타입 전부 성공(§3.5) 기존 tiktok/instagram과 동일한 ApifyClient.runActorSync() 패턴 재사용 가능비용 발생(건당 ~$0.022, pay-per-event), 승인 절차 없이 바로 사용 가능. subreddit/post/user는 이 actor로 직접, share만 우리 쪽 redirect resolve 한 스텝 추가 — §3.5

2026-07-17 결정: A(공식 API 승인)는 대기시간이 불확실해서 D(Apify)로 진행. 1차로 시도한 actor(trudax~reddit-scraper-lite)는 subreddit/user에서 반복 실패해 harshmaur~reddit-scraper로 교체, 4개 타입 전부 실측 성공(§3.5). A는 병행 신청 후보로 남겨둠(§3.4).

3.4 공식 API 신청 흐름 (Responsible Builder Policy 이후)

이 섹션 전체의 확신도: 낮음~중간. 조사 에이전트도 reddit.com / support.reddithelp.com 직접 fetch가 403으로 막혀서 1차 공식문서를 확인 못 했다. 아래는 2차 출처(블로그·커뮤니티 요약) 교차 인용 기반이고, 그중 상세도가 가장 높은 출처 2곳(redditapis.com, molehill.io)은 유료 프록시/대체 API를 파는 벤더라 "공식 절차가 어렵다"고 서술할 금전적 유인이 있다 — 사실관계는 액면 그대로 받아들이지 말 것. 프로덕션 결정 전에 reddit.com/prefs/apps에 직접 신청해서 ground truth를 확인하는 걸 권장한다.
단계내용확신도
1. 시작 지점reddit.com/prefs/apps에서 앱 생성(이름·타입=script·redirect URI)은 예전과 동일. 다만 이제 이것만으론 토큰이 안 나오고, 별도 승인 절차(Developer Support 티켓/폼)를 통과해야 실제 client_id/secret이 활성화되는 구조로 보임중간
2. 신청 시 요구 정보use-case 설명(무슨 데이터를 읽는지, 쓰기 작업 여부) · 예상 호출량 · 대상 subreddit/범위 · 데이터 보존/삭제 정책(Reddit이 48시간 삭제 요구를 강제한다는 보고 — 미확인, §하단 참고) · 살아있는 privacy policy 링크 · 경우에 따라 아키텍처 다이어그램/영상낮음~중간
3. 신청자 카테고리Developer(비상업) · Researcher(별도 r/reddit4researchers 트랙, 기관 윤리승인 필요) · Moderator(본인이 운영하는 subreddit, 특히 10만+ 구독자 sub — 승인율 가장 높다고 보고됨) · Commercial(별도 파트너 트랙, 가장 까다로움)중간
4. 우리 케이스가 어디에 속하는지크립토 트레이딩 회사의 내부 백엔드, 저볼륨 읽기전용 메타데이터 조회 — 공개 서비스도 아니고 Reddit 데이터를 상업적으로 재판매하지도 않지만, 회사 소속 상업적 활동이라 "Developer"에도 "Commercial"에도 깔끔하게 안 들어맞음. 2차 출처들이 공통으로 지적하는 "애매한 케이스가 거절/무응답으로 이어지는" 전형적 패턴낮음(추정)
5. 소요기간·승인율공식 SLA 확인 안 됨(한 출처는 "7일 목표"라 주장, 다른 출처는 "SLA 없음, 수 주~무기한"). 개인/소규모·설명 모호한 케이스가 가장 많이 거절되거나 무응답이라는 보고 다수낮음
6. 승인되면표준 OAuth2 client_credentials flow 그대로 사용(§3.3 옵션 A와 동일), 무료 티어 분당 100건, User-Agent 포맷 여전히 요구. 별도 SDK 강제나 호스팅 제약은 보고 안 됨중간
7. Grandfathering정책 시행 이전에 이미 발급된 credential은 계속 동작한다는 보고 다수(예: Apollo-Reborn 프로젝트가 옛 앱 credential을 재사용). 신규 앱 등록은 계정에 예전 승인 이력이 있어도 다시 승인 게이트를 거쳐야 함중간

체크할 것: 회사 내에(다른 팀 포함) 2025년 말 이전에 등록된 Reddit API credential이 이미 있는지 — 있다면 grandfathering으로 바로 쓸 수 있어 승인 절차를 건너뛸 가능성.

3.5 Apify 실측 테스트 결과

1차로 시도한 trudax~reddit-scraper-lite는 post/share만 되고 subreddit/user가 반복 실패해서(아래 3.5.1), 2차로 harshmaur~reddit-scraper를 테스트한 결과 4개 타입 전부 커버 가능함을 확인했다(3.5.2). 최종 채택은 harshmaur.

3.5.1 1차 시도 — trudax~reddit-scraper-lite (post/share만 성공)

조회 대상결과비고
Post성공 (1/1)title/username/createdAt 정확
Share(/s/{shareId})성공 (1/1)actor가 redirect를 내부적으로 resolve — 응답의 parsedId가 우리가 curl로 수동 확인한 진짜 postId(1u3vkxd)와 정확히 일치
Subreddit(community)전부 실패 (0/3, TIMED-OUT)서로 다른 subreddit 2개, 총 3회 시도 전량 실패
User실패 (TIMED-OUT)

3.5.2 2차 시도 — harshmaur~reddit-scraper (채택)

30일 통계 SUCCEEDED 143,805 / TOTAL 149,079 ≈ 96.5%(trudax 68%보다 훨씬 안정적), 4.9★/17리뷰, 오늘(2026-07-17) 빌드 갱신. 가격은 PAY_PER_EVENT — actor 시작 $0.02 + 저장된 결과 1건당 $0.002(공식 API로 직접 확인, 확신도 높음). 결과 1건짜리 단건 조회 기준 건당 약 $0.022.

조회 대상입력결과받은 데이터
Subreddit(reddit)startUrls + maxPostsCount: 0성공 dataType: "community" 1건title/publicDescription/membersCount/createdAt/nsfw — §4에서 필요하다고 정리한 5개 필드 전부
Post(reddit_post)startUrls성공 dataType: "post" 1건title/authorName/createdAt + 보너스로 score/upVotes/subredditSubscribers/commentsCount 등 다수
Share(reddit_share)startUrls/s/{shareId}를 그대로 줌실패 — 0건 반환 (에러도 없이 그냥 빈 결과)이 actor는 share 링크를 못 품 — 아래 우회 필요
Share, 우리가 redirect 먼저 resolveplain HTTP로 301 Location 확보(§3.2에서 이미 확인한 방식, Apify 비용 없음) → 그 canonical post URL을 startUrls성공Post와 동일 — parsedId가 진짜 postId(1u3vkxd)로 정확히 나옴
User(reddit_user)startUrls + maxPostsCount: 0 + maxCommentsCount: 0성공 dataType: "user_profile" 1건만 깨끗하게createdAt + totalKarma/linkKarma/commentKarma/verified 등. maxPostsCount만으론 댓글이 안 걸러져서(기본 10건) maxCommentsCount: 0도 같이 필요했음
최종 아키텍처 — actor는 harshmaur~reddit-scraper 하나로 통일. reddit_share만 예외적으로 우리 쪽에서 plain HTTP 301 redirect를 먼저 resolve(Apify 호출 아님, 무료)해서 진짜 post URL로 바꾼 뒤 나머지 3개 타입과 동일하게 처리. router는 여전히 순수함수 유지(redirect resolve는 router가 아니라 fetcher 책임 — §2 원칙과 일치).
// Subreddit 실측 응답 (community, 2026-07-17)
{
  "name": "wallstreetbets", "title": "wallstreetbets",
  "publicDescription": "Like 4chan found a Bloomberg Terminal.",
  "membersCount": 20159454,
  "createdAt": "2012-01-31T21:26:07.000Z",
  "nsfw": false, "dataType": "community"
}

// Post 실측 응답 (share 링크 resolve 후 동일 경로로 조회, 2026-07-17)
{
  "parsedId": "1u3vkxd", "title": "The Original \"Hopium\" Needle Guy Image",
  "authorName": "Rytheredditguy", "createdAt": "2026-06-12T13:39:54.000Z",
  "parsedCommunityName": "TheMatpatEffect", "dataType": "post"
}

// User 실측 응답 (user_profile, 2026-07-17)
{
  "username": "hell0k1ttycum", "totalKarma": 3790,
  "linkKarma": 2280, "commentKarma": 1510, "verified": true,
  "createdAt": "2023-03-13T08:58:18.000Z", "dataType": "user_profile"
}

4. 객체별 필드 통합 (변동성 · 파싱상태 · 트레이딩 유용성) 👤 판단 반영

Creator/Content 객체별로 필드를 한 표에 통합. 값 변동성 = 값이 시간에 따라 변하나(immutable 역사적 사실 / mutable 드리프트 / derivable 계산값) → look-ahead 안전성이 여기서 도출(immutable=safe · mutable=conditional/as-of · derivable=unsafe). 파싱 상태 = 현재 파이프라인이 실제로 저장하나.

4.1 · Venue (subreddit) — reddit (subreddit)

source: Apify harshmaur~reddit-scraper, dataType=community (확정) · membersCount 등 mutable은 actor crawledAt 스냅샷. immutable은 look-ahead safe. 현재 RedditFetcher는 about.json(§3, 100% 403)이라 실캡처 0 — Apify 전환 시 확보(parseStatus=dropped=구현 대기)
필드설명값 변동성파싱 상태(현재)최종 결정트레이딩 유용성
namesubreddit slug(sourceKey와 동일). 예: 'wallstreetbets'immutable⚠️ actor제공·미파싱🔑 keycontext — 사칭 subreddit 대조
membersCount구독자 수. 예: 20159454mutable⚠️ actor제공·미파싱keepcontext — 도달 규모(밈 모멘텀 직결은 약함)
createdAtsubreddit 생성 시각. 예: '2012-01-31T...'immutable⚠️ actor제공·미파싱keepactionable — 러그리스크(런칭 직전 급조 subreddit)
title / publicDescription / description표시 제목·태그라인·사이드바 설명mutable⚠️ actor제공·미파싱keepcontext — 사칭/내러티브(운영자 변경 가능이라 conditional)
nsfw / over18성인 컨텐츠 여부. 예: falsemutable⚠️ actor제공·미파싱removenoise — 거의 고정, 매매 무관
id / parsedIdReddit 내부 fullname/ID. 예: 't5_2th52' / '2th52'immutable⚠️ actor제공·미파싱removenoise — join 안정키이나 name이 이미 불변이라 불필요
crawledAtactor가 이 아이템을 스크랩한 시각. 2026-07-29 rubric 11b에서 발견 — snapshotNote가 "mutable은 actor crawledAt 스냅샷"이라 설명하면서 정작 필드가 없었음immutable⚠️ actor제공·미파싱keepactionable — mutable 지표의 as-of 페어링 앵커. 이게 없으면 시점 없는 카운트를 저장하게 돼 look-ahead 방어가 깨짐

4.2 · Content (post) — reddit_post (post)

source: Apify harshmaur~reddit-scraper, dataType=post (확정) · upVotes/score 등 mutable은 actor crawledAt 스냅샷 — as-of 페어링 필요. createdAt/parsedId는 immutable
필드설명값 변동성파싱 상태(현재)최종 결정트레이딩 유용성
parsedIdpost 고유 base36 ID (sourceKey와 동일)immutable⚠️ actor제공·미파싱🔑 keycontext — 원본/카피 대조
authorName작성자 username — creator 조인키. Reddit username은 불변이라 그대로 링크 가능immutable⚠️ actor제공·미파싱🔑 keycontext — 누가 올렸나 + creator 링크
title게시물 제목immutable⚠️ actor제공·미파싱keepactionable — 내러티브/티커 언급
body / bodyHtml게시물 본문(텍스트/HTML). 2026-07-29 완료 rubric 11b에서 발견 — 문서 objectFields에 통째로 빠져 있었음immutable⚠️ actor제공·미파싱keepactionable — CA·티커·내러티브가 담기는 곳. IG caption·TikTok text·X text에 해당하는 콘텐츠 재사용/카피캣 판별의 1차 매칭 입력
createdAt게시 시각immutable⚠️ actor제공·미파싱keepactionable — 타이밍/신선도(원본 vs 재활용)
upVotes / score추천 수 / 점수mutable⚠️ actor제공·미파싱keepactionable — 모멘텀
commentsCount댓글 수mutable⚠️ actor제공·미파싱keepactionable — 참여 강도
upvoteRatio추천 비율mutable⚠️ actor제공·미파싱keepcontext — 논란도
numCrossposts / subredditSubscribers크로스포스트 수 / 게시 시점 subreddit 구독자mutable⚠️ actor제공·미파싱keepcontext — 확산/도달
removedByCategory / removedBy / bannedBy삭제 주체·사유 분류mutable⚠️ actor제공·미파싱keepactionable — 러그리스크(모더레이터 삭제 = 스캠/스팸 tell)
domain / outboundUrlHost링크 게시물의 외부 도메인immutable⚠️ actor제공·미파싱keepactionable — 드레이너/사칭 도메인 tell
postType / isSelf / isVideo / stickied / distinguished게시물 형식 / 텍스트글 여부 / 영상 여부 / 모더레이터 상단 고정 / 모더레이터·관리자 자격 작성 표시mutable⚠️ actor제공·미파싱removecontext — stickied/distinguished는 공식성 약한 보조, 나머지 noise
ageHours / scorePerHour / commentsPerHour / engagementTotal / commentToScoreRatio파생 지표derivable❌ 없음skipnoise — actor scrapedAt 기준이라 저장 금지, score+createdAt raw만 저장 후 as-of 재계산
crawledAtactor가 이 아이템을 스크랩한 시각. 2026-07-29 rubric 11b에서 발견 — snapshotNote가 "mutable은 actor crawledAt 스냅샷"이라 설명하면서 정작 필드가 없었음immutable⚠️ actor제공·미파싱keepactionable — mutable 지표의 as-of 페어링 앵커. 이게 없으면 시점 없는 카운트를 저장하게 돼 look-ahead 방어가 깨짐

4.3 · Creator (user) — reddit_user (user)

source: Apify harshmaur~reddit-scraper, dataType=user (확정) · karma/followersCount는 mutable 스냅샷. username/createdAt은 immutable
필드설명값 변동성파싱 상태(현재)최종 결정트레이딩 유용성
username계정 고유 이름 — 변경 불가·삭제 후 재사용 불가라 영구 키로 안전immutable⚠️ actor제공·미파싱🔑 keycontext — 사칭 계정 대조 + 조인키
createdAt계정 생성 시각immutable⚠️ actor제공·미파싱keepactionable — 러그리스크(런칭 직전 급조 계정)
totalKarma / linkKarma / commentKarma누적 카르마mutable⚠️ actor제공·미파싱keepcontext — 계정 신뢰도(급조+저karma = 봇/스팸 tell)
followersCount팔로워 수mutable⚠️ actor제공·미파싱keepcontext — 도달(reddit은 약함)
bio자기소개 텍스트mutable⚠️ actor제공·미파싱keepcontext — 사칭/링크 tell
verified / hasVerifiedEmail / isGold인증·유료 배지mutable⚠️ actor제공·미파싱skipnoise — 신뢰 신호 약함
isMod / isEmployee모더레이터·직원 여부mutable⚠️ actor제공·미파싱skipnoise
crawledAtactor가 이 아이템을 스크랩한 시각. 2026-07-29 rubric 11b에서 발견 — snapshotNote가 "mutable은 actor crawledAt 스냅샷"이라 설명하면서 정작 필드가 없었음immutable⚠️ actor제공·미파싱keepactionable — mutable 지표의 as-of 페어링 앵커. 이게 없으면 시점 없는 카운트를 저장하게 돼 look-ahead 방어가 깨짐

4-부록. raw 스키마 (ground truth)

위 통합표의 원천 — Apify 응답 원문 + 레거시 about.json. 필드 전체 목록은 여기서 확인.

4.1 (구) about.json 스키마 레거시 — Apify로 대체 예정

비인증 about.json(§3.1~3.3, 100% 403) 기준으로 작성했던 원래 필드 정리. RedditFetcher가 Apify로 교체되면 이 스키마 자체가 안 쓰인다 — 과거 판단 기록으로만 남겨둠.

필드valueNature의미검토
createdAtfixedsubreddit(또는 유저 계정) 생성 시각✅ 타당함 — 생성일은 불변, liveTags age anchor 계산에 적합
subscribersmutable구독자 수 — §1a Karma/Subscribers✅ 타당함 — 시간에 따라 계속 변하는 관측시점 스냅샷
titlefixedsubreddit 표시 제목재검토 운영자가 언제든 바꿀 수 있어 엄밀히는 fixed 아님. 변경 빈도는 낮지만 look-ahead 관점에선 Conditional에 가까움
publicDescription선언 없음subreddit 소개 문구버그 후보 data엔 있는데 valueNature엔 키 자체가 없음 → 소비 측에서 undefined. title과 마찬가지로 mutable/conditional에 가까움
over18선언 없음NSFW 여부 — §1a NSFW버그 후보 valueNature 누락. 의미상으로는 거의 fixed에 가까움
(user fetch 시 동일 인터페이스 재사용)미확인 /user/{name}/about.json도 같은 RedditAbout 인터페이스로 파싱하는데, 실제 Reddit 유저 프로필 JSON 스키마가 subreddit과 동일한지 §3의 403 차단 때문에 실측 불가. OAuth 확보 후 재검증 필요

4.2 Apify(harshmaur~reddit-scraper) 응답 스키마 레퍼런스 raw — keep/drop 판단 전

§3.5 실측에서 실제로 받은 응답 원문 기준, dataType별 전체 필드. 유용성 판단(어떤 필드를 실제로 저장할지)은 다음 단계 — 여기는 스키마 그 자체만.

4.2.1 dataType: "community" (subreddit)

필드예시값설명
id"t5_2th52"Reddit 내부 fullname(t5_ = subreddit 타입 접두사)
parsedId"2th52"id에서 접두사 뗀 순수 ID
name"wallstreetbets"subreddit slug(우리 sourceKey와 동일)
title"wallstreetbets"표시 제목(name과 다를 수 있음)
description(긴 마크다운 — 규칙/위키 링크 등)사이드바 전체 설명
descriptionHtml(HTML)description의 HTML 렌더
publicDescription"Like 4chan found a Bloomberg Terminal."한 줄 소개(태그라인)
publicDescriptionHtml(HTML)publicDescription의 HTML 렌더
membersCount20159454구독자 수
communityIcon(이미지 URL)subreddit 아이콘
bannerImage""배너 이미지(없으면 빈 문자열)
createdAt"2012-01-31T21:26:07.000Z"subreddit 생성 시각
crawledAt"2026-07-17T06:41:06.762Z"actor가 이 데이터를 긁은 시각(우리 fetchedAt과 별개)
nsfwfalse성인 컨텐츠 subreddit 여부
dataType"community"이 아이템의 타입 태그(post/user_profile/comment와 구분)
url(subreddit URL)정규화된 원본 URL

4.2.2 dataType: "post"

2개 actor에서 관측된 필드를 합침 — trudaxid/parsedId/url/username/title/communityName/parsedCommunityName/body/html/createdAt/scrapedAt/dataType 정도만 주고, harshmaur가 아래처럼 훨씬 풍부하게 준다(채택은 harshmaur).

필드예시값설명
id / parsedId"t3_1usx4yr" / "1usx4yr"포스트 fullname(t3_) / postId(base36)
title"Weekly Earnings Thread 7/13 - 7/17"포스트 제목
body / bodyHtml"" / null본문(텍스트 포스트만 값 있음, 이미지/링크 포스트는 보통 빈 값)
contentUrl(이미지 URL)첨부 미디어 원본 URL
authorId / parsedAuthorId"t2_17bz2ly0" / "17bz2ly0"작성자 fullname / ID
authorName"OSRSkarma"작성자 닉네임
communityName / parsedCommunityName"r/wallstreetbets" / "wallstreetbets"소속 subreddit
communityId / parsedCommunityId"t5_2th52" / "2th52"소속 subreddit fullname/ID
flair / authorFlairText"Earnings Thread" / "Flipping at the Grand Exchange"포스트/작성자 커스텀 태그(subreddit마다 어휘 다름)
images(URL 배열)첨부 이미지 목록
postType"image"포스트 유형(image/video/link/self 등)
upVotes / score182추천수(두 필드가 같은 값을 중복 표기)
commentsCount707댓글 수
postUrl(permalink)원본 URL(envelope의 sourceUrl과 중복)
createdAt"2026-07-10T19:10:46.000Z"작성 시각
upvoteRatio0.96추천/전체 투표 비율(0~1)
over18false성인 컨텐츠 플래그
isSelf / isVideo / isGalleryfalse/false/false텍스트/영상/갤러리 포스트 여부
spoiler / locked / hidden / archivedfalse ×4스포일러 표시 / 댓글잠금 / 숨김 / 보관(수정불가) 여부
pinned / stickiedfalse / true프로필 고정 / subreddit 상단 고정(모더레이터가 고정한 정기 스레드류)
edited / editedAtfalse / null수정 여부/시각
distinguishednull모더레이터/관리자 자격으로 작성했는지(값 있으면 "moderator" 등)
scoreHiddenfalse추천수 비공개 여부
isOriginalContentfalseOC(원작) 태그 여부
numCrossposts2다른 subreddit으로 크로스포스트된 횟수
totalAwardsReceived / gilded0 / 0어워드(유료 리액션) 수
domain"i.redd.it"링크 포스트의 외부 도메인(자체 이미지 호스팅이면 reddit 도메인)
thumbnail(URL)썸네일 이미지
urlOverriddenByDest / outboundUrlHost(URL) / "i.redd.it"실제 링크 대상 URL / 그 호스트명
subredditSubscribers20159450포스트 조회 시점의 소속 subreddit 구독자수(§4.2.1 membersCount와 같은 값, 포스트 응답에 보너스로 포함)
authorPremiumtrue작성자의 Reddit Premium(유료) 가입 여부
numDuplicates0동일 콘텐츠의 중복 포스트 수
removedByCategory / removedBy / bannedBy / removalReason / modReasonTitle전부 null(이 예시는 삭제 안 됨)모더레이터/자동시스템에 의한 삭제 여부와 사유
isRobotIndexabletrue검색엔진 색인 허용 여부
media / secureMedia / mediaMetadata / galleryData / galleryImages / mediaAssets / galleryCount / videoUrl / mediaType / hasMedia대부분 null/빈 배열, mediaType: "image", hasMedia: true첨부 미디어 상세 렌더링 정보 일체
ageHours155.4803actor의 scrapedAt 기준 경과 시간(시간 단위) — 우리 시스템 as-of와 무관하게 actor 스크랩 시점에 고정 계산됨
scorePerHour / commentsPerHour1.1706 / 4.5472ageHours 기준으로 actor가 계산한 시간당 추천/댓글 속도
engagementTotal / commentToScoreRatio / isHighEngagement889 / 3.8846 / trueactor가 자체 정의한 참여도 파생 지표(정의 방식 비공개)
titleLength / bodyLength / wordCount34 / 0 / 6제목/본문 길이, 단어수(actor가 계산)
dataType"post"타입 태그

4.2.3 dataType: "user_profile"

필드예시값설명
id / parsedId"t2_w63u27cf" / "w63u27cf"유저 fullname(t2_) / ID
username"hell0k1ttycum"계정명(우리 sourceKey와 동일)
totalKarma / linkKarma / commentKarma3790 / 2280 / 1510전체/포스트/댓글 평판 점수
awardeeKarma / awarderKarma0 / 0어워드 받은/준 karma
isGoldfalseReddit Premium(유료) 가입 여부
isMod / isEmployeefalse / false모더레이터 / Reddit 직원 계정 여부
hasVerifiedEmail / verifiedtrue / true이메일 인증 여부(두 필드 동일 의미로 중복)
iconImg / snoovatarImg / bannerImg(URL) ×3프로필 아바타/배너 이미지
acceptFollowerstrue팔로우 허용 여부(설정값)
bio""자기소개 텍스트
followersCount0팔로워 수
profileUrl(URL)원본 프로필 URL(envelope의 sourceUrl과 중복)
createdAt"2023-03-13T08:58:18.000Z"계정 생성 시각
crawledAt(스크랩 시각)actor가 이 데이터를 긁은 시각
dataType"user_profile"타입 태그

참고: user URL을 조회하면 maxCommentsCount를 0으로 안 주면 dataType: "comment"(그 유저가 단 최근 댓글들)가 같이 딸려온다 — §3.5에서 maxPostsCount: 0 + maxCommentsCount: 0으로 억제한 이유. comment 아이템 자체 스키마는 이번 audit 범위 밖(우리는 항상 억제해서 안 받을 예정)이라 여기 정리 안 함.

다음 액션 (결정 필요)