Website Table to CSV & JSON - HTML Table Extractor by URL
Pricing
from $16.00 / 1,000 table extractions
Website Table to CSV & JSON - HTML Table Extractor by URL
Converts a website table to CSV or JSON: pass a page URL and every HTML data table comes back as rows keyed by column header, layout tables skipped. Returns one record per page with headers and rows, exportable to CSV or Excel. For analysts, developers, AI agents and no-code builders.
Pricing
from $16.00 / 1,000 table extractions
Rating
0.0
(0)
Developer
Broke to Built
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Features
Share
Webpage Tables Extractor: Website Table to CSV or JSON from Any URL
Send a URL, get every HTML data table on the page as JSON rows keyed by column header. Export to CSV or Excel from the dataset.
| You give it | You get back | You pay (Free plan) |
|---|---|---|
| One URL or a list of URLs | Per table: headers, row count, rows as objects | $0.02 per page (all its tables included) |
Pay per result only: no subscription, no start fee. Pages that fail are not charged. Paid Apify plans get Store discounts on the price above.
Try it in one click
The input form is already filled in with this example. Press Start and you get the output below.
{"url": "https://en.wikipedia.org/wiki/List_of_largest_companies_by_revenue","urls": []}
Real output from that exact input (run on 2026-09-24; long arrays shortened to a few entries and long text cut for display):
[{"url": "https://en.wikipedia.org/wiki/List_of_largest_companies_by_revenue","tableCount": 6,"tables": [{"index": 0,"headers": ["Ranks","Name"],"rowCount": 51,"rows": [{"Ranks": "USD (in billions)","Name": "USD (in billions)"},{"Ranks": "1","Name": "Amazon","Industry": "Retail Information technology","Revenue": "716","Profit": "79.9","Employees": "1,576,000","Headquarters[note 1]": "United States","State-owned": "","Ref.": "[5]"}]},{"index": 1,"headers": ["Rank","Country"],"rowCount": 12,"rows": [{"Rank": "1","Country": "United States","Companies": "24"},{"Rank": "2","Country": "China","Companies": "11"}]}]}]
Give it a page URL, get every real data <table> on it back as JSON — headers detected,
rows as objects keyed by header, ready for a spreadsheet, an LLM, or a data pipeline. One page
or up to 50 in a run. Download the same result as CSV or Excel from the run's Storage tab.
Callable as a plain HTTP API and as an agent tool over Apify MCP.
$0.02 per page processed, however many tables that page holds.
Who uses it
- Analysts pulling a reference table off a wiki, a government page, or a docs site without copy-paste.
- Developers who need tabular data from a page that has no API and do not want to write a parser.
- AI agents handed a page link that must reason over the numbers on it.
- No-code builders (Make, n8n, Zapier) that can call a URL but cannot parse HTML.
What you get
One dataset item per page:
| Field | Meaning |
|---|---|
url | The final URL after redirects |
tableCount | How many data tables were kept |
tables[].index | 0-based position among the kept tables |
tables[].headers | Detected header labels, in column order |
tables[].rowCount | Body rows in that table |
tables[].rows | Array of objects keyed by header text; unnamed columns fall back to col0, col1, … and a duplicate header gets _<index> appended |
error | Present instead of tables when the page could not be fetched. Never charged |
All cell values are strings with whitespace collapsed — "1,576,000", not 1576000. Cast
on your side.
Layout and spacer tables are skipped automatically: anything with fewer than 2 body rows or fewer than 2 columns is dropped, so you get data tables, not page scaffolding.
Examples
1. A real Wikipedia page
Input:
{ "url": "https://en.wikipedia.org/wiki/List_of_largest_companies_by_revenue" }
Output from an actual run, trimmed to the first rows of the first of 6 tables:
{"url": "https://en.wikipedia.org/wiki/List_of_largest_companies_by_revenue","tableCount": 6,"tables": [{"index": 0,"headers": ["Ranks", "Name", "Industry", "Revenue", "Profit", "Employees", "Headquarters[note 1]", "State-owned", "Ref."],"rowCount": 51,"rows": [{"Ranks": "1", "Name": "Amazon","Industry": "Retail Information technology","Revenue": "716", "Profit": "79.9", "Employees": "1,576,000","Headquarters[note 1]": "United States", "State-owned": "", "Ref.": "[5]"},{"Ranks": "3", "Name": "State Grid Corporation of China","Industry": "Electricity","Revenue": "545", "Profit": "9.2", "Employees": "1,361,423","Headquarters[note 1]": "China", "State-owned": "", "Ref.": "[7]"}]}]}
Read the next section before you trust every row of a page like that one. The same real run also produced this row, and it is wrong:
{"Ranks": "5", "Name": "China National Petroleum Corporation","Industry": "476", "Revenue": "25.2", "Profit": "1,026,301", "Employees": "China"}
The values shifted one column left because that row uses rowspan (a merged cell carried down
from the row above), which this actor does not reconstruct. On tables with merged vertical
cells, some rows will be misaligned. That is a real limitation, stated here rather than
discovered by you in production.
2. Several pages in one run
{"urls": ["https://example.com/report-a","https://example.com/report-b"],"maxUrls": 25}
One dataset item per page. Download the whole dataset as CSV or Excel from the run's Storage tab if a spreadsheet is where this is going.
3. A page that cannot be fetched
{ "url": "https://example.com/gone", "error": "HTTP 404" }
The run continues with the remaining pages and that page is not charged.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
url | string | a demo page | A single page URL |
urls | string[] | string | [] | More page URLs. A plain string is split on commas and newlines |
maxUrls | integer | 25 | Cap on pages per run (hard max 50). This is also your spend cap |
url and urls are merged and deduplicated, and a URL with no scheme gets https:// added.
Call it from code
curl -X POST "https://api.apify.com/v2/acts/eliai~webpage-tables-extractor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"url":"https://en.wikipedia.org/wiki/List_of_largest_companies_by_revenue"}'
Python (pip install apify-client):
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("eliai/webpage-tables-extractor").call(run_input={"url": "https://example.com/report"})page = next(client.dataset(run["defaultDatasetId"]).iterate_items())for t in page["tables"]:print(t["index"], t["headers"], t["rowCount"], "rows")
Pricing
Pay per event, one event: table-extracted.
| Event | What one event covers | Price |
|---|---|---|
table-extracted | One page fetched and parsed — every data table on it, however many | $0.02 |
A page with one table and a page with six both cost $0.02. A 25-page run costs $0.50, the
50-page maximum costs $1.00. maxUrls is your spend cap. There is no start fee and no monthly
fee, and a page that fails to load is recorded with an error and never charged.
Honest comparison: pandas.read_html(url) is free and does much the same job, with better
rowspan handling. Use this when you want it hosted — no Python runtime, batching, a JSON
dataset a no-code tool or an agent can consume, and CSV/Excel export from the run page.
When NOT to use this
- The table is rendered by JavaScript. Only the served HTML is parsed; no browser runs. A
dashboard or a client-rendered grid will come back with
tableCount: 0. - The table uses merged cells vertically (
rowspan). Those rows shift, as shown above.colspanis handled;rowspanis not. - You need typed numbers. Every cell is a string with whitespace collapsed.
"716", not716. - You need the table's links, images, or formatting. Only visible text is extracted.
- The data is not in a
<table>element. CSS-grid "tables" and<div>layouts are invisible to this. - You want to crawl a site for tables. It reads exactly the URLs you list; it never follows links.
- The page needs a login or is behind bot protection. No credentials, no proxy — those come back as errors.
Honest limits
rowspanis not reconstructed;colspanis expanded loosely by repeating the value.- Tables with fewer than 2 body rows or fewer than 2 columns are skipped as layout tables.
- Sub-header rows inside a table body become ordinary rows with mostly-empty values.
- Header detection prefers
<thead> <th>, then the first row containing<th>, then the first row. - Cells are text only; whitespace is collapsed to single spaces.
- 20-second fetch timeout per page; pages are fetched one at a time.
- Hard cap of 50 pages per run.
FAQ
How do I scrape an HTML table into JSON or a spreadsheet? Pass the page URL — every real data table comes back as headers plus rows objects. For a spreadsheet, open the run's Storage tab and export the dataset as CSV or Excel.
How does it know what the column names are? It prefers <thead> header cells, falls back to the first row containing <th>, and finally to the first row. Each body row becomes an object keyed by that header text; unnamed columns fall back to col0, col1, … so nothing is lost.
Does it handle merged cells? colspan yes — the value is repeated across the spanned columns so cells stay aligned. rowspan no — rows that inherit a merged cell from above will be shifted by one or more columns. Check any table that merges cells vertically.
Why did a table on the page not appear in the output? Single-column tables and tables with fewer than 2 body rows are skipped on purpose; those are almost always layout or spacer tables. Every table that survives that filter is returned with its index.
Can it extract tables that JavaScript renders after load? No — it parses the served HTML, which is what keeps it fast and cheap. A client-rendered grid needs a browser-based scraper instead.
Are the numbers returned as numbers? No. Every cell is a string exactly as rendered, including thousands separators and footnote markers like "[5]". Strip and cast on your side.
Can I extract tables from several pages at once? Yes — up to 50 per run via urls. Each page is its own dataset item and one dead link never stops the rest.
Can an AI agent call this? Yes — it is exposed over Apify MCP. Input { "url": "<page url>" }, and it returns tableCount plus a tables array of header/row objects.
Who made this
Broke to Built. Questions or problems: open an issue on this Actor's Issues tab.
For AI agents
This Actor is built to be called by software, not just by people.
- Mount it directly as an MCP tool — no Store search, no ranking, just this one tool:
https://mcp.apify.com/?actors=eliai/webpage-tables-extractor - Or call it over HTTP and get the results in the same request:
POST https://api.apify.com/v2/acts/eliai~webpage-tables-extractor/run-sync-get-dataset-items - Costs are predictable before you call. Pricing is pay-per-event (see Pricing above), so an agent can budget a run in advance instead of discovering the bill afterwards.
- Send only the field you mean. If you pass the bulk field, it is used on its own; the single-value field is a fallback, never merged into your request. You are charged for the items you sent and nothing else.
Changelog
- 2026-11-01: Page text only: a clearer page heading. Code, output and price are unchanged.
- 2026-09-24: New summary at the top of this page: what you send, what you get, the price, and a real output from the prefilled example.