Metadata-Version: 2.4
Name: duckduckgo-async-search
Version: 1.0.0
Summary: Async DuckDuckGo search helper with SERP wrapper + Instant Answer API fallback.
Author: S M Abrar Jahin
License-Expression: MIT
Project-URL: Homepage, https://github.com/AbrarJahin/pip-duckduckgo_async_search
Project-URL: Repository, https://github.com/AbrarJahin/pip-duckduckgo_async_search
Project-URL: Issues, https://github.com/AbrarJahin/pip-duckduckgo_async_search/issues
Keywords: duckduckgo,search,async,httpx
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: httpx>=0.27
Requires-Dist: ddgs>=9.0.0
Dynamic: license-file

# duckduckgo-async-search
A tiny, easy-to-use **async** wrapper around DuckDuckGo search with best-effort fallbacks.

## Why this exists

The popular DuckDuckGo search interfaces (`ddgs` / legacy `duckduckgo-search`)
are **synchronous/blocking** by design (network + parsing happen in the calling thread).
In async applications (FastAPI, agents, notebooks, concurrent pipelines), calling them
directly can block the event loop and slow down unrelated tasks.

This library provides a **non-blocking async API**:

- You call `await DuckDuckGoSearch().top_n_result(...)`
- Under the hood, blocking SERP calls run in a **background worker thread**
  (via `asyncio.to_thread`) so the main event loop stays responsive
- Searches can safely run alongside other async tasks

## Fallback strategy (best-effort reliability)

DuckDuckGo may rate-limit or intermittently fail depending on IP, region,
or network conditions. To improve reliability, searches use a multi-stage approach:

1) **Primary: DDGS SERP results (`ddgs` / legacy `duckduckgo-search`)**
   - Tries multiple backends (default: `lite`, `html`, `auto`)
   - Retries with exponential backoff + jitter (configurable)
   - Returns normalized items (`title`, `url`, `snippet`) with a `source` tag like
     `ddgs:lite`

2) **Secondary: DuckDuckGo HTML SERP fallback**
   - Fetches and parses the public DuckDuckGo HTML results page
   - No additional third-party dependencies
   - Best-effort parsing (may break if DuckDuckGo changes markup)
   - Returns items with `source="ddg_html_fallback"`

3) **Final fallback: DuckDuckGo Instant Answer API (JSON)**
   - More stable and less likely to rate-limit
   - Not a full web SERP, but can return useful links from
     `Results` and `RelatedTopics`
   - Returns items with `source="instant_answer_api"`

If all strategies fail or return no items, a clear error is raised.

## Output

`top_n_result()` returns `List[DuckDuckGoResult]`, where each item includes:

- `title: str`
- `url: str`
- `snippet: str` (when available)
- `source: str` (which backend produced the item)

## Install

```bash
pip install duckduckgo-async-search
```

Colab:

```bash
!pip install duckduckgo-async-search
```

## Requirements

- **Python**: 3.9 or newer
- **Dependencies**:
  - `httpx>=0.27`
  - `ddgs>=9.0.0`

> **Note:**
> This library relies on the unofficial `ddgs` package to fetch DuckDuckGo search results.  

> DuckDuckGo may apply rate limits or blocking, so occasional retries or empty results are expected.

> Dependencies are installed automatically when using:

```bash
pip install duckduckgo-async-search
```

## Usage

### Jupyter / Colab

```python
from duckduckgo_async_search import top_n_result

async def main():
    items = await top_n_result("World's largest mangrove forest", n=5)
    for item in items:
        print(item.title)
        print(item.url)
        print(item.snippet)
        print(item.source)
        print("-" * 30)

await main()
```

### Python script (.py)

```python
import asyncio
from duckduckgo_async_search import top_n_result

async def main():
    items = await top_n_result("World's largest mangrove forest", n=5)
    for item in items:
        print(item.title, item.url, item.source)

if __name__ == "__main__":
    asyncio.run(main())
```

### Class usage

```python
from duckduckgo_async_search import DuckDuckGoSearch

async def main():
    client = DuckDuckGoSearch(
        request_timeout_s = 20.0,
        ddg_backends = None,   # e.g., ["lite", "html", "auto"]
        ddg_pause_s = 1.0,                   # pause between attempts - 2.0 or 3.0
        ddg_max_attempts = 3,                  # total attempts across all backends
        instant_answer_max_bytes = 2_000_000,
        html_fallback_max_bytes = 2_500_000,
        debug = False
    )
    items = await client.top_n_result("World's longest beach", n=10)
    for item in items:
        print(item.title, item.url, item.source)

await main()
```

## Notes

- SERP wrappers can be rate-limited depending on your IP/network.
- Instant Answer API is more reliable but may not reflect “top web results”.

## License

MIT License — free for personal and commercial use, modification, and distribution.

## Contributing

Source:
https://github.com/AbrarJahin/pip-duckduckgo_async_search
