1. 2scraper
  2. Social media & video
  3. TikTok Ad Library
Open source · MITRuns with no key, proxy or account

TikTok Ad Library Scraper

Ads from TikTok's EU Ad Library — advertiser, creatives, run dates and audience bucket across 33 regions, no key needed.

  • Playwright
  • Selenium
  • Puppeteer
  • Scraping Browser API
  • JSON
  • CSV
tiktok-ads-scraper
# Ads by region
$ ./venv/bin/python playwright_scraper.py --region DE,FR,GB,IT --pages 10 --days 90 --format both
✓ 21 sample rows · 26 columns in sample_output.json
33regions covered (EU, EEA, CH, GB, TR)
12ads per request, the library's fixed page size
3browser engines
0keys, proxies or accounts needed
Available TikTok Ad Library scrapers

What the TikTok Ad Library scraper collects

Extracts advertisements from TikTok's EU Ad Library by region or advertiser keyword: the advertiser, video and image creatives, first and last shown dates, TikTok's audience bucket and the policy reason on rejected ads. A browser opens the library once to mint its request header, then pages through the search endpoint by cursor and writes JSON or CSV.

Ads by region

Input: One or more region codes (--region), a look-back window (--days)

One row per ad per region: advertiser, creative type and media URLs, run dates, days shown, estimated audience and audit status.

Ads by keyword

Input: A region and a search keyword (--query)

The same rows, filtered by the library's own keyword search — which matches more than the advertiser's name, so resellers appear too.

Quick start

From zero to TikTok Ad Library data in 3 steps

  1. Install

    Clone the repository and install one browser engine. Python 3.9+.

    $ git clone https://github.com/2scraper/tiktok-ads-scraper && cd tiktok-ads-scraper
    $ python3 -m venv venv
    $ ./venv/bin/pip install -r requirements.txt -r requirements-playwright.txt
    $ ./venv/bin/python -m playwright install chromium
    $ ./venv/bin/python playwright_scraper.py --region DE
  2. Run a scraper

    Pick what you need and run it. Every flag is documented in the README.

    $ ./venv/bin/python playwright_scraper.py --region DE,FR,GB,IT --pages 10 --days 90 --format both
  3. Get structured data

    Rows land in JSON or CSV (--format), with a .meta.json sidecar recording status, pages fetched and why the run stopped.

Output

TikTok Ad Library data fields

26 columns per row in a predictable schema. The most useful ones:

FieldWhat it holds
ad_idTikTok's ad id — not unique across regions
row_key{region}:{ad_id}, the unique key for dedupe and diffs
advertiser_nameAdvertiser; a few ads are published anonymously
regionRegion the ad was found in
first_shown_atWhen the ad first ran (ISO, converted from milliseconds)
last_shown_atWhen the ad last ran
days_shownDays between first and last shown
creative_typevideo or image, derived from the creatives
video_countNumber of video creatives
video_urlsThe library's proxied video URLs
cover_urlsVideo cover image URLs
image_urlsImage creative URLs
estimated_audienceTikTok's audience bucket, e.g. "0-1K", kept as a string
rejection_infoTikTok's policy reason on a rejected ad
audit_statusTikTok's own audit value, passed through unmapped
[
  {
    "source": "library.tiktok.com",
    "scraped_at": "2026-09-22T22:26:45Z",
    "url": "https://library.tiktok.com/ads/detail/?ad_id=1877067575828514&region=DE",
    "sku": "1877067575828514",
    "title": "Faisal bin Abdullah",
    "ad_id": "1877067575828514",
    "row_key": "DE:1877067575828514",
    "advertiser_name": "Faisal bin Abdullah",
    "region": "DE",
    "first_shown_at": "2026-09-22T21:33:56+00:00",
    "last_shown_at": "2026-09-22T21:33:56+00:00",
    "days_shown": 0,
    "creative_type": "video",
    "video_count": 1,
    "image_count": null,
    "video_urls": [
      "https://library.tiktok.com/api/v1/cdn/1790116006/video/aHR0cHM6Ly92MTZtLnRpa3Rva2Nkbi5j…"
    ],
    "cover_urls": [
      "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0037/oYhIw9WIyuvhAc1itiUmCXilyB7jf4AH…"
    ],
    "image_urls": null,
    "audit_status": "1",
    "sor_audit_status": "1",
    "show_mode": 1,
    "ad_type": "2",
    "rejection_info": null,
    "estimated_audience": "0-1K",
    "page": 1,
    "position": 1
  },
  {
    "source": "library.tiktok.com",
    "scraped_at": "2026-09-22T22:26:45Z",
    "url": "https://library.tiktok.com/ads/detail/?ad_id=1877067300684802&region=DE",
    "sku": "1877067300684802",
    "title": "لاتفوتكم فعالياتهم😍 @الهرم #الهرم #القصيم #بريده ad",
    "ad_id": "1877067300684802",
    "row_key": "DE:1877067300684802",
    "advertiser_name": "n.b.alqassim",
    "region": "DE",
    "first_shown_at": "2026-09-22T21:07:26+00:00",
    "last_shown_at": "2026-09-22T21:07:26+00:00",
    "days_shown": 0,
    "creative_type": "video",
    "video_count": 1,
    "image_count": null,
    "video_urls": [
      "https://library.tiktok.com/api/v1/cdn/1790116006/video/aHR0cHM6Ly92MTZtLnRpa3Rva2Nkbi5j…"
    ],
    "cover_urls": [
      "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0037/ooiQOrz1WAqoAgrEixyXiI6AuQyfICc4…"
    ],
    "image_urls": null,
    "audit_status": "1",
    "sor_audit_status": "1",
    "show_mode": 1,
    "ad_type": "2",
    "rejection_info": null,
    "estimated_audience": "0-1K",
    "page": 1,
    "position": 2
  }
]

First 2 of 21 rows from a real run — sample_output.json. Long values shortened here.

Built for TikTok Ad Library

Handles what breaks naive TikTok Ad Library scrapers

Pages by cursor, not the decoy offset

The request's offset does nothing — offsets 0, 12 and 24 returned the same twelve ads. The scraper follows the search_id cursor and ends a region when the cursor stops advancing, whatever has_more claims.

One ad, many regions, distinct rows

An EU ad buy runs in several member states and the library lists it under each. Every row carries row_key ({region}:{ad_id}), so a multi-region run no longer collapses one country's ads into another's.

Millisecond dates read correctly

TikTok publishes run dates in milliseconds; read as seconds they land in the year 58,700. first_shown_at and last_shown_at are converted properly.

A stale token is not a block

An HTTP 421 mid-run means the library's header went stale. The run re-primes the browser to mint a fresh one instead of spending a proxy rotation on the wrong problem.

Everything built in

Scraping infrastructure included

The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.

CAPTCHA handling

Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.

Proxy rotation

Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.

Fingerprints

Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.

Honest run metadata

Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.

Diff two runs

diff_runs.py compares two complete TikTok Ad Library snapshots by id — new, removed and changed rows for monitoring.

Use cases

What teams do with TikTok Ad Library data

Competitor ad monitoring

Track which creatives a brand runs on TikTok, where, and for how long, with --query and diffs between runs.

Creative research

Collect video and image creatives by region to study formats and messaging that advertisers use.

Ad policy research

Find rejected ads and TikTok's stated policy reason in rejection_info.

Cross-market campaign mapping

Compare which ads run in which of the 33 regions to see how campaigns are rolled out across Europe.

Access & pricing

Free to run. Pay only for what the site requires.

Runs with no key, proxy or account

The library's search endpoint was served to a bare datacentre address with no credentials, measured Sep 22 2026, and the daily canary runs from a GitHub runner with no secrets. A browser is required because one request header is minted by the library's own JavaScript. The repo states it needs none of 2Captcha's paid products; proxies and the Scraping Browser API are supported for volume or a chosen exit.

Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.

Good to know

  • Only the 33 regions the EU Ad Library covers; the rest of the world is not in it.
  • Twelve ads per request, so large regions can only be sampled.
  • This repo does not implement a Scraper API path: a fetched URL cannot mint the header the endpoint needs.

2scraper vs. hosted scraper APIs

2scraperTypical hosted API
CodeOpen source, MIT — read and patch itClosed
CostFree; optional services billed only when usedSubscription or per request / per result
Runs onYour machine, server or CIThe vendor's infrastructure
OutputJSON/CSV files + run metadataAPI responses
When the site changesFix it yourself or open an issue / PRWait for the vendor
FAQ

TikTok Ad Library scraper FAQ

Do I need a key, proxy or TikTok account?

No. The library's search endpoint is served to a datacentre address with no credentials. Only a header minted by its own JavaScript gates it, which is why a browser is used.

Which regions are covered?

33: the EU and EEA states plus CH, GB and TR. --region US is refused with that reason, since the library does not cover it.

How many ads can I get?

Twelve per request; limit does not change that. The library reported over 17 million ads for Germany alone, so any run is a sample, and the sidecar records sample_share_pct.

Why are impressions and spend missing?

TikTok publishes both fields but they were empty on all 216 ads measured across six regions, so they are not columns.

Does --query filter by advertiser?

Since v0.1.2 it uses the library's own keyword search, which matches more than the advertiser's name, so reseller ads appear too.

Is it legal?

It reads TikTok's public advertising transparency library. Follow TikTok's terms and the law where you operate.