Skip to main content

Scraping-friendly HTTP, string parsing, JSON helpers, and AI parsing.

Project description

Respondo

Scraping-friendly Python library for response parsing, text extraction, JSON detection, and AI-powered parsing. Zero external dependencies—standard library only.

Installation

pip install respondo

Quick Example

from respondo import between, find_first_json, extract_links, Response

# Extract text between delimiters
between("<title>Hello World</title>", "<title>", "</title>")
# => "Hello World"

# Find JSON embedded in text
find_first_json('callback({"user": "alice", "id": 42})')
# => {"user": "alice", "id": 42}

# Parse HTTP response
resp = Response(200, {"content-type": "text/html"}, b"<a href='/page'>Link</a>")
resp.is_success()  # => True
extract_links(resp.text, base="https://example.com")
# => ["https://example.com/page"]

Error Handling

All functions return empty values instead of raising exceptions. This is intentional for scraping where missing data is expected.

between("no match here", "<a>", "</a>")  # => ""
find_first_json("not json")              # => None
regex_first("abc", r"\d+")               # => ""
b64_decode("invalid!!!")                 # => b""
json_get({"a": 1}, "b", "c")             # => None

API Reference

Text Extraction

from respondo import between, betweens, between_last, between_n

# First match between delimiters
between("a]x[b]y[c", "[", "]")           # => "b"

# All matches
betweens("a[b]c[d]e", "[", "]")          # => ["b", "d"]

# Last occurrence of left delimiter
between_last("a[b]c[d]e", "[", "]")      # => "d"

# Nth match (1-indexed)
between_n("a[b]c[d]e", "[", "]", 2)      # => "d"

Regex

from respondo import regex_first, regex_all

# First match (returns capture group if present)
regex_first("price: $42.99", r"\$(\d+)")      # => "42"
regex_first("abc123def", r"\d+")              # => "123"

# All matches
regex_all("<a>1</a><a>2</a>", r"<a>(\d+)</a>")  # => ["1", "2"]

HTML Processing

from respondo import (
    strip_tags,
    get_text,
    extract_links,
    extract_forms,
    extract_tables,
    parse_csrf_token,
)

html = """
<html>
  <script>var x=1;</script>
  <body>
    <h1>Title</h1>
    <p>Hello &amp; welcome</p>
    <a href="/page1">Link 1</a>
    <a href="/page2">Link 2</a>
  </body>
</html>
"""

# Strip HTML tags (keeps script/style content)
strip_tags("<b>bold</b> text")           # => "bold text"

# Extract visible text (removes scripts/styles, decodes entities)
get_text(html)                           # => "Title Hello & welcome Link 1 Link 2"

# Extract all links
extract_links(html, base="https://example.com")
# => ["https://example.com/page1", "https://example.com/page2"]

# Filter to same host only
extract_links(html, base="https://example.com", same_host=True)

# Filter by extension
extract_links(html, extensions=[".pdf", ".doc"])

# Extract forms with fields
extract_forms('<form action="/login" method="post"><input name="user"></form>')
# => [{"action": "/login", "method": "post", "fields": {"user": ""}}]

# Extract tables as dicts
extract_tables("<table><tr><th>Name</th></tr><tr><td>Alice</td></tr></table>")
# => [{"headers": ["Name"], "rows": [{"Name": "Alice"}]}]

# Find CSRF tokens
parse_csrf_token('<input name="csrf_token" value="abc123">')  # => "abc123"
parse_csrf_token('<meta name="csrf-token" content="xyz">')    # => "xyz"

JSON Utilities

from respondo import find_first_json, find_all_json, json_get, json_in_html

# Find first valid JSON in text
find_first_json('prefix {"a": 1} middle [1,2,3] suffix')
# => {"a": 1}

# Find all JSON objects/arrays
find_all_json('{"a":1} text [1,2] more {"b":2}')
# => [{"a": 1}, [1, 2], {"b": 2}]

# Safe nested access (returns None if path missing)
data = {"user": {"profile": {"name": "Alice"}}}
json_get(data, "user", "profile", "name")  # => "Alice"
json_get(data, "user", "missing", "key")   # => None

# Array indexing
json_get([{"id": 1}, {"id": 2}], 0, "id")  # => 1

# Extract JSON from HTML script tags
html = '<script type="application/json">{"config": true}</script>'
json_in_html(html)  # => [{"config": true}]

Encoding Utilities

from respondo import url_encode, url_decode, b64_encode, b64_decode

# URL encoding
url_encode({"q": "hello world", "page": 1})  # => "q=hello+world&page=1"
url_encode({"tags": ["a", "b"]})              # => "tags=a&tags=b"

# URL decoding (returns lists for multi-values)
url_decode("a=1&b=2&b=3")  # => {"a": ["1"], "b": ["2", "3"]}

# Base64
b64_encode("hello")           # => "aGVsbG8="
b64_decode("aGVsbG8=")        # => b"hello"

# URL-safe base64
b64_encode("data", urlsafe=True)
b64_decode("ZGF0YQ", urlsafe=True)

Response Class

from respondo import Response

# Create from status, headers, body
resp = Response(
    status=200,
    headers={"Content-Type": "application/json"},
    body=b'{"ok": true}'
)

# Status checks
resp.is_success()       # 200-299
resp.is_redirect()      # 300-399
resp.is_client_error()  # 400-499
resp.is_server_error()  # 500-599

# Headers (case-insensitive)
resp.header("content-type")      # First value: "application/json"
resp.headers_all("set-cookie")   # All values: ["a=1", "b=2"]

# Content type parsing
resp.content_type()  # => ("application/json", "utf-8")

# Body access
resp.body        # Raw bytes
resp.text        # Decoded string (utf-8)
resp.json()      # Parsed JSON

# Hashing
resp.hash()          # SHA-256 of body
resp.hash("md5")     # MD5 of body

# Charset detection (from headers or <meta>)
text, charset = resp.charset_sniff()

# Cookie parsing
resp.cookies()
# => [{"name": "session", "value": "abc", "attrs": {"path": "/", "httponly": ""}}]

# HTML helpers (delegates to htmlutil)
resp.extract_links(base="https://example.com", same_host=True)
resp.extract_json()           # Find JSON in HTML
resp.strip_scripts_styles()   # Clean HTML text

AI Parsing

Parse text using LLM APIs (OpenAI, Anthropic, Grok). No SDK dependencies—uses urllib.

from respondo import parse_ai, parse_ai_json

# Set API key in environment
# export OPENAI_API_KEY=sk-...
# export ANTHROPIC_API_KEY=sk-ant-...
# export GROK_API_KEY=xai-...

# Basic parsing
text = "Contact us at support@example.com or sales@example.com"
parse_ai("Extract all email addresses", text, provider="openai")
# => "support@example.com, sales@example.com"

# Get structured JSON response
text = "John Smith is 32 years old and works as an engineer"
parse_ai_json("Extract name, age, and job as JSON", text, provider="anthropic")
# => {"name": "John Smith", "age": 32, "job": "engineer"}

# Specify model
parse_ai("Summarize this", long_text, provider="openai", model="gpt-4o")

# Pass API key directly
parse_ai("Extract dates", text, provider="grok", api_key="xai-...")

Providers and defaults:

Provider Env Variable Default Model
openai OPENAI_API_KEY gpt-4o-mini
anthropic ANTHROPIC_API_KEY claude-3-5-haiku-latest
grok GROK_API_KEY grok-2-latest

Returns empty string ("") or None on errors—no exceptions.

Real-World Examples

Scrape Hacker News titles

import urllib.request
from respondo import Response, regex_all, normalize_space

req = urllib.request.urlopen("https://news.ycombinator.com")
resp = Response(req.status, dict(req.headers), req.read())

titles = regex_all(resp.text, r'class="titleline"[^>]*><a[^>]*>([^<]+)</a>')
for title in titles[:5]:
    print(normalize_space(title))

Extract data from JSON API

from respondo import Response, json_get

resp = Response(200, {}, b'{"users": [{"name": "Alice"}, {"name": "Bob"}]}')
data = resp.json()

# Safe traversal
first_user = json_get(data, "users", 0, "name")  # => "Alice"
missing = json_get(data, "users", 99, "name")    # => None (no error)

Parse form and submit

from respondo import extract_forms, parse_csrf_token, url_encode

html = '''
<form action="/login" method="post">
  <input name="csrf" value="token123">
  <input name="username">
  <input name="password" type="password">
</form>
'''

forms = extract_forms(html, base="https://example.com")
# => [{"action": "https://example.com/login", "method": "post", "fields": {"csrf": "token123", "username": "", "password": ""}}]

# Or just grab the CSRF token
csrf = parse_csrf_token(html)  # => "token123"

Running Tests

cd src
python demo_real_sites.py

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

respondo-0.2.0.tar.gz (15.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

respondo-0.2.0-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

File details

Details for the file respondo-0.2.0.tar.gz.

File metadata

  • Download URL: respondo-0.2.0.tar.gz
  • Upload date:
  • Size: 15.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for respondo-0.2.0.tar.gz
Algorithm Hash digest
SHA256 584d9845a04070c8b3677d0b4699d2b91aa26f8cff61b51183dbd517d72953e9
MD5 73183d7b6c0c76c493390cbb5de0bba6
BLAKE2b-256 b459c3a74d4fcbe905acd943e0603772864f513ea725f89316a32b1000d84c64

See more details on using hashes here.

File details

Details for the file respondo-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: respondo-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for respondo-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1fba1fa180a6799fa556d7bcf8c6e7360874974f17c3886249890ed42c612ff
MD5 7fed0990fef548192287dbca4e5fc88d
BLAKE2b-256 aacf8a5b96af36ca4b58e51ac210acaf251a970bf1566ccafa53fc705def70ce

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page