{"slug": "compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python", "title": "Compare how a keyword is doing on TikTok, YouTube and X in Python", "summary": "A developer at Qovia published a Python walkthrough for comparing keyword activity across TikTok, YouTube and X using the company's unified public-data API, which exposes search across roughly 20 platforms behind a single key and JSON envelope. The script normalizes posts into one table and reports post counts, recency, reach and top posts per platform, handling pagination, 429 rate limits via Retry-After, and platform-specific metric differences. The author notes reach (plays or views) is comparable across all three platforms, while engagement is only comparable between TikTok and X without enriching YouTube data.", "body_md": "You have a product name, a campaign hashtag or a competitor, and someone asks: \"Where are people actually talking about this, TikTok, YouTube or X?\" Answering that usually means three different APIs, three auth schemes and three response shapes. In this tutorial we'll write one short Python script that searches all three for the same keyword, normalizes the posts into one table, and prints a side-by-side comparison: how many posts, how recent, how much reach, and the top posts per platform.\n\nWe'll use [Qovia](https://qovia.ai), a paid public-data API that exposes TikTok, YouTube and X search (plus about 20 other platforms) behind one key and one JSON envelope. Full disclosure: I work on Qovia. Everything below uses only the documented endpoints from the [API reference](https://qovia.ai/docs/api-reference/).\n\n`requests` (`pip install requests pandas`)\n\n```\nexport QOVIA_KEY=\"sk-qovia-...\"\n```\n\n| Platform | Endpoint | Useful params | \n|---|---|---|\n| TikTok | `GET /v1/raw/tiktok/search` | `keyword` ,`sort` (`general` /`most_liked` ),`publish_time` (`all` ,`day` ,`week` ,`month` ,`quarter` ,`half_year` ),`region` (e.g.`US` ),`count` (max 30), integer`cursor` | \n| YouTube | `GET /v1/raw/youtube/search` | `keyword` ,`upload_date` (`hour` ,`today` ,`week` ,`month` ,`year` ; default`month` ), string`cursor` | \n| X | `GET /v1/raw/x/search` | `keyword` (X search syntax works, e.g.`#hashtag` ),`type` (`top` /`media` ), string`cursor` | \n\nAll three return the same record shape inside `data.items`: `id`, `platform`, `text`, `author` (with `nickname`, `followers`), `metrics`, and `extra` (with `published_at` in UTC and `url`). Pagination is the same everywhere: pass back `data.cursor`, stop when `data.has_more` is false.\n\nThe metrics differ per platform, and it's worth knowing that up front:\n\n`plays`, `likes`, `comments`, `shares`, `saves`.` raw.youtube.video`.` views`, `quotes`, So \"reach\" (plays or views) is comparable across all three, and \"engagement\" only across TikTok and X unless you enrich YouTube.\n\nEvery Qovia response is either `{\"request_id\", \"data\", \"meta\"}` on success or `{\"error\": {\"code\", \"message\", ...}}` on failure. Rate limits are 60 requests per minute on a new account, and a 429 comes with `Retry-After`, so the client honours that.\n\n``` python\nimport os, time, requests\n\nBASE = \"https://api.qovia.ai\"\nSESSION = requests.Session()\nSESSION.headers[\"Authorization\"] = f\"Bearer {os.environ['QOVIA_KEY']}\"\n\ndef call(path, **params):\n    for attempt in range(4):\n        r = SESSION.get(BASE + path, params=params, timeout=60)\n        if r.status_code == 429:\n            time.sleep(int(r.headers.get(\"Retry-After\", \"5\")))\n            continue\n        body = r.json()\n        if \"error\" in body:\n            code = body[\"error\"][\"code\"]\n            if code in (\"upstream_busy\", \"upstream_timeout\") and attempt < 3:\n                time.sleep(2 * (attempt + 1))\n                continue\n            raise RuntimeError(f\"{path}: {code}: {body['error']['message']}\")\n        return body[\"data\"] or {}, body[\"meta\"]\n    raise RuntimeError(f\"{path}: gave up after retries\")\n```\n\nTwo details from the docs worth respecting: unknown query parameters are rejected with `400 invalid_params` (so typos fail loudly instead of being ignored), and an empty result is still HTTP 200 with `meta.empty: true`, which is why we fall back to `{}`.\n\n``` python\ndef search(path, pages=3, **params):\n    items, cursor = [], None\n    for _ in range(pages):\n        p = dict(params)\n        if cursor is not None:\n            p[\"cursor\"] = cursor\n        data, meta = call(path, **p)\n        items += data.get(\"items\", [])\n        if not data.get(\"has_more\"):\n            break\n        cursor = data.get(\"cursor\")\n    # TikTok notes adjacent pages may repeat a few posts: dedupe by id\n    return list({it[\"id\"]: it for it in items}.values())\n\ndef collect(keyword, pages=3):\n    return {\n        \"tiktok\": search(\"/v1/raw/tiktok/search\", pages, keyword=keyword,\n                         publish_time=\"month\", region=\"US\", count=30),\n        \"youtube\": search(\"/v1/raw/youtube/search\", pages, keyword=keyword,\n                          upload_date=\"month\"),\n        \"x\": search(\"/v1/raw/x/search\", pages, keyword=keyword, type=\"top\"),\n    }\n```\n\nThree pages per platform is nine calls, under one cent once the trial is used up.\n\nOne honest caveat about the time windows: TikTok and YouTube let you ask for \"the last month\", while X search returns X's \"top\" order with no date filter. We'll filter X by `published_at` ourselves after the fact so the comparison is fair.\n\n``` python\nimport pandas as pd\n\ndef to_rows(results):\n    rows = []\n    for platform, items in results.items():\n        for it in items:\n            m, ex, au = it.get(\"metrics\") or {}, it.get(\"extra\") or {}, it.get(\"author\") or {}\n            reach = m.get(\"plays\") if platform != \"x\" else m.get(\"views\")\n            engagement = None\n            if platform in (\"tiktok\", \"x\"):\n                engagement = sum((m.get(k) or 0) for k in (\"likes\", \"comments\", \"shares\", \"saves\"))\n            rows.append({\n                \"platform\": platform,\n                \"id\": it[\"id\"],\n                \"published_at\": pd.to_datetime(ex.get(\"published_at\"), utc=True),\n                \"author\": au.get(\"nickname\"),\n                \"author_followers\": au.get(\"followers\"),\n                \"reach\": reach,\n                \"engagement\": engagement,\n                \"text\": (ex.get(\"title\") or it.get(\"text\") or \"\").replace(\"\\n\", \" \")[:90],\n                \"url\": ex.get(\"url\"),\n            })\n    return pd.DataFrame(rows)\n```\n\nYouTube records put the video title in `extra.title` and title-plus-snippet in `text`, so we prefer the title. Note that YouTube's `author.followers` is null in search results; the channel endpoint has subscriber counts if you need them.\n\n``` python\ndef compare(keyword, days=30):\n    df = to_rows(collect(keyword))\n    cutoff = pd.Timestamp.now(tz=\"UTC\") - pd.Timedelta(days=days)\n    df = df[df[\"published_at\"] >= cutoff]\n\n    summary = df.groupby(\"platform\").agg(\n        posts=(\"id\", \"count\"),\n        median_reach=(\"reach\", \"median\"),\n        total_reach=(\"reach\", \"sum\"),\n        median_engagement=(\"engagement\", \"median\"),\n        newest=(\"published_at\", \"max\"),\n    )\n    print(f\"\\n=== '{keyword}' over the last {days} days ===\")\n    print(summary.to_string())\n\n    for platform, group in df.groupby(\"platform\"):\n        print(f\"\\nTop 3 on {platform} by reach:\")\n        for _, r in group.nlargest(3, \"reach\").iterrows():\n            print(f\"  {int(r.reach or 0):>10,}  {r.author}: {r.text}\")\n            print(f\"              {r.url}\")\n    return df\n\nif __name__ == \"__main__\":\n    import sys\n    df = compare(sys.argv[1] if len(sys.argv) > 1 else \"cursor ai\")\n    df.to_csv(\"comparison.csv\", index=False)\n```\n\nRun it:\n\n```\npython compare.py \"your product name\"\n```\n\nYou get a summary table with one row per platform plus the three biggest posts on each, and a `comparison.csv` you can open in a spreadsheet or feed into a notebook.\n\nA few patterns show up quickly when you run this on real keywords:\n\n`author_followers`. If the top posts are all from accounts with huge followings, the \"conversation\" is really a couple of announcements and their replies.\nBecause the three samples are each platform's own ranking (TikTok relevance, YouTube relevance, X \"top\"), treat counts as a sample, not a census. The useful signal is the relative shape: where reach concentrates, how fresh the newest posts are, and who is driving it.\n\n`GET /v1/raw/youtube/video?video_id=...` with `extra.source_id` for the top videos to get likes and comment counts.`raw.tiktok.comments`, `raw.youtube.comments` and `raw.x.comments` for the top posts and run the same sentiment or keyword pass across all three.`https://api.qovia.ai/mcp` (OAuth or the same API key as a Bearer token), so Claude, Cursor or VS Code can run this comparison conversationally. See the The full parameter list for every endpoint is in the [API reference](https://qovia.ai/docs/api-reference/), and there's a single-file version at [qovia.ai/llms-full.txt](https://qovia.ai/llms-full.txt) if you want to drop it into a model's context.\n\n*This article was written with AI assistance and reviewed against Qovia's documentation. I work on Qovia.*", "url": "https://wpnews.pro/news/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python", "canonical_source": "https://dev.to/qovia/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python-4nc3", "published_at": "2026-11-04 03:45:10+00:00", "updated_at": "2026-11-04 03:55:44.321927+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Qovia", "TikTok", "YouTube", "X", "Python"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python", "markdown": "https://wpnews.pro/news/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python.md", "text": "https://wpnews.pro/news/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python.txt", "jsonld": "https://wpnews.pro/news/compare-how-a-keyword-is-doing-on-tiktok-youtube-and-x-in-python.jsonld"}}