Export TikTok Profiles, Posts, and Shop Data to CSV
Senior Web Scraping Engineer
TL;DR:
- Exporting TikTok data to CSV requires one table per entity level. Profiles, posts, products, and SKUs do not share a safe flat schema.
- Identifiers should remain text. Account, post, product, seller, and SKU IDs can lose precision when spreadsheet software interprets them as numbers.
- Numeric metrics need explicit conversion. Preserve missing values while converting valid counts and prices to the intended types.
- Nested lists need deliberate serialization. Hashtags and option pairs can use readable delimited text or separate child tables.
- CSV is created by the caller. The documented actors return JSON rather than native Excel workbooks.
- Free to start. New Scrapeless accounts include free credit through the Scrapeless Dashboard.
Introduction: flattening JSON is a data-model decision
JSON can represent nested statistics, arrays, product options, and SKU objects. A CSV file cannot. A useful export therefore begins by deciding which entity belongs in each row and how null values, strings, and nested collections will survive the conversion.
This tutorial maps Scrapeless TikTok profile, post, and Shop responses into four CSV files: profiles, posts, products, and SKUs. The conversion preserves identifier precision, keeps raw responses available, and avoids copying media files when a URL is sufficient.
The TikTok data actor overview maps the request inputs that produce these JSON objects.
Pipeline at a Glance
| JSON object | CSV grain | Primary key candidate |
|---|---|---|
| User Detail | One observed profile | account ID + collection time |
| User Work item | One observed post | post ID + collection time |
| Shop Page product | One observed product | product ID + region + collection time |
| Shop Page SKU | One observed variant | product ID + region + SKU ID + collection time |
The export is an application-side transformation. It does not claim that the actors return CSV or Excel files directly.
Prerequisites
- Saved JSON responses from
scraper.tiktok.user.detail,scraper.tiktok.user.work, orscraper.tiktok.shop.page - A collection timestamp recorded by the calling application
- A field dictionary defining type and null handling
- UTF-8-compatible spreadsheet or analytics software for inspection
The transformation below is locally runnable with reader-supplied JSON files. No Scrapeless credential is required after the responses have been saved.
Stage 1: Define Four Export Schemas
Profiles and posts connect through account identifiers, while products and SKUs connect through product ID and region. Keep these entity levels separate:
| File | Selected fields |
|---|---|
profiles.csv |
account ID, username, sec_uid, public statistics, flags, collection time |
posts.csv |
post ID, account ID, URL, description, hashtags, public metrics, collection time |
products.csv |
product ID, region, seller ID, name, price, currency, stock, rating, counts, collection time |
skus.csv |
product ID, region, SKU ID, options, SKU price, availability, collection time |
RFC 4180 defines a common CSV format and quoting rules; the CSV format specification is useful when exports move between systems. Python's CSV module documentation explains why files should be opened with newline="".
Stage 2: Preserve IDs and Nulls
Every numeric-looking identifier should be converted to a string before export. This applies to account, post, product, seller, music, and SKU IDs. A spreadsheet may otherwise display scientific notation or round digits.
Missing values should remain empty or use a documented null marker. Do not turn a missing follower count, price, stock quantity, rating, or engagement metric into zero.
Python's Decimal documentation describes exact decimal arithmetic for prices. For a plain CSV export, preserving source price text is often safer than converting it to binary floating point.
Start Scraping with Scrapeless
Power up your web scraping and automation workflow with Scrapeless!
Sign up today and get $5 in free credit — no credit card required.Claim your free credit now in the Scrapeless Dashboard.
Stage 3: Flatten Nested Fields Without Losing Meaning
Small display lists can use a delimiter that the field dictionary records. For example, hashtags can be joined with |, and SKU options can become Color=Black | Size=M. Keep a separate child table when list elements need their own fields or analysis.
Nested dictionaries should be mapped explicitly. Product currency and prices live under price; aggregate quantity and availability live under stock; profile counts live under statistics. A generic recursive flattener tends to produce unstable column names and mix entity levels.
Stage 4: Export the Four Tables in Python
The script expects up to three saved files in the current directory: profile.json, posts.json, and shop-product.json. It writes a CSV only when the corresponding source exists.
python
import csv
import json
from pathlib import Path
COLLECTED_AT = "reader-supplied-utc-timestamp"
def load_if_present(path):
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else None
def text_id(value):
return "" if value is None else str(value)
def write_csv(path, rows):
if not rows:
return
with path.open("w", newline="", encoding="utf-8") as output:
writer = csv.DictWriter(output, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
profile = load_if_present(Path("profile.json"))
posts_response = load_if_present(Path("posts.json"))
shop = load_if_present(Path("shop-product.json"))
if profile:
statistics = profile.get("statistics") or {}
write_csv(Path("profiles.csv"), [{
"collected_at": COLLECTED_AT,
"account_id": text_id(profile.get("account_id")),
"unique_id": profile.get("unique_id"),
"sec_uid": profile.get("sec_uid"),
"followers": statistics.get("followers"),
"following": statistics.get("following"),
"likes": statistics.get("likes"),
"videos": statistics.get("videos"),
"is_verified": profile.get("is_verified"),
"is_private": profile.get("is_private"),
}])
if posts_response:
post_rows = []
for item in posts_response.get("items") or []:
post_rows.append({
"collected_at": COLLECTED_AT,
"post_id": text_id(item.get("post_id")),
"author_id": text_id(item.get("author_id")),
"post_url": item.get("post_url"),
"description": item.get("description"),
"create_time": item.get("create_time"),
"hashtags": "|".join(str(v) for v in (item.get("hashtags") or [])),
"play_count": item.get("play_count"),
"like_count": item.get("like_count"),
"comment_count": item.get("comment_count"),
"share_count": item.get("share_count"),
"collect_count": item.get("collect_count"),
"is_pinned": item.get("is_pinned"),
})
write_csv(Path("posts.csv"), post_rows)
if shop:
price = shop.get("price") or {}
stock = shop.get("stock") or {}
product_id = text_id(shop.get("product_id"))
region = shop.get("region")
write_csv(Path("products.csv"), [{
"collected_at": COLLECTED_AT,
"product_id": product_id,
"region": region,
"seller_id": text_id(shop.get("seller_id")),
"name": shop.get("name"),
"currency": price.get("currency"),
"sale_price": price.get("sale_price"),
"original_price": price.get("original_price"),
"available_quantity": stock.get("available_quantity"),
"in_stock": stock.get("in_stock"),
"rating": shop.get("rating"),
"review_count": shop.get("review_count"),
"sold_count": shop.get("sold_count"),
}])
sku_rows = []
for sku in shop.get("skus") or []:
sku_price = sku.get("price") or {}
sku_rows.append({
"collected_at": COLLECTED_AT,
"product_id": product_id,
"region": region,
"sku_id": text_id(sku.get("sku_id")),
"options": " | ".join(
f"{v.get('name', '')}={v.get('value', '')}"
for v in (sku.get("options") or [])
),
"currency": sku_price.get("currency"),
"sale_price": sku_price.get("sale_price"),
"available_quantity": sku.get("available_quantity"),
"in_stock": sku.get("in_stock"),
})
write_csv(Path("skus.csv"), sku_rows)
The timestamp is deliberately supplied by the reader because it belongs to the collection event, not to the export run. Replace the placeholder before execution with the timestamp stored beside the source response.
Stage 5: Validate the CSV Output
Validation should check the file and the analytical meaning:
- Open every CSV as UTF-8 and confirm descriptions and product names remain readable.
- Import identifier columns explicitly as text and compare their digits with the source JSON.
- Count source entities and exported rows at the same grain.
- Check that missing values remain empty rather than zero.
- Confirm price always travels with currency and product or SKU context.
- Inspect descriptions, hashtags, and option strings containing commas, quotes, or line breaks.
Keep a small field dictionary beside the files with source path, CSV column, type, null policy, and entity grain. This makes each export reproducible when the selected fields or downstream tools change.
Keep Media as References
Avatar, cover, product, and SKU image fields can contain URLs. Export only the links needed for the analysis and avoid downloading unrelated media into the dataset. URL lifetimes can vary, so a CSV link should not be treated as a permanent media archive.
For larger pipelines, keep the raw JSON and collection metadata in durable storage, then regenerate CSV views for each analyst or tool. Scrapeless provides the actors through Scraping API; review the current pricing page when planning collection volume.
Conclusion: export by entity, not by response shape
Exporting TikTok data to CSV works when the conversion preserves entity grain, identifier strings, nulls, nested-field meaning, currency, and collection time. Four focused files are easier to validate and join than one wide table that mixes profiles, posts, products, and variants.
Ready to Build Analysis-Ready CSV Exports?
Join the Scrapeless Discord or Telegram community to compare export schemas. Create an account in the Scrapeless Dashboard when you are ready to collect the source JSON.
FAQ
Q: Can the TikTok actors export CSV or Excel files directly?
The documented actors return JSON. The calling application converts the response into CSV or another analytical format.
Q: Why should TikTok IDs be exported as text?
Text preserves every digit when spreadsheet and analytics tools would otherwise coerce a long numeric-looking identifier.
Q: Should profiles, posts, products, and SKUs share one CSV?
Profiles, posts, products, and SKUs should use separate tables because each has a different row grain and key.
Q: How should missing TikTok metrics appear in CSV?
Missing metrics should remain empty or use a documented null marker. They should not become zero without source evidence.
Q: Does the export need to download images and videos?
The export does not need to download media. Store only the source URLs required by the analysis and respect the project's retention policy.
Q: Is exporting public TikTok data legal?
Legality depends on jurisdiction, purpose, access method, data, and applicable terms. Minimize exported fields and obtain legal advice for the intended use.
At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.



