DEV Community

Devil Scrapes
Devil Scrapes

Posted on

The TikTok 'block' that was actually Camoufox running out of memory

Quick answer: TikTok never blocked our Ad Library scraper as often as our own logs suggested. Camoufox running below roughly 4 GB of memory gets OOM-killed mid-search and throws TargetClosedError — an error that reads exactly like the target hanging up on the connection. It interleaves with genuine captcha walls inside the same batch of runs, so a naive average across both numbers describes nothing real. Check defaultMemoryMbytes before you file any browser NO-GO; this Actor now ships at 4096 MB.

Symptom What it actually was Fix
TargetClosedError mid-search Camoufox OOM-killed under ~4 GB Ship defaultMemoryMbytes: 4096
One malformed ad kills the whole query Missing per-item fault isolation Skip the bad ad, keep the rest

Why did an out-of-memory crash look exactly like a block?

Because both failure modes throw from the same place and leave a run with the same shape: a browser session that stops responding partway through a search. A crash from resource exhaustion and a hard stop from a genuine anti-bot wall land in the exact same log signature, and averaging a "block rate" across a mix of both produces a number with no physical meaning — it's not measuring the target's defenses, it's measuring how often the container ran out of RAM that day.

Check defaultMemoryMbytes before you file any browser NO-GO.

Why did one bad ad used to sink an entire query's results?

The old enrichment path had no fault isolation: if a single ad object failed to parse or enrich, the exception escaped the per-row loop and took down the whole query — discarding every row already found, including the good ones. That's the fleet's most common failure shape: a recoverable, per-item error treated as fatal for the whole run. Enrichment now runs inside its own per-ad guard. A bad ad gets skipped and logged; the query keeps going and ships every row it actually matched.

Cloud proof on build 0.4.1: run I2xUkWWpwC3qDbMVF matched and delivered 50 ad rows, with chargedEventCounts showing ad-result: 50 — the row count and the charge count match exactly.

What does this Actor actually cover, and where does it not?

TikTok built the Commercial Content Library to satisfy the EU's Digital Services Act ad-transparency mandate — so coverage is genuinely EU/EEA + UK + Switzerland + Turkey only. There's no US region, and no amount of scraper cleverness changes that; region is validated live against TikTok's own support-regions list before any search runs, and an out-of-scope region fails loud rather than silently returning nothing. spend and impressions are also honestly empty for most ordinary commercial ads — TikTok only populates them for DSA-flagged categories like political or social-issue ads — and this Actor ships that emptiness as null rather than inventing a number to fill the column.

None of the reliability work changes what's underneath it: every run still opens a real browser session to earn the search a seat, replays that trust across the rest of the run's requests, follows TikTok's own cursor token exactly rather than guessing an offset, and retries with backoff on 429/503. That layer wasn't the story here — the memory ceiling and the missing fault isolation were.


TikTok Ad Library Scraper searches TikTok's official DSA Commercial Content Library by advertiser name or keyword and returns one row per matching ad — advertiser, caption, first/last shown dates, estimated audience band, and creative URLs. It completes the ad-transparency set alongside our Google, LinkedIn, and Meta ad-library Actors. Pricing is Pay-Per-Event: a $0.20 start fee (charged only once a row actually lands) plus $0.003 per ad row, so 1,000 matched ads run about $3.20. Apify gives every new account free trial credit, no card required.

👉 https://apify.com/DevilScrapes/tiktok-ad-library-scraper

We chase every ad down TikTok's cursor. 😈

Top comments (0)