Skip to content

feat(crawlers): 댓글과 답글을 정본 본문에 담는다 - #13

Merged
seungwonme merged 5 commits into
mainfrom
worktree-feat-comments-extraction
Aug 10, 2026
Merged

feat(crawlers): 댓글과 답글을 정본 본문에 담는다#13
seungwonme merged 5 commits into
mainfrom
worktree-feat-comments-extraction

Conversation

@seungwonme

Copy link
Copy Markdown
Owner

TL;DR

댓글/답글을 content_markdown에 담는다. 이전에는 hackernews만 토론 본문을 저장했고,
나머지는 댓글 만 남기고 내용을 버려서 "DB는 소비 준비가 끝난 상태"라는 계약을 어기고 있었다.

7개 플랫폼을 붙였고, 그중 둘은 추가 요청 없이 얻는다.

작업 중 발견한 GeekNews 403 회귀도 함께 고쳤다.

플랫폼별

플랫폼 섹션 추가 요청
geeknews ## GeekNews Comments 없음 — 지표 수집이 이미 받던 토픽 HTML 재사용
x ## X Replies 스레드는 없음 — TweetDetail 응답에서 버리던 답글 활용
reddit ## Reddit Comments 게시글당 1건 (초당 1요청)
linkedin ## LinkedIn Comments 게시글당 1건
youtube ## YouTube Comments 영상당 yt-dlp 1회
producthunt ## Product Hunt Comments 제품당 1건
threads ## Threads Replies 답글 있는 게시물 전부, 게시물당 1건

포맷은 skim_core.comments가 단독으로 책임진다. 각 크롤러는 응답을 Comment로 정규화만 한다.

함께 고친 것: GeekNews 403

news.hada.io2026-08-09부터 Chrome/124를 차단하기 시작했다. FEED_HEADERS가 정확히 124여서
그날 21건 중 10건이 지표 없이 저장됐다(8/8까지 결측 0). 128 이상이 통과하는 걸 확인해 139로 올렸다.

threads: doc_id 없이 답글을 얻는 방법

타임라인 GraphQL 응답에는 타인 답글이 오지 않는다. 대신 게시물 문서의 SSR 페이로드가 답글까지 담고
있고 로그인도 필요 없어서, persisted query 좌표를 새로 떠안지 않았다.

두 가지 함정:

  • threads.net으로 요청하면 리다이렉트 뒤 페이로드가 빠진 255KB 셸이 온다. threads.com으로 직접 받으면 843KB에 답글이 들어온다.
  • 작성자 self-reply 연작이 답글과 같은 edges 배열에 담긴다. 그대로 두면 24편짜리 연작이 통째로 "답글"로 중복 저장된다.

페이지네이션은 의도적으로 넣지 않았다. 문서가 주는 것보다 더 가져오려면
BarcelonaPostPageRefetchableDirectQuery를 4건씩 반복 호출해야 하는데, 그 요청은 세션 쿠키와
x-fb-lsd 토큰을 요구해 로그인 계정으로 식별된다. 답글 수집량보다 계정 안전을 택했다.

조사 중 정정한 판단 3건
  • reddit 403을 레이트 리밋으로 진단했으나, 실제로는 worktree에 data/sessions/가 없어 비로그인 상태였다.
  • x 답글에 대화 밖 광고 트윗이 섞여 들어왔다. conversation_id 필터를 추가했다.
  • threads 답글 조회 임계를 x에서 복사한 3으로 뒀는데, 답글 1~2개인 게시물 12.6%(519건)를 버리고 있었다. direct_reply_count가 삭제·비공개 답글까지 세는 부정확한 값이라 1로 낮췄다.
알려진 한계
  • comments가 0보다 커도 답글 섹션이 안 붙을 수 있다. 삭제·비공개 답글까지 세는 값이라 실제 노출 답글이 없는 게시물이 있다(브라우저로 열어도 안 보인다). 추출 실패로 오판하지 않도록 AGENTS.md에 적어뒀다.
  • threads는 문서가 한 번에 주는 만큼(실측 최대 24건)이 전부다. 답글 160개짜리 글은 24개만 들어온다.
  • 기존 데이터는 소급 적용되지 않는다. 앞으로 크롤하는 것부터 적용된다.

크롤 시간 영향

reddit, linkedin, producthunt, threads는 게시글당 요청이 1건씩 는다. 급하면 --no-content로 댓글
수집만 끌 수 있다(geeknews와 x 스레드 경로는 원래 추가 요청이 없어 영향 없음).

검증

  • flake8 통과, pylint 10.00/10
  • Python 371개 통과 (댓글 관련 22개 신규), Swift 18개 통과
  • 실제 크롤로 DB까지 확인: geeknews 30건 중 댓글 있는 26건 전부 섹션 부착, threads는 한 게시물에 답글 21개 저장

seungwonme added 5 commits August 10, 2026 08:13
news.hada.io started rejecting Chrome/124 on 2026-08-09 — that day 10 of 21
geeknews rows landed without metrics (0 gaps the day before). Verified 124 is
blocked and 128+ passes, so the shared feed UA moves to 139.
Only hackernews stored discussion text; geeknews/reddit/x/linkedin kept a
comment count and dropped the content, which breaks the AGENTS.md contract
that content_markdown is consumption-ready.

Adds skim_core.comments (platform-neutral Comment + section renderer) and
wires six crawlers to it. Two cost nothing extra: geeknews reuses the topic
HTML already fetched for metrics, and x reuses the TweetDetail response whose
non-author replies were being filtered out. Standalone tweets are re-fetched
only when they have 3+ replies, capped at 20 per run.

threads is left out: its timeline response carries no third-party replies and
a separate post-detail doc_id would have to be captured from the web app.
The timeline GraphQL response carries no third-party replies, so threads was
the one platform left storing only a reply count. The post page turns out to
ship the replies in its SSR payload and needs no login, so no new persisted
query coordinate has to be maintained.

Two traps this had to work around: requesting threads.net redirects to a shell
with the payload stripped (threads.com must be hit directly), and the author's
own self-reply chain sits in the same edges array as the replies, which would
duplicate body text already stored.

Fetches only posts with 3+ replies, capped at 20 per run.
The threshold of 3 was copied from x, where a re-fetch costs 3-9s and has to
be rationed. A threads reply fetch is a single GET (1-4s measured), and the
per-run cap already bounds the cost, so the threshold only decides where that
budget goes.

Gating on `comments` at 3 dropped 12.6% of posts (519 of 4132 have 1-2
replies), and that field is unreliable in both directions: it counts deleted
and private replies, and a post reporting 1 reply yielded 2. Only skip posts
reporting zero.
Two caps were discarding replies that had already been fetched. The per-run
cap of 20 posts skipped whole posts, and MAX_REPLIES=15 truncated documents
that carried more - one post shipped 24 replies and 9 were dropped. A crawl
now stores 21 replies on a single post where 15 was the ceiling before.

render_comment_section takes max_comments=None for "keep everything".

Pagination is deliberately not added. Going past what the document ships
requires BarcelonaPostPageRefetchableDirectQuery in batches of 4, and that
request needs session cookies and an x-fb-lsd token, so it is attributable to
the logged-in account. The current path needs no login at all, and keeping
that property was chosen over collecting more replies.
@seungwonme
seungwonme merged commit 5e41658 into main Aug 10, 2026
3 checks passed
@seungwonme
seungwonme deleted the worktree-feat-comments-extraction branch August 10, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant