Skip to content

Allow this site to count your visits with Yandex Metrica? Metrica will store cookies in your browser to do this. How the site uses cookies

VC
Case 5 of 22 · Data collection in daily use · Ad research

GMBSPYLAB: from the Facebook Ad Library to a Telegram feed

A working system that collects ads from the public Facebook Ad Library. It's built on Playwright (browser automation) and async Python. Most of the work happens after collection: cleaning up the data, removing near-duplicate images, queues and delivery. 30–75k cards a day from 30+ markets go to Telegram, with a separate topic for each country.

Type
My own tool, in daily use
Technology
Python · Playwright · aiogram 3 · Postgres · arq
Timeline
About 2 months to the first release, then ongoing work
Scale
30+ markets · 30–75k ads a day
01 · Problem

Ad research subscriptions cost $300–800 a month

Commercial ad research services charge $300–800 a month for a subscription. I needed a live stream of ads from 30+ countries, including smaller markets in Europe, Latin America and Asia.

Getting the same public data through Meta's API requires a developer app that passes App Review. And facebook.com/ads/library builds the page in the browser, so simply downloading the page returns it empty.

The goal: collect a live stream of ads from 30+ countries into my own database, filter it by format (image, video, PWA landing page), by how long an ad has been running and by a consistent set of tags, and then post it to Telegram.

02 · Architecture

Four parts: collection, resilience, storage, delivery

01
COLLECT

Playwright without parsing the page

The ad listing is built in the browser, so a headless Chromium opens the page once. After that the system works with the same data the browser receives and spends no time going through the page structure card by card.

Requests go out at a measured pace: a pool of sessions, each tied to a market, at least 2.5 s between requests and a hard limit per session. The aim is a steady stream that covers the daily volume.

02
RESILIENCE

Changes on the source's side don't stop the collection

The source keeps changing: its markup and response parameters change often, and a simple parser quietly returns zeros after each change. Here the structure of the response is checked at every step. If it doesn't match, the session starts over and the stream keeps going. If the source returns an error or slows down, the session waits longer between attempts and recovers on its own. This happens regularly and needs no manual work.

03
STORE

Postgres 16, arq, and MinIO for media

SQLAlchemy 2 async, asyncpg and alembic migrations, with indexes on advertiser ID, country and first-seen date. arq on Redis runs background tasks (downloading media, removing duplicates). Images and videos go to MinIO / R2 through aioboto3. Near-duplicate images are removed by perceptual hash, which compares how images look: 30–75k raw cards become 5–12k unique ones a day.

04
DISTRIBUTE

Telegram via aiogram 3, in two formats

v1: a forum-style group. A topic is created with createForumTopic for each of the 30+ markets (the 27 EU countries, the UK, Brazil and others such as TZ, MX, IN, CA, ZA and RS). Each country's ads go to its own topic, so filtering by market is easy.

v2: a hashtag channel. One stream, tagged with #GEO #PWA #VIDEO #1DAY #FBPAGE. A daily scheduled job updates the #NDAY tag, which shows how many days the ad has been running.

03 · Technology

What it's built with

Collection and browser
  • Playwright (headless Chromium)
  • A pool of sessions, each tied to a market
  • Request limits and growing pauses after errors
  • Response structure checked at every step
Back end
  • Python 3.12 + uv (package manager)
  • SQLAlchemy 2 async + asyncpg
  • PostgreSQL 16 + alembic migrations
  • Pydantic v2 for all schemas
Queues, schedules, storage
  • arq + Redis 7 (background tasks)
  • APScheduler (scheduled jobs)
  • MinIO / Cloudflare R2 (aioboto3)
  • SQLite (cache, local state)
Telegram and alerts
  • aiogram 3 (Bot API)
  • Forum-style group with a topic per market (createForumTopic)
  • Hashtag channel with a daily "running for N days" update
  • Health checks with alerts to Discord and Telegram
Data sources
  • Playwright on Facebook (main source)
  • ScrapeCreators (paid backup source outside the EU)
  • Meta Graph API (for confirmed IDs)
  • EU DSA Repository (regulatory data)
Quality and monitoring
  • structlog (structured logs)
  • 16/16 tests passing (pytest)
  • Health check and emergency stop
  • Status messages to Discord via webhooks
04 · Results

What it delivers

Ads a day
30–75k

after removing duplicates: 5–12k unique

Markets covered
30+

the 27 EU countries, the UK, Brazil and markets outside the EU

Running cost
$30–60

a month for the whole setup: server and storage

How this helps clients: the same chain (collect, clean up, remove duplicates, deliver) works with any source you have lawful access to: your own Avito, Wildberries or Ozon seller account, official data exports, open ad libraries. On a call we pick the exact source and check what the platform's own terms allow you to do with the data.

Ready to start?

Start with the 5,000 ₽ audit: a written report and a payback estimate

I'll tell you what to automate first, when it will pay for itself and whether your task needs AI at all. Sometimes it doesn't.

Or just ask a question. During business hours I reply within 2 hours