Repository navigation
Releases
Frostwork 0.2.2
Performance
navigation()no longer takes quadratic time on a document with many unclosed<metatags, e.g.
"<meta " * 80000, which took over 30 seconds in 0.2.1.
Frostwork 0.2.1
Correctness
navigation()no longer reads a<base>or meta refresh written after a self-closed element whose
content browsers read as text, e.g.<script/><base href="…">: unless it is an SVG or MathML
element, browsers ignore the/of<script/>,<style/>,<title/>,<textarea/>,
<noscript/>(with scripting enabled), … and read the markup up to the matching end tag as text.
Performance
navigation()no longer tokenizes a document that cannot hold what was asked for: a<base>for
the base URL, a<meta>with anhttp-equivattribute for the meta refresh.frostwork.encoding.bom_encoding(),codec_name()anddecode()have less call overhead.
Frostwork 0.2.0
Backward-incompatible changes
-
A document with no BOM, label or declaration is no longer assumed to be UTF-8: it is UTF-8 if it is
valid UTF-8, and otherwise its encoding is autodetected, as Firefox does. Pass the response URL as
urltoextract,extract_grouped,detect_encoding,Plan,Page.extractand
frostwork.encoding.autodetect_encodingso that its top-level domain informs the guess (.jp
favours Shift_JIS,.ruwindows-1251, …), as it does in browsers. -
In Rust,
extract,extract_grouped,detect_encoding,Plan::extract,
Page::extract_with_encodingandCompiledPage::extract_with_encodingtake the response URL as a new
url: Option<&str>argument after the encoding. PassNonewhen there is no URL. -
bgsound,embed,keygen,source,trackandwbrare now void elements, matching libxml2
2.15, the oracle Frostwork now targets. Markup after one of them is no longer its child, e.g.
<div><source><span>x</span></div>matchesdiv > spaninstead ofsource span.
Python API and tooling
-
frostwork.encodingexposes each step ofdetect_encoding()(bom_encoding(),
declared_encoding(),autodetect_encoding()), plusdecode(), which decodes as browsers do where
Python's codecs differ (e.g.euc_jp), andcodec_name(), which gives the Python codec name of any
labelresolve_label()resolves. It lets a library that keeps its own encoding API, such as w3lib,
give browser answers. Importing it registers Python codecs forx-user-definedandreplacement.
See PYTHON.md. -
frostwork.navigation()reads the base URL and meta refresh of a document as a browser does, and
Page.extract_with_navigation()reads them in the same pass as the fields; in Rust,navigation()
andPlan::extract_with_navigation(). See
PYTHON.md. -
The refusal reason of a selector with an unsupported pseudo-class or pseudo-element now names it,
e.g.pseudo-class `:hover` is unsupported, also when it sits next to a supported one
(li:nth-child(2):hover). -
frostwork.page.detect_encoding()andfrostwork.page.resolve_label()are deprecated; use
frostwork.detect_encoding()andfrostwork.resolve_label().
Correctness
-
The
<meta>prescan skips other tags together with their attributes, as browsers do, so a
<meta charset>inside an attribute value (<img alt="<meta charset=big5>">) no longer declares the
encoding. -
A selector deferred on an ancestor (
:has(),:contains(), a reverse position such as:last-child
or[last()]) followed by a descendant step that the ancestor itself could match, such as*or its
own tag, no longer returns that ancestor, e.g.li:last-child *returned the<li>as well as its
descendants, and//li[last()]//*/@classitsclass.
Frostwork 0.1.6
Python API and tooling
frostwork.resolve_label(label)returns the WHATWG name of the encoding a charset label names, or
None. It accepts the same labels asextract, Python codec names included (cp874→
windows-874), so a caller can check whether a Python-side encoding such as Scrapy's
response.encodingis the one Frostwork will decode with.
Frostwork 0.1.5
Python API and tooling
-
Selectors can declare their syntax instead of being routed by prefix: pass
syntax="css"or
syntax="xpath"toextract,extract_grouped,checkand everyPagefield,manyandone
(where it covers the container and its sub-fields); in Rust, pass aQuerywherever a selector
string is accepted. Without a declaration the prefix rule is unchanged —/,./and
normalize-space(are XPath, everything else is CSS.Relative XPath is what needs it. Declared as XPath,
h1ortd/text()is refused as unsupported,
like every relative path; read as CSS,h1matches every<h1>instead, which is a wrong value
rather than a refusal.frostwork-auditnow audits each call site in the language of the call it
came from, so a.css()site is explained as CSS and an.xpath()site as XPath. -
Charset labels accept the Python codec names of WHATWG encodings —
cp932,euc_jp,big5hkscs,
cp949,utf-16-leand the rest — which is whatw3lib.encoding.resolve_encoding, and therefore
Scrapy'sresponse.encoding, returns from aContent-Typeheader. Such a label was previously
ignored and the document sniffed instead, so a Shift_JIS page passed ascp932could decode wrongly.
Every Python entry point resolves labels the same way now,Planand the web-poet integration
included. A label naming a real encoding WHATWG has no equivalent for (utf-7,cp437) is still
ignored, the way a browser ignores it, and sniffing continues.
Frostwork 0.1.4
Correctness
- Sibling selectors retain document-level history across multiple HTML roots. Content after
</html>
can now matchhtml + htmlandhtml ~ html, including preceding:has()/:contains()predicates.
The minimized malformed-HTML regression is covered by the rule audit and both language suites. - Python and Rust
Item.get_all()now consistently respect the field declaration: a single-valued
fieldreturns zero or one raw match, whilefield_allandfield_joinreturn every raw match.
Previously, adding an unrelated field could expose additional matches by disabling early exit.
Declarefield_allwhen every match is needed; single-valued schemas retain their early-exit behavior. - The corpus gate now uses the main differential's verdict without discarding empty or whitespace-only
values. Regressions that drop an empty attribute or change value count can no longer pass as
whitespace-only differences. Command-level regression tests cover the verdict, report and exit status. - CSS class and ID selectors now decode literal and hexadecimal escapes, including utility classes
such as.sm\:text-lg. Escape terminators stay part of the identifier instead of becoming descendant
combinators. Non-executable character escapes and ambiguous decoded-backslash escapes are refused throughout
the compiler, including quoted attribute values. - Grouped subfields support immediate-child XPath (
./h2/text()), own attributes (@id), own text
(text()), subtree text/attributes (.//text(),.//@id) and context HTML (.). Child paths are
anchored to the actual container; nested descendants cannot satisfy the first child step. - Grouped extraction and schema audits share input normalization: malformed subfield pairs are
rejected instead of unpacking a string or mapping key into unrelated selectors. Extraction also
accepts subfield mappings directly.Page.many/onerejects malformed cardinality tuples and
non-string separators at declaration time instead of ignoring extra arguments. Pagealways applies a supplied callable transform, even when that callable has a false boolean
value. First, all and joined fields use the same rule.- Saved-response verification uses independent item shaping, so a cardinality bug cannot change both
the engine result and its reference. It preserves escaped CSS whitespace, handles padded XPath
consistently and checks types inside tuple-valued transforms. - Rule-mutation checks propagate failures while restoring both normal builds, including after failed
setup. Release wheels now exercise the expanded public API suite and run a core smoke check before
optional dependencies are installed.
Developer experience
- Documentation starts with reusable
Pageschemas for ordinary Scrapy callbacks and an API chooser.
URL and grouped-item recipes are self-contained and tested with missing values; migration examples
are executed from the docs. The compatibility guide distinguishes selector support from value parity.
Release checks render Markdown and check repository links against heading IDs; Twine skips that step. - Source audits use the grouped compiler for literal
Page.many/oneand web-poetMany/Onefields,
with context-specific diagnostics. Dynamic-only scans report unknown coverage;--require-complete
can fail CI on unresolved sites or empty scans. Full schema audits still check shared budgets.
Group detection follows field modifier chains; callbacks and unknown factories cannot make an
unresolved group look complete. Page.extract_response()uses response bytes and encoding without a Scrapy dependency.
Page.frost_schema()exposes the same named selector format as the web-poet integration.Item.validate()checks required processed values, retained match counts and required group
subfields. Reports distinguish absent matches, empty matches, empty processed values and transform
failures, and can record Scrapy stats.FieldProcessingErroradds field/selector context while
preserving the original exception. An empty row still counts as a matched group container.- The migration workflow now includes complete-item verification on hashed saved responses, an
inspectable fixture manifest, parity-gated timing reports and a manual Linux benchmark workflow.
Historical benchmark ratios are no longer described as a guaranteed performance floor. - Default pytest discovery is scoped to
tests/, so Cargo package snapshots undertarget/cannot
be collected as duplicate test modules.
Performance
- Immediate first-value text and attribute fields stop retaining later matches even when an all-value
field or group requires a complete scan. Deferred predicates, normalization and raw-HTML captures keep
their existing ordering and retention rules. The A/B harness now includes a mixed-cardinality workload;
measurement details and limitations are indocs/BENCHMARKS.md. PageandItemshare stable field definitions instead of copying parallel metadata lists on each
response. Named item access uses direct lookups, and required-field validation uses set membership.
Extending a page preserves earlier items' schemas.Page.one()shapes only the first matched row;
the engine still scans and collects grouped matches as before.
Frostwork 0.1.3
Packaging and documentation
- Includes all 0.1.2 and 0.1.1 changes below. Neither version was uploaded to PyPI: the 0.1.2
pre-upload smoke test exposed locale-dependent file decoding in the release checker on Windows. The
checker now reads repository text explicitly as UTF-8 and can report Unicode diagnostics safely on a
cp1252 console.