The data you need is sitting right there on the page, in a neat table of 300 rows. There’s no API and no download button. You could copy and paste it into a spreadsheet, and then spend the afternoon fixing the columns that came out wrong. Or you could extract it.
How an HTML table is structured
Financial reports, sports statistics, price lists and government datasets are often published as HTML tables. A table is a <table> element with an optional <thead> (header), <tbody> (body) and <tfoot> (footer). Each row is a <tr>, and each cell is either a header cell <th> or a data cell <td>.
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ethiopia Yirgacheffe</td>
<td>$14.50</td>
<td>42</td>
</tr>
<tr>
<td>Colombia Huila</td>
<td>$12.00</td>
<td>7</td>
</tr>
</tbody>
</table>Why extract HTML tables
Researchers tracking economic indicators, analysts watching competitor prices and journalists compiling public records all hit the same wall. The numbers are public, but only as a web page.
Copying by hand is fine for ten rows. At a few hundred it’s slow, and you will miss a row somewhere. Converting the table to JSON or CSV lets you load it straight into a spreadsheet, database or script.
JSON or CSV
- JSON suits web apps, APIs and document databases. Each row becomes an object keyed by the column headers, which is easy to work with in JavaScript or Python.
- CSV suits Excel, Google Sheets, pandas and R. It keeps the row-and-column shape of the original table.
For the table above, the two outputs look like this:
[
{ "Name": "Ethiopia Yirgacheffe", "Price": "$14.50", "Stock": "42" },
{ "Name": "Colombia Huila", "Price": "$12.00", "Stock": "7" }
]"Name","Price","Stock"
"Ethiopia Yirgacheffe","$14.50","42"
"Colombia Huila","$12.00","7"How to use the HTML table extractor
- Open the HTML table extractor. Either enter a page URL and select Fetch HTML, or paste HTML into the input box.
- Choose JSON or CSV and select Extract. Every
<table>in the HTML is extracted, and each one gets its own output panel with a row count. - Copy a table’s output or Download it as
table-1.json,table-1.csvand so on. You can switch formats after extracting without running it again.
A few details about how the extractor reads a table:
- Column names come from the
<thead>cells, or from the<th>cells in the first row. If there are none, the first row is used as the header. - Cell values are the cell’s text with surrounding whitespace trimmed. Links, images and formatting tags are reduced to their text.
- In JSON, an empty header becomes
column_0,column_1and so on, and every value is a string. - In CSV, every field is quoted, and values starting with
=,+,-or@get a leading'so spreadsheets do not run them as formulas.
Pasted HTML is parsed in your browser. When you fetch a URL, PulpMiner downloads the page on its server first, and only public URLs are allowed.
One catch: the fetch gets the HTML the site sends before any JavaScript runs. If the page fills its table in with JavaScript after loading, the table may be missing. In that case, open the page in your browser, right-click the table, choose Inspect, copy the <table> element and paste it in.
Complex tables
Real-world tables aren’t always tidy. Watch for these:
- Merged cells. The extractor does not expand
colspanorrowspan. A merged cell appears once, so the cells after it shift left in that row. Check those rows and fill the gaps in a spreadsheet. - Nested tables. A table inside a cell is extracted as its own table, and its rows can also show up in the outer table. Strip the inner tables from the HTML first if the result looks wrong.
- Div-based layouts. Some sites style
<div>elements to look like a table. These are not<table>elements, so the extractor will not find them. Use CSS selectors or a scraper instead.
Cleaning the data
Extracted values are text exactly as the page showed them, so “$14.50” is a string, not a number. A little cleanup usually helps:
- Whitespace: cells can contain non-breaking spaces (
) and line breaks in the middle of the text. - Numbers: remove currency symbols, thousands separators and percent signs before treating a column as numeric.
- Missing values: decide whether an empty cell means an empty string,
nullor a placeholder, and apply it consistently. - Dates: convert locale-specific dates to ISO 8601 (
2026-02-07).
Extracting tables in code
If you’re scraping the same table every day, write it once in code. In the browser, or in Node with a DOM library such as jsdom, the DOM API is enough for simple tables:
function extractTable(html) {
const doc = new DOMParser().parseFromString(html, "text/html");
const table = doc.querySelector("table");
if (!table) return [];
const headers = Array.from(table.querySelectorAll("thead th")).map((th) =>
th.textContent.trim(),
);
return Array.from(table.querySelectorAll("tbody tr")).map((row) => {
const cells = Array.from(row.querySelectorAll("td"));
return Object.fromEntries(
headers.map((header, i) => [header, cells[i]?.textContent.trim() ?? ""]),
);
});
}In Python, pandas.read_html returns every table on a page as a DataFrame and does expand merged cells.
import pandas as pd
tables = pd.read_html("https://example.com/report.html")
tables[0].to_csv("report.csv", index=False)