nanoserp
A tiny, free Python library and CLI for web search and page scraping. No API keys, no accounts, no rate limit tiers -- just search and scrape.
Built on top of DuckDuckGo's HTML endpoint and markdownify, nanoserp is designed for small-scale coding agents and agentic Python applications that need lightweight, zero-config access to web search and content extraction.
Install
pip install nanoserp
Requires Python 3.11+.
Claude Code Skill
To install nanoserp as a skill for Claude Code and other coding agents:
npx skills add https://github.com/fkodom/nanoserp --skill nanoserp
CLI
Search
nanoserp search "python web scraping"
Filter results by time range (d/w/m/y):
nanoserp search "python web scraping" --date-filter w
Paginate through results with --offset:
nanoserp search "python web scraping" --offset 10
Scrape
nanoserp scrape "https://example.com"
Prints the page content as markdown, followed by an aggregated list of all links found on the page.
Python API
Search
from nanoserp import search, DateFilter
# Basic search
result = search("python web scraping")
for r in result.results:
print(r.title, r.url, r.snippet)
# Filter by time range
result = search("python web scraping", date_filter=DateFilter.WEEK)
# Pagination
page1 = search("python web scraping")
page2 = search("python web scraping", offset=len(page1.results), vqd=page1.vqd)
The search function returns a SearchResponse:
| Field | Type | Description |
|---|---|---|
query |
str |
The original query string |
results |
list[SearchResult] |
Parsed search results |
vqd |
str | None |
Session token for pagination |
Each SearchResult contains:
| Field | Type | Description |
|---|---|---|
title |
str |
Result title |
url |
str |
Result URL |
snippet |
str |
Text snippet |
date |
datetime | None |
Publish date, if available |
Scrape
from nanoserp import scrape
result = scrape("https://example.com")
print(result.markdown) # Full page content as markdown
for link in result.links:
print(link.text, link.url)
The scrape function returns a ScrapeResponse:
| Field | Type | Description |
|---|---|---|
url |
str |
The scraped URL |
markdown |
str |
Page content converted to markdown |
links |
list[ScrapeLink] |
All links found on the page |
Error Handling
All errors inherit from NanoserpError:
from nanoserp import search
from nanoserp.exceptions import NanoserpError, RateLimitError
try:
result = search("test")
except RateLimitError:
# DuckDuckGo returned HTTP 429
...
except NanoserpError as e:
# Any other HTTP or parsing error
print(e.message)
Limitations
- Search results come from DuckDuckGo's HTML endpoint, which is not an official API. The response format may change without notice.
- DuckDuckGo may rate-limit or block requests under heavy use. This tool is intended for small-scale, low-frequency usage.
- Scraping respects the target server's response but does not check
robots.txt.
For Developers
Setup
This project uses uv for dependency management, but any virtual environment or package manager (pip, venv, poetry, conda) will work.
# Create and activate a virtual environment
uv venv --python 3.12
source .venv/bin/activate
# Install development dependencies and pre-commit hooks
uv sync --extra dev
pre-commit install
Verification
Before committing, make sure all checks pass:
# Lint and format
uv run ruff check --fix . && uv run ruff format .
# Type check
uv run ty check .
# Unit tests
uv run pytest
# Include integration tests (makes real HTTP requests)
uv run pytest --slow
Tooling
| Tool | Description |
|---|---|
| ruff | Linting and formatting |
| ty | Static type checking |
| pytest | Unit and integration testing |
| pre-commit | Git hook management |
Publishing
To publish to PyPI, move .github/disabled-workflows/publish.yaml.disabled to .github/workflows/publish.yaml and set PYPI_API_TOKEN in the repo secrets. Then tag a new release, and GitHub Actions will build and publish the package automatically.
Metadata
Release files for nanoserp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nanoserp-0.1.0.tar.gz | 21.2 kB | Without provenance | Details for nanoserp-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nanoserp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for nanoserp-0.1.0-py3-none-any.whl |
Total release size: 35.5 kB
Release files / nanoserp-0.1.0.tar.gz
| Download URL | nanoserp-0.1.0.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
24fbf60b72190d5ec4fc8bb5a083934b98e12ba23f0939fb2774ceb18c6f18e2
|
|
BLAKE2b-256 checksum How to use checksums |
aadf195bfd9d3bc8034c22c8889ec5eb6ec615eadaeb367bd768932069ad5e91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / nanoserp-0.1.0-py3-none-any.whl
| Download URL | nanoserp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
848c10d1d903770658a80d6d80b84cd66426a9a5facdd75db94c17f717a2490f
|
|
BLAKE2b-256 checksum How to use checksums |
6fccf7a8c871635606bae8be42156d71211455eb772d46560ba71ef262e5ba32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for nanoserp 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.