# ddgs: ten text backends, one video backend, one book backend

> DDGS is a Python metasearch library, a FastAPI server and an MCP server built from one package with three install extras. Its own tables show that the word aggregate applies almost entirely to one function: text() fans out to ten engines while videos() and books() each have exactly one.

**deedy5/ddgs** — A metasearch library that aggregates results from diverse web search services

- Repository: https://github.com/deedy5/ddgs
- Stars: 3,000 · Forks: 285
- Language: Python
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/deedy5-ddgs

## Ten backends for text and one or two for everything else

The Engines table is the most informative thing on the page and it is a five row table mapping each function to its available backends. text() has ten: bing, brave, duckduckgo, google, grokipedia, mojeek, startpage, yandex, yahoo and wikipedia. images() has two, bing and duckduckgo. videos() has one, duckduckgo. news() has three, bing, duckduckgo and yahoo. books() has one, annasarchive. So the description's claim that the library aggregates results from diverse web search services describes text() and little else. Every function signature carries the same controls: a query, a region defaulting to us-en, a safesearch defaulting to moderate, a time limit defaulting to None, a maximum result count defaulting to 10, a page number defaulting to 1, and a backend parameter defaulting to auto. The images and videos signatures add their own filters on top, and the videos documentation narrows the time limit values to d, w and m while text documents d, w, m and y.

## books() has exactly one backend and it is named in the table

Two rows of the engines table deserve to be read on their own. videos() resolves to a single backend, and books() resolves to a single backend as well, and that backend is named in the table as annasarchive. Nothing on the page elaborates it: there is no note about what it indexes, no licensing comment, no fallback, and no second source. The consequence is structural rather than editorial. For books there is no aggregation to configure, so the backend parameter on that function either has one legal value or none, and a reader cannot tell which from the table. For videos the same is true with duckduckgo. Where the library is genuinely a metasearch, in text, there are ten choices and an auto default. Everywhere else it is a thin client over one upstream, and the page's framing does not distinguish the two cases.

## The documented default API port is 4479 and the container uses 8000

The API server section documents five command lines and the default port in one of them:

```bash
ddgs api              # Start server in foreground
ddgs api -d           # Start in detached mode (background)
ddgs api -s           # Stop detached server
ddgs api --host 127.0.0.1 --port 4479  # Default port 4479
ddgs api -pr socks5h://127.0.0.1:9150  # With proxy
```

So the standalone server defaults to 4479 and binds 127.0.0.1 by default. The container disagrees on both counts. The Dockerfile exposes 8000 and runs uvicorn bound to 0.0.0.0 on 8000, and the compose file publishes 8000 to 8000 with no host interface restriction. A deployment that reads the command line reference and then deploys the image will be listening somewhere different from what it expects, and on all interfaces rather than loopback. The compose healthcheck probes http://localhost:8000/health with curl, which is why curl is installed into the image, and the environment block passes a single variable, DDGS_PROXY, through with no value so the container inherits whatever the host sets.

## The class docstring gives five seconds for the whole fan-out

The DDGS class is described as lazy-loaded, and its docstring names three constructor arguments. proxy is a string for the HTTP client, supporting http, https and socks5, with an example of a credentialed URL, and it defaults to None. timeout is an integer for the HTTP client and defaults to 5. verify takes True to verify, False to skip, or a string path to a PEM file, and defaults to True. Those defaults are the operationally interesting part. Five seconds is the budget for whatever the backend parameter selects, so with backend set to auto on a text query the request has five seconds to reach and satisfy up to ten search engines rather than five seconds each. And verify accepting False or a PEM path means TLS checking is a caller decision, which is a legitimate need behind a corporate proxy and also the setting that most easily turns a library into one that will accept anything presented to it.

## The CLI help example is written with a space between the dashes

The shortest section on the page is the CLI version, and it contains a typo that will be typed by somebody. The whole section is this:

```python3
ddgs - -help
```

A space sits between the two hyphens, so copied literally it is not a long option and the command will not do what the reader expects. The rest of the page is careful about flags, using a short form for proxy and the long form for host and port, so the stray space is an isolated slip rather than a house style. It is worth noting because this is the first thing in the document a new user runs, and because the same section gives no indication of what the help output contains, so there is no second chance to learn the option names from the page itself.

## The images example prints a variable it never assigned

The images example assigns to one name and prints another. The call is bound to results, the same name the text example uses, and then the last line prints images:

```python
results = DDGS().images(
    query="butterfly",
    region="us-en",
    safesearch="off",
    timelimit="m",
    page=1,
    backend="auto",
    size=None,
    color="Monochrome",
    type_image=None,
    layout=None,
    license_image=None,
)
print(images)
```

The shown output does not match the query either. The request is for butterfly with a monochrome colour filter, and the single result displayed is a photograph of the Sun from a NASA solar observatory, hosted on Wikimedia, with a thumbnail URL that stops mid-string. The result keys differ from the text function as well, image and thumbnail here against href and body there, so a caller cannot assume one shape across functions.

## The image upgrades its base packages and installs the project in place

The Dockerfile makes two choices worth knowing before you build it. The first is a single upgrade line: the base is python:3.11-slim, and the install step runs apt-get update followed by apt-get upgrade -y before adding curl and clearing the package lists. That means the layers above the base tag are not fixed by the tag alone, so two builds of the same Dockerfile months apart need not contain the same system packages. Note also that the package claims Python 3.10 through 3.14 in its classifiers with no ceiling, while the container pins 3.11. The second choice is the install line, pip install --no-cache-dir -e .[api], which is editable: the image carries a link back to /app rather than a copied package, and PYTHONPATH is set to /app as well. The server is then started as uvicorn against ddgs.api_server:fastapi_app as a non-root user named app.

## The clean target deletes a uv lockfile while setup uses pip

The build file has seven targets and they imply three different tool chains. setup creates a .venv with the standard library venv module and installs the package into it in editable mode with the dev extra, using pip. lint runs ruff with the fix flag and then mypy with --install-types --non-interactive, so linting both edits your source and installs missing stub packages. test runs pytest. And clean removes the virtual environment, four cache directories, the build and dist directories, egg-info, every __pycache__ directory found by find, and then a file called uv.lock. Two things follow. The project ships a pre-commit runner named prek as a dev dependency, so hooks are managed by one tool, dependencies by pip, and a lockfile belonging to neither is nonetheless deleted by clean. And because all is ordered as setup, lint, format and test, the fix pass in lint runs before the formatter.

## Conclusion

DDGS is a reasonable choice if you want one library instead of ten and you mainly search text, because the backend fan-out, the safesearch and time limit controls and the region setting are all exposed through one signature, and the MCP server means an agent can call it directly. It is a poor choice for video or book search, where the tables show one backend each, and a poor choice for anything that needs a documented timeout budget, because five seconds covers the whole fan-out. Before deploying the API server, note that the documented default port and the container port differ, that the endpoints accept GET, and that the page describes no authentication at all.

## FAQ

### What does DDGS stand for?

Dux Distributed Global Search. That expansion is given in the repository heading and repeated in the package description, which reads Dux Distributed Global Search. A metasearch library that aggregates results from diverse web search services. The distribution and import name are lowercase ddgs.

### What is DDGS search?

It is the library's search surface: a DDGS class with six methods, text, images, videos, news, books and extract. The class is lazy-loaded and each method takes a query plus a region, a safesearch level, a time limit, a maximum result count, a page number and a backend name defaulting to auto.

### Which search backends does DDGS use?

text() has ten: bing, brave, duckduckgo, google, grokipedia, mojeek, startpage, yandex, yahoo and wikipedia. images() has bing and duckduckgo, news() has bing, duckduckgo and yahoo, videos() has duckduckgo alone, and books() has annasarchive alone. Every method takes a backend parameter that defaults to auto.

### How do I install the DDGS API server or MCP server?

Three extras are documented. A base install with pip install -U ddgs, pip install -U ddgs[api] for the FastAPI server, and pip install -U ddgs[mcp] for the MCP server over stdio. The MCP side exposes six tools named search_text, search_images, search_news, search_videos, search_books and extract_content, which do not map one to one onto the HTTP endpoint paths.

### What is the default timeout and proxy setting in the DDGS class?

The constructor docstring says timeout is an integer for the HTTP client and defaults to 5, that proxy supports http, https and socks5 and defaults to None, and that verify takes True, False or a path to a PEM file and defaults to True. Five seconds covers the whole request including any backend fan-out the auto setting chooses.

### Does the DDGS API server need authentication?

The page describes none. It lists nine endpoints, six of which accept both GET and POST, plus a health check, a Swagger UI at /docs and ReDoc at /redoc. The standalone server is documented as binding 127.0.0.1 on port 4479 by default, while the container publishes 8000 to all interfaces.

## Sources

- [deedy5/ddgs on GitHub](https://github.com/deedy5/ddgs)
- [Issues](https://github.com/deedy5/ddgs/issues)
- [License: MIT](https://github.com/deedy5/ddgs/blob/main/LICENSE)
- [README](https://github.com/deedy5/ddgs/blob/main/README.md)
- [Releases](https://github.com/deedy5/ddgs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/deedy5-ddgs
