Skip to content

Commit 72126e8

Browse files
Make curl_cffi the sole HTTP dependency, remove requests and httpx
Facebook blocks any HTTP client without Chrome-like TLS fingerprints, making requests and httpx dead-weight fallbacks that fail with 403. curl_cffi is now required (not optional), and the [stealth] and [async] extras are removed. Installation is simplified to just `pip install meta-ads-collector`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3253412 commit 72126e8

27 files changed

Lines changed: 229 additions & 389 deletions

CHANGELOG.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.3.0] - 2026-02-21
9+
10+
### Changed
11+
- **curl_cffi is now the sole HTTP dependency.** Removed `requests` and `httpx` entirely. Facebook blocks any HTTP client without Chrome-like TLS fingerprints, so `curl_cffi` (with `impersonate="chrome"`) is the only backend that actually works. Both `requests` and `httpx` were dead-weight fallbacks that failed with HTTP 403.
12+
- **Simplified installation.** `pip install meta-ads-collector` now includes everything -- no more `[stealth]` or `[async]` extras needed.
13+
- **Async client simplified.** Removed the `_AsyncResponse` wrapper and httpx fallback code paths. The async client now uses `curl_cffi.AsyncSession` directly.
14+
- Renamed `ProxyPool.get_requests_proxies()` to `get_proxy_dict()`.
15+
16+
### Removed
17+
- `requests` dependency (was required, now removed)
18+
- `httpx` dependency and `[async]` optional extra
19+
- `[stealth]` optional extra (curl_cffi is now always installed)
20+
- `types-requests` from dev dependencies
21+
822
## [1.2.0] - 2026-02-21
923

1024
### Fixed
@@ -90,10 +104,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
90104
- Optional batch mode for webhook sends
91105

92106
#### Async Support
93-
- `AsyncMetaAdsClient` with `curl_cffi.AsyncSession` (preferred) or `httpx.AsyncClient` (fallback)
107+
- `AsyncMetaAdsClient` with `curl_cffi.AsyncSession`
94108
- `AsyncMetaAdsCollector` mirroring the sync API with `async for` generators
95109
- Async `search()`, `collect()`, `collect_to_json()`, `collect_to_csv()`, `search_pages()`
96-
- Optional dependency: `pip install meta-ads-collector[stealth]` (recommended) or `pip install meta-ads-collector[async]`
97110

98111
#### Proxy Support
99112
- Single proxy configuration (host:port or host:port:user:pass)

README.md

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,18 @@ meta-ads-collector -q "solar panels" -c US -n 10 -o ads.json
4949
pip install meta-ads-collector
5050
```
5151

52-
With stealth TLS fingerprinting (recommended, also enables async support):
53-
54-
```bash
55-
pip install meta-ads-collector[stealth]
56-
```
57-
58-
With async support only (uses [httpx](https://www.python-httpx.org/)):
59-
60-
```bash
61-
pip install meta-ads-collector[async]
62-
```
63-
6452
From source:
6553

6654
```bash
6755
git clone https://github.com/promisingcoder/MetaAdsCollector.git
68-
cd meta-ads-collector
69-
pip install -e ".[dev,async,stealth]"
56+
cd MetaAdsCollector
57+
pip install -e ".[dev]"
7058
```
7159

7260
**Requirements:** Python 3.9+
7361

62+
`curl_cffi` is installed automatically and provides Chrome-like TLS fingerprints so requests are indistinguishable from a real browser.
63+
7464
## Features
7565

7666
- **Search & Collection** -- keyword search, exact phrase, page-level collection by URL/name/ID
@@ -79,13 +69,13 @@ pip install -e ".[dev,async,stealth]"
7969
- **Media Downloads** -- download images, videos, and thumbnails from ad creatives
8070
- **Ad Enrichment** -- fetch additional detail data from the ad snapshot endpoint
8171
- **Events & Webhooks** -- 7 lifecycle events with callback registration, webhook POST integration
82-
- **Async Support** -- full async/await API using curl_cffi (preferred) or httpx (fallback)
72+
- **Async Support** -- full async/await API using curl_cffi
8373
- **Proxy Support** -- single proxy, proxy rotation with failure tracking and dead-proxy cooldown
8474
- **Structured Logging** -- text or JSON log format, optional file output
8575
- **Collection Reporting** -- summary statistics with throughput metrics
8676
- **Export Formats** -- JSON, CSV, JSONL
8777
- **Stream Mode** -- yield lifecycle events alongside ads through a single iterator
88-
- **Detection Avoidance** -- browser fingerprint randomization, TLS fingerprint impersonation (via `curl_cffi`), dynamic token extraction, session management
78+
- **Detection Avoidance** -- browser fingerprint randomization, Chrome TLS fingerprint impersonation via `curl_cffi`, dynamic token extraction, session management
8979

9080
---
9181

@@ -381,14 +371,6 @@ with MetaAdsCollector() as collector:
381371

382372
Full async API with the same TLS fingerprint impersonation as the sync client.
383373

384-
```bash
385-
# Recommended: uses curl_cffi for TLS fingerprinting (same as sync client)
386-
pip install meta-ads-collector[stealth]
387-
388-
# Alternative: uses httpx (may be detected by Facebook)
389-
pip install meta-ads-collector[async]
390-
```
391-
392374
```python
393375
import asyncio
394376
from meta_ads_collector.async_collector import AsyncMetaAdsCollector
@@ -405,7 +387,7 @@ async def main():
405387
asyncio.run(main())
406388
```
407389

408-
The async collector mirrors the sync API: `search()`, `collect()`, `collect_to_json()`, `collect_to_csv()`, `search_pages()`, `get_stats()`. When `curl_cffi` is installed, the async client uses `curl_cffi.AsyncSession` with Chrome TLS impersonation. Otherwise it falls back to `httpx.AsyncClient`.
390+
The async collector mirrors the sync API: `search()`, `collect()`, `collect_to_json()`, `collect_to_csv()`, `search_pages()`, `get_stats()`. The async client uses `curl_cffi.AsyncSession` with Chrome TLS impersonation.
409391

410392
---
411393

@@ -718,7 +700,7 @@ All exceptions inherit from `MetaAdsError`.
718700
## Development
719701

720702
```bash
721-
# Install with dev dependencies
703+
# Install with dev dependencies (curl_cffi is included automatically)
722704
pip install -e ".[dev]"
723705

724706
# Run tests

docs/api-reference.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,6 @@ Close the collector and release resources.
173173

174174
**Module:** `meta_ads_collector.async_collector`
175175

176-
**Requires:** `pip install meta-ads-collector[stealth]` (recommended) or `pip install meta-ads-collector[async]`
177-
178176
### class AsyncMetaAdsCollector
179177

180178
Async mirror of `MetaAdsCollector`. All methods are `async def` and iterators are `async for`.
@@ -247,11 +245,9 @@ Close the session.
247245

248246
**Module:** `meta_ads_collector.async_client`
249247

250-
**Requires:** `pip install meta-ads-collector[stealth]` (recommended) or `pip install meta-ads-collector[async]`
251-
252248
### class AsyncMetaAdsClient
253249

254-
Async mirror of `MetaAdsClient` using `curl_cffi.AsyncSession` (preferred) or `httpx.AsyncClient` (fallback). Handles Facebook's 403 verification challenges automatically. All HTTP methods are async, while pure-logic methods are delegated to the sync client.
250+
Async mirror of `MetaAdsClient` using `curl_cffi.AsyncSession`. Handles Facebook's 403 verification challenges automatically. All HTTP methods are async, while pure-logic methods are delegated to the sync client.
255251

256252
Same method signatures as `MetaAdsClient` with `async def`.
257253

@@ -544,7 +540,7 @@ Download images, videos, and thumbnails from ad creatives.
544540
| Parameter | Type | Default | Description |
545541
|---|---|---|---|
546542
| `output_dir` | `str \| Path` | (required) | Download directory |
547-
| `session` | `requests.Session \| None` | `None` | HTTP session to reuse |
543+
| `session` | `curl_cffi.requests.Session \| None` | `None` | HTTP session to reuse |
548544
| `timeout` | `int` | `30` | Per-request timeout |
549545
| `max_retries` | `int` | `2` | Retry attempts per download |
550546

@@ -594,7 +590,7 @@ Round-robin proxy pool with failure tracking and cooldown recovery.
594590
| `mark_success(proxy)` | Record successful request |
595591
| `mark_failure(proxy)` | Record failed request |
596592
| `reset()` | Reset all counters and revive all proxies |
597-
| `get_requests_proxies(proxy_url) -> dict` | Convert to `requests`-compatible proxy dict |
593+
| `get_proxy_dict(proxy_url) -> dict` | Convert proxy URL to `{"http": url, "https": url}` dict |
598594

599595
#### Properties
600596

docs/architecture.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ This document describes the internal architecture of `meta-ads-collector`. It is
2828
| Module | Layer | Responsibility |
2929
|---|---|---|
3030
| `collector.py` | Collector | High-level search, pagination, export, media, enrichment |
31-
| `async_collector.py` | Collector | Async mirror of `collector.py` using `httpx` |
31+
| `async_collector.py` | Collector | Async mirror of `collector.py` using `curl_cffi` |
3232
| `client.py` | Client | Session management, token extraction, GraphQL requests |
3333
| `async_client.py` | Client | Async mirror of `client.py`, delegates logic to sync client |
3434
| `models.py` | Data | `Ad`, `AdCreative`, `PageInfo`, `PageSearchResult`, etc. |
@@ -172,9 +172,9 @@ Sessions become stale after `MAX_SESSION_AGE` (30 minutes by default). Before ea
172172

173173
`_refresh_session()` performs a full re-initialization:
174174

175-
1. Close the old session (`requests.Session` or `curl_cffi.requests.Session`)
175+
1. Close the old session (`curl_cffi.requests.Session`)
176176
2. Generate a new `BrowserFingerprint`
177-
3. Create a fresh session with new headers (preferring `curl_cffi` when available)
177+
3. Create a fresh session with new headers
178178
4. Re-run `initialize()` to get new tokens
179179
5. Track consecutive refresh failures to prevent infinite loops
180180

@@ -204,7 +204,7 @@ All headers derived from the fingerprint are self-consistent -- the Chrome versi
204204

205205
### TLS Fingerprint Impersonation
206206

207-
When the optional `curl_cffi` dependency is installed (`pip install meta-ads-collector[stealth]`), the client uses `curl_cffi.requests.Session(impersonate="chrome")` instead of `requests.Session`. This provides Chrome-like TLS fingerprints (JA3/JA4) at the connection level, making requests indistinguishable from a real Chrome browser to TLS-based bot detection systems. If `curl_cffi` is not installed, the library falls back to `requests.Session` transparently.
207+
The client uses `curl_cffi.requests.Session(impersonate="chrome")` which provides Chrome-like TLS fingerprints (JA3/JA4) at the connection level, making requests indistinguishable from a real Chrome browser to TLS-based bot detection systems.
208208

209209
### Request Mimicry
210210

@@ -265,7 +265,7 @@ Seven lifecycle events:
265265
self._logic = MetaAdsClient.__new__(MetaAdsClient) # No __init__ call
266266
```
267267

268-
All pure-logic methods (token extraction, payload building, response parsing) are delegated to `_logic`. Only HTTP methods are reimplemented using `curl_cffi.AsyncSession` (preferred) or `httpx.AsyncClient` (fallback). The async client also handles Facebook's 403 verification challenges automatically, matching the sync client's behavior.
268+
All pure-logic methods (token extraction, payload building, response parsing) are delegated to `_logic`. Only HTTP methods are reimplemented using `curl_cffi.AsyncSession`. The async client also handles Facebook's 403 verification challenges automatically, matching the sync client's behavior.
269269

270270
`AsyncMetaAdsCollector` mirrors `MetaAdsCollector` method-for-method, using `async def` and `async for` throughout.
271271

docs/async.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
# Async Usage Guide
22

3-
`meta-ads-collector` provides a full async API for use with `asyncio`. The async client uses the same TLS fingerprint impersonation as the sync client to avoid detection.
3+
`meta-ads-collector` provides a full async API for use with `asyncio`. The async client uses `curl_cffi.AsyncSession` with Chrome TLS fingerprint impersonation, matching the sync client's behavior.
44

55
## Installation
66

7-
The async client uses `curl_cffi` (recommended) for TLS fingerprint impersonation, or falls back to `httpx`:
7+
Async support is included out of the box -- no extra install required:
88

99
```bash
10-
# Recommended: uses curl_cffi (same TLS fingerprinting as sync client)
11-
pip install meta-ads-collector[stealth]
12-
13-
# Alternative: uses httpx (may get blocked by Facebook's TLS fingerprint detection)
14-
pip install meta-ads-collector[async]
10+
pip install meta-ads-collector
1511
```
1612

17-
If both `curl_cffi` and `httpx` are installed, `curl_cffi` is preferred automatically.
18-
1913
## AsyncMetaAdsCollector
2014

2115
The async collector mirrors the sync `MetaAdsCollector` API with `async def` methods and `async for` generators.
@@ -141,7 +135,7 @@ async with AsyncMetaAdsClient() as client:
141135

142136
## Notes
143137

144-
- The async client prefers `curl_cffi.AsyncSession` for TLS fingerprint impersonation, falling back to `httpx.AsyncClient` if curl_cffi is not installed
138+
- The async client uses `curl_cffi.AsyncSession` with Chrome TLS fingerprint impersonation
145139
- Facebook's 403 verification challenges are handled automatically (same as the sync client)
146140
- Rate limiting uses `asyncio.sleep()` instead of `time.sleep()`
147141
- Session initialization is performed asynchronously on the first request

docs/contributing.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ source .venv/bin/activate # Linux/macOS
2222
# .venv\Scripts\activate # Windows
2323

2424
# Install in editable mode with all dev dependencies
25-
pip install -e ".[dev,async,stealth]"
25+
pip install -e ".[dev]"
2626
```
2727

2828
Or use the Makefile:
@@ -35,10 +35,8 @@ make install-dev
3535

3636
| Group | What it includes | When to install |
3737
|---|---|---|
38-
| (none) | `requests>=2.28.0` | Always (core dependency) |
39-
| `async` | `httpx>=0.24.0` | Async fallback when curl_cffi is unavailable |
40-
| `stealth` | `curl_cffi>=0.7.0` | TLS fingerprint impersonation for sync and async clients (recommended) |
41-
| `dev` | `pytest`, `pytest-cov`, `pytest-asyncio`, `ruff`, `mypy`, `types-requests` | Always for development |
38+
| (none) | `curl_cffi>=0.7.0` | Always (core dependency, provides Chrome TLS fingerprinting) |
39+
| `dev` | `pytest`, `pytest-cov`, `pytest-asyncio`, `ruff`, `mypy` | Always for development |
4240

4341
## Running Tests
4442

docs/proxy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ pool.reset()
120120
| `reset()` | Revive all proxies, reset all counters |
121121
| `alive_proxies` | List of currently usable proxy URLs |
122122
| `from_file(path)` | Create pool from a text file |
123-
| `get_requests_proxies(url)` | Convert URL to `{"http": url, "https": url}` dict |
123+
| `get_proxy_dict(url)` | Convert URL to `{"http": url, "https": url}` dict |
124124

125125
## Disabling proxies
126126

docs/quickstart.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ Get from zero to collecting ads in under 60 seconds.
88
pip install meta-ads-collector
99
```
1010

11-
For stealth TLS fingerprinting (recommended for production use):
12-
13-
```bash
14-
pip install meta-ads-collector[stealth]
15-
```
11+
This installs `curl_cffi` automatically, providing Chrome-like TLS fingerprinting so requests are indistinguishable from a real browser.
1612

1713
## Python API
1814

@@ -89,6 +85,6 @@ meta-ads-collector -q "test" --proxy "host:port:user:pass" -o ads.json
8985
- [Deduplication guide](deduplication.md) -- avoid collecting the same ad twice
9086
- [Media downloads](media.md) -- download images and videos from ad creatives
9187
- [Events & webhooks](events.md) -- react to collection lifecycle events
92-
- [Async usage](async.md) -- use with asyncio (curl_cffi or httpx)
88+
- [Async usage](async.md) -- use with asyncio
9389
- [Proxy configuration](proxy.md) -- proxy rotation and failure handling
9490
- [CLI reference](cli.md) -- complete list of CLI flags

meta_ads_collector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from .url_parser import extract_page_id_from_url
3333
from .webhooks import WebhookSender
3434

35-
__version__ = "1.2.0"
35+
__version__ = "1.3.0"
3636
__all__ = [
3737
# Models
3838
"Ad",

0 commit comments

Comments
 (0)