DEV Community

Aurelix Solutions
Aurelix Solutions

Posted on

See competitors' TikTok ads in Europe, with targeting and reach, in Python

Under the EU Digital Services Act, TikTok publishes the ads shown in the EU/EEA, Switzerland, the UK and Turkey in a public library: who ran it, who paid for it, when it ran, how many people saw it, and the targeting behind it, down to reach by country, age and gender. For a marketer that is a view into a competitor's TikTok strategy. The library is built for looking up one ad at a time, though, not for a table of every ad a brand ran this month or a morning list of what is new.

This post pulls those ads into Python, builds a competitor sheet with targeting and reach, and sets up a daily "new ads only" watch.

Disclosure: I'm Aurelix, and I built the TikTok Ads Library API. It's a paid tool (prices below).

What each row holds

One row per ad:

  • ad_id, ad_library_url, format (video or image), industry, and removed when TikTok shows a removal reason.
  • The advertiser: advertiser_name, advertiser_id, advertiser_registered_location, paid_for_by, and the linked TikTok account (tiktok_business_account, tiktok_account_display_name, tiktok_business_account_followers, tiktok_account_url).
  • first_shown and last_shown (ISO 8601 UTC), and unique_users_seen as TikTok shows it (100K-200K) plus unique_users_seen_range as numbers.
  • ad_text, call_to_action, landing_url (the app-store link for app ads) and objective (Reach, Sales, Traffic and so on).
  • video_urls, video_cover_urls and image_urls.
  • targeting: regions, ages and genders per country, interests, audience_size, custom and excluded audiences, languages, devices and operating systems, and reach_by_region with reach per age and gender.

Queries can be keywords ("sneakers"), advertiser names ("Nike", searched like a keyword) or Ad Library URLs copied from the browser. A URL that names one advertiser returns only that advertiser's ads.

A competitor's ads as a sheet

pip install apify-client
Enter fullscreen mode Exit fullscreen mode
import csv
import os

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("aurelix/tiktok-ads-library-scraper").call(
    run_input={
        "queries": ["sneakers", "running shoes"],
        "country": "FR",
        "days": 30,
        "maxResultsPerQuery": 100,
        "includeDetails": True,
    }
)
ads = list(client.dataset(run["defaultDatasetId"]).iterate_items())

fields = ["advertiser_name", "paid_for_by", "first_shown", "last_shown", "unique_users_seen",
          "objective", "call_to_action", "landing_url", "ad_text", "ad_library_url"]
with open("tiktok_ads.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
    w.writeheader()
    w.writerows(ads)

reach = {}
for ad in ads:
    low = (ad.get("unique_users_seen_range") or {}).get("min") or 0
    reach[ad["advertiser_name"]] = reach.get(ad["advertiser_name"], 0) + low
for name, users in sorted(reach.items(), key=lambda x: -x[1])[:10]:
    print(f"{name[:40]:40} at least {users:>12,} users reached")
Enter fullscreen mode Exit fullscreen mode

The first file is the swipe file: every ad with its text, call to action and landing page. The second loop ranks advertisers by the low end of the reach TikTok reports, a quick read on who reaches the most people in that niche.

Who the ads were aimed at

from collections import Counter

interests, ages = Counter(), Counter()
for ad in ads:
    t = ad.get("targeting") or {}
    interests.update(t.get("interests") or [])
    for country_ages in (t.get("ages") or {}).values():
        ages.update(country_ages)

print("Top interests:", interests.most_common(8))
print("Age groups:", ages.most_common())
Enter fullscreen mode Exit fullscreen mode

Each ad's targeting is read from its public detail page: the countries, ages, genders and interests it was aimed at, the estimated audience size, and reach_by_region, which splits the people reached by country, age and gender. Turn includeDetails off for a faster list without targeting at the same price per ad; details_loaded says which rows have the detail fields.

New ads every morning

run_input = {
    "queries": ["sneakers", "https://library.tiktok.com/ads?region=FR&adv_biz_ids=..."],
    "country": "FR",
    "days": 7,
    "maxResultsPerQuery": 100,
    "incremental": True,
    "stateKey": "fr-sneakers-watch",
    "slackWebhookUrl": "https://hooks.slack.com/services/...",
}
Enter fullscreen mode Exit fullscreen mode

With incremental on, the first run returns every matching ad. Each later run with the same stateKey skips ads an earlier run already returned, so a daily schedule pays only for new ones. Save the input as a task in the Apify Console, schedule it daily, and the Slack URL gets the count and the top 10 new ads; nothing is sent when a run finds nothing. webhookUrl posts the same digest as JSON to Zapier, Make or n8n.

Use cases

  • Competitor research: which products a competitor pushes on TikTok, with what message, aimed at whom, and for how long.
  • Creative swipe files: ad text, call to action, landing page and video links for a category, refreshed weekly.
  • Media planning: reach and audience size by country, age and gender to benchmark your own campaigns.
  • Lead lists for agencies: businesses that actively advertise on TikTok in a market, with their landing pages.

Why this tool

  • Targeting and reach on every ad: countries, ages, genders, interests, audience size, and reach by country, age and gender.
  • Several keywords, advertisers or Ad Library URLs in one run.
  • Exact limits: ask for 50 ads per query, get and pay for at most 50.
  • An all-in price with no platform usage on top, and a new-ads-only mode with a Slack or webhook digest.
  • What it doesn't cover: ads shown only outside the EU/EEA, Switzerland, the UK and Turkey, because TikTok's library doesn't list them.

Limits worth knowing

  • TikTok's search shows at most about 5,000 ads per query, and it sometimes ends a result list early. The query is then marked PARTIAL in the run status and you pay only for the ads returned; split big searches by country or a shorter period.
  • With targeting on, each ad needs its own detail page, so runs take about 2 seconds per ad.
  • Video and image links are signed by TikTok and expire after a while; download creatives soon after the run.
  • days goes back at most 365, because TikTok keeps ads in the library for a year after they last ran.

The tool reads only what a logged-out visitor sees in TikTok's public ad library, never logs in and solves no CAPTCHAs. Advertiser names are returned as the library shows them.

FAQ

Do I need a TikTok account? No. The tool never logs in and needs no TikTok account.

How do I get one exact advertiser's ads? Search the advertiser in TikTok's ad library, copy the page address (it contains adv_biz_ids) and paste it into queries. A plain name such as Nike is searched like a keyword.

Can I search every covered country at once? Yes: set country to all.

Are duplicates charged? No. Each ad is returned once per run even when several queries find it, and with incremental on, ads from earlier runs are skipped and not charged.

Can an AI agent use it? Yes, through Apify's MCP server or API: give queries and optional filters.

Cost

$1.40 per 1,000 ads, with or without the targeting summary, plus a $0.04 start fee per run at the default 2 GB of memory. The price is the same on every Apify plan, with no platform usage on top. Duplicate ads are never charged, and a search with no ads pays only the start fee. The current prices are on the Store page; Apify's free plan includes $5 of credit a month, enough for about 3,500 ads.

Links

Top comments (0)