#!/usr/bin/env catnip
# Parsing HTML avec selectolax
# selectolax utilise le moteur Lexbor (C) - 10-30x plus rapide que BeautifulSoup
# Complète httpx : fetch (httpx) → parse (selectolax) → extract (Catnip)
#
# DEPS: selectolax
selectolax = import('selectolax.parser')
# Structs
struct SearchResult {
title; href; score; meta;
display(self) => {
print(f" [{self.score}] {self.title} -> {self.href}")
print(f" {self.meta}")
}
}
struct TableRow {
key; value;
display(self) => { print(f" {self.key} : {self.value}") }
}
# Extraction d'un résultat depuis un noeud HTML
extract_result = (node) => {
title_node = node.css_first('a.title')
SearchResult(
title_node.text(),
title_node.attributes['href'],
node.attributes['data-score'],
node.css_first('span.meta').text(),
)
}
# HTML de test - une page de résultats fictive
HTML = '
<html>
<head><title>Résultats de recherche</title></head>
<body>
<nav class="breadcrumb">
<a href="/">Accueil</a> / <a href="/search">Recherche</a>
</nav>
<h1>3 résultats pour "catnip"</h1>
<ul id="results">
<li class="result" data-score="0.98">
<a href="/doc/intro" class="title">Introduction à Catnip</a>
<span class="meta">Guide - 5 min de lecture</span>
</li>
<li class="result" data-score="0.85">
<a href="/doc/broadcast" class="title">Broadcasting</a>
<span class="meta">Référence - 12 min de lecture</span>
</li>
<li class="result featured" data-score="0.72">
<a href="/doc/patterns" class="title">Pattern Matching</a>
<span class="meta">Tutoriel - 8 min de lecture</span>
</li>
</ul>
<table class="stats">
<tr><th>Métrique</th><th>Valeur</th></tr>
<tr><td>Temps</td><td>0.042s</td></tr>
<tr><td>Index</td><td>12,847 pages</td></tr>
</table>
<footer><p>Généré en 42ms</p></footer>
</body>
</html>
'
tree = selectolax.HTMLParser(HTML)
# Accès direct aux éléments structurels
print("⇒ Éléments structurels")
print(f" <title>: {tree.css_first('title').text()}")
print(f" <h1>: {tree.css_first('h1').text()}")
# Sélecteurs CSS - même syntaxe que les navigateurs
print()
print("⇒ Sélecteurs CSS")
# Par classe
results = tree.css('li.result')
print(f" li.result: {len(results)} éléments")
# Par id
ul = tree.css_first('#results')
print(f" #results tag: {ul.tag}")
# Combiné
featured = tree.css_first('li.featured a.title')
print(f" li.featured a.title: {featured.text()}")
# Extraction de données structurées via broadcasting
print()
print("⇒ Extraction des résultats")
search_results = tree.css('li.result').[(n) => { extract_result(n) }]
search_results.[(r) => { r.display() }]
# Attributs et navigation
print()
print("⇒ Attributs")
links = tree.css('nav.breadcrumb a')
for a in links {
print(f" <a href=\"{a.attributes['href']}\">{a.text()}</a>")
}
# Parsing de table HTML via struct TableRow
print()
print("⇒ Table de stats")
rows = tree.css('table.stats tr')
table_rows = list()
for row in rows {
cells = row.css('td')
if len(cells) == 2 {
table_rows.append(TableRow(cells[0].text(), cells[1].text()))
}
}
table_rows.[(r) => { r.display() }]
# Texte brut (strip tags)
print()
print("⇒ Texte brut du footer")
footer = tree.css_first('footer')
print(f" {footer.text(strip=True)}")
# Combinaison avec httpx (pattern complet)
# fetch = httpx.get("https://example.com")
# tree = selectolax.HTMLParser(fetch.text)
# data = tree.css("article h2").[text()]
print()
print("⇒ Terminé")