How to Scrape Airtable with LlamaIndex

Scrape Airtable pages into a LlamaIndex pipeline with EnConvert Perceive, returning clean Markdown with navigation and sidebars stripped in a single batch call

Get API key

A developer building a LlamaIndex pipeline that reads Airtable pages needs clean page text before passing it into a vector index or query engine. Airtable pages mix content with base navigation, table tabs, view selectors, field headers, filter and sort controls, record expansion buttons, and comment threads, and scraping them by hand requires CSS selectors that break when Airtable updates its layout. EnConvert Perceive renders up to 10 Airtable pages in a single batch call and returns clean Markdown for each one, with navigation and sidebars stripped by default. The extracted Markdown is ready to load into a LlamaIndex VectorStoreIndex with no HTML parsing.

Example

python
import os
import requests
from llama_index.core import VectorStoreIndex
from llama_index.core.schema import TextNode

# LlamaIndex uses OpenAI for embeddings and answers, and reads OPENAI_API_KEY from the environment

BASE_URL = "https://api.enconvert.com/v2"
headers = {
    "X-API-Key": os.environ["ENCONVERT_API_KEY"],
    "Content-Type": "application/json",
}

page_urls = [url.strip() for url in os.environ["AIRTABLE_PAGE_URLS"].split(",")]  # public Airtable page URLs

if len(page_urls) > 10:
    raise ValueError("This example covers inline batches of 10 URLs or fewer")

response = requests.post(f"{BASE_URL}/perceive/batch", headers=headers,
    json={"urls": page_urls, "options": {"outputs": ["markdown"]}}, timeout=300)

if response.status_code == 402:
    raise RuntimeError("Monthly ops allowance exhausted")
response.raise_for_status()

nodes = []
for result in response.json()["items"]:
    page_url = result["url"]
    if result.get("status") != "completed":
        print(f"Skipped {page_url}: {result.get('status')}")
        continue
    if result.get("is_blocked"):
        print(f"Skipped {page_url}: blocked page")
        continue
    render_quality = result.get("render_quality", 1.0)
    if render_quality < 0.40:
        print(f"Skipped {page_url}: low render quality ({render_quality:.2f})")
        continue
    markdown_url = result["outputs"]["markdown"]["url"]
    markdown = requests.get(markdown_url, timeout=60)
    markdown.raise_for_status()
    nodes.append(TextNode(
        text=markdown.text,
        id_=page_url,
        metadata={"source_url": page_url, "render_quality": render_quality},
    ))

if not nodes:
    raise RuntimeError("No Airtable pages passed the render checks")

index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine(similarity_top_k=3)
print(query_engine.query("Summarise what these Airtable pages cover."))

What you get back

Perceive returns clean Markdown for each URL in the batch, with navigation and sidebars stripped by default, delivered as pre-signed URLs that you fetch once per page. Scraping Airtable by hand needs CSS selectors that break when the layout changes, and passing raw Airtable HTML into a LlamaIndex node fills the index with base navigation, view controls, and interface chrome that add noise to every similarity search result.

Get free API key — 500 ops per month, no card required.