팀이 다음 캠페인 주제를 찾을 때 수십 개 프로필을 수작업으로 살펴보면 금세 일관된 방법론 없는 메모만 쌓입니다. 한 분석가는 조회수를 기록하고 다른 분석가는 동영상 캡션을 기록하며, 비슷한 문구가 들어간 게시물도 서로 다른 표에 놓입니다. 일주일 뒤에는 무엇이 더 자주 반복되는지 파악하기 어렵습니다. 주제인지, 표현 형식인지, 아니면 단지 반응이 좋았던 동영상인지 말입니다.

실용적인 방법은 공개 계정 목록을 작게 유지하고, 해당 계정의 동영상 정보를 정기적으로 구조화된 형태로 가져와, 매번 수집한 데이터를 별도의 스냅샷으로 저장하는 것입니다. 그런 다음 시간에 따른 게시물과 공개된 지표를 비교하고, 언어 모델을 활용해 캡션을 미리 분류할 수 있습니다. 아래에서는 jsonscraper의 TikTok Scraper API를 사용하는 Python 기반 조사 파이프라인을 소개합니다.
데이터를 수집하기 전에 질문을 정하세요
소규모 스포츠 영양 브랜드의 마케팅팀을 예로 들어보겠습니다. 팀은 선정한 공개 TikTok 크리에이터에게 어떤 주제가 반복적으로 등장하는지, 어떤 동영상을 직접 살펴봐야 하는지 알고 싶어 합니다. 목표는 미리 정한 계정의 게시물을 관찰 기간 동안 비교하는 것이지, TikTok 전체의 범용 인기 순위를 만드는 것이 아닙니다.
이렇게 하면 분석 범위가 명확해집니다. 선정한 프로필의 피드를 수집하면 해당 계정의 콘텐츠와 게시 빈도를 살펴볼 수 있습니다. 공개 카운터가 제공된다면 요청 시점의 값을 기록할 수 있습니다. 자체 광고의 성과를 평가하려면 광고 플랫폼이나 웹 분석 도구의 비용 및 전환 데이터처럼 별도의 데이터가 필요합니다.
API를 연결하기 전에 조사 프로토콜을 기록하세요. 계정 목록, 시작일, 수집 주기, 분석할 필드, 게시물 포함 기준을 정합니다. 각 요청의 시각도 기록하세요. 그러면 동영상 하나의 지표가 변한 것인지, 서로 다른 두 관측 집합의 차이인지 구분할 수 있습니다.
수집할 데이터와 계산할 지표
소규모 프로젝트에는 세 가지 데이터 계층이 필요합니다. 첫째는 계정 식별자와 수집 시작 시각입니다. 둘째는 나중에 검증할 수 있도록 저장한 API 원본 응답입니다. 셋째는 조회와 보고서 작성에 사용할 정규화된 필드, 즉 동영상 ID, 캡션, 게시 시각, 제공되는 참여 지표입니다.
필드 이름과 응답의 중첩 구조는 해당 엔드포인트의 실제 응답을 보고 확인해야 합니다. 목록 이름이 항상 videos라거나 조회수 카운터가 views라고 미리 가정하지 마세요. 먼저 문서에 기재된 요청을 실행해 실제 JSON을 살펴본 다음, 자체 스키마에 맞게 변환하도록 설정하세요.
유용한 지표로는 기간별 신규 게시물 수, 주제별 게시물 분포, 동일 동영상의 스냅샷 간 공개 카운터 변화가 있습니다. 마지막 지표는 동영상 ID가 일치하는 레코드끼리 비교하세요. 이렇게 하면 이미 게시된 동영상의 추이를 신규 게시물의 지표와 분리할 수 있습니다.
TikTok Scraper API 연결
TikTok Scraper API 설명에는 기본 주소 https://tiktok.evelode.com가 명시되어 있으며, 엔드포인트 문서에서는 GET /getUserFeed와 query, max_cursor, count, region, with_pinned_posts, sort_type, cache_timeout 매개변수를 확인할 수 있습니다. 첫 요청에는 query에 프로필을 지정하고, 예를 들어 count로 가져올 항목 수를 정하면 됩니다. license_key는 query string으로 전달합니다. 키 설정은 API 엔드포인트 문서에도 설명되어 있습니다. 통합하기 전에 Postman 컬렉션에서 엔드포인트를 확인하고 응답을 테스트할 수 있습니다.
키는 환경 변수에 보관하세요. 소스 코드, 저장소, 스프레드시트, 클라이언트 애플리케이션에 넣지 마세요. 요청을 실행하고 성공한 JSON 예시를 날짜와 엔드포인트 이름과 함께 저장한 뒤, 이를 정규화 어댑터와 테스트의 기준으로 사용하세요.
Python으로 작성하는 최소 실행 요청
문서에 따르면 사용자 피드 응답에는 외부 상태와 tiktok 객체가 포함되며, 게시물 목록은 aweme_list에 있습니다. 레코드에는 ID, 캡션, 게시 시각, 작성자, 통계가 포함될 수 있습니다. 구체적인 필드와 값은 실제 응답을 확인해야 하며, 일부 데이터는 없을 수 있습니다.
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import requests
API_KEY = os.environ["JSONSCRAPER_TIKTOK_LICENSE_KEY"]
ENDPOINT_URL = "https://tiktok.evelode.com/getUserFeed"
def fetch_user_feed(handle: str, count: int = 20) -> dict:
response = requests.get(
ENDPOINT_URL,
params={
"license_key": API_KEY,
"query": handle,
"count": count,
"region": "US",
},
timeout=45,
)
response.raise_for_status()
payload = response.json()
if not isinstance(payload, dict):
raise ValueError("JSON 객체가 필요합니다")
if payload.get("status") != "ok":
raise RuntimeError(f"API 상태: {payload.get('status')!r}")
tiktok = payload.get("tiktok")
if not isinstance(tiktok, dict):
raise ValueError("응답에 tiktok 객체가 없습니다")
return payload
def save_raw(payload: dict, account: str, output_dir: str = "data/raw") -> Path:
collected_at = datetime.now(timezone.utc).isoformat()
safe_account = "".join(c for c in account if c.isalnum() or c in "-_@")
path = Path(output_dir) / f"{safe_account}_{collected_at.replace(':', '-')}.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps(
{"collected_at": collected_at, "account": account, "payload": payload},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
return path
def normalize_items(payload: dict, account: str, collected_at: str) -> list:
items = payload["tiktok"].get("aweme_list", [])
if not isinstance(items, list):
raise ValueError("tiktok.aweme_list 필드가 목록이 아닙니다")
observations = []
for item in items:
if not isinstance(item, dict):
continue
stats = item.get("statistics")
stats = stats if isinstance(stats, dict) else {}
author = item.get("author")
author = author if isinstance(author, dict) else {}
observations.append({
"account_handle": account,
"collected_at": collected_at,
"video_id": item.get("aweme_id"),
"caption": item.get("desc"),
"published_at_epoch": item.get("create_time"),
"author_handle": author.get("unique_id"),
"views": stats.get("play_count"),
"likes": stats.get("digg_count"),
"comments": stats.get("comment_count"),
"shares": stats.get("share_count"),
})
return observations
if __name__ == "__main__":
account = "creator_handle"
payload = fetch_user_feed(account, count=20)
collected_at = datetime.now(timezone.utc).isoformat()
raw_path = save_raw(payload, account)
observations = normalize_items(payload, account, collected_at)
print(f"저장됨: {raw_path}; 레코드 수: {len(observations)}")
실행 전에 환경 변수 JSONSCRAPER_TIKTOK_LICENSE_KEY를 설정하고 requests 라이브러리를 설치하세요. 이 코드는 HTTP 응답, 외부 상태, tiktok 객체의 존재 여부를 확인한 뒤 aweme_list를 읽습니다. 원본 JSON을 저장하고 문서에서 확인된 필드를 추출하며, 값이 없는 필드는 비워 둡니다. 가져온 피드는 API가 반환한 결과 집합이므로 보고서에는 요청 시각과 선택한 프로필을 함께 기록해야 합니다.
스냅샷을 보관하고 게시물을 비교하세요
소규모 프로젝트에는 SQLite나 PostgreSQL을 사용할 수 있습니다. 원본 응답은 정규화 레코드와 분리해 보관하세요. 관측 테이블에는 다음과 같은 스키마를 사용할 수 있습니다.
CREATE TABLE IF NOT EXISTS video_observations (
account_handle TEXT NOT NULL,
video_id TEXT NOT NULL,
collected_at TEXT NOT NULL,
published_at_epoch INTEGER,
caption TEXT,
views INTEGER,
likes INTEGER,
comments INTEGER,
shares INTEGER,
raw_file TEXT,
PRIMARY KEY (account_handle, video_id, collected_at)
);
실행할 때마다 이전 데이터를 덮어쓰지 말고 새 스냅샷을 추가하세요. 카운터를 비교할 때는 동일한 동영상의 두 값이 모두 있는 경우에만 차이를 계산합니다. 요청 간격과 관측 시각을 보고서에 포함하면 변화가 어느 기간에 걸쳐 누적됐는지 설명할 수 있습니다.
주제별 분류는 작은 범주 사전으로 시작하고, 필요하면 분류기나 언어 모델을 추가하세요. 모델에는 정제된 캡션, 게시물 ID, 미리 정한 범주를 전달합니다. 범주와 짧은 근거를 포함한 구조화된 결과는 자유 형식 요약보다 실행 간 비교가 쉽습니다.
검증 가능한 라벨링으로 언어 모델을 추가하세요
모델은 캡션을 ‘레시피’, ‘운동’, ‘제품 리뷰’, ‘개인 이야기’ 같은 주제로 미리 분류할 수 있습니다. 먼저 수십 개 게시물에 직접 라벨을 붙이세요. 자동 라벨을 이 예시들과 비교하고, 범주 정의를 다듬은 뒤 프롬프트나 모델을 변경하면 다시 평가합니다.
신규 게시물 수, 카운터의 중앙값, 스냅샷 간 차이는 일반 코드로 계산하는 편이 더 신뢰할 만합니다. 모델은 텍스트 분류와 간단한 설명 작성에 사용하세요. 원본 캡션과 수치 관측값은 검증할 수 있도록 남겨 둡니다.
라벨과 함께 게시물 ID, 프롬프트 버전, 처리 날짜, 사용한 모델을 저장하세요. 분류가 잘못되면 분석가는 캡션을 찾아 규칙을 수정할 수 있습니다. 최초 라벨과 수동 수정값은 별도로 보관하는 것이 좋습니다.
실행을 자동화하세요
수동 실행을 검증한 뒤 스크립트를 cron, GitHub Actions, n8n 또는 Make로 옮기세요. 스케줄러가 정해진 시간에 수집을 실행하고, 계정 목록을 전달하며, 결과를 기록하고, 보고서를 지정한 저장소로 보냅니다. 키는 실행 환경의 비밀 저장소에 보관하세요. 먼저 한 계정으로 프로세스를 확인한 뒤 목록을 늘립니다.
자동화는 계정 대기열, API 호출, 원본 응답 저장, 정규화, 스냅샷 기록, 보고서 작성 단계로 나누세요. 한 계정에서 오류가 나더라도 나머지 결과가 사라져서는 안 됩니다. 실행 ID와 데이터베이스의 고유성 제약 조건을 활용하면 재실행을 멱등성 있게 처리할 수 있습니다.
시각, 계정, 엔드포인트, HTTP 상태, 소요 시간, 처리 항목 수, 오류 설명을 로그로 남기세요. API 키는 로그에 기록하지 마세요. cache_timeout은 /getUserFeed에 지정된 매개변수로, 캐시 기간을 정해야 할 때 요청에 추가할 수 있습니다. API 문서에는 기본값이 3600초로 설명되어 있습니다.
보고서를 배포하기 전에 품질을 확인하세요
정규화된 레코드 몇 개를 원본 JSON과 대조하세요. 재수집 시 같은 동영상을 ID로 식별하는지, 날짜가 예상대로 해석되는지, 숫자 필드의 자료형이 적절한지, 결측 비율을 파악했는지 확인합니다. 그런 다음 다시 실행해 데이터베이스가 기존 관측값을 보존하고 새 값을 추가하는지도 점검하세요.
계정과 수집 기간을 뒤섞지 마세요. 주제를 비교할 때는 같은 길이의 기간을 사용하거나 보고서에 기간을 명확히 표시하세요. 카운터 변화를 분석할 때는 스냅샷 간격을 기록해 독자가 비교 대상 구간의 규모를 이해할 수 있도록 합니다.
캡션 원문을 보존하고 정제 버전은 별도로 만드세요. 이모지, 해시태그, 링크, 언어적 특성은 분류에 영향을 줄 수 있습니다. 각각을 별도 필드에 보관하면 정제의 영향을 확인하고 API를 다시 호출하지 않고도 범주를 재계산할 수 있습니다.
이 파이프라인으로 얻을 수 있는 결과
몇 차례 실행하면 게시물과 스냅샷 테이블, 주제별 캡션 예비 분류, 선택한 기간의 신규 동영상 보고서를 확보할 수 있습니다. 이를 활용해 전문가가 검토할 동영상을 고르고, 선택한 계정에서 반복되는 주제를 찾으며, 관측일 사이에 공개 카운터가 어떻게 변했는지 확인할 수 있습니다.
이 데이터를 바탕으로 편집 일정에 대한 가설을 세울 수 있습니다. 한 질문을 다루는 여러 표현 방식을 비교하거나 자체 콘텐츠에서 테스트할 주제를 선정할 수 있습니다. 상업적 효과를 평가하려면 관측 결과를 내부 캠페인 데이터와 대조하세요.
시작하려면 공개 계정을 소수 선택하고 Postman 요청 컬렉션에서 GET /getUserFeed를 확인하세요. 엔드포인트 문서와 실제 응답으로 확인된 매개변수 및 필드 경로만 코드에 반영하세요.
공개 데이터의 책임 있는 수집
조사 목적에 맞게 계정 목록을 제한하고 필요한 필드만 수집하며, 원본 응답의 보관 기간을 정하세요. 상업적 목적으로 데이터를 사용하기 전에 적용되는 플랫폼 이용 약관과 관할 지역의 개인정보 보호 요건을 확인하세요. 접근을 통제하고 오래된 스냅샷을 삭제하면 수집 과정을 목적에 맞게 관리할 수 있습니다.
데이터를 AI 서비스에 전달하기 전에 모델에 필요하지 않은 필드를 제거하세요. 보고서에는 게시물 ID와 관측 날짜를 표시해 담당자가 레코드를 찾아 결론의 근거를 확인할 수 있도록 합니다.
간단한 실행 체크리스트
- 분야, 계정 목록, 기간, 분석 질문을 정합니다.
- 문서에서
GET /getUserFeed매개변수를 확인하고 Postman에서 실제 응답을 살펴봅니다. - JSON 예시를 저장하고 확인된 필드를 바탕으로 정규화기를 설정합니다.
- 원본 응답과 수집 시각이 포함된 개별 스냅샷을 기록합니다.
- 수치 지표는 코드로 계산하고, AI는 텍스트의 예비 분류에 사용합니다.
- 일정, 로깅, 재시도 처리, 보관 기간을 추가합니다.
이렇게 하면 모니터링이 재현 가능한 조사로 발전합니다. 모든 결론을 게시물과 관측 시각에 연결할 수 있고, 코드는 선택한 엔드포인트의 실제 스키마를 기반으로 작동합니다.