Nano SERP analysis library
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nanoserp-0.1.0.tar.gz.
File metadata
- Download URL: nanoserp-0.1.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24fbf60b72190d5ec4fc8bb5a083934b98e12ba23f0939fb2774ceb18c6f18e2
|
|
| MD5 |
2bcbef097624dec4125797ae3f7dcd42
|
|
| BLAKE2b-256 |
aadf195bfd9d3bc8034c22c8889ec5eb6ec615eadaeb367bd768932069ad5e91
|
File details
Details for the file nanoserp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: nanoserp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
848c10d1d903770658a80d6d80b84cd66426a9a5facdd75db94c17f717a2490f
|
|
| MD5 |
1b629c58ef531aeb886222dcf3f6f76a
|
|
| BLAKE2b-256 |
6fccf7a8c871635606bae8be42156d71211455eb772d46560ba71ef262e5ba32
|