You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix async client TLS fingerprinting and political ad parsing
- Rewrite async client to use curl_cffi.AsyncSession for Chrome TLS
impersonation (was httpx-only, getting 403 blocked by Facebook)
- Add async 403 verification challenge handling (matching sync client)
- Fix political ad spend/impression parsing for string formats
- Add from __future__ import annotations for Python 3.9 compatibility
- Update async docs and README to recommend stealth extra
- Bump version to 1.2.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+24-2Lines changed: 24 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
8
+
## [1.2.0] - 2026-02-21
9
+
10
+
### Fixed
11
+
-**Async client**: Rewrote to use `curl_cffi.AsyncSession` for TLS fingerprint impersonation, fixing 403 blocks from Facebook. Falls back to `httpx.AsyncClient` when curl_cffi is not installed.
12
+
-**Async client**: Added 403 verification challenge handling (same as sync client), enabling the async client to work without proxies.
13
+
-**Political ads parsing**: Fixed `'str' object has no attribute 'get'` crash when parsing political ads where `spend` is a string (e.g., `"$9K-$10K"`) instead of a dict.
14
+
-**Impression text parsing**: Handle `impressions_with_index` format (`{"impressions_text": ">1M", "impressions_index": 39}`) returned for political ads.
-**Delivery dates**: Added `start_date`/`end_date` key lookups for delivery times.
17
+
-**Reach parsing**: Added `_parse_reach` classmethod to handle `reach`/`reach_estimate` in string and dict formats.
18
+
-**Audience distributions**: Added `isinstance(item, dict)` guards for demographic and region distribution parsing.
19
+
-**Estimated audience size**: Added `isinstance(dict)` check before calling `.get()`.
20
+
21
+
### Changed
22
+
-**Python 3.9 compatibility**: Added `from __future__ import annotations` to `models.py` and modernized all type annotations from `Optional[X]` to `X | None`.
23
+
-**Async transport**: The async client now prefers `curl_cffi.AsyncSession` over `httpx.AsyncClient` when both are installed, matching the sync client's TLS impersonation behavior.
24
+
25
+
## [1.1.0] - 2026-02-21
26
+
27
+
### Changed
28
+
- Version bump for PyPI release.
29
+
8
30
## [1.0.0] - 2026-02-08
9
31
10
32
### Added
@@ -68,10 +90,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
68
90
- Optional batch mode for webhook sends
69
91
70
92
#### Async Support
71
-
-`AsyncMetaAdsClient`using httpx for non-blocking HTTP
93
+
-`AsyncMetaAdsClient`with `curl_cffi.AsyncSession` (preferred) or `httpx.AsyncClient` (fallback)
72
94
-`AsyncMetaAdsCollector` mirroring the sync API with `async for` generators
With async support (requires [httpx](https://www.python-httpx.org/)):
52
+
With stealth TLS fingerprinting (recommended, also enables async support):
53
53
54
54
```bash
55
-
pip install meta-ads-collector[async]
55
+
pip install meta-ads-collector[stealth]
56
56
```
57
57
58
-
With stealth TLS fingerprinting (requires [curl_cffi](https://github.com/lexiforest/curl_cffi)):
58
+
With async support only (uses [httpx](https://www.python-httpx.org/)):
59
59
60
60
```bash
61
-
pip install meta-ads-collector[stealth]
61
+
pip install meta-ads-collector[async]
62
62
```
63
63
64
64
From source:
@@ -379,9 +379,13 @@ with MetaAdsCollector() as collector:
379
379
380
380
## Async Support
381
381
382
-
Full async API using httpx (optional dependency).
382
+
Full async API with the same TLS fingerprint impersonation as the sync client.
383
383
384
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)
385
389
pip install meta-ads-collector[async]
386
390
```
387
391
@@ -401,7 +405,7 @@ async def main():
401
405
asyncio.run(main())
402
406
```
403
407
404
-
The async collector mirrors the sync API: `search()`, `collect()`, `collect_to_json()`, `collect_to_csv()`, `search_pages()`, `get_stats()`.
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`.
Copy file name to clipboardExpand all lines: docs/async.md
+11-3Lines changed: 11 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,14 +1,20 @@
1
1
# Async Usage Guide
2
2
3
-
`meta-ads-collector` provides a full async API for use with `asyncio`. The async implementation uses [httpx](https://www.python-httpx.org/) instead of `requests`.
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.
4
4
5
5
## Installation
6
6
7
+
The async client uses `curl_cffi` (recommended) for TLS fingerprint impersonation, or falls back to `httpx`:
8
+
7
9
```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)
8
14
pip install meta-ads-collector[async]
9
15
```
10
16
11
-
This installs `httpx>=0.24.0` as an additional dependency.
17
+
If both `curl_cffi` and `httpx` are installed, `curl_cffi` is preferred automatically.
12
18
13
19
## AsyncMetaAdsCollector
14
20
@@ -135,7 +141,9 @@ async with AsyncMetaAdsClient() as client:
135
141
136
142
## Notes
137
143
138
-
- The async client handles rate limiting with `asyncio.sleep()` instead of `time.sleep()`
144
+
- The async client prefers `curl_cffi.AsyncSession` for TLS fingerprint impersonation, falling back to `httpx.AsyncClient` if curl_cffi is not installed
145
+
- Facebook's 403 verification challenges are handled automatically (same as the sync client)
146
+
- Rate limiting uses `asyncio.sleep()` instead of `time.sleep()`
139
147
- Session initialization is performed asynchronously on the first request
140
148
- Event callbacks are still synchronous (they run in the event loop thread)
141
149
- Proxy rotation with `ProxyPool` works the same way as in the sync client
0 commit comments