Scrape an HTML table to JSON, one object per row
To scrape an HTML table to JSON you want rows as objects and columns as named keys, not a flat list of cell strings. Rankings, pricing tables, sports results, directories and search result pages all repeat the same block over and over. With the Scraping API you describe one row once and get the whole table back as an array.
Table cells come out flat and lose which row they belong to
Querying every td on a page gives you one long list of strings. Which cell was the company and which the country depends on counting positions, and one empty cell or a colspan shifts every value after it into the wrong column.
Hand-written loops over rows fix the pairing but live in your code, one per site, and still return strings you convert yourself. Lists that are not tables, like product cards or search results, need yet another loop, and the whole thing fails on pages whose rows are rendered in the browser.
Nested rules keep each row together. selectorAll matches every row, an object under attr describes the columns, and each column rule runs relative to its own row. The result is an array of objects with the keys you chose, typed per column. The nested rules reference covers deeper structures.
How to extract table data and lists with nested rules
One parent rule selects the repeated element, the children describe a single item. The same pattern covers tables, cards and search results.
import createClient from 'microlink.io'
const microlink = createClient({
apiKey: process.env.MICROLINK_API_KEY
})
const { rows } = await microlink.extract(
'https://www.w3schools.com/html/html_tables.asp',
{
rows: {
selectorAll: '#customers tr:not(:first-child)',
attr: {
company: { selector: 'td:nth-child(1)', attr: 'text' },
contact: { selector: 'td:nth-child(2)', attr: 'text' },
country: { selector: 'td:nth-child(3)', attr: 'text' }
}
}
}
)The :not(:first-child) selector skips the header row. rows resolves to an array such as { company: 'Alfreds Futterkiste', contact: 'Maria Anders', country: 'Germany' } for each data row.
import createClient from 'microlink.io'
const microlink = createClient({
apiKey: process.env.MICROLINK_API_KEY
})
const { products } = await microlink.extract('https://books.toscrape.com', {
products: {
selectorAll: 'article.product_pod',
attr: {
title: { selector: 'h3 a', attr: 'title' },
price: { selector: '.price_color', attr: 'text', type: 'number' },
url: { selector: 'h3 a', attr: 'href', type: 'url' }
}
}
})Each card becomes an object. The price column is validated as a number and the relative href resolves to an absolute URL, so the array is ready to store.
curl 'https://api.microlink.io/?url=https%3A%2F%2Fnews.ycombinator.com&data.titles.selectorAll=.titleline+%3E+a&data.titles.attr=text&meta=false'Without a nested attr, each match contributes one plain value, so titles is an array of strings. Use it for headlines, tags or any single-column list.
- selectorAll Matches every row, card or list item and returns an array.
- attr An object of rules here describes the columns of one item.
- type Validates each column on its own: number, url, date, image and more.
- data The same rules as query parameters when calling the API directly.
- filter Returns only the named fields when a request extracts several lists.
Build the smallest piece first: get one column right on its own, then wrap it in the row rule. The defining rules guide follows the same order, and when rows span several pages, scrape paginated lists shows how to cover them all.
Why nested rules beat looping over cells yourself
The row is the unit you care about, so the rule set is shaped like a row. Pairing, typing and absolute URLs happen before the data leaves the API.
A column rule can be a fallback array when some rows use a different markup for the same value.
Product grids are just tables with more layout. For a single product page, scrape product prices and stock adds fallbacks and client-rendered stores.
When not to: if a model or a report only needs to read the table, attr: 'markdown' on the table element converts it into a Markdown table in one rule, or convert the whole page with the URL to Markdown tool.
FAQ
How do I scrape an HTML table to JSON?
How do I skip the header row when extracting table data?
Can I scrape a list from a website when items have different fields?
Why does my table scrape return only the first row?
Can I scrape a table that loads with JavaScript?
Solve the next problem with the same API
Any website to JSON
Pagination and Load more buttons
Product prices and stock
Every link and email on a page
Clean Markdown, no boilerplate
Custom fields alongside metadata
Ready to turn tables into JSON arrays?
Describe one row, get every row. Start on the free tier and scrape your first table today.