From microdata¶
microdata is a small pure-Python library that pulls the HTML Microdata items a
page embeds – the WHATWG itemscope/itemprop vocabulary sites use to mark up schema.org products, recipes,
people, and events. Its get_items entry point parses the page with html5lib, walks the itemscope elements in
Python, and returns Item objects you read with .itemtype, .itemid, .props, .get/.get_all, and
.json. It has long been the go-to dependency for scrapers that need schema.org markup off a page.
turbohtml serves that surface from turbohtml.extract.microdata(), backed by the same C walk that
turbohtml.Document.microdata() runs over the WHATWG tree. It returns the page’s top-level items as frozen
MicrodataItem records with the same accessors, so a scraper’s item-reading code ports without a
rewrite while the parse-and-walk runs in the C extension rather than in Python over an html5lib tree.
turbohtml vs microdata¶
Dimension |
turbohtml |
microdata |
|---|---|---|
Scope |
Full WHATWG HTML5 parser, serializer, and an extraction suite (Microdata, JSON-LD, OpenGraph, links, dates, boilerplate) |
Microdata extraction only |
Feature breadth |
Microdata, JSON-LD, and OpenGraph gathered in one walk; nested |
|
Performance |
One C walk over the parsed tree; 35 to 42x faster on the benchmark pages |
Python walk over an html5lib tree, built on each |
Typing |
Fully typed; |
Untyped |
Dependencies |
C extension, no pure-Python runtime dependencies |
Requires html5lib |
Maintenance |
Actively developed |
Minimal, low-activity single-maintainer project, stable for years |
Feature overlap¶
The item-reading surface ports one-to-one:
microdata.get_items(html)->turbohtml.extract.microdata(), both returning the page’s top-level items in document order.item.itemid->item.id.item.props->item.properties, eachitempropname mapped to its values in document order.item.get(name)/item.get_all(name)->get()/get_all(), with the same first-value-or-Noneand list-or-[]contract.item.json()->json(), the same two-space-indented JSON of the tree below the item.A property that is itself an
itemscopecomes back as a nested item in both libraries, not a flat string.
What turbohtml adds¶
Nested items and multi-valued properties resolve directly off the record, so a property that is another
itemscopeis a nestedMicrodataItem:from turbohtml.extract import microdata item = microdata( '<div itemscope itemtype="https://schema.org/Person">' '<span itemprop="name">Ada</span>' '<span itemprop="name">Lovelace</span>' '<div itemprop="address" itemscope><span itemprop="city">London</span></div>' "</div>" )[0] print(item.type) print(item.get("name"), item.get_all("name")) print(item.get("address").get("city"))
https://schema.org/Person Ada ['Ada', 'Lovelace'] London
JSON-LD and OpenGraph off the same page in the same C walk:
turbohtml.Document.json_ld(),turbohtml.Document.opengraph(), andturbohtml.Document.structured_data(), plus the string-input helpersturbohtml.extract.opengraph()and the rest of the extraction suite.microdatacovers Microdata alone.turbohtml.Document.microdata()extracts from an already-parsedturbohtml.parse()document, so pages you hold for other work do not get reparsed.The records are frozen and hold no reference back into the tree, so they outlive the document:
item = microdata('<div itemscope itemid="urn:isbn:1"><span itemprop="name">B</span></div>')[0] print(item.id, item.json())
urn:isbn:1 { "id": "urn:isbn:1", "properties": { "name": [ "B" ] } }A text property’s value follows the spec’s
textContentrule, so text nested in the property element is kept wheremicrodatadrops it (see Gotchas and pitfalls).URL-valued properties (an
a/area/linkhref, a mediasrc, anobjectdata) are returned verbatim by default; passbase_url=tomicrodata()(ormicrodata()) to absolutize them against it, the waymicrodata’surlargument resolves them. A<base href>refines the base URL.
What microdata has that turbohtml does not¶
Fetching from a URL or file.
get_items(location, encoding=None)accepts a URL or file-like object and reads it for you.microdata()takes an HTML string only. Workaround: fetch the bytes yourself (urllib/requests) and pass the decoded text, or feed the response toturbohtml.parse()and callmicrodata().An explicit encoding argument.
get_itemstakesencodingto decode the fetched bytes. turbohtml handles encoding at parse time on the byte input instead; there is no separate argument on the string helper.``itemtype`` as parsed URI objects.
item.itemtypeis a list ofURIobjects (item.itemtype[0].string).typeis the rawitemtypestring; call.type.split()for the token list.A mutable, buildable item.
microdata’sItemis an open object whosepropsdict you can edit and add to.MicrodataItemis a frozen read-only record. No equivalent; build your own structure if you need to synthesize or mutate items.
Performance¶
microdata starts from the raw HTML string, so it parses before it extracts: it builds an html5lib tree and walks the
itemscope elements in Python, where microdata() parses to the WHATWG tree and gathers the
items in one C walk. On a product page carrying Microdata alongside JSON-LD and OpenGraph, and on an 8 KiB catalog of
the same, the walk runs 35 to 42 times faster:
structured-data extraction |
turbohtml |
microdata |
|---|---|---|
product |
6.22 µs |
243 µs (39.1x) |
catalog 8 KiB |
56.3 µs |
2.58 ms (45.8x) |
Over the fixtures in microdata’s own test suite the two libraries return byte-identical json() for every page
but one, where turbohtml follows the spec more closely (see Gotchas and pitfalls).
How to migrate¶
Swap the import and the entry point:
# before
from microdata import get_items
# after
from turbohtml.extract import microdata
The call and item API map directly:
turbohtml |
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
The item-reading code is the same shape on both sides; only the itemtype accessor changes:
# before
from microdata import get_items
items = get_items(
'<div itemscope itemtype="https://schema.org/Person">'
'<span itemprop="name">Ada</span>'
'<div itemprop="address" itemscope><span itemprop="city">London</span></div>'
"</div>"
)
item = items[0]
print(item.itemtype[0].string)
print(item.get("name"))
print(item.get("address").get("city"))
# after
from turbohtml.extract import microdata
item = microdata(
'<div itemscope itemtype="https://schema.org/Person">'
'<span itemprop="name">Ada</span>'
'<div itemprop="address" itemscope><span itemprop="city">London</span></div>'
"</div>"
)[0]
print(item.type)
print(item.get("name"))
print(item.get("address").get("city"))
Gotchas and pitfalls¶
item.itemtypeis a list ofURIobjects, read viaitem.itemtype[0].string.typeis the rawitemtypestring, which the WHATWG spec treats as a space-separated token set. Call.type.split()for the list, the formjson()emits.A text property’s value is the element’s
textContentper the spec, so a<script>or<style>nested inside a property element contributes its text;microdatadrops that text. This is the one page inmicrodata’s fixtures where thejson()output differs.get()returns the first value orNoneandget_all()returns the list or[], matchingItem.get/Item.get_all; a property that is itself anitemscopecomes back as a nestedMicrodataItem, not a string.microdata()takes an HTML string and reparses it on each call. To read a URL or file, fetch the text yourself first. When you also needjson_ld()oropengraph()off the same page, hold aturbohtml.parse()document and callmicrodata()so the page is parsed once.