ブログに戻ります

TikTokデータパイプラインを構築して、再利用可能な分析を行う

Alex Johnson
Alex Johnson

Senior Web Scraping Engineer

04-Sep-2026

TL;DR:

  • TikTokデータパイプラインは、正規化の前に生のレスポンスを保持する必要があります。 ソースペイロードはパーサーの変更やフィールドのドリフトをレビュー可能にします。
  • 収集ランにはそれぞれのステータスターブルが必要です。 失敗したリクエストはカバレッジのギャップであり、空のプロファイル、投稿リスト、または商品ではありません。
  • TikTok識別子はテキスト列に属します。 文字列ストレージは長いIDが数値変換によって変更されるのを防ぎます。
  • スナップショットテーブルは時間を通じた観察を説明します。 それらは以前のクリエイター、投稿、またはショップの値を上書きするべきではありません。
  • 履歴カバレッジは収集されたページと検証された継続データに依存します。 1ページ目のレスポンスは完全なアカウント履歴ではありません。
  • 無料で始められます。 新しいScrapelessアカウントにはScrapelessダッシュボードを通じた無料クレジットが含まれています。

Introduction: analytics begins with collection evidence

ダッシュボードはそのデータベースに到達したレコードのみを説明できます。生のペイロード、実行ステータス、収集タイムスタンプがなければ、空のチャートは活動がないことと失敗した収集やパーサーの変更を区別することができません。

このガイドはScrapeless TikTokアクターからSQLiteへのコンパクトなTikTokデータパイプラインを構築します。設計は生のJSONを保持し、クリエイター、投稿、ショップのスナップショットを正規化し、データ品質チェックを実行し、分析のための小さなSQLクエリを公開します。スケジューリング、生産データベース操作、ビジネスインテリジェンスの配信はアプリケーションの責任のままです。

TikTokアクターガイドはコレクターが使用するアクターマップを文書化しています。

Pipeline at a Glance

Stage Action Output
Collect プロファイル、投稿、オプションのショップアクターを呼び出す APIレスポンス
Preserve アクターと実行メタデータを持つ生のJSONを保存 不変のソースレイヤ
Normalize ID、カウンタ、タイムスタンプ、および次元を解析 スナップショットテーブル
Validate キー、タイプ、ヌル、および実行カバレッジを確認 データ品質結果
Query 変更と現在の状態を集約 分析ビュー

パイプラインは、各リクエストが返した内容を記録します。必要なページと継続値がすべて収集され確認されない限り、完全なTikTok履歴を主張することはありません。

Prerequisites

  • ScrapelessダッシュボードからのScrapelessアカウントとAPIキー
  • 標準ライブラリとSQLiteを持つPython 3
  • プロファイルと投稿収集用の公開TikTokユーザー名1つ
  • 商品スナップショット用のオプションのTikTokショップ商品IDと地域
  • ストレージロケーション、保持ポリシー、および収集スケジュール
  • コレクター用のSCRAPELESS_API_KEYTIKTOK_USERNAME; ショップ環境変数はオプション

エンドツーエンドコードは、ライブScrapeless資格情報とターゲット識別子がリーダーから来るため、前提条件のギャップになります。アクター名、入力フィールド、および正規化された列は、発行されたインターフェースドキュメントに従っており、発明された出力を示すことはありません。

Stage 1: Give Every Collection Attempt an Identity

論理的なコレクションのための1つのrun_idと、各アクターリクエストごとに1行を作成します。アクター名、リクエスト入力、収集時間、ステータス、およびエラー詳細を保存します。生のペイロードテーブルは同じ実行を参照し、パーサーバージョンを記録する必要があります。

HTTPレスポンスの処理は、成功したアプリケーションレスポンスと失敗を区別する必要があります。 HTTPセマンティクス仕様は、クライアントとサーバーによって使用されるステータスコードフレームワークを定義しています。

カバレッジテーブルは、次の3つの基本的な質問に答えることができます。

  • どのエンティティがリクエストされましたか?
  • どのリクエストが使用可能なペイロードを生成しましたか?
  • どの正規化されたテーブルが各ペイロードから行を受け取りましたか?

失敗した投稿リクエストを空のitems配列として表現しないでください。その状態は異なる分析的意味を持ちます。

Stage 2: Preserve Raw JSON Before Parsing

生のレスポンスは、TikTok APIからデータベースへのワークフローの監査レイヤーです。アクター名、リクエストされた入力、収集時間、レスポンスJSON、およびパーサーバージョンを一緒に保存します。PythonのJSONドキュメントは、例で使用されるシリアライゼーションインターフェースを定義します。

生のストレージは3つの実用的な目的に役立ちます。

  1. スキーマ変更後にパーサーを再実行できます。
  2. 疑わしい正規化された値をそのソースフィールドに追跡できます。
  3. 新しいフィールドは、収集を繰り返すことなく保持されたペイロードからバックフィルできます。

リクエストメタデータを書き込む前に秘密を削除します。APIキーはプロセス構成に属し、生ペイロードや実行テーブルには入るべきではありません。

Scrapelessでスクレイピングを開始

Scrapelessを使用して、ウェブスクレイピングと自動化ワークフローをパワーアップしましょう!
今日はサインアップして**$5の無料クレジット**を獲得しましょう — クレジットカードは不要です。
今すぐScrapeless Dashboardで無料クレジットを請求してください。

ステージ 3: クリエイター、投稿、および製品のスナップショットを正規化する

自然な識別子はテキストとして保持し、すべてのスナップショットキーにcollected_atを含めます。クリエイタープロファイルは変更される可能性があり、投稿カウンターは増加し、Shop製品は価格、在庫、評価、またはレビュー数を変更する可能性があります。

正規化レイヤーは以下のテーブルから始めることができます:

テーブル エンティティキー スナップショットフィールド
profile_snapshots アカウントID + 収集時間 ユーザー名、フォロワー数、いいね、動画
post_snapshots 投稿ID + 収集時間 クリエイターID、期間、再生数、いいね、コメント、シェア
product_snapshots 製品ID + 地域 + 収集時間 名前、価格、通貨、在庫、評価、レビュー数
post_hashtags 投稿ID + 収集時間 + ハッシュタグ 正規化タグ

SQLiteのCREATE TABLE ドキュメントは、このモデルのプライマリキーおよびタイプ制約を説明しています。

注意: 下記のコードはライブのSCRAPELESS_API_KEYおよびTIKTOK_USERNAME値を必要とします。TIKTOK_SHOP_PRODUCT_IDおよびTIKTOK_SHOP_REGIONはオプションであり、Shopブランチを有効にします。

python Copy
import json
import os
import sqlite3
import uuid
from datetime import datetime, timezone
from urllib.request import Request, urlopen

ENDPOINT = "https://api.scrapeless.com/api/v1/scraper/request"
PARSER_VERSION = "tiktok-v1"


def utc_now():
    return datetime.now(timezone.utc).isoformat()


def request_actor(actor, actor_input):
    body = json.dumps({"actor": actor, "input": actor_input}).encode()
    request = Request(
        ENDPOINT,
        data=body,
        headers={
            "content-type": "application/json",
            "x-api-token": os.environ["SCRAPELESS_API_KEY"],
        },
        method="POST",
    )
    with urlopen(request, timeout=60) as response:
        return json.load(response)


def integer(value):
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


database = sqlite3.connect("tiktok-analytics.sqlite3")
database.executescript("""
CREATE TABLE IF NOT EXISTS collection_runs (
  run_id TEXT NOT NULL, actor TEXT NOT NULL, collected_at TEXT NOT NULL,
  request_json TEXT NOT NULL, status TEXT NOT NULL, detail TEXT,
  PRIMARY KEY (run_id, actor)
);
CREATE TABLE IF NOT EXISTS raw_payloads (
  run_id TEXT NOT NULL, actor TEXT NOT NULL, parser_version TEXT NOT NULL,
  payload_json TEXT NOT NULL, PRIMARY KEY (run_id, actor)
);
CREATE TABLE IF NOT EXISTS profile_snapshots (
  collected_at TEXT NOT NULL, account_id TEXT NOT NULL, unique_id TEXT,
  followers INTEGER, likes INTEGER, videos INTEGER,
  PRIMARY KEY (collected_at, account_id)
);
CREATE TABLE IF NOT EXISTS post_snapshots (
  collected_at TEXT NOT NULL, post_id TEXT NOT NULL, account_id TEXT NOT NULL,
  video_duration INTEGER, play_count INTEGER, like_count INTEGER,
  comment_count INTEGER, share_count INTEGER,
  PRIMARY KEY (collected_at, post_id)
);
CREATE TABLE IF NOT EXISTS post_hashtags (
  collected_at TEXT NOT NULL, post_id TEXT NOT NULL, hashtag TEXT NOT NULL,
  PRIMARY KEY (collected_at, post_id, hashtag)
);
CREATE TABLE IF NOT EXISTS product_snapshots (
  collected_at TEXT NOT NULL, product_id TEXT NOT NULL, region TEXT NOT NULL,
  name TEXT, sale_price TEXT, currency TEXT, available_quantity INTEGER,
  rating TEXT, review_count INTEGER,
  PRIMARY KEY (collected_at, product_id, region)
);
""")

run_id = str(uuid.uuid4())
collected_at = utc_now()


def collect(actor, actor_input):
    request_json = json.dumps(actor_input, sort_keys=True)
    try:
        payload = request_actor(actor, actor_input)
        database.execute(
            "INSERT INTO raw_payloads VALUES (?, ?, ?, ?)",
            (run_id, actor, PARSER_VERSION, json.dumps(payload, ensure_ascii=False)),
        )
        status, detail = "success", None
    except Exception as error:
        payload = None
        status, detail = "failed", f"{type(error).__name__}: {error}"
    database.execute(
        "INSERT INTO collection_runs VALUES (?, ?, ?, ?, ?, ?)",
        (run_id, actor, collected_at, request_json, status, detail),
    )
    return payload


profile = collect(
    "scraper.tiktok.user.detail",
    {"unique_id": os.environ["TIKTOK_USERNAME"]},
)

if profile:
    stats = profile.get("statistics") or {}
    account_id = str(profile.get("account_id") or "")
    database.execute(
        "INSERT INTO profile_snapshots VALUES (?, ?, ?, ?, ?, ?)",
        (
            collected_at, account_id, profile.get("unique_id"),
            integer(stats.get("followers")), integer(stats.get("likes")),
            integer(stats.get("videos")),
        ),
    )

    posts = collect(
        "scraper.tiktok.user.work",
        {"sec_uid": profile["sec_uid"], "cursor": "0", "count": 10},
    )
    if posts:
        for post in posts.get("items") or []:
            post_id = str(post.get("post_id") or post.get("video_id") or "")
            database.execute(
                "INSERT INTO post_snapshots VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                (
                    collected_at, post_id, account_id,
                    integer(post.get("video_duration")),
                    integer(post.get("play_count")), integer(post.get("like_count")),
                    integer(post.get("comment_count")), integer(post.get("share_count")),
                ),
            )
            for hashtag in set(post.get("hashtags") or []):
                normalized = str(hashtag).strip().removeprefix("#").casefold()
                if normalized:
                    database.execute(
                        "INSERT INTO post_hashtags VALUES (?, ?, ?)",
                        (collected_at, post_id, normalized),
                    )

product_id = os.getenv("TIKTOK_SHOP_PRODUCT_ID")
product_region = os.getenv("TIKTOK_SHOP_REGION")
if product_id and product_region:
    product = collect(
        "scraper.tiktok.shop.page",
        {"product_id": product_id, "region": product_region},
    )
    if product:
        price = product.get("price") or {}
        stock = product.get("stock") or {}
        database.execute(
            "INSERT INTO product_snapshots VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (
                collected_at, str(product.get("product_id") or product_id),
                str(product.get("region") or product_region).casefold(),
                product.get("name"), price.get("sale_price"),
                price.get("currency"), integer(stock.get("available_quantity")),
                str(product.get("rating")) if product.get("rating") is not None else None,
                integer(product.get("review_count")),
            ),
        )

database.commit()
database.close()

この例では、最初にリクエストされた投稿ページのみを収集します。確認されたレスポンスを超える継続フィールドは想定されないため、完全なアカウント履歴を約束するものではありません。

ステージ 4: 分析前にデータ品質チェックを追加する

品質チェックは、収集レイヤーと正規化レイヤーの両方で実行する必要があります。最低限、次の項目にフラグを立てます:

  • collection_runsにおける失敗したアクターリクエスト
  • 期待されるエンティティ行を生成しなかった成功した生のペイロード
  • 空のアカウント、投稿、または製品ID
  • ネガティブカウンター
  • 期間分析におけるゼロまたは欠落している動画の長さ
  • 地域または通貨コンテキストが欠如している製品行
  • 1つのコレクションタイムスタンプに対する重複したエンティティキー

チェックは、コンソールのみのメッセージではなく、クエリ可能な結果として保持します。その後、ダッシュボードは影響を与えるメトリックの横にカバレッジギャップを表示できます。

ステージ 5: 時間を消去せずにスナップショットをクエリする

ウィンドウ関数は、各エンティティをその前の観察と比較します。SQLiteのウィンドウ関数ドキュメントは、このユースケースのためのLAG()を定義しています。

sql Copy
-- Illustrative follower-change query over the normalized schema.
SELECT
  account_id,
  collected_at,
  followers,
  followers - LAG(followers) OVER (
    PARTITION BY account_id ORDER BY collected_at
  ) AS follower_change
FROM profile_snapshots;

-- Illustrative run-coverage query.
SELECT actor, status, COUNT(*) AS run_count
FROM collection_runs
GROUP BY actor, status
ORDER BY actor, status;

ダッシュボードのために現在の状態ビューを使用し、基礎となるテーブルは追加専用として保持します。同じパターンは、投稿カウンターの変更、ハッシュタグレポート、期間バンドの比較、製品価格の変更、在庫イベント、および評価モニタリングをサポートします。

ScrapelessはTikTokアクターを提供します。エンティティカバレッジと収集頻度を選択する前に、現在の価格ページを確認してください。

TikTokデータを責任を持って扱う

定義された分析目的に必要な公のフィールドを収集し、生のペイロードへのアクセスを制限し、クリエイターレベルデータの保持制限を設定します。NISTプライバシーフレームワークは、プライバシーリスクガバナンスおよびデータ最小化に関する一般的なガイダンスを提供します。

運用設定は、アナリストと共有されるデータベース行の外に保持します。APIキー、内部アラートルート、およびアクセス資格情報は、アプリケーション環境によって制御されるシークレットシステムに保持する必要があります。

結論: すべてのメトリックの横にカバレッジを可視化する

信頼できるTikTokデータパイプラインは、生のJSON、実行ステータス、パーサーのバージョン、収集時間、安定したIDによって接続された正規化されたスナップショットを保持します。その構造により、アナリストは実際のゼロアクティビティを欠落したコレクションから分離し、フィールドの変更後にパーサーを再実行し、すべてのメトリックを観察に追跡できます。生産スケジューリング、ストレージスケーリング、およびBIデリバリーは、同じ証拠モデルの周りに成長できます。

TikTok分析パイプラインの構築に準備はできましたか?

スナップショットや倉庫スキーマについて議論するために、Scrapeless DiscordまたはTelegramコミュニティに参加してください。最初のエンティティリストが準備できたら、Scrapeless Dashboardにアカウントを作成してください。

FAQ

Q: TikTok APIはデータベースにどのように接続すべきですか?

TikTok APIは、実行ステータスを記録し、生のJSONを保持し、フィールドを検証し、タイムスタンプ付きエンティティスナップショットを挿入するコレクタを介してデータベースに接続すべきです。

Q: なぜ生のTikTokレスポンスを保存するのですか?

生のTikTokレスポンスは、チームが正規化された値を追跡し、スキーマの変更を確認し、保持されたソースデータに対してパーサーを再実行することを可能にします。
Q: TikTokのIDは整数列を使用すべきですか?

TikTokのIDはテキスト列を使用すべきです。識別子は不透明な文字列であり、数値変換によって変更されるべきではありません。

Q: 最初のポストリクエストには完全なアカウント履歴が含まれていますか?

最初のポストリクエストは完全なアカウント履歴を確立しません。カバレッジは収集されたページと検証された継続データに依存します。

Q: Scrapelessはスケジューラとデータウェアハウスを管理していますか?

Scrapelessはコレクションのための構造化されたアクター応答を提供します。呼び出し元はスケジューリング、データベース操作、変換、品質監視、およびBI配信を管理します。

Q: 公共のTikTokデータをスクレイピングすることは合法ですか?

合法性は管轄、目的、アクセス方法、適用される条件、および収集されたフィールドに依存します。許可された目的のために公共データを使用し、意図したパイプラインに関して法律的アドバイスを求めてください。

Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。

最も人気のある記事

カタログ