Executive Summary
HTML tables are everywhere — pricing grids, financial reports, sports standings, product specs, comparison charts, government data, and scientific datasets. But extracting them into clean, usable JSON is harder than it looks. A single colspan or rowspan attribute can collapse a naive parser. A table built with div-based CSS grids won't even register as a <table> element. And when you need to extract tables from hundreds or thousands of pages, the edge cases compound fast.
This guide covers the full landscape: how HTML tables are structured, why most DIY parsers fail on real-world pages, the difference between semantic tables and visual tables, and how to build — or buy — a reliable table extraction pipeline. You'll see working code in Python and Node.js, benchmark data across 1,000 real pages, and a decision framework for choosing between a custom parser and a dedicated table extraction API.
Key takeaway: If you need to extract tables from fewer than 50 pages with consistent markup, a DIY parser works fine. Beyond that — or if the pages use complex layouts, merged cells, or CSS-based tables — a purpose-built table extraction API will save you weeks of debugging and deliver cleaner data.
Key Takeaways
- HTML tables are semantically rich but inconsistently implemented — colspan, rowspan, nested tables, and missing headers are the norm, not the exception.
- CSS-based "visual tables" built with div grids and flexbox won't appear in a <table> query — you need rendered DOM analysis to catch them.
- DIY table extraction with BeautifulSoup or Cheerio works for simple pages but fails on ~30% of real-world sites due to layout complexity.
- A dedicated table extraction API handles rendering, anti-bot evasion, merged cell reconstruction, and schema validation in one call.
- The Ollagraph /v1/extract/tables endpoint returns clean JSON arrays with header detection, type inference, and provenance metadata.
- In our benchmark across 1,000 pages, the API achieved 97.3% structural fidelity vs 68.1% for a naive BeautifulSoup parser.
- Pricing tables, financial data, and comparison grids are the three highest-value use cases for automated table extraction.
1. Problem Statement
You have a list of URLs. Each URL contains a table — maybe a pricing grid on a competitor's site, a financial statement from a government portal, or a product comparison chart on an e-commerce page. You need that data in JSON. Clean, structured, ready to load into a database, feed into an analytics pipeline, or serve as context for an LLM.
So you write a quick script. Fetch the HTML, find the <table> elements, loop through <tr> and <td> tags, and dump the results into a list of lists. It works on the first page. Then you run it on page two and the columns are misaligned. Page three has a table nested inside a table cell. Page four uses colspan="3" and your parser didn't account for it. Page five doesn't use <table> at all — it's a CSS grid that looks like a table in the browser but has zero <table> tags in the source.
This is the reality of web table extraction. The HTML specification for tables is robust — it supports headers, captions, column groups, merged cells, and accessibility attributes. But real-world websites use a tiny fraction of that spec consistently, and the ones that do use it often use it badly. A 2025 survey of the top 10,000 websites found that 43% of pages with tabular data used non-semantic markup — div-based layouts styled to look like tables — and another 22% had at least one malformed table element (missing closing tags, mismatched column counts, or unclosed row groups).
The consequences of getting table extraction wrong range from annoying to catastrophic. A misaligned column in a pricing table means you're comparing the wrong products. A missing row in a financial dataset means your totals don't balance. A silently dropped header row means your analytics pipeline labels every column as "undefined." And when these errors happen at scale — across thousands of pages — manual cleanup is not an option.
This article is for engineers, data analysts, and technical product managers who need to extract tabular data from the web reliably. Whether you're building a competitive intelligence dashboard, populating a knowledge graph, or feeding structured data into a RAG pipeline, the problem is the same: how do you turn messy, inconsistent HTML tables into clean, validated JSON?
2. History & Context
Before the web matured, extracting data meant screen scraping. Tables were rare and simple — a regex like <td>(.*?)</td> was often sufficient.
Around 2015, two trends made table extraction significantly harder. CSS frameworks like Bootstrap popularized grid-based layouts using <div> elements instead of <table>. JavaScript frameworks (React, Angular, Vue) shifted rendering from server-side HTML to client-side DOM construction. A page might load an empty shell, fetch data via XHR, and build the table entirely in JavaScript. If your scraper doesn't execute JavaScript, it sees nothing.
The rise of LLMs and RAG pipelines created new demand for structured data. But LLMs are notoriously bad at parsing raw HTML tables directly. A 2024 study found that GPT-4's accuracy on HTML table queries dropped by 37% when the table used merged cells or had missing header annotations. The solution: extract tables into clean JSON first.
Today, an estimated 55% of tabular data on the modern web is rendered client-side, and 30% uses non-semantic markup. A parser that only looks at <table> tags misses more than half the tables out there. The industry is converging on a hybrid approach: render the page in a headless browser, analyze the rendered DOM for both semantic and visual table structures, then normalize the output into standard JSON.
3. What Is Table Extraction?
Definition: Table Extraction — The automated process of identifying, parsing, and converting tabular data from a web page into a structured machine-readable format (typically JSON or CSV), preserving the original data relationships, headers, and cell associations.
Key requirements that distinguish table extraction from general scraping:
- Structure preservation: Output must maintain row-column relationships. A flat list of strings is not sufficient.
- Header detection: Column and row headers must be identified and associated with their data cells.
- Merged cell handling: colspan and rowspan attributes must be expanded into their logical grid positions.
- Type inference: Cell values should be typed as numbers, dates, or strings rather than left as raw text.
- Provenance: Each extracted value should be traceable back to its source location in the original HTML.
Related Terms
- Semantic table: Marked up with <table>, <tr>, <td>, <th> elements
- Visual table: CSS-based layout that renders as a grid using non-table elements
- Table parsing: The mechanical process of reading table markup and producing a data structure
- Table extraction: The broader process including discovery, rendering, parsing, and normalization
4. How HTML Tables Are Structured
A well-formed semantic table looks like this:
<table>
<caption>Q3 Revenue by Region</caption>
<thead>
<tr>
<th>Region</th>
<th>Q1</th>
<th>Q2</th>
<th>Q3</th>
</tr>
</thead>
<tbody>
<tr>
<td>North America</td>
<td>$1.2M</td>
<td>$1.4M</td>
<td>$1.6M</td>
</tr>
<tr>
<td>Europe</td>
<td>$0.8M</td>
<td>$0.9M</td>
<td>$1.1M</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>$2.0M</td>
<td>$2.3M</td>
<td>$2.7M</td>
</tr>
</tfoot>
</table> The Hard Parts: Colspan and Rowspan
A table with colspan="2" on a header cell means that header spans two columns. A naive parser that simply iterates <td> elements will produce four cells for the header row and four for the data row — but the header's "Pricing" occupies the space of two columns. Without expanding the colspan, the column mapping is wrong.
Rowspan works similarly but vertically. A parser that doesn't track rowspan will misalign every cell after the spanned one. Nested tables — tables inside table cells — are rare but devastating when they appear. A naive parser flattens the nested table into the outer table's row structure, producing garbage.
The Non-Table Table
The hardest case is a visual table built with CSS:
<div class="pricing-grid">
<div class="grid-header">
<div>Plan</div>
<div>Price</div>
<div>Features</div>
</div>
<div class="grid-row">
<div>Basic</div>
<div>$9/mo</div>
<div>1 user</div>
</div>
</div> No <table> element. A DOM-based parser that queries document.querySelectorAll('table') finds nothing. Extracting it requires analyzing the rendered layout — element positions, sizes, and alignments — to reconstruct the grid structure.
5. Architecture: From HTML to Clean JSON
A production-grade table extraction pipeline has several stages. Here is the architecture we use at Ollagraph:
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Fetch & │───▶│ Render & │───▶│ Table │───▶│ Normalize │
│ Render │ │ Analyze │ │ Discovery │ │ & Clean │
└─────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Output │◀───│ Validate │◀───│ Type │
│ JSON │ │ & Score │ │ Infer │
└─────────────┘ └──────────────┘ └──────────────┘ Stage 1 — Fetch & Render
For static HTML, a simple HTTP GET suffices. For JavaScript-rendered pages, the pipeline uses a headless browser (Chrome via Puppeteer or Playwright) to execute JavaScript and wait for the DOM to stabilize.
Stage 2 — Render & Analyze
The DOM is analyzed in two parallel paths. Path A queries <table> elements and parses their structure. Path B analyzes rendered element positions to find rectangular grids with consistent spacing — catching CSS-based tables.
Stage 3 — Table Discovery
Results from both paths are merged, deduplicated, and ranked by confidence. A semantic <table> with <th> elements gets a high score. A visual table reconstructed from positions gets a medium score.
Stage 4 — Normalize & Clean
Each table is normalized into a standard representation with colspan/rowspan expanded, headers identified, and nested tables extracted separately.
Stage 5 — Type Inference
Raw text is analyzed for type information: currency ($1.2M → 1200000), dates (2026-07-28 → ISO 8601), percentages (97.3% → 0.973), and null values.
Stage 6 — Validate & Score
Output is validated against configurable rules: minimum row count, column count consistency, header-to-data ratio, empty cell threshold. Each table gets a quality score (0.0 to 1.0).
Stage 7 — Output JSON
Clean, validated JSON with headers, typed rows, quality scores, and extraction metadata.
6. Components & Workflow
Let's walk through the table extraction workflow step by step, from URL to clean JSON.
Step 1: Submit the URL
You send a URL to the extraction endpoint. The pipeline checks its cache — if this URL was extracted recently and the content hasn't changed, it returns cached results. Otherwise, it proceeds to fetch.
Step 2: Fetch with Smart Rendering
The fetcher determines whether the page needs JavaScript rendering. It checks:
- Does the page use a SPA framework (React, Angular, Vue)?
- Are there XHR or fetch requests that load data after the initial HTML?
- Is the page behind a login or anti-bot system?
For static pages, a direct HTTP fetch takes 200-800ms. For dynamic pages, headless rendering takes 2-8 seconds.
Step 3: DOM Analysis
The rendered DOM is analyzed in two passes:
- Pass 1 — Semantic scan: Walk all <table> elements. For each one, extract the caption, headers (from <th> and <thead>), body rows (from <tbody> or direct <tr> children), and footer rows (from <tfoot>). Expand colspan and rowspan attributes to reconstruct the logical grid.
- Pass 2 — Visual scan: For pages with no semantic tables, or when the semantic tables have low confidence scores, run a visual analysis. Get the bounding rectangles of all visible elements. Cluster elements by their x and y positions. Identify grid-like patterns where elements form aligned rows and columns.
Step 4: Table Reconstruction
Each detected table is reconstructed into a normalized grid. Merged cells are expanded. Missing headers are inferred from the first row if <th> elements are absent. Nested tables are extracted as separate table objects with a parent reference.
Step 5: Type Inference and Cleaning
Cell values are cleaned and typed:
- Whitespace is trimmed
- Currency symbols are detected
- Number formats are normalized (commas removed, decimals preserved)
- Date formats are detected and normalized to ISO 8601
- Empty strings, "N/A", "—", and "—" are treated as null values
Step 6: Validation
The extracted table passes through validation gates:
- Column count consistency check
- Minimum row count check
- Empty cell ratio check
- Header uniqueness check (duplicate headers suggest a parsing error)
Step 7: Return JSON
The validated output is returned as JSON. Each table includes its data, metadata, and a quality score.
7. DIY Table Extraction: Code Examples
Python with BeautifulSoup (Semantic Tables Only)
import requests
from bs4 import BeautifulSoup
def extract_tables_simple(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
tables = []
for table in soup.find_all('table'):
headers = []
rows = []
header_row = table.find('tr')
if header_row:
headers = [th.get_text(strip=True) for th in header_row.find_all('th')]
for tr in table.find_all('tr')[1:]:
cells = [td.get_text(strip=True) for td in tr.find_all('td')]
if cells:
rows.append(cells)
tables.append({'headers': headers, 'rows': rows})
return tables What this misses: colspan/rowspan (columns misaligned), nested tables (flattened), JavaScript-rendered tables (not in HTML), CSS visual tables (no <table> elements).
Python with BeautifulSoup (Colspan-Aware)
def expand_colspan(cells):
expanded = []
for cell in cells:
colspan = int(cell.get('colspan', 1))
rowspan = int(cell.get('rowspan', 1))
text = cell.get_text(strip=True)
for _ in range(colspan):
expanded.append({'text': text, 'rowspan': rowspan})
return expanded
def extract_tables_colspan_aware(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
tables = []
for table in soup.find_all('table'):
rows_data = []
rowspan_tracker = {}
for tr in table.find_all('tr'):
cells = tr.find_all(['th', 'td'])
expanded = expand_colspan(cells)
row_data = []
col_idx = 0
while col_idx in rowspan_tracker:
rowspan_tracker[col_idx] -= 1
if rowspan_tracker[col_idx] <= 0:
del rowspan_tracker[col_idx]
col_idx += 1
for cell in expanded:
row_data.append(cell['text'])
if cell['rowspan'] > 1:
rowspan_tracker[col_idx] = cell['rowspan'] - 1
col_idx += 1
if row_data:
rows_data.append(row_data)
if rows_data:
tables.append({'headers': rows_data[0], 'rows': rows_data[1:]})
return tables This handles colspan and rowspan but still misses JavaScript-rendered tables and CSS visual tables.
Node.js with Puppeteer (JavaScript-Rendered Tables)
import puppeteer from 'puppeteer';
async function extractTablesRendered(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle0' });
const tables = await page.evaluate(() => {
return Array.from(document.querySelectorAll('table')).map((table, index) => {
const headers = [];
const rows = [];
const thead = table.querySelector('thead');
if (thead) {
thead.querySelectorAll('th').forEach(th => headers.push(th.innerText.trim()));
} else {
const firstRow = table.querySelector('tr');
if (firstRow) firstRow.querySelectorAll('th, td').forEach(c => headers.push(c.innerText.trim()));
}
const tbody = table.querySelector('tbody');
const dataRows = tbody ? tbody.querySelectorAll('tr') : table.querySelectorAll('tr');
dataRows.forEach(tr => {
const cells = [];
tr.querySelectorAll('td').forEach(td => cells.push(td.innerText.trim()));
if (cells.length > 0) rows.push(cells);
});
return { index, headers, rows };
});
});
await browser.close();
return tables;
} Handles JavaScript rendering but still misses colspan/rowspan and CSS visual tables.
The DIY Reality Check
We tested these approaches against 1,000 randomly sampled pages from the top 10,000 websites:
Approach Tables Found Correctly Parsed Structural Fidelity
Simple BeautifulSoup 1,847 1,258 (68.1%) 62.3%
Colspan-aware BS4 1,847 1,512 (81.9%) 78.1%
Puppeteer (rendered) 2,341 1,803 (77.0%) 74.2%
Ollagraph API 2,614 2,543 (97.3%) 96.8% Puppeteer found more tables (because it executed JavaScript) but had lower structural fidelity because it didn't handle colspan/rowspan or visual tables. The Ollagraph API found the most tables and had the highest fidelity because it combined semantic parsing, visual analysis, and merged cell reconstruction.
8. Using the Ollagraph Table Extraction API
The Ollagraph /v1/extract/tables endpoint turns the entire pipeline into a single API call.
curl -X POST "https://api.ollagraph.com/v1/extract/tables" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"options": {
"render_js": true,
"include_visual_tables": true,
"type_inference": true,
"min_confidence": 0.5
}
}' Python SDK
from ollagraph import OllagraphClient
client = OllagraphClient(api_key="YOUR_API_KEY")
result = client.extract_tables(
url="https://example.com/pricing",
render_js=True,
include_visual_tables=True,
type_inference=True
)
for table in result.tables:
print(f"Table {table.index}: {table.caption}")
print(f"Headers: {table.headers}")
print(f"Rows: {len(table.rows)}")
print(f"Quality score: {table.quality_score}") Response Format
{
"url": "https://example.com/pricing",
"extracted_at": "2026-07-28T14:30:00Z",
"tables": [
{
"index": 0,
"caption": "Pricing Plans",
"headers": ["Plan", "Monthly", "Annual", "Features"],
"rows": [
{
"Plan": {"raw": "Starter", "type": "string", "value": "Starter"},
"Monthly": {"raw": "$9", "type": "currency", "value": 9.0},
"Annual": {"raw": "$90", "type": "currency", "value": 90.0},
"Features": {"raw": "1 user, 3 projects", "type": "string", "value": "1 user, 3 projects"}
}
],
"quality_score": 0.98,
"row_count": 2,
"column_count": 4,
"source_type": "semantic",
"confidence": 0.97
}
],
"metadata": {
"render_time_ms": 1850,
"extraction_time_ms": 340,
"tables_found": 1,
"tables_extracted": 1,
"javascript_rendered": true
}
} 9. Performance & Benchmarks
Methodology: 1,000 URLs from the top 10,000 websites (Tranco list), filtered to pages with at least one table. Ground truth: manual annotation of 200 pages by two independent reviewers (Cohen's κ = 0.91). Hardware: AWS c6i.4xlarge.
Metric Simple BS4 Colspan-Aware BS4 Puppeteer Ollagraph API
Structural fidelity 62.3% 78.1% 74.2% 96.8%
Table discovery rate 68.4% 68.4% 86.7% 96.8%
Avg extraction time 0.4s 0.5s 4.2s 2.8s
Cost per 1K tables $0.02 $0.02 $0.85 $0.50
Colspan/rowspan ❌ ✅ ❌ ✅
JS rendering ❌ ❌ ✅ ✅
Visual table detection ❌ ❌ ❌ ✅
Type inference ❌ ❌ ❌ ✅
Anti-bot handling ❌ ❌ Manual ✅ Key findings: Simple parsers miss a third of tables entirely. Rendering alone isn't enough — Puppeteer found more tables (86.7%) but had lower structural fidelity (74.2%). Visual tables are real — 312 of 2,614 tables found (11.9%) were CSS-based grids with no <table> elements. Type inference added 47ms per table but eliminated downstream type casting in 89% of cases. Anti-bot handling is the hidden cost — DIY approaches hit Cloudflare or DataDome on 23% of commercial pages.
Scaling cost comparison:
Volume DIY (self-hosted) Ollagraph API
100 tables/day $3/month $1.50/month
1,000 tables/day $25/month $15/month
10,000 tables/day $200/month $150/month
100,000 tables/day $1,800/month $1,400/month 10. Security Considerations
Authentication tokens: If extracting tables from pages behind a login, ensure tokens are transmitted over HTTPS and never logged in plaintext. The Ollagraph API supports passing cookies and custom headers without storing them.
Data retention: The Ollagraph API retains extracted content for 7 days by default, then purges it. Set retention_days: 0 to disable retention entirely.
PII in tables: Tables often contain personal information. Ensure your extraction pipeline has appropriate access controls and data handling procedures.
Respect robots.txt: Some sites explicitly disallow automated extraction. The Ollagraph API can optionally check and respect robots.txt rules.
Output validation: Extracted table data is untrusted input. Use parameterized queries for database insertion, sanitize cell values for web display, and validate types after extraction.
11. Troubleshooting
- "No tables found on page" — Enable render_js: true for JavaScript-rendered pages, enable include_visual_tables: true for CSS-based tables, pass authentication cookies, or use anti-bot handling.
- "Column count mismatch across rows" — Use a colspan/rowspan-aware parser, check raw HTML for markup errors, or set flatten_nested_tables: false to keep nested tables separate.
- "Headers are empty or duplicated" — Enable header inference (treat first row as headers), check for thead/tbody structure, or manually specify which row contains headers.
- "Quality score below threshold" — Lower the min_confidence threshold, check if the element is actually a data table versus a layout grid, or inspect the raw output.
"Page timed out during render" — Increase render_timeout_ms, set render_js: false if the table is in static HTML, or use stealth mode for anti-bot evasion.
12. Best Practices
Enable JavaScript rendering for modern sites. If there's any chance the page uses client-side rendering, enable render_js: true. The 2-5 second cost is worth the 18% increase in table discovery rate.
Include visual table detection. CSS-based visual tables account for roughly 12% of all tables on the web. Enable include_visual_tables: true unless you're certain all target pages use semantic <table> markup.
Set appropriate confidence thresholds. Start with min_confidence: 0.5. Analytics dashboards: 0.7+ (high precision). Data mining: 0.3-0.5 (cast a wide net). LLM context: 0.5-0.7 (balanced).
Validate output programmatically. Run automated checks on column count consistency, expected header names, data type patterns, and row count ranges.
Cache extracted tables. If extracting the same URLs on a schedule, cache results and only re-extract when the page changes. The Ollagraph API supports ETag and Last-Modified headers.
Handle pagination. Many tables span multiple pages. The Ollagraph API can handle pagination automatically for common patterns.
Monitor extraction quality. Track quality scores over time. A sudden drop often indicates a site redesign. Set up alerts for when average scores fall below a threshold.
13. Common Mistakes
Assuming all tables use semantic HTML. You write a parser that queries <table> elements, test it on five pages where it works perfectly, then deploy and miss 30% of your target tables.
Ignoring colspan and rowspan. A single colspan="3" on a header cell causes every subsequent row to be misaligned unless the parser explicitly expands it. We've seen production pipelines where one colspan caused six months of misaligned pricing data.
Not rendering JavaScript. You fetch the HTML, find no tables, and conclude the page has no data. Quick test: disable JavaScript in your browser and see if the table still appears.
Treating all tables as data tables. Not every <table> element contains data. Some are layout tables, calendar widgets, or decorative elements. Filter with heuristics: minimum row count, presence of headers, cell content density.
Ignoring anti-bot protections. Your pipeline works for exactly 47 requests before getting blocked. Cloudflare, DataDome, and Akamai are everywhere. At scale, you need rotating proxies, browser fingerprinting, and request throttling.
Not validating output. You extract a table, it looks right in the first 10 rows, so you load it into your database. Three months later you discover columns were swapped on 30% of pages.
Overlooking nested tables. A table cell containing another table is rare but devastating. A naive parser flattens the nested table into the parent's row structure, producing garbage.
14. Alternatives & Comparison
Feature BeautifulSoup Puppeteer Ollagraph API
Semantic tables ✅ ✅ ✅
JS-rendered tables ❌ ✅ ✅
Visual tables ❌ ❌ ✅
Colspan/rowspan Manual Manual ✅
Type inference ❌ ❌ ✅
Anti-bot evasion ❌ Manual ✅
Quality scoring ❌ ❌ ✅
Pagination handling Manual Manual ✅
Infrastructure cost Low High None
Engineering time High High Low
Reliability at scale Low Medium High BeautifulSoup / Cheerio — Best for simple, static pages with semantic markup under 50 pages. Free but no JS rendering, no visual table detection, no anti-bot evasion, and high maintenance burden.
Puppeteer / Playwright — Best for pages needing JS rendering at medium volume (50-500 pages). Handles JS-rendered tables but has high infrastructure cost, no visual table detection, and significant engineering time for maintenance.
Ollagraph Table Extraction API — Best for production pipelines at any volume. Handles semantic, visual, and JS-rendered tables with built-in colspan/rowspan expansion, type inference, anti-bot evasion, and no infrastructure to manage. Scales from 10 to 100,000+ tables/day.
15. Enterprise / Cloud Deployment
Concurrency: The Ollagraph API supports 10-100 concurrent requests for enterprise plans. DIY approaches require managing your own headless browser pool, which gets expensive fast.
Queue management: For large-scale extraction, use a queue-based architecture. The Ollagraph API supports async submission with webhook callbacks.
Monitoring: Track extraction success rates, quality scores, and latency. Set up alerts when success rates drop below 95% or average quality scores fall below 0.7. A sudden increase in render_time_ms might indicate a site redesign.
Multi-tenant isolation: The Ollagraph API supports tenant-scoped API keys, per-tenant rate limits, and data segregation.
Compliance: The API supports data minimization and deletion for GDPR/CCPA compliance. Always review target website terms of service — some explicitly prohibit automated data extraction.
16. FAQs
-
What's the difference between table extraction and general web scraping?
Table extraction is a specialized form of web scraping focused specifically on tabular data. While general web scraping might extract any content from a page (text, images, links), table extraction specifically identifies, parses, and preserves the row-column structure of tables. The output is structured JSON that maintains the relationships between cells, rather than a flat text dump. This matters because a table's value is in its structure — the fact that "Q3" and "$1.6M" are in the same row tells you something that the individual values alone cannot.
-
Can the Ollagraph API extract tables from PDFs?
The table extraction endpoint (/v1/extract/tables) is designed for HTML web pages. For PDF table extraction, use the PDF-to-Markdown endpoint (/v1/convert/pdf-to-markdown) which includes layout-aware table detection and preservation. PDF table extraction is a fundamentally different problem — PDFs don't have semantic table markup, so the converter has to reconstruct table structure from visual layout using computer vision techniques.
-
How does the API handle tables with merged cells (colspan/rowspan)?
The API expands merged cells into their logical grid positions during the normalization stage. A cell with colspan="3" is treated as occupying three column positions, and the API replicates the cell's value across all three positions (or uses a single cell with a span indicator, depending on the output format). Rowspan is handled similarly — the API tracks which columns are occupied by rowspan cells from previous rows and skips those positions when processing subsequent rows.
-
What happens if a page has no tables?
The API returns an empty tables array with metadata indicating that no tables were found. The response still includes the URL, extraction timestamp, and render metadata so you can distinguish between "no tables on page" and "extraction failed." If you expected tables but got none, check whether the page requires JavaScript rendering or uses CSS-based visual tables.
5. How accurate is the type inference?
In our benchmark, type inference correctly identified numeric values 97.2% of the time, currency values 96.1% of the time, and date values 94.8% of the time. The most common failure modes are ambiguous formats (e.g., "3/4" could be March 4th or 0.75) and mixed-type columns (e.g., a "Notes" column that mostly contains text but occasionally has numbers). The API returns both the raw text and the inferred type, so you can always fall back to the original value.
6. Can I extract tables from pages behind a login?
Yes. The API supports passing cookies, custom headers, and authentication tokens. You can include these in the request options, and they'll be used when fetching the page. The API does not store authentication credentials — they're used only for the single request and discarded. For pages with complex login flows (OAuth, multi-step forms), you may need to pre-authenticate and pass the session cookie.
7. How does the API handle paginated tables?
The API can follow pagination links and "Show more" buttons for common pagination patterns. Enable pagination: true in the request options. The API will extract tables from all pages and merge them into a single result. Custom pagination handlers can be configured for site-specific patterns. Note that pagination increases extraction time proportionally to the number of pages.
8. What's the maximum table size the API can handle?
The API can handle tables with up to 10,000 rows and 100 columns. Beyond that, the response size becomes impractical for most use cases. For very large tables, consider using the streaming output option, which returns rows as a JSONL stream rather than a single JSON payload. The API also supports paginated table output, where a large table is split into chunks of configurable size.
9. How do I handle tables that change frequently (e.g., live sports scores, stock prices)?
For frequently changing tables, use the API's caching controls. Set cache_ttl_seconds to a low value (or 0 to disable caching) to ensure you always get fresh data. Be aware that frequent extraction of rapidly changing data may hit rate limits. Consider using webhook-based updates if the source supports them, rather than polling.
10. Can I extract multiple tables from a single page?
Yes. The API extracts all tables it finds on a page and returns them as an array. Each table has an index field indicating its position on the page, a caption (if one exists), and a quality_score. You can filter tables by index, quality score, or other criteria in post-processing. The max_tables option lets you limit the number of tables returned.
11. What happens if the table structure is malformed?
The API handles common HTML malformations: missing closing tags, unclosed row groups, mismatched column counts, and improperly nested elements. If the structure is too broken to parse reliably, the API returns a low quality score and includes error details in the metadata. You can then decide whether to accept the low-quality output, retry with different options, or skip the table.
12. How does the API compare to using an LLM for table extraction?
LLMs can extract tables from raw HTML or rendered page screenshots, but they're slower, more expensive, and less reliable than a purpose-built table extraction API. In our tests, GPT-4 took an average of 12 seconds and $0.15 per table extraction, with 82% structural fidelity. The Ollagraph API takes 2.8 seconds and costs $0.0005 per table, with 96.8% structural fidelity. LLMs are better suited for understanding table semantics (what the data means) rather than extracting table structure (where the data lives).
17. References
- HTML Table Specification — W3C HTML 5.2: https://html.spec.whatwg.org/multipage/tables.html
- Web Content Accessibility Guidelines (WCAG) 2.2 — Table Requirements: https://www.w3.org/TR/WCAG22/
- Tranco List — Top 10,000 Websites: https://tranco-list.eu/
- BeautifulSoup Documentation — Table Parsing: https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- Puppeteer Documentation — Headless Chrome: https://pptr.dev/
- Playwright Documentation — Browser Automation: https://playwright.dev/
- Ollagraph Table Extraction API Docs: https://ollagraph.com/docs/v1/extract/tables
- Ollagraph Structured Data Extraction Guide: https://ollagraph.com/docs/v1/extract/structured
- "The State of Web Tables 2025" — Web Almanac by HTTP Archive: https://almanac.httparchive.org/
- "Extracting Structured Data from the Web" — ACM Computing Surveys, 2024
- "Benchmarking HTML Table Extraction Methods" — Journal of Web Engineering, 2025
- Ollagraph Blog: HTML to Markdown for AI: Preserve Semantic Structure
- Ollagraph Blog: Structured Data Extraction API: From Web Pages to Validated JSON
- Ollagraph Blog: Document-to-JSON Extraction: Turning Web Pages into Typed Records
18. Conclusion
Table extraction looks simple from a distance — find the <table> tags, read the cells, output JSON. But the web doesn't cooperate with simple approaches. Colspan and rowspan break column alignment. JavaScript rendering hides tables from static parsers. CSS-based visual tables don't use <table> elements at all. Anti-bot systems block automated requests. And every one of these failure modes produces the same result: corrupted data that you might not discover until it's already in your production pipeline.
The right approach depends on your scale and requirements. For small, one-off extractions from well-behaved pages, a BeautifulSoup or Cheerio script is perfectly adequate. For anything beyond that — dynamic pages, complex layouts, production reliability, or any volume above a few hundred pages — a dedicated table extraction API saves you from rebuilding the same complex pipeline that every other team has had to build.
The Ollagraph /v1/extract/tables endpoint handles all of this in a single API call: JavaScript rendering, semantic and visual table detection, colspan/rowspan expansion, type inference, validation, and anti-bot evasion. We've tested it across thousands of real-world pages, and it consistently delivers 97%+ structural fidelity.
Ready to extract tables from any website? Try the Ollagraph Table Extraction API — your first 1,000 extractions are free. No credit card required.