Back to use cases

    Use case / SOCIAL DATA / Python 3 / Published

    Export TikTok Search and Hashtag Posts to One CSV

    Search TikTok for a keyword and a hashtag, merge the videos, drop duplicates, and write one CSV of views, likes, posting hour and engagement rate.

    Card for Export TikTok Search and Hashtag Posts to One CSV

    TikTok Scraper to CSV turns one keyword and one hashtag into a single CSV of the public TikToks Desearch returned, plus a short summary: median views, the top 10 by engagement rate, and how many videos were posted in each UTC hour.

    How this differs

    Export Twitter search results to CSV writes an X search to a sheet. TikTok trending hashtags by region counts tags on the TikTok trending feed. This script searches TikTok by a keyword and a hashtag, merges the two lists, and writes one CSV. It does not read the trending feed.

    The problem

    An analyst who wants views, likes, comments, shares, captions and post times for one topic should not have to copy videos by hand. A keyword search and a hashtag feed are different sets. Putting them in one sheet, without duplicate video ids, is the useful file.

    What the app does

    • Calls POST /desearch/tiktok/search with {"query", "count"} and GET /desearch/tiktok/hashtag/{tag}.
    • Asks for a second page by sending the running offset as cursor when the list body has no cursor field.
    • Dedupes by video id across pages and sources. source is search, hashtag, or search+hashtag.
    • Labels creators "Creator 1", "Creator 2" and so on. Account names and links inside captions are masked.
    • Sets engagement_rate to (likes + comments + shares) / views.
    • Writes the CSV and a summary. --from-raw rebuilds the CSV from saved JSON with no new calls.

    How it works

    1. Keyword. POST /desearch/tiktok/search once per page.
    2. Hashtag. GET /desearch/tiktok/hashtag/{tag}?count=... once per page.
    3. Flatten. One row per video id. The hashtag feed is not ordered by date, so filter create_time_utc if you want recent videos only.
    4. Summarise median views, the top 10 by engagement rate, and the UTC posting-hour spread.

    The code

    One row of the CSV:

    def row(p, source, creators):
        st = p.get("stats") or {}
        a = (p.get("author") or {}).get("id") or (p.get("author") or {}).get("username") or "?"
        creators.setdefault(a, f"Creator {len(creators) + 1}")
        t = to_utc(p.get("createTime"))
        views = st.get("playCount")
        eng = sum(x or 0 for x in (st.get("digCount"), st.get("commentCount"), st.get("shareCount")))
        vid = str(p.get("id") or "")
        return {"video_id": vid, "link": f"https://www.tiktok.com/video/{vid}" if vid else "",
                "source": source, "creator": creators[a],
                "caption": clean(p.get("text")),
                "create_time_utc": t.strftime("%Y-%m-%d %H:%M") if t else "", "posting_hour_utc": t.hour if t else "",
                "views": views, "likes": st.get("digCount"), "comments": st.get("commentCount"),
                "shares": st.get("shareCount"), "saves": st.get("collectCount"),
                "engagement_rate": round(eng / views, 4) if views else "",
                "duration_s": round(p["videoDuration"] / 1000, 1) if isinstance(p.get("videoDuration"), int) else ""}
    

    The live calls:

    def run_live(a):
        import requests
        key = os.environ.get("DESEARCH_API_KEY") or open(os.path.expanduser(os.environ["DESEARCH_KEY_FILE"])).read().strip()
        s = requests.Session(); s.headers["Authorization"] = key
        raw = {"keyword": a.keyword, "hashtag": a.hashtag, "count": a.count, "started": now(), "calls": []}
        cursor = None
        for i in range(a.pages):
            body = {"query": a.keyword, "count": a.count}
            if cursor: body["cursor"] = cursor
            rec = call(s, "POST", "/desearch/tiktok/search", f"search p{i+1}", body=body); raw["calls"].append(rec)
            cursor = next_cursor(rec, a.count * (i + 1))
            if not posts(rec): break
        cursor = None
        for i in range(a.pages):
            params = {"count": a.count}
            if cursor: params["cursor"] = cursor
            rec = call(s, "GET", f"/desearch/tiktok/hashtag/{a.hashtag}", f"hashtag p{i+1}", params=params); raw["calls"].append(rec)
            cursor = next_cursor(rec, a.count * (i + 1))
            if not posts(rec): break
        if a.empty_check:
            rec = call(s, "GET", f"/desearch/tiktok/hashtag/{a.empty_check}", "empty-result check", params={"count": 5})
            rec["probe"] = True; raw["calls"].append(rec)
        os.makedirs("evidence", exist_ok=True)
        json.dump(raw, open(f"evidence/raw-{a.label}.json", "w"), indent=1, default=str)
        return raw
    

    The key comes from the environment and is sent as the Authorization header. Captions mask account names and links before they are written.

    Endpoints

    • POST /desearch/tiktok/search
    • GET /desearch/tiktok/hashtag/{tag}

    Example output

    The 2026-10-16 sample used the keyword "skincare routine" and the hashtag skincare, 2 pages of 15 each. 60 rows came back and 59 were unique videos. In that run the keyword set and the hashtag set did not overlap. Every row had views, likes, comments, shares and saves filled.

    Run it yourself

    The public examples folder linked above includes tiktok_to_csv.py and a README. Install its requirements, set DESEARCH_API_KEY from console.desearch.ai, and use the run command in that README.

    What it costs

    Each response records X-Desearch-Cost-Usd. The sample write-up does not add those headers into a single total. Start with a small count and read the header on the first response before you raise --pages. See pricing.

    Ideas to extend it

    • Filter the CSV to videos posted in the last 30 days.
    • Add a column for the page number that returned the video.
    • Schedule a weekly run and append new video ids only.

    Use with AI agents

    Point an MCP client at the hosted Desearch server. Replace the placeholder with a key from the console. Do not commit the key.

    {
      "mcpServers": {
        "desearch": {
          "url": "https://mcp.desearch.ai/mcp",
          "headers": {
            "Authorization": "Bearer <DESEARCH_API_KEY>"
          }
        }
      }
    }

    Prompt you can paste for this example:

    Use Desearch to search TikTok for the keyword and hashtag I name. Merge the videos, drop duplicate ids, and write a CSV with views, likes, comments, shares, posting hour and engagement rate. Label creators as Creator 1, Creator 2 and so on. Do not add videos you did not retrieve.

    Build your own example.

    Give your agents access to web and social search. Get an API key to start building, or talk to us about your integration.

    Read docs