Skyscraper - HTML scraping with XPath
Rust library to scrape HTML documents with XPath expressions.
This library is major-version 0 as the API is still evolving. See the Supported XPath Features section for details.
HTML Parsing
Skyscraper has its own HTML parser implementation. The parser outputs a tree structure that can be traversed manually with parent/child relationships.
Example: Simple HTML Parsing
use ;
let html_text = r##"
<html>
<body>
<div>Hello world</div>
</body>
</html>"##;
let document = parse?;
Example: Traversing Parent/Child Relationships
// Parse the HTML text into a document
let text = r#"<parent><child/><child/></parent>"#;
let document = parse?;
// Get the children of the root node
let parent_node: DocumentNode = document.root_node;
let children: = parent_node.children.collect;
assert_eq!;
// Get the parent of both child nodes
let parent_of_child0: DocumentNode = children.parent.expect;
let parent_of_child1: DocumentNode = children.parent.expect;
assert_eq!;
assert_eq!;
WHATWG Compliance Note
Skyscraper's HTML parser follows the WHATWG parsing specification. One notable consequence is implicit <tbody> insertion: when <tr>, <td>, or <th> elements appear as direct children of <table>, the parser automatically wraps them in a <tbody> element (per WHATWG §13.2.6.4.9). This matches browser behavior but differs from parsers like Python's lxml, which does not insert <tbody>. As a result, XPath expressions like //table/* or //table//* may return different results than lxml for the same input HTML. To avoid this discrepancy, use explicit <tbody> tags in your HTML or account for the implicit element in your XPath expressions.
XPath Expressions
Skyscraper is capable of parsing XPath strings and applying them to HTML documents.
Below is a basic xpath example. Please see the docs for more examples.
use html;
use ;
use Error;
Supported XPath Features
Below is a non-exhaustive list of all the features that are currently supported.
- Basic xpath steps:
/html/body/div,//div/table//span - Attribute selection:
//div/@class - Text selection:
//div/text() - Wildcard node selection:
//body/* - Predicates:
- Attributes:
//div[@class='hi'] - Indexing:
//div[1] - Arbitrary expressions:
//div[contains(@class, 'hi')]
- Attributes:
- Forward axes:
child::,descendant::,attribute::,self::,descendant-or-self::,following-sibling::,following::,namespace:: - Reverse axes:
parent::,ancestor::,preceding-sibling::,preceding::,ancestor-or-self:: - Operators:
- Logical:
and,or - Comparison:
=,!=,<,>,<=,>=,eq,ne,lt,gt,le,ge - Arithmetic:
+,-,*,div,idiv,mod - String concatenation:
|| - Sequence:
union/|,intersect,except,to - Simple map:
! - Arrow:
=> - Node comparison:
is,<<,>>
- Logical:
- Expressions:
if/then/else,for,let,some/every(quantified) - Type expressions:
instance of,cast as,castable as,treat as - Functions (100+): string (
contains,starts-with,ends-with,substring,concat,normalize-space,upper-case,lower-case,translate,matches,replace,tokenize, ...), numeric (round,floor,ceiling,abs,sum,avg,min,max, ...), boolean (not,true,false,boolean), sequence (count,empty,exists,reverse,sort,distinct-values,head,tail,subsequence, ...), node (name,local-name,root,path,has-children,data, ...), higher-order (for-each,filter,fold-left,fold-right, ...), and more - Maps and arrays construction and access
If your use case requires an unimplemented feature, please open an issue on GitHub.
See docs/features-backlog.md for a detailed list of spec gaps, known limitations, and design decisions.