ddgs: a Python metasearch client that fans a query out across ten text backends
A metasearch library that aggregates results from diverse web search services
At a glance
- What is it?
- ddgs wraps Bing, Brave, DuckDuckGo, Google, Mojeek, Startpage, Yandex, Yahoo, Wikipedia and Grokipedia behind one Python call, and also ships as a FastAPI service and an MCP stdio server. The aggregation is real; the ranking is not yours to control, and every backend can break independently.
- Who is it for?
- Adopt ddgs if you need programmatic search results in Python and can tolerate a library whose output shape is fixed by whatever the upstream engines return. Do not adopt it if you need a contractual SLA, a stable result schema across backends, or a search index you control.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem ddgs solves: one call instead of ten scrapers
If you have ever written a script that queries one search engine, you have probably then written a second one for a different engine when the first returned thin results. Each engine has its own URL parameters, its own result markup, its own rate limiting behaviour and its own way of failing. ddgs collapses that work into a single class. The README describes it as a metasearch library that aggregates results from diverse web search services, and the class docstring repeats the phrase: Dux Distributed Global Search. The audience is Python developers who want search results inside a program, not a browser. That includes people building retrieval steps for a language model, people building internal dashboards that track mentions of a term, and people who want a CLI they can pipe into other tools. The package also targets two adjacent audiences through extras: teams that want an HTTP service rather than an import, and teams wiring tools into an MCP-capable client such as Cursor or Claude Desktop. The scope is deliberately narrow. ddgs does not crawl, does not index, and does not rank results itself. It issues requests and normalises the responses into a list of dictionaries.
How aggregation actually works: backends, the auto setting, and one result shape
The mechanism is visible in the engine table. Each of the six public methods maps to a fixed set of named backends. text() has the widest set: bing, brave, duckduckgo, google, grokipedia, mojeek, startpage, yandex, yahoo and wikipedia. images() covers bing and duckduckgo. news() covers bing, duckduckgo and yahoo. videos() covers duckduckgo only. books() covers annasarchive. That table is the honest description of what the library can do, and it also tells you where the redundancy is thin. Video search has a single backend, so there is nothing to aggregate; if that one backend changes, videos() has no fallback. The backend parameter accepts a single name or a comma-delimited list, and defaults to the string auto. The README does not define how auto selects among the available engines, so treat that behaviour as unspecified in the material available. The data flow is straightforward: you construct a DDGS instance with proxy, timeout and verify settings, call a method, and receive a list of dictionaries. The text() example shows the keys as title, href and body. The class is described as lazy-loaded, which means construction does not open network connections or resolve backends until a method is called. That matters for import cost in a CLI or a serverless handler. The return type is declared as list[dict[str, str]], which is a flat string-to-string mapping. If an engine returns a numeric field such as an image width, the type annotation does not describe it, and the README does not show an images() result body to settle the question.
Installing ddgs and choosing between the library, the API server and MCP
Installation is split into three extras. pip install -U ddgs gives the base library. pip install -U ddgs[api] adds the FastAPI server. pip install -U ddgs[mcp] adds the stdio MCP server. The README states Python >= 3.10 in the badge. The library path is two lines: from ddgs import DDGS, then results = DDGS().text("python programming", max_results=5). The CLI is invoked as ddgs, and the README lists ddgs - -help as the help form. The API server runs with ddgs api for the foreground, ddgs api -d for detached background mode, and ddgs api -s to stop a detached server. The default host and port are shown as ddgs api --host 127.0.0.1 --port 4479, so port 4479 is the default. A proxy can be attached with ddgs api -pr socks5h://127.0.0.1:9150. Two alternative launch paths are given: docker-compose up --build after cloning the repository, and a shell script at start_api.sh that you chmod +x and run. The server exposes GET and POST on /search/text, /search/images, /search/news, /search/videos, /search/books and /extract, plus GET on /health, /docs for Swagger UI and /redoc. The MCP server starts with ddgs mcp, accepts the same -pr proxy flag, and exposes six tools: search_text, search_images, search_news, search_videos, search_books and extract_content. Client configuration is a JSON block with command ddgs and args ["mcp"]. Note the naming difference: the Python methods are text(), images(), news(), videos(), books(), extract(); the MCP tools are search_text, search_images and so on; the HTTP routes are /search/text and /extract. Three surfaces, three vocabularies.
Parameters worth reading twice: region, safesearch, timelimit and the images filters
The shared parameters across the search methods are region, safesearch, timelimit, max_results, page and backend. region defaults to us-en and the docstring lists us-en, uk-en and ru-ru as examples. safesearch accepts on, moderate or off and defaults to moderate, which is a sensible default for a library that may be embedded in something user-facing. timelimit accepts d, w, m or y and defaults to None. max_results defaults to 10, and page defaults to 1. The images() method adds five filters that the other methods do not have: size (Small, Medium, Large, Wallpaper), color (a list that includes Monochrome, Red, Orange and others), type_image (photo, clipart, gif, transparent, line), layout (Square, Tall, Wide) and license_image. That last one is the most interesting for anyone building a product. The allowed values are any, Public, Share, ShareCommercially, Modify and ModifyCommercially, which map onto Creative Commons style permissions. The docstring spells out the mapping, for example ShareCommercially means free to share and use commercially. The filters only apply where the backend supports them. Since images() lists only bing and duckduckgo as available backends, the practical filter surface is the intersection of those two engines, not the full documented list. The README does not state which filter each backend honours.
Where ddgs breaks: single-backend paths, silent schema drift and the extract() question
The clearest structural weakness is uneven backend coverage. videos() has one backend. books() has one backend and it is annasarchive, a site whose legal status varies by jurisdiction; the README carries its own Disclaimer section, and anyone shipping books() in a product should read it rather than assume the MIT licence covers the downstream use. The second weakness is schema stability. The documented return is list[dict[str, str]] with title, href and body shown for text(). Because ddgs parses whatever the upstream engines return, a change in an engine's response format is a change in your data. There is no versioned result schema in the material, no field that says which backend produced a given row, and no documented way to tell a partial failure from a complete one. If you ask for backend="bing,duckduckgo" and bing is unreachable, the README does not say whether you get duckduckgo results, an exception, or a shorter list. The third weakness is the timeout. The DDGS constructor defaults timeout to 5 seconds for the HTTP client. Five seconds is a short budget for a fan-out across multiple engines, and the README does not describe how that budget is divided among backends. The fourth is verification: verify accepts True, False, or a path to a PEM file, and defaults to True. Setting verify=False to work around a corporate proxy is a deliberate downgrade, and the docstring offers it without commentary. Finally, extract() and extract_content are documented as extracting content from a URL, but the README excerpt does not show the return shape or the size limits, so plan for that to be discovered at runtime.
ddgs against a hosted search API, and against writing your own clients
The obvious alternative is a hosted search API with a contract: you pay, you get a key, you get a documented JSON schema, a rate limit you can read, and a support channel. The difference in approach is not cosmetic. A hosted API owns the index and the ranking, so the same query returns the same shape next year. ddgs owns neither. It is a client that speaks to engines you do not control, which is why it can be free and MIT-licensed, and also why it cannot promise uptime or field stability. The second alternative is writing the clients yourself. That gives you control over retries, parsing and per-engine quirks, and it costs you the maintenance of ten parsers. ddgs is the middle position: less control than your own scrapers, less certainty than a paid API. The extras are worth noting as a differentiator. A hosted search API gives you an HTTP endpoint; ddgs also gives you an MCP stdio server, so an MCP-capable client can call search_text after a two-line JSON edit, with no key and no account. For a local assistant or a prototype, that is a shorter path than provisioning a search vendor. For anything with a budget and a compliance review, the hosted API is the more defensible choice.
Maintenance cost, release cadence and the MIT licence in practice
The release history shows three versions in the supplied window: v9.14.4, v9.15.0 and v9.16.0, with the latest pushed on 2026-08-26. A fast minor cadence on a client library usually means upstream engines changed and the parsers were adjusted. That is the maintenance model you inherit: pin a version, and expect that an unpinned install can change behaviour when an engine shifts. The repository is not archived, the default branch is main, and the primary language is Python. The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That covers the ddgs code. It does not cover the search results, the images you retrieve, or the books that books() returns through annasarchive. The images() method's license_image filter exists precisely because image licensing is a separate question from library licensing, and the README's Disclaimer section is the place to read before shipping. None of this is legal advice; if you are redistributing retrieved content, the licence of the content is the one that governs.
Editorial conclusion
Adopt ddgs if you need programmatic search results in Python and can tolerate a library whose output shape is fixed by whatever the upstream engines return. Do not adopt it if you need a contractual SLA, a stable result schema across backends, or a search index you control. Before committing, verify three things: that the backends you name in the backend argument still appear in the Engines table for your ddgs version, that your proxy and timeout settings survive a real query, and that your use of the extract() and books() paths matches what the upstream sites permit.
Community notes