Skip to content

Commit c218833

Browse files
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>
1 parent d962f68 commit c218833

9 files changed

Lines changed: 476 additions & 178 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@ 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.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.
15+
- **Publisher platform**: Added `publisher_platform` (singular) key lookup alongside plural `publisher_platforms`.
16+
- **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+
830
## [1.0.0] - 2026-02-08
931

1032
### Added
@@ -68,10 +90,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6890
- Optional batch mode for webhook sends
6991

7092
#### Async Support
71-
- `AsyncMetaAdsClient` using httpx for non-blocking HTTP
93+
- `AsyncMetaAdsClient` with `curl_cffi.AsyncSession` (preferred) or `httpx.AsyncClient` (fallback)
7294
- `AsyncMetaAdsCollector` mirroring the sync API with `async for` generators
7395
- Async `search()`, `collect()`, `collect_to_json()`, `collect_to_csv()`, `search_pages()`
74-
- Optional dependency: `pip install meta-ads-collector[async]`
96+
- Optional dependency: `pip install meta-ads-collector[stealth]` (recommended) or `pip install meta-ads-collector[async]`
7597

7698
#### Proxy Support
7799
- Single proxy configuration (host:port or host:port:user:pass)

README.md

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

52-
With async support (requires [httpx](https://www.python-httpx.org/)):
52+
With stealth TLS fingerprinting (recommended, also enables async support):
5353

5454
```bash
55-
pip install meta-ads-collector[async]
55+
pip install meta-ads-collector[stealth]
5656
```
5757

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/)):
5959

6060
```bash
61-
pip install meta-ads-collector[stealth]
61+
pip install meta-ads-collector[async]
6262
```
6363

6464
From source:
@@ -379,9 +379,13 @@ with MetaAdsCollector() as collector:
379379

380380
## Async Support
381381

382-
Full async API using httpx (optional dependency).
382+
Full async API with the same TLS fingerprint impersonation as the sync client.
383383

384384
```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)
385389
pip install meta-ads-collector[async]
386390
```
387391

@@ -401,7 +405,7 @@ async def main():
401405
asyncio.run(main())
402406
```
403407

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`.
405409

406410
---
407411

docs/async.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
# Async Usage Guide
22

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.
44

55
## Installation
66

7+
The async client uses `curl_cffi` (recommended) for TLS fingerprint impersonation, or falls back to `httpx`:
8+
79
```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)
814
pip install meta-ads-collector[async]
915
```
1016

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.
1218

1319
## AsyncMetaAdsCollector
1420

@@ -135,7 +141,9 @@ async with AsyncMetaAdsClient() as client:
135141

136142
## Notes
137143

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()`
139147
- Session initialization is performed asynchronously on the first request
140148
- Event callbacks are still synchronous (they run in the event loop thread)
141149
- Proxy rotation with `ProxyPool` works the same way as in the sync client

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.1.0"
35+
__version__ = "1.2.0"
3636
__all__ = [
3737
# Models
3838
"Ad",

0 commit comments

Comments
 (0)