jsonscraper

איך לנתח נושאים של יוצרי TikTok: צינור עבודה מעשי ב-Python

כך מקבלים פיד ציבורי באמצעות TikTok Scraper API, שומרים את התשובות המקוריות ומכינים ניתוח חוזר של פרסומים.

מדיניות עריכה דיווח על שגיאה

כשצוות מחפש נושא לקמפיין הבא, מעבר ידני על עשרות פרופילים הופך במהרה לאוסף הערות ללא מתודולוגיה אחידה. אנליסט אחד רושם צפיות, אחר — כיתובים לסרטונים, ופרסומים בעלי ניסוחים דומים מוצאים את עצמם בטבלאות שונות. כעבור שבוע קשה להבין מה חוזר בתדירות גבוהה יותר: נושא, אופן ההצגה או פשוט סרטון מוצלח במיוחד.

מאמר: איך לנתח נושאים של יוצרי TikTok: צינור עבודה מעשי ב-Python
תקציר: נסביר כיצד להגדיר ניטור של חשבונות TikTok ציבוריים ב-Python: לבדוק את הנתיב והפרמטרים ב-Postman, לשמור תשובות API, לנרמל שדות מאומתים ולהשוות פרסומים לאורך זמן.
תפקיד התמונה: תמונת שער — הרעיון המרכזי
מדור: 
נושא חזותי: צילום מערכתי של אריחי סרטונים קצרים אנונימיים לצד כרטיסיות נתונים עם חותמות זמן ותוויות נושא על שולחן מחקר, להמחשת
© jsonscraper · איור שנוצר באמצעות בינה מלאכותית

גישה מעשית היא לנהל רשימה קצרה של חשבונות ציבוריים, לאסוף בקביעות מידע על הסרטונים שלהם בפורמט מובנה ולשמור כל איסוף כתמונת מצב נפרדת. לאחר מכן אפשר להשוות פרסומים ומדדים זמינים לאורך זמן, ולהשתמש במודל שפה לקיבוץ ראשוני של הכיתובים. בהמשך מוצגת תבנית לצינור עבודה מחקרי כזה ב-Python עם TikTok Scraper API של jsonscraper.

נסחו את שאלת המחקר לפני איסוף הנתונים

נניח שצוות שיווק של מותג קטן לתזונת ספורט רוצה לבדוק אילו נושאים מופיעים באופן קבוע אצל יוצרי TikTok ציבוריים שנבחרו, ואילו סרטונים כדאי לבדוק ידנית. המטרה היא להשוות פרסומים של חשבונות שנבחרו מראש לאורך תקופת תצפית, ולא לבנות דירוג אוניברסלי של הפופולריות ב-TikTok.

כך מוגדרים גבולות הניתוח. איסוף הפיד של הפרופילים שנבחרו מסייע לחקור את התוכן ואת תדירות הפרסום של אותם חשבונות. אפשר לתעד מוני נתונים ציבוריים זמינים בזמן הבקשה. כדי להעריך את תוצאות הפרסום שלכם יידרשו נתונים נפרדים — למשל הוצאות והמרות ממערכת הפרסום או מניתוח האתר.

לפני חיבור ה-API, תעדו את פרוטוקול המחקר: רשימת החשבונות, תאריך ההתחלה, תדירות האיסוף, השדות לניתוח והקריטריונים להכללת פרסומים. תעדו את מועד כל בקשה. כך אפשר להבחין בין שינוי במדד של סרטון אחד לבין הבדל בין שתי קבוצות תצפיות.

מה לאסוף ומה לחשב

לפרויקט קטן דרושות שלוש שכבות נתונים. הראשונה היא מזהה החשבון ומועד תחילת האיסוף. השנייה היא תשובת ה-API המקורית, שנשמרת לצורך בדיקה בהמשך. השלישית היא שדות מנורמלים לשאילתות ולדוחות: מזהה הסרטון, כיתוב, מועד פרסום ומוני אינטראקציה זמינים.

יש לקבוע את שמות השדות ואת המבנה המקונן לפי דוגמה מהנתיב המסוים. אל תניחו מראש שהרשימה תמיד נקראת videos או שמונה הצפיות נקרא views. תחילה שלחו בקשה מתועדת ובדקו את ה-JSON שהתקבל בפועל, ורק אז התאימו את ההמרה לסכמה משלכם.

מדדים שימושיים כוללים את מספר הפרסומים החדשים בפרק זמן, את התפלגות הפרסומים לפי נושאים ואת השינוי במונים הציבוריים בין תמונות מצב של אותו סרטון. במקרה האחרון, השוו רשומות בעלות מזהה סרטון זהה. כך הדינמיקה של סרטון שכבר פורסם נשארת נפרדת ממדדי פרסומים חדשים.

חיבור ל-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. הרשומות עשויות לכלול מזהה, כיתוב, מועד פרסום, מחבר ונתונים סטטיסטיים. יש לבדוק את השדות והערכים המסוימים מול התשובה שהתקבלה: ייתכן שחלק מהנתונים לא יהיו זמינים.

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)
);

כל הפעלה מוסיפה תמונת מצב חדשה במקום לדרוס את הקודמת. בעת השוואת מונים חשבו את ההפרש רק כשקיימים שני הערכים עבור אותו סרטון. ציינו בדוח את המרווח בין הבקשות ואת מועד התצפית: הם מבהירים באיזו תקופה נצבר השינוי.

לקיבוץ נושאי, התחילו במילון קטן של קטגוריות והוסיפו מסווג או מודל שפה לפי הצורך. העבירו למודל כיתובים שעברו ניקוי, מזהי פרסומים וקטגוריות שהוגדרו מראש. קל יותר להשוות בין הפעלות תוצאה מובנית הכוללת קטגוריה והנמקה קצרה מאשר סקירה חופשית.

הוסיפו מודל שפה עם תיוג שניתן לבדוק

מודל יכול למיין כיתובים באופן ראשוני לנושאים כמו „מתכונים”, „אימונים”, „סקירות מוצרים” ו„סיפורים אישיים”. תחילה תייגו ידנית כמה עשרות פרסומים. השוו את התוויות האוטומטיות לדוגמאות האלה, דייקו את הגדרות הקטגוריות והעריכו שוב לאחר שינוי ההנחיה או המודל.

עדיף לחשב בקוד רגיל את מספר הפרסומים החדשים, את חציון המונים ואת ההפרש בין תמונות מצב. השתמשו במודל לסיווג טקסט ולהכנת הסברים קצרים. השאירו את הכיתובים המקוריים ואת התצפיות המספריות זמינים לבדיקה.

שמרו לצד התווית את מזהה הפרסום, גרסת ההנחיה, תאריך העיבוד והמודל שבו נעשה שימוש. במקרה של סיווג שגוי, האנליסט יוכל לאתר את הכיתוב ולתקן את הכלל. כדאי לשמור את התווית הראשונית ואת התיקון הידני בנפרד.

בצעו אוטומציה להפעלה

לאחר בדיקה מוצלחת של הרצה ידנית, העבירו את הסקריפט ל-cron, ל-GitHub Actions, ל-n8n או ל-Make. המתזמן מפעיל איסוף לפי לוח זמנים, מקבל רשימת חשבונות, רושם את התוצאה ומעביר את הדוח לאחסון שנבחר. שמרו את המפתח בסודות של סביבת ההרצה. בדקו תחילה את התהליך מול חשבון אחד ורק אחר כך הרחיבו את הרשימה.

חלקו את האוטומציה לשלבים: תור חשבונות, קריאה ל-API, שמירת התשובה הגולמית, נרמול, כתיבת תמונת מצב והכנת דוח. שגיאה בחשבון אחד לא צריכה למחוק את התוצאות של האחרים. אפשר להפוך הרצה חוזרת לאידמפוטנטית באמצעות מזהה הרצה ואילוצי ייחודיות במסד הנתונים.

תעדו ביומן את השעה, החשבון, הנתיב, סטטוס ה-HTTP, משך הפעולה, מספר הפריטים שעובדו ותיאור השגיאה. אל תכתבו את מפתח ה-API ביומן. הפרמטר cache_timeout מצוין עבור /getUserFeed; אפשר להוסיף אותו לבקשה אם נדרשת תקופת מטמון מוגדרת. במדריך ה-API הערך המתואר כברירת מחדל הוא 3600 שניות.

בדקו את האיכות לפני פרסום הדוח

השוו כמה רשומות מנורמלות ל-JSON המקורי. בדקו שאיסוף חוזר מזהה את אותו סרטון לפי מזהה, שהתאריכים מפוענחים כמצופה, שהשדות המספריים מטיפוס מתאים וששיעור הערכים החסרים ידוע. לאחר מכן הריצו שוב וודאו שמסד הנתונים שומר את התצפיות הישנות ומוסיף חדשות.

אל תערבבו חשבונות ותקופות איסוף. להשוואת נושאים השתמשו בתקופות באורך זהה או הציגו בדוח במפורש את אורכן. למעקב אחר דינמיקת המונים תעדו את המרווח בין תמונות המצב, כדי שהקורא יבין את היקף פרקי הזמן המושווים.

שמרו את טקסט הכיתוב המקורי וצרו בנפרד גרסה שעברה ניקוי. אמוג'י, תגיות, קישורים ומאפייני שפה עשויים להשפיע על הסיווג; שדות נפרדים יסייעו לבדוק את השפעת הניקוי ולחשב מחדש את הקטגוריות בלי לפנות שוב ל-API.

מה יפיק צינור העבודה

לאחר כמה מחזורי איסוף יהיה לצוות טבלה של פרסומים ותמונות מצב, קיבוץ ראשוני של הכיתובים לפי נושא ודוח על סרטונים חדשים בתקופה שנבחרה. כך אפשר לבחור סרטונים לבדיקה מקצועית, לזהות נושאים שחוזרים בחשבונות שנבחרו ולבדוק שינויים במונים הציבוריים בין מועדי התצפית.

על בסיס הנתונים אפשר לנסח השערות ללוח תוכן: להשוות דרכים להציג שאלה מסוימת או לבחור נושאים לניסוי בתוכן שלכם. כדי להעריך את ההשפעה המסחרית, השוו את התצפיות לנתונים הפנימיים של הקמפיינים.

כדי להתחיל, בחרו רשימה קצרה של חשבונות ציבוריים ובדקו את GET /getUserFeed באוסף הבקשות של Postman. הכניסו לקוד רק פרמטרים ונתיבי שדות שאושרו בתיעוד הנתיב ובתשובה שהתקבלה בפועל.

איסוף אחראי של נתונים ציבוריים

הגבילו את רשימת החשבונות למטרת המחקר, אספו רק את השדות הנחוצים והגדירו תקופת שמירה לתשובה הגולמית. לפני שימוש מסחרי בנתונים, בדקו את תנאי הפלטפורמה החלים ואת דרישות הפרטיות והמידע האישי בתחום השיפוט שלכם. בקרת גישה ומחיקת תמונות מצב שהתיישנו מסייעות להשאיר את התהליך בגבולות המשימה.

לפני שליחת נתונים לשירות AI, הסירו שדות שהמודל אינו זקוק להם. בדוח ציינו את מזהה הפרסום ואת מועד התצפית, כדי שעובד יוכל להגיע לרשומה ולבדוק את הבסיס למסקנה.

רשימת בדיקה קצרה להפעלה

  1. הגדירו תחום, רשימת חשבונות, תקופה ושאלות לניתוח.
  2. בדקו את הפרמטרים של GET /getUserFeed במדריך ואת התשובה בפועל ב-Postman.
  3. שמרו דוגמת JSON והגדירו מנרמל לשדות שאומתו.
  4. רשמו את התשובה הגולמית ואת תמונות המצב הנפרדות עם מועד האיסוף.
  5. חשבו מדדים מספריים בקוד; השתמשו ב-AI לקיבוץ ראשוני של טקסט.
  6. הוסיפו תזמון, רישום ביומן, טיפול בניסיונות חוזרים ותקופת שמירה.

כך הניטור הופך למחקר שניתן לשחזר: כל מסקנה קשורה לפרסום ולמועד תצפית, והקוד נשען על הסכמה בפועל של הנתיב שנבחר.

Keep readingכיצד לארכב תוצאות חיפוש ב-TikTok באמצעות Python
Read the next article

הפכו את מה שקראתם לאינטגרציה עובדת

גלו את ממשקי ה-API לנתונים חברתיים של jsonscraper, בדקו בקשות ובנו את תהליך העבודה הבא.

גלה API