Skip to content
hsblabs OSS

This procedure starts with an empty directory and ends with a structured result. It uses only the CLI. It sends no network request, because the extractor operates on a saved HTML file. Test each new extractor in this manner before you point it at a live service.

You must have the scrape-kdl binary. Refer to Installation.

Save this text as page.html. The spaces in the heading are intentional. They show you the function of a transform.

<!doctype html>
<html>
<body>
<h1> Scraping KDL Runtime </h1>
<ul class="items">
<li><span class="value">1</span></li>
<li><span class="value">2</span></li>
<li><span class="value">3</span></li>
</ul>
</body>
</html>

Save this text as extractor.kdl.

extractor "basic-http" version="2026-07-15" language-version="2026-07-15" {
source "html" {
fetch mode="http" url="https://example.invalid/{id}"
}
input "id" type="string" required=#true
field "title" type="string" required=#true {
select "h1" match="one"
value "text"
apply "normalize-whitespace"
}
collection "items" min-items=1 {
select "ul.items > li"
field "value" type="u8" required=#true {
select ".value" match="one"
value "text"
apply "trim"
apply "parse-int" as="u8"
}
}
}

Read the document from the top to the bottom:

  • version identifies this revision of the document. language-version selects the language contract. Its value must be 2026-07-15. Both properties are necessary.
  • source declares the method to get the document. The value mode="http" selects the static HTTP runtime. The runtime puts a declared input in the position of {id}.
  • The node field "title" selects one h1, reads its text, and removes the unwanted spaces.
  • The node collection "items" makes one row from each li that agrees with the selector. It also requires a minimum of one row.
  • The property type="u8" is a true constraint. The runtime parses the text into an 8-bit unsigned integer. A value that is too large causes an extraction error. The runtime does not truncate the value.
Terminal window
scrape-kdl validate ./extractor.kdl
valid: ./extractor.kdl

Validation is analysis only. It parses the document, resolves the symbols, checks the types, and calculates the capabilities. It does not open a socket. The exit status is 0 for a correct document and 1 when the diagnostics contain an error.

Now cause an error. Change the selector of the title to h1:has(a) and validate the document again:

extractor.kdl:9:5: error E_SELECTOR_UNSUPPORTED: selector byte 9: unsupported pseudo-class "has" [output.title.selection]

The pseudo-class :has() is outside of the portable selector profile. Thus the compiler rejects it and gives you a source location and an output path. You get this error immediately, not at the twentieth page of a crawl. Change the selector to h1 again before you continue. Refer to Diagnostics.

Terminal window
scrape-kdl compile ./extractor.kdl --out ./extractor.ir.json
wrote: ./extractor.ir.json

The Validated IR is the language-neutral contract between the compiler and each runtime. If you do not give --out, the CLI writes the IR to the standard output. Examine these fields first:

{
"irVersion": "2026-07-15",
"languageVersion": "2026-07-15",
"capabilities": ["http.fetch"]
}

The array capabilities contains the exact set of the capabilities that this program needs. This program only fetches with HTTP. If you add a browser workflow or an evaluate-js field, the set becomes larger. Thus a host can decide what to permit before it executes the program. Refer to How It Operates.

Terminal window
scrape-kdl extract ./extractor.kdl --html ./page.html
{
"value": {
"items": [
{
"value": 1
},
{
"value": 2
},
{
"value": 3
}
],
"title": "Scraping KDL Runtime"
},
"warnings": [],
"partial": false
}

The heading has no unwanted spaces. The item values are numbers, not strings. The flag partial: false shows you that the runtime recovered no error.

The option --html does no acquisition. There is no URL expansion, no URL policy, no session, and no HTTP request. Thus you do not have to supply the necessary id input. Refer to Offline Snapshots.

For a script, the option --json puts the result in an envelope with an explicit success flag:

Terminal window
scrape-kdl extract ./extractor.kdl --html ./page.html --json
{
"ok": true,
"result": {
"value": {
"items": [
{
"value": 1
},
{
"value": 2
},
{
"value": 3
}
],
"title": "Scraping KDL Runtime"
},
"warnings": [],
"partial": false
}
}

In an automated procedure, examine ok and also the exit status of the process. Refer to CLI.

The same extractor operates on a live URL. Supply the declared input in the place of --html:

Terminal window
scrape-kdl extract ./extractor.kdl --input id=123

Read Security and Responsible Use before you do this against a real service. By default the CLI rejects a target that is not globally accessible. It accepts a session only from --session-file. Thus your credentials do not go into the history of your shell.