Quick answer: Structured data is organized into a predefined model (rows, columns, fields with types) so a machine can query it directly, like a database table or a JSON object. Unstructured data has no such model: it is free-form text, images, audio, and the raw HTML of web pages, where meaning is there but not machine-labeled. Most of the world's data, and most of the web, is unstructured. Data extraction is the work of turning unstructured content into structured records you can actually use.
The core difference
Structured data fits neatly into a schema decided in advance. Each piece has a known place and type: a price is a number, a name is a string, an in_stock is a boolean. Because the shape is fixed, software can filter, sort, join, and aggregate it without guessing.
Unstructured data has no fixed shape. A product description, a news article, a customer email, a PDF, a photo: the information is real and often rich, but nothing tells a machine "this token is the price and that one is the SKU." A human reads it fine. A program has to infer the structure.
There is a useful middle category, semi-structured data, which carries some organization but not a rigid schema: JSON with varying fields, XML, or HTML with tags. It has markers a parser can use even though the shape is not fully regular.
| Aspect | Structured | Semi-structured | Unstructured |
|---|---|---|---|
| Schema | Fixed, predefined | Flexible, self-describing | None |
| Examples | SQL tables, CSV | JSON, XML, HTML | Prose, images, audio, PDFs |
| Machine-queryable | Directly | With parsing | Only after extraction |
| Share of real-world data | Small | Medium | Large |
Examples you meet every day
Structured: a spreadsheet of orders, a users table, a CSV export, an API that returns clean JSON. You can SELECT * WHERE price < 20 and it just works.
Semi-structured: an HTML page. The tags (<h1>, <table>, <span class="price">) give a parser something to grab, but two sites markup the same product completely differently, so there is no single schema.
Unstructured: the sentence "The new model ships in March for two hundred dollars." A person knows the release date and the price. A database sees a string. Nothing is labeled.
Why the web is mostly unstructured
The web was built for humans to read, not for machines to query. Pages optimize for visual layout and reading, so the same fact appears in wildly different markup from site to site. One store puts the price in <span class="price">, another in a <div> with an inline style, a third injects it with JavaScript after load.
Even within one site, layouts drift over time and vary by page type. There is no universal schema for "a product page" or "an article," which is exactly why extracting structured data from the web is a real, ongoing engineering problem rather than a solved one. If it were structured, everyone would just query it.
How extraction turns pages into records
Extraction is the bridge from unstructured or semi-structured content to structured records. The process, in stages:
- Fetch the page (and render it if the content is injected by JavaScript).
- Parse the HTML into a navigable tree.
- Locate the fields you want with CSS selectors or XPath.
- Normalize the raw values into clean types (parse a price string to a number, a date string to a date).
- Emit a record that matches your target schema, usually JSON.
The end product is a predictable object. You decide the schema up front, then extraction fills it in for every page:
{
"name": "Aeropress Go",
"price": 39.95,
"currency": "USD",
"in_stock": true,
"rating": 4.7
}
For the deeper how-to, our guide on extracting structured JSON from any webpage walks through the selector-and-normalize approach with real code.
A code sketch
Here is the traditional selector-based extraction in Python: parse, locate, normalize, emit a structured record.
import requests
from bs4 import BeautifulSoup
html = requests.get("https://example.com/product/aeropress-go").text
soup = BeautifulSoup(html, "html.parser")
def to_price(text: str) -> float:
return float(text.replace("$", "").replace(",", "").strip())
record = {
"name": soup.select_one("h1.product-title").get_text(strip=True),
"price": to_price(soup.select_one("span.price").get_text()),
"currency": "USD",
"in_stock": soup.select_one(".stock-status").get_text(strip=True) == "In stock",
}
print(record)
That works, and it is precise, but every selector is tied to one site's markup. When the layout changes, the selectors break and you maintain them by hand. Across many sites that maintenance is the real cost of extraction.
Where LLMs help
Large language models change the economics of the last mile. Because an LLM reads content the way a human does, you can hand it messy page text and a target schema and get structured output back, without writing a selector for every field on every site.
The catch is that LLMs work best on clean text, not raw HTML full of navigation, scripts, and ads. Feeding a model a wall of markup wastes tokens and confuses it. So the reliable pipeline is: get clean content first, then let the model impose structure.
That is the two-step link.sc is built for. First, fetch clean content from the URL:
curl -X POST https://api.link.sc/v1/fetch \
-H "x-api-key: lsc_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/product/aeropress-go", "format": "markdown"}'
The response content is readable Markdown with the page noise stripped. Then pass that text to your model with the schema you want:
schema = {
"name": "string",
"price": "number",
"currency": "string",
"in_stock": "boolean",
}
# prompt your LLM with `content` + `schema`, ask for JSON matching the schema
Because the input is already clean, the model spends its budget on structure instead of fighting HTML, and the same prompt generalizes across sites that have completely different markup. See the docs for the JSON output mode when you want the API to return fields directly.
Ethics note
Extraction should stay on public data. Respect robots.txt and rate limits, do not bypass authentication or paywalls to reach content, and be mindful of personal data and how you store it. Turning unstructured pages into structured records is a powerful capability, and using it responsibly is part of the job.
Recap
Structured data has a fixed schema and is directly queryable; unstructured data (most of the web) does not and is not. Semi-structured formats like HTML sit in between. Extraction bridges the gap by fetching, parsing, locating, normalizing, and emitting records that match a schema you define. Selectors give precise control at the cost of per-site maintenance, and LLMs generalize that last step, especially when you feed them clean content instead of raw HTML.
Turn any messy page into clean, schema-ready content for your models. Try link.sc free.