elliotgao2
toapi
Python

Every web site provides APIs.

Last updated Jul 8, 2026
3.6k
Stars
240
Forks
1
Issues
+1
Stars/day
Attention Score
79
Language breakdown
Python 100.0%
β–Έ Files click to expand
README

toapi

CI PyPI Python License Downloads Ruff uv

Turn any website into a JSON API β€” declaratively.

toapi lets you point at a web page, declare the fields you want with CSS selectors, and get back a clean JSON API. No crawler to babysit, no database to maintain β€” pages are fetched and parsed on demand, with built‑in caching.

Install

pip install toapi

Requires Python 3.10+.

Quickstart

from htmlparsing import Attr, Text
from toapi import Api, Item

api = Api()

@api.site("https://news.ycombinator.com") @api.list(".athing") @api.route("/posts", "/news") @api.route("/posts?page={page}", "/news?p={page}") class Post(Item): title = Text(".titleline > a") url = Attr(".titleline > a", "href")

api.run(host="127.0.0.1", port=5000)

Run it:

python app.py

Then visit and you get:

{
  "Post": [
    {"title": "Mathematicians Crack the Cursed Curve", "url": "https://www.quantamagazine.org/..."},
    {"title": "Stuffing a Tesla Drivetrain into a 1981 Honda Accord", "url": "https://jalopnik.com/..."}
  ]
}

How it works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  /posts    β”‚ ─▢ β”‚  fetch     β”‚ ─▢ β”‚  parse     β”‚ ─▢  JSON
   β”‚  (route)   β”‚    β”‚  (cache)   β”‚    β”‚  (Item)    β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Route β€” @api.route("/posts", "/news") maps your API path to a source URL.
  • Fetch β€” pages are fetched with requests (or a headless browser if you pass browser=) and cached in memory.
  • Parse β€” each Item extracts fields with CSS selectors via htmlparsing.
  • Serve β€” Flask returns the result as JSON; subsequent calls hit the cache.

Features

  • Declarative β€” describe data, not scraping logic.
  • Routes β€” map clean API paths to messy source URLs with {param} placeholders.
  • Multi-site β€” merge several websites behind one API.
  • Cleaning hooks β€” define clean_<field> methods to post-process values.
  • Caching β€” pages and parsed results are cached automatically.
  • Headless browser β€” pass Api(browser="/path/to/geckodriver") for JS-heavy sites.

Cleaning values

Add a clean_<fieldname> method on the Item to transform a value before it's returned:

@api.site("https://news.ycombinator.com")
@api.route("/posts", "/news")
class Page(Item):
    next_page = Attr(".morelink", "href")

def cleannextpage(self, value): return f"/posts?{value.split('?', 1)[1]}"

Development

git clone https://github.com/elliotgao2/toapi.git
cd toapi
uv sync          # install deps into .venv
uv run pytest    # run tests
uv run ruff check .

We use uv for packaging and ruff for lint + format. Pre-commit hooks keep both clean:

uv run pre-commit install

Contributing

Pull requests are welcome. For non-trivial changes, please open an issue first to discuss what you'd like to change. Make sure uv run pytest and uv run ruff check . pass before submitting.

License

MIT Β© Elliot Gao

Β© 2026 GitRepoTrend Β· elliotgao2/toapi Β· Updated daily from GitHub