자체 방문 통계는 항상 수집됩니다. 사용자가 공개 페이지를 어떻게 이용하는지 파악하는 데 쓰이는 Google Analytics(로그인한 워크스페이스는 제외)는 동의한 경우에만 사용됩니다. 자세한 내용은 개인정보 처리방침을 참고하세요.

Douyin 댓글 가져오기: 동영상 URL에서 답글 구조를 유지한 JSON까지

douyin.comments의 현재 파라미터에서 출발해 최소한의 요청으로 댓글을 읽고, 동영상 출처, 댓글 ID, 답글 관계를 보존하며, 한도, 빈 결과, 타임스탬프 파싱, 반복 관측을 처리하는 방법을 다룹니다.

Douyin 댓글을 JSON으로 내보낸 뒤 흔히 놓치는 문제가 있습니다. 파일의 한 줄은 독립된 댓글일 수도, 다른 댓글에 단 답글일 수도 있습니다. 모두 독립된 사용자 피드백으로 취급하면 맥락이 사라지고, 좋아요 수, 답글 수, 댓글 총수를 섞으면 토론 참여 지표가 모호해집니다.

따라서 동영상 URL에서 댓글을 가져오는 작업은 API가 배열을 반환하는 데서 끝날 수 없습니다. 완전한 연동 워크플로에서는 동영상 객체를 확인하고, 현재 파라미터를 읽고, 댓글 식별 정보와 답글 관계를 보존하고, 이번 관측이 무엇을 다루고 무엇을 다루지 않는지 밝혀야 합니다. 이 글은 EveryInfra의 douyin.comments 액션으로 이 과정을 단계별로 설명하며, 댓글을 조사용 스프레드시트, 콘텐츠 분석, 수동 검토 워크플로에 연동하려는 개발자에게 적합합니다.

이 섹션의 파라미터와 필드는 2026-09-04 기준 공개 카탈로그와 소스 코드로 확인했습니다. 이번에 요청 템플릿으로 유료 작업을 실행하지는 않았습니다. 아래의 합성 JSON은 정규화를 보여 주기 위한 것일 뿐, 실제 동영상, 사용자, 댓글 샘플이 아닙니다.

연동하려는 인터페이스 유형부터 확인하기

Douyin 인증 로그인, 게시 같은 공식 오픈 기능을 개발한다면 Douyin Open Platform의 인터페이스 목록에서 해당 절차로 들어가 애플리케이션 유형과 권한을 확인해야 합니다. 공식 액세스 토큰, 파라미터, 오류 코드는 공식 API에 속하며, 이 글의 EveryInfra 데이터 요청에 그대로 적용할 수 없습니다.

이 글은 대상 동영상이 정해진 뒤 댓글을 읽는 과정만 다룹니다. EveryInfra에서 동영상 상세 정보, 댓글, 검색, 음성 텍스트 변환은 서로 다른 액션입니다. 댓글란에서 무엇이 논의되는지 분석하려면 동영상 제목이나 음성 변환 텍스트를 댓글 텍스트 대신 써서는 안 됩니다. 비공개, 삭제, 제한된 콘텐츠는 동영상 URL이 있다는 이유만으로 여전히 읽을 권한이 있다고 가정할 수 없습니다.

1단계: 댓글 기능의 현재 파라미터 확인하기

Douyin 댓글 기능의 전체 선언 확인
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/catalog?platform=douyin' \
  | jq -e '.capabilities[] | select(.action == "comments") | {
      action, required_params, optional_params, param_meanings,
      mode, available, returns_list,
      default_limit, max_limit, response_fields
    }'

현재 카탈로그에 따르면 douyin.comments는 url이 필수이고, 동기 방식으로 반환하며, 목록을 출력하고, 기본 count는 20, 최대 limit은 100입니다. 목록형 기능에는 공통 limit을 쓸 수 있습니다. 이 값들은 요청 규모를 제한할 뿐, 항목 100개가 반환된다거나 동영상의 댓글이 최대 100개라는 보장이 아닙니다.

이 액션의 현재 선언은 호출자에게 page, cursor, has_more 파라미터를 제공하지 않으며 정렬 필터 파라미터도 노출하지 않습니다. 최신 댓글 100개를 가져오려고 sort: newest를 임의로 추가하지 마십시오. 다른 액션이 지원하는 파라미터라고 해서 comments도 지원하는 것은 아닙니다. 필요한 정렬이나 페이지네이션을 문서에서 확인할 수 없다면, 프로그램이 조용히 임의 샘플로 품질을 낮추게 두지 말고 요구 사항이 아직 충족되지 않았다고 명시해야 합니다.

카탈로그의 response_fields 설명은 페이지네이션 프로토콜이 아니라 댓글 레코드를 설명합니다. 나중에 레코드에 cursor라는 필드가 추가되더라도, 그 필드가 페이지네이션 메커니즘에 속하는지 먼저 확인한 뒤에 요청을 진행하는 데 사용해야 합니다. 필드 이름만 보고 루프를 작성하지 마십시오.

2단계: 요청 전에 동영상 식별 정보 확정하기

입력은 이 기능이 받는 유효한 동영상 링크여야 합니다. 실제 업무에서는 운영 담당자가 공유 문구, 단축 링크, 또는 쿼리 파라미터만 다른 같은 동영상의 주소 여러 개를 전달할 수 있습니다. 아무 문자열이나 url에 넘기는 것보다 호출 전에 대상을 정리해 두는 편이 문제 해결이 쉽습니다.

비즈니스 계층에는 사용자가 처음 제출한 참조, 확인된 요청 URL, 확실하게 식별한 동영상 ID를 저장하는 것이 좋습니다. 동영상 ID를 확실하게 얻을 수 없다면 우선 확인된 출처 참조로 통제된 연결을 하고, 자릿수로 ID를 추측하거나 짧은 공유 코드를 동영상 식별 정보로 취급하지 마십시오.

같은 동영상이 여러 조사 목록에 나오면 해당 동영상에 대해 승인된 관측 결과를 재사용할 수 있지만, 어떤 조사 작업에 속하는지는 보존해야 합니다. 반대로 제목이나 등장인물이 같다고 두 동영상을 자동으로 병합해서는 안 됩니다. 중복 제거는 문구의 유사성이 아니라 콘텐츠 식별 정보를 기준으로 합니다.

링크 정규화는 무관하다고 확인된 표현 차이만 처리해야 합니다. 쿼리 파라미터를 일괄 삭제하지 말고, 원래 입력과 실제로 제출한 값을 따로 보존하십시오. 확인되지 않은 리디렉션, 접근할 수 없는 콘텐츠, 다른 객체를 가리키는 링크는 형태를 바꿔 가며 계속 재시도하지 말고 수동 확인으로 넘기십시오.

3단계: 동영상 하나로 최소 요청 보내기

서버에 EVERYINFRA_API_KEY와, 승인된 동영상 URL이 담긴 EVERYINFRA_DOUYIN_VIDEO_URL이 설정되어 있다고 가정합니다. 아래 요청을 한 번만 보내고 소량의 결과로 필드를 확인하십시오. 자동 재시도를 켜지 말고, 요청한 수량이 모두 반환된다고 주장하지 마십시오.

Douyin 댓글 최소 요청 템플릿
: "${EVERYINFRA_API_KEY:?set the API Key first}"
: "${EVERYINFRA_DOUYIN_VIDEO_URL:?set the authorized full video URL first}"
jq -cn --arg url "${EVERYINFRA_DOUYIN_VIDEO_URL}" '{
  platform: "douyin",
  action: "comments",
  params: {url: $url, limit: 5}
}' | curl --silent --show-error --include --max-time 180 \
  'https://api.everyinfra.com/api/v1/social' \
  -H "Authorization: Bearer ${EVERYINFRA_API_KEY}" \
  -H 'Content-Type: application/json' \
  --data-binary @-

정상 동기 응답의 최상위 계층에는 요청 정보가 담기고, 댓글 레코드는 results에 들어 있습니다. id, count, billing 등의 정보도 함께 확인해야 합니다. 최상위 id는 API 요청 식별자로 기록하고, results 안의 댓글 id를 덮어쓰지 마십시오. 180초는 이 예시의 클라이언트 대기 설정일 뿐 API 성능 보장이 아니며, 시간 초과가 나면 결과를 알 수 없다고만 판단할 수 있습니다.

처음 연동할 때 서둘러 텍스트만 남기지 마십시오. 데이터베이스에 연결하기 전에 댓글이 예상한 동영상에서 왔는지, ID가 안정적인지, 답글 표시와 관계 필드가 나타나는지 확인하십시오. 응답이 파싱할 수 있는 JSON이 아니거나 비즈니스 객체가 예상한 목록이 아니라면, 빈 배열을 반환해 성공처럼 꾸미지 말고 실패 근거를 보존한 뒤 해당 건의 파싱을 중단하십시오.

4단계: 댓글 내용, 참여 지표, 답글 관계 구분하기

필드 사전 확인: 이름만 보고 해석하지 않기
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/fields?platform=douyin&action=comments' \
  | jq '{fields, undocumented}'

현재 선언에는 id, text, like_count, reply_count, author_name, author_id, ip_location, liked_by_author, is_reply, reply_to_id, posted_at, platform이 있습니다. 모든 열을 억지로 채우기보다 용도에 따라 다음과 같이 네 그룹으로 나눌 수 있습니다.

  • 식별 정보와 출처: id, platform, 그리고 애플리케이션이 이번 요청을 근거로 연결한 동영상 출처. 작성자 닉네임은 댓글 식별 정보로 쓰지 않습니다.
  • 내용과 시간: text와 posted_at, 그리고 실제 관측 시각을 나타내기 위해 직접 기록하는 observed_at.
  • 참여 기록: like_count, reply_count, liked_by_author. 누락된 값은 0을 의미하지 않으며, 작성자가 누른 좋아요를 전환 효과나 상업적 가치와 동일시해서는 안 됩니다.
  • 답글 관계: is_reply와 reply_to_id. 누락된 관계는 확인 대상으로 남기고, 텍스트 유사도나 배열 위치로 상위 댓글을 자동으로 채우지 않습니다.

코드는 가져온 댓글과 답글을 목록으로 정리하고, 근거가 있을 때 연결 ID를 보존합니다. 따라서 배열 자체가 평면적이라고 해서 모든 행이 의미상 같은 수준이라는 뜻은 아닙니다. 읽을 때는 다음 행이 이전 행의 답글이라고 가정하지 말고, 먼저 댓글 ID 인덱스를 만든 뒤 답글 관계를 처리하십시오.

reply_count는 답글 수 정보를 나타낼 뿐, 전달된 답글 배열의 길이를 보장하지 않습니다. is_reply가 true인데 reply_to_id가 없거나, reply_to_id가 현재 결과에 없는 댓글을 가리키면 보존해야 할 관계 공백이 됩니다. 가짜 상위 댓글로 트리를 채우지 마십시오. 트리를 그릴 수 있다고 해서 보고서에서 스레드가 완전하다고 주장해서도 안 됩니다.

5단계: 식별 정보를 보존하는 업무용 JSON 설계하기

동영상 출처는 댓글 페이로드가 동영상 URL을 다시 반환해 주기를 기대하지 말고, 확인된 요청 컨텍스트에서 레코드에 연결하십시오. 데이터베이스에서는 플랫폼, 동영상 식별 정보, 댓글 ID의 조합을 키로 쓸 수 있습니다. 같은 댓글이 다시 나오면 매번 새 댓글로 중복 집계하지 말고 관측을 추가하거나 변경 가능한 필드를 업데이트하십시오.

ID는 원래 문자열 그대로 보존하는 것이 좋습니다. 긴 10진수 ID를 JavaScript Number로 변환했다가 다시 문자열로 바꾸면 원래 값을 복구하지 못할 수 있습니다. JSON 명세는 숫자 상호 운용성의 정밀도 한계를 설명합니다. 산술 연산이 필요 없는 식별자는 문자열로 유지하면 불필요한 숫자 변환을 피할 수 있습니다.

아래 JavaScript는 의도적으로 범위를 좁힌 오프라인 변환기입니다. 문자열로 확인된 댓글 ID만 받고, 인식할 수 없는 타입은 데이터베이스에 자동으로 넣지 않고 거부합니다. 이 글에 필요한 필드와 관계 상태만 보존하며, 완전한 SDK가 아니고 네트워크 요청도 보내지 않습니다. 합성 텍스트는 합성 예시임을 명시해 두었으므로 실제 사용자 피드백으로 쓸 수 없습니다.

오프라인 데모: 관계 공백은 상태로 남기고 상위 댓글을 지어내지 않음
function mapComment(row, source) {
  if (!row || typeof row.id !== "string" || !row.id.trim()) {
    throw new Error("string comment id required");
  }
  if (row.reply_to_id != null && typeof row.reply_to_id !== "string") {
    throw new Error("string parent id required");
  }
  const parentId = row.reply_to_id || null;
  const isReply = typeof row.is_reply === "boolean" ? row.is_reply : null;
  const relation = isReply === true
    ? parentId ? "reply_with_reference" : "reply_parent_unknown"
    : isReply === false && !parentId ? "top_level"
    : "needs_review";
  return {
    platform: "douyin",
    video_ref: source.video_ref,
    comment_id: row.id,
    text: typeof row.text === "string" ? row.text : null,
    posted_at_raw: row.posted_at ?? null,
    observed_at: source.observed_at,
    like_count: typeof row.like_count === "number"
      && Number.isFinite(row.like_count) && row.like_count >= 0
      ? row.like_count : null,
    reply_to_id: parentId,
    relation
  };
}

console.log(mapComment(
  {
    id: "synthetic-reply",
    text: "synthetic example, not a real review",
    is_reply: true,
    like_count: null
  },
  {
    video_ref: "synthetic-video",
    observed_at: "2026-09-04T00:00:00Z"
  }
));

이 예시는 타임스탬프를 억지로 변환하지 않고 posted_at_raw를 보존합니다. 실제로 데이터베이스에 넣을 때는 파싱된 타임스탬프, 적용한 시간대, 파싱 상태를 별도로 둘 수 있습니다. 단위나 형식을 확인할 수 없다면 초, 밀리초, 현지 시각 중 하나로 자동 추측하지 마십시오. 누락된 text도 마찬가지로 null로 두어 모델이 원래 문구를 채워 넣지 못하게 합니다.

reply_with_reference는 상위 참조가 있다는 뜻일 뿐이며, 상위 댓글이 실제로 있는지는 같은 동영상에서 가져온 레코드 안에서 다시 확인해야 합니다. 없으면 외부 참조나 아직 가져오지 않은 참조로 남겨 둡니다. top_level도 현재 필드를 기준으로 한 분류일 뿐, 원래 플랫폼의 토론 구조가 완전히 복원되었다는 증거가 아닙니다.

원본 시간의 단위와 의미를 확인한 뒤에는 파싱된 시각을 RFC 3339에 따라 기록하고 Z 또는 명시적인 UTC 오프셋을 유지할 수 있습니다. 명세는 표현 형식을 정할 뿐, 원본의 숫자열이 초인지 밀리초인지 판단해 주지 않으며, 알 수 없는 원본 시간대를 현재 머신의 시간대로 바꿔 주지도 않습니다.

6단계: 정기 관측과 전체 페이지 순회 구분하기

현재 공개된 요청 형식에는 페이지네이션 파라미터가 없으므로, 같은 요청을 그대로 반복하면 2페이지나 3페이지가 아니라 여러 번의 관측 결과를 얻게 됩니다. 몇몇 결과가 우연히 다르더라도 댓글란 전체를 순회했다고 단정할 수 없습니다.

예약 작업을 구성할 때는 댓글 식별자를 기준으로 인접한 관측 결과를 병합하고, 처음 확인된 시각과 가장 최근에 확인된 시각을 함께 보존할 수 있습니다. 늦게 달린 댓글, 참여 지표 변화, 새로 추가된 답글 관계는 업데이트할 수 있지만, 이번 회차에서 보이지 않았다고 해서 댓글이 삭제된 것은 아닙니다. 명확한 삭제 신호나 별도의 검증 근거가 없다면 이번에 관측되지 않았다는 사실만 기록하십시오.

두 기간을 비교해야 한다면 먼저 동영상 집합, 호출 파라미터, 관측 빈도, 성공한 요청의 커버리지를 맞춰야 합니다. 이전 주에는 모든 대상을 확인했는데 다음 주에는 일부만 성공했다면, 원시 댓글 수를 참여 감소의 근거로 바로 쓸 수 없습니다. 보고서에 커버리지 차이를 적는 것이 감성 태그를 하나 더 추가하는 것보다 중요한 경우가 많습니다.

집계 단위도 명확히 해야 합니다. 댓글 행을 세는지, 토론 스레드를 세는지, 작성자를 중복 제거해 세는지 정해야 합니다. 한 스레드에 여러 사람이 답글을 다는 것과 한 사람이 연달아 글을 올리는 것은 서로 다른 토론 구조입니다. 보고서의 고유 사용자 수를 행 수만으로 대신해서는 안 되며, 신뢰할 수 있는 작성자 식별자가 없을 때는 고유 인원을 집계했다고 주장해서는 안 됩니다.

7단계: 오류, 빈 결과, 과금을 각각 검증하기

실패를 처리할 때는 최소한 잘못된 입력, 인증 또는 권한 실패, 할당량 부족, 일시적인 서비스 장애, 빈 목록 반환, 클라이언트가 전체 응답을 받지 못한 경우를 구분해야 합니다. 각각 필요한 조치가 다르므로, 모두 “댓글 없음”으로 기록하면 사용자와 모니터링 모두 잘못된 판단을 하게 됩니다.

422 오류는 error.code와 안내 메시지에 따라 요청을 수정해야 하며, 횟수 제한 없이 재시도해서는 안 됩니다. 일시적인 문제라면 원래 요청이 이미 실행되었을 가능성을 확인한 뒤 재시도 여부를 결정합니다. 타임아웃된 POST 요청이 제출되지 않았다고 가정할 수는 없습니다. 접근할 수 없다고 명확히 확인된 콘텐츠는 자동 시도를 중단하고 범위를 다시 검토하며, 제한을 우회하려 해서는 안 됩니다.

과금도 같은 요청과 연결해야 합니다. 수집한 댓글 수로 결제 금액을 직접 계산하지 말고, 실제 과금 내역과 API 요청 ID를 기록하십시오. 데이터 파싱 실패는 로컬 처리 실패이므로, 이것만으로 서버가 요청 처리를 완료했는지 또는 과금을 되돌렸는지 추론할 수 없습니다. 마찬가지로 응답에 과금 필드가 없으면 무료로 기록하지 말고 검증 대기로 표시해야 합니다.

댓글을 모델에 넘기기 전에 맥락과 수동 검토 경로를 남겨 두기

댓글을 분류할 때는 스레드나 경계가 명확한 일부 구간을 입력하는 것이 가장 좋습니다. 답글이 “꼭 그렇지는 않아요”뿐이라면 그 말만으로는 무엇에 반대하는지 알 수 없습니다. 상위 댓글을 가져오지 못했다면 긍정이나 부정 라벨을 억지로 붙이지 말고 맥락 부족 상태를 유지하십시오.

주제, 감성, 판단 규칙 버전, 모델 버전, 수동 수정 내용 같은 파생 결과는 별도로 저장하십시오. 이런 필드를 원본 댓글의 사실 열에 다시 쓰지 말고, 모델이 제시한 신뢰도를 사용자의 실제 태도를 측정한 값으로 해석해서도 안 됩니다. 보고서에는 권한 있는 담당자가 검토할 수 있을 만큼 출처와의 연결을 남기고, 공개 자료에는 표시가 허용된 필요한 내용만 담습니다.

규모를 확장하기 전 체크리스트

  • 대상: 동영상 식별 정보가 요청 URL과 대응하고, 범위와 용도를 확인했으며, 비공개 콘텐츠의 접근 제한을 우회하지 않습니다.
  • 구조: results가 예상한 목록이고, 댓글 ID의 정밀도가 유지되며, 레코드를 특정 동영상으로 다시 연결할 수 있습니다.
  • 관계: 답글 필드가 보존되고, 상위 댓글 누락과 알 수 없는 상태가 드러나며, 지어낸 스레드가 없습니다.
  • 관측: 누락 값을 0으로 채우지 않고, 게시 시각과 관측 시각을 구분하며, 근거 없이 전체 커버리지를 주장하지 않습니다.
  • 운영: 타임아웃 발생 시 자동으로 재전송하지 않고, 오류 및 과금 기록에 요청 식별자를 보존하며, 반복 관측 결과를 새 데이터로 중복 집계하지 않습니다.
  • 저장: 필요한 필드만 보존하고, 원문 텍스트와 작성자 정보에 접근 제어, 보존 기간, 삭제 일정을 적용합니다.

업무용 JSON에 맞춰 스키마를 작성할 때는 필드가 정의되어 있는 것, 필드가 필수인 것, 값에 null이 허용되는 것이 서로 다른 조건임에 유의하십시오. JSON Schema의 properties는 기본적으로 필드를 필수로 만들지 않습니다. 모든 알 수 없는 값을 빈 문자열 하나로 통과시키지 말고, required와 type 제약으로 요구 사항을 따로 표현하십시오.

JSON 파일만 넘기지 말고 설명 가능한 데이터셋을 전달하기

재사용 가능한 댓글 데이터셋은 인수받는 쪽이 대상 범위, 출처, 관측 시각, 답글 관계, 누락 상태를 이해할 수 있어야 합니다. 이를 갖추면 JSON을 분석, 검토, 보고 워크플로에 안정적으로 투입할 수 있습니다. 그렇지 않으면 행을 많이 내보내도 원래의 의문을 다른 파일 형식으로 저장하는 데 그칩니다.

권한이 있는 동영상 하나로 시작해 구조와 관계를 먼저 검증한 다음 대상을 점차 늘리십시오. 아래 문서와 실시간 카탈로그로 현재 기능을 확인할 수 있습니다. 연동 과정의 검증을 예전 글에 나온 파라미터로 대신해서는 안 됩니다.

참고 LF-03