Skip to main content
Put a schema between Zenrows and your database. Each row from Extract, css_extractor or outputs goes through a model that converts types ("$69.00" becomes 69.0, "Yes" becomes true), checks required fields, and sends any row that doesn’t fit to a flagged list for review. Your pipeline stores clean, typed data every time. Uses: Extract (extract=auto), css_extractor and outputs, with Pydantic or Zod

Ask your AI agent

With the Zenrows MCP server connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.
The same prompt works for thousands of products across many pages: ask your agent to check every row the same way before it saves them.

Validate css_extractor rows

css_extractor returns each selector as a list of strings. Zip the lists into rows, then validate each row. The model trims names, turns "$1,299.00" into 1299.0, requires a positive price and an absolute URL.
Output (Python)

What a flagged row looks like

A row with an empty name, a price that isn’t a number and a relative URL fails all three checks. The error names each field, so a reviewer, or your agent, knows what to fix:
Python (Pydantic)
Output (trimmed)
With Zod, z.flattenError(result.error).fieldErrors gives the same answer as an object:
Output (Node.js)

Validate Extract output

extract=auto already returns numbers for prices and null for fields a page leaves empty. A schema pins the fields your pipeline relies on, fills defaults (a null or missing currency becomes "USD"), and drops every other field, so new fields from Extract never break your storage. This one requires a name, a positive price, a SKU and a product URL, and saves valid and flagged rows to separate files.
Output (Python)

Normalize table rows

Rows from outputs=tables are keyed by the column headings on the page, and every value is a string. Field aliases map headings such as "Product ID" to clean names, and Pydantic turns "Yes"/"No" into booleans on its own:
Python (Pydantic)
Output

Example output

The first rows of products.json from the Extract run above. Every row is typed, carries the USD default, and keeps only the schema’s fields:
flagged.json holds [] for this page. Any row that fails the schema lands there with its errors, as shown in What a flagged row looks like.

Customize it

Troubleshooting

  • Every row is flagged on price: the price arrives in a format your parser doesn’t expect, such as "69,00 €". Print one raw value and adjust the parse_price step for that currency format.
  • A required field is null on many rows: the page leaves it empty, or loads it late. Make the field Optional with a default, or fetch with js_render=true and wait_for so it renders first.
  • URLs fail as relative: the selector returned paths such as /product/blue-shirt. Join them with the page URL (urljoin in Python, new URL(path, page) in Node.js) before validation.
  • Names and prices don’t line up: one card has no price, so the lists differ in length. Use extract=auto, which returns one object per item, or parse one card at a time as in Extract data with CSS selectors.

Get structured JSON from any page

Get product, listing and article fields as JSON.

Turn search and listing pages into JSON

Collect every item across every page into one dataset.

Pull tables and links from any page

Save every table on a page as CSV.

Extract data with CSS selectors

Pick exact elements with css_extractor.