
wxpath - deklaratives Web-Crawling mit XPath; eine Webabfragesprache (WQL)
NEU: TUI – Interaktive Terminal-Oberfläche (angetrieben von Textual) zum Testen von wxpath-Ausdrücken und Exportieren von Daten.

Erfordert Python 3.10+.
pip install wxpath
# Für TUI-Unterstützung:
pip install "wxpath[tui]"
# TUI sofort via uv starten:
uvx --from "wxpath[tui]" wxpath-tui
wxpath ist ein deklarativer Web-Crawler, bei dem die Traversierung direkt in XPath ausgedrückt wird. Anstatt imperative Crawl-Schleifen zu schreiben, ermöglicht wxpath dir in einem einzigen Ausdruck zu beschreiben, was verfolgt und was extrahiert werden soll. wxpath führt diesen Ausdruck nebenläufig, breitenstufen-ähnlich aus und streamt die Ergebnisse, sobald sie entdeckt werden.
Dieser Ausdruck ruft eine Seite ab, extrahiert Links und streamt sie nebenläufig – keine Crawl-Schleife erforderlich:
import wxpath
expr = "url('https://quotes.toscrape.com')//a/@href"
for link in wxpath.wxpath_async_blocking_iter(expr):
print(link)
Durch die Einführung des url(...)-Operators und der ///-Syntax ist die wxpath-Engine in der Lage, rekursive (oder paginierte) Web-Crawling- und Extraktionsdurchläufe durchzuführen:
import wxpath
path_expr = """
url('https://quotes.toscrape.com')
///url(//a/@href)
//a/@href
"""
for item in wxpath.wxpath_async_blocking_iter(path_expr, max_depth=1):
print(item)
Die meisten Web-Scraper zwingen dich, zuerst die Crawl-Steuerung zu schreiben und dann die Extraktion.
wxpath vereint diese beiden Schritte in einem:
Extrahiere saubere, strukturierte JSON-Hierarchien direkt aus dem Graphen – füttere deine LLMs mit Signal, nicht mit Rauschen. Siehe LangChain-Integration für weitere Details.
wxpath ist deterministisch (lies: nicht von LLMs angetrieben). Während wir keine Garantie für die Netzwerkstabilität geben können, können wir die Traversierung garantieren.
Die Dokumentation ist jetzt hier verfügbar.
url(...) und ///url(...) erklärtimport wxpath
from wxpath.settings import CRAWLER_SETTINGS
# Benutzerdefinierte Header für Höflichkeit; für einige Seiten notwendig (z.B. Wikipedia)
CRAWLER_SETTINGS.headers = {'User-Agent': 'my-app/0.4.0 (contact: [email protected])'}
# Crawlen, Felder extrahieren, einen Wissensgraphen aufbauen
path_expr = """
url('https://en.wikipedia.org/wiki/Expression_language')
///url(
//main//a/@href[
starts-with(., '/wiki/') and not(contains(., ':'))
]
)
/map{
'title': (//span[contains(@class, "mw-page-title-main")]/text())[1] ! string(.),
'url': string(base-uri(.)),
'short_description': //div[contains(@class, 'shortdescription')]/text() ! string(.),
'forward_links': //div[@id="mw-content-text"]//a/@href ! string(.)
}
"""
for item in wxpath.wxpath_async_blocking_iter(path_expr, max_depth=1):
print(item)
Hinweis: Einige Seiten (einschließlich Wikipedia) können Anfragen ohne passende Header blockieren.
Siehe Fortgeschritten: Engine- & Crawler-Konfiguration zum Setzen eines benutzerdefinierten User-Agent.
Der obige Ausdruck führt Folgendes aus:
https://en.wikipedia.org/wiki/Expression_language.<main>, die mit /wiki/ beginnen und keinen Doppelpunkt (:) enthalten.url(...) und ///url(...) erklärturl(...) ist ein benutzerdefinierter Operator, der den Inhalt der benutzer-spezifizierten oder intern generierten URL abruft und als lxml.html.HtmlElement für die weitere XPath-Verarbeitung zurückgibt.///url(...) zeigt ein tiefes Crawlen an. Es teilt der Laufzeit-Engine mit, den Links bis zur angegebenen max_depth weiter zu folgen. Im Gegensatz zu wiederholten url()-Springen ermöglicht es einem einzelnen Ausdruck, eine tiefere Grapherkundung zu beschreiben. WARNUNG: Mit Vorsicht und Einschränkungen (via max_depth oder XPath-Prädikaten) verwenden, um eine Traversierungsexplosion zu vermeiden.Siehe DESIGN.md für Details zum Sprachdesign. Dort siehst du die Kernkonzepte und das Design der Sprache von Grund auf.
wxpath wertet einen Ausdruck als Liste von Traversierungs- und Extraktionsschritten (intern als Segments bezeichnet) aus.
url(...) erzeugt Crawl-Aufgaben entweder statisch (über eine feste URL) oder dynamisch (über eine URL, die aus dem XPath-Ausdruck stammt). URLs werden global, auf Best-Effort-Basis dedupliziert – nicht pro Tiefe.
XPath-Segmente arbeiten auf abgerufenen Dokumenten (die über die unmittelbar vorhergehenden url(...)-Operationen abgerufen wurden).
///url(...) zeigt das tiefe Crawlen an – es verläuft breitenstufen-ähnlich bis zu max_depth.
Ergebnisse werden ausgegeben, sobald sie bereit sind.
wxpath ist asyncio/aiohttp-first und bietet eine asynchrone API zum Crawlen und Extrahieren von Daten.
import asyncio
from wxpath import wxpath_async
items = []
async def main():
path_expr = "url('https://en.wikipedia.org/wiki/Expression_language')///url(//@href[starts-with(., '/wiki/')])//a/@href"
async for item in wxpath_async(path_expr, max_depth=1):
items.append(item)
asyncio.run(main())
wxpath bietet auch eine Sync-API für asyncio, die es dir ermöglicht, mehrere Seiten nebenläufig zu crawlen und dabei die Einfachheit von synchronem Code beizubehalten. Dies ist besonders nützlich für Crawls in strikt synchronen Ausführungsumgebungen (d. h. nicht innerhalb einer asyncio-Event-Schleife), bei denen die Leistung eine Rolle spielt.
from wxpath import wxpath_async_blocking_iter
path_expr = "url('https://en.wikipedia.org/wiki/Expression_language')///url(//@href[starts-with(., '/wiki/')])//a/@href"
items = list(wxpath_async_blocking_iter(path_expr, max_depth=1))
wxpath respektiert standardmäßig robots.txt über den Konstruktor WXPathEngine(..., robotstxt=True).
Die wxpath-Python-API liefert strukturierte Objekte.
Je nach Ausdruck können Ergebnisse Folgendes enthalten:
lxml.*- und lxml.html.*-Objekteelementpath.datatypes.*-Objekte (für XPath 3.1-Funktionen)WxStr (Zeichenkettenwerte mit Herkunft)Das CLI flacht diese Objekte zur Anzeige in einfaches JSON ab. Die Python-API erhält standardmäßig die Struktur.
wxpath verwendet die elementpath-Bibliothek, um XPath 3.1 zu unterstützen und ermöglicht so erweiterte XPath-Funktionen wie maps, arrays und mehr. Damit kannst du noch mächtigere XPath-Abfragen schreiben.
path_expr = """
url('https://en.wikipedia.org/wiki/Expression_language')
///url(//div[@id='mw-content-text']//a/@href)
/map{
'title':(//span[contains(@class, "mw-page-title-main")]/text())[1],
'short_description':(//div[contains(@class, "shortdescription")]/text())[1],
'url'://link[@rel='canonical']/@href[1]
}
"""
# [...
# {'title': 'Computer language',
# 'short_description': 'Formal language for communicating with a computer',
# 'url': 'https://en.wikipedia.org/wiki/Computer_language'},
# {'title': 'Machine-readable medium and data',
# 'short_description': 'Medium capable of storing data in a format readable by a machine',
# 'url': 'https://en.wikipedia.org/wiki/Machine-readable_medium_and_data'},
# {'title': 'Domain knowledge',
# 'short_description': 'Specialist knowledge within a specific field',
# 'url': 'https://en.wikipedia.org/wiki/Domain_knowledge'},
# ...]
wxpath bietet einen Fortschrittsbalken (via tqdm), um den Crawl-Fortschritt zu verfolgen. Dies ist besonders nützlich für langlebige Crawls.
Aktiviere ihn, indem du engine.run(..., progress=True) setzt, oder übergib progress=True an eine der wxpath_async*(...)-Funktionen.
items = wxpath.wxpath_async_blocking("...", progress=True)
> 100%|██████████████████████████████████████████████████████████▎| 469/471 [00:05<00:00, 72.00it/s, depth=2, yielded=457]
wxpath bietet eine Befehlszeilenschnittstelle (CLI), um wxpath-Ausdrücke schnell über das Terminal zu testen und auszuführen.
Das folgende Beispiel zeigt, wie man Wikipedia ausgehend von der Seite "Expression language" durchsucht, Links zu anderen Wiki-Seiten extrahiert und bestimmte Felder von jeder verlinkten Seite abruft.
HINWEIS: Aufgrund der sich ständig ändernden Art von Webinhalten kann die Ausgabe im Laufe der Zeit variieren.
> wxpath --depth 1 \
--header "User-Agent: my-app/0.1 (contact: [email protected])" \
"url('https://en.wikipedia.org/wiki/Expression_language') \
///url(//div[@id='mw-content-text']//a/@href[starts-with(., '/wiki/') \
and not(matches(@href, '^(?:/wiki/)?(?:Wikipedia|File|Template|Special|Template_talk|Help):'))]) \
/map{ \
'title':(//span[contains(@class, 'mw-page-title-main')]/text())[1], \
'short_description':(//div[contains(@class, 'shortdescription')]/text())[1], \
'url':string(base-uri(.)), \
'backlink':wx:backlink(.), \
'depth':wx:depth(.) \
}"
{"title": "Computer language", "short_description": "Formal language for communicating with a computer", "url": "https://en.wikipedia.org/wiki/Computer_language", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Machine-readable medium and data", "short_description": "Medium capable of storing data in a format readable by a machine", "url": "https://en.wikipedia.org/wiki/Machine_readable", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Domain knowledge", "short_description": "Specialist knowledge within a specific field", "url": "https://en.wikipedia.org/wiki/Domain_knowledge", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Advanced Boolean Expression Language", "short_description": "Hardware description language and software", "url": "https://en.wikipedia.org/wiki/Advanced_Boolean_Expression_Language", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Data Analysis Expressions", "short_description": "Formula and data query language", "url": "https://en.wikipedia.org/wiki/Data_Analysis_Expressions", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Jakarta Expression Language", "short_description": "Computer programming language", "url": "https://en.wikipedia.org/wiki/Jakarta_Expression_Language", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Rights Expression Language", "short_description": [], "url": "https://en.wikipedia.org/wiki/Rights_Expression_Language", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
{"title": "Computer science", "short_description": "Study of computation", "url": "https://en.wikipedia.org/wiki/Computer_science", "backlink": "https://en.wikipedia.org/wiki/Expression_language", "depth": 1.0}
Kommandozeilenoptionen:
--depth <Tiefe> Maximale Crawl-Tiefe
--verbose [true|false] Liefert oberflächliche CLI-Informationen
--debug [true|false] Liefert ausführliche Laufzeitausgabe und Informationen
--concurrency <Nebenläufigkeit> Anzahl gleichzeitiger Abrufe
--concurrency-per-host <Nebenläufigkeit> Anzahl gleichzeitiger Abrufe pro Host
--header "Key:Value" Benutzerdefinierten Header hinzufügen (z.B. 'Key:Value'). Kann mehrfach verwendet werden.
--respect-robots [true|false] (Standard: True) Respektiert robots.txt
--cache [true|false] (Standard: False) Crawl-Ergebnisse in einer lokalen Datenbank speichern
wxpath bietet eine Terminal-Oberfläche (TUI) zum interaktiven Testen von Ausdrücken und Extrahieren von Daten.
Siehe TUI-Schnellstart für weitere Details.
wxpath kann Crawl-Ergebnisse optional in einer lokalen Datenbank speichern. Dies ist besonders nützlich, wenn du eine große Anzahl von URLs durchsuchst und den Crawl pausieren, Extraktionsausdrücke ändern oder den Crawl neu starten musst.
wxpath unterstützt zwei Backends: SQLite und Redis. SQLite eignet sich gut für kleine Crawls mit einem einzelnen Worker (d.h. engine.crawler.concurrency == 1). Redis eignet sich hervorragend für große Crawls mit mehreren Workern. Du erhältst eine Warnung, wenn min(engine.crawler.concurrency, engine.crawler.per_host) > 1 bei Verwendung des SQLite-Backends ist.
Zur Nutzung musst du die entsprechende optionale Abhängigkeit installieren:
pip install wxpath[cache-sqlite]
pip install wxpath[cache-redis]
Sobald die Abhängigkeit installiert ist, musst du den Cache aktivieren:
from wxpath.settings import SETTINGS
# Um Caching zu aktivieren; SQLite ist die Voreinstellung
SETTINGS.http.client.cache.enabled = True
# Für Redis-Backend
SETTINGS.http.client.cache.enabled = True
SETTINGS.http.client.cache.backend = "redis"
SETTINGS.http.client.cache.redis.address = "redis://localhost:6379/0"
# wxpath wie gewohnt ausführen
items = list(wxpath_async_blocking_iter('...', max_depth=1, engine=engine))
Details zu den Einstellungen findest du in settings.py.
wxpath unterstützt ein erweiterbares Hook-System, mit dem du das Crawling- und Extraktionsverhalten anpassen kannst. Du kannst Hooks registrieren, um URLs vorzuverarbeiten, HTML nachzuverarbeiten, extrahierte Werte zu filtern und mehr. Hooks werden in der Reihenfolge ihrer Registrierung ausgeführt. Hooks können die Leistung beeinträchtigen.
from wxpath import hooks
@hooks.register
class OnlyEnglish:
def post_parse(self, ctx, elem):
lang = elem.xpath('string(/html/@lang)').lower()[:2]
return elem if lang in ("en", "") else None
HINWEIS: Hooks können synchron oder asynchron sein, aber alle Hooks in einem Projekt sollten dem gleichen Stil folgen. Das Mischen von synchronen und asynchronen Hooks wird nicht unterstützt und kann zu unerwartetem Verhalten führen.
from wxpath import hooks
@hooks.register
class OnlyEnglish:
async def post_parse(self, ctx, elem):
lang = elem.xpath('string(/html/@lang)').lower()[:2]
return elem if lang in ("en", "") else None
JSONLWriter (alias NDJSONWriter) ist ein eingebauter Hook, der extrahierte Daten in eine zeilengetrennte JSON-Datei schreibt. Dies ist nützlich, um Ergebnisse in einem strukturierten Format zu speichern, das später einfach verarbeitet werden kann.
from wxpath import hooks
hooks.register(hooks.JSONLWriter)
Erfordert Python 3.10+.
pip install wxpath
Für persistierte/gecachte Versionen unterstützt wxpath die folgenden Backends:
pip install wxpath[cache-sqlite]
pip install wxpath[cache-redis]
Siehe EXAMPLES.md für weitere Anwendungsbeispiele.
Siehe COMPARISONS.md für Vergleiche mit anderen Web-Scraping-Werkzeugen.
Du kannst das Verhalten von Engine und Crawler wie folgt ändern:
from wxpath import wxpath_async_blocking_iter
from wxpath.core.runtime import WXPathEngine
from wxpath.http.client.crawler import Crawler
crawler = Crawler(
concurrency=8,
per_host=2,
timeout=10,
respect_robots=False,
headers={
"User-Agent": "my-app/0.1.0 (contact: [email protected])", # Seiten wie Wikipedia werden das zu schätzen wissen
},
)
# Wenn `crawler` nicht angegeben ist, wird ein Standard-Crawler erstellt mit
# den angegebenen Werten für concurrency, per_host und respect_robots, oder mit den Standardwerten.
engine = WXPathEngine(
# concurrency: int = 16,
# per_host: int = 8,
# respect_robots: bool = True,
# allowed_response_codes: set[int] = {200},
# allow_redirects: bool = True,
crawler=crawler,
)
path_expr = "url('https://en.wikipedia.org/wiki/Expression_language')//url(//main//a/@href)"
items = list(wxpath_async_blocking_iter(path_expr, max_depth=1, engine=engine))
wxpath_async*)max_depth: int = 1progress: bool = Falseengine: WXPathEngine | None = Noneyield_errors: bool = FalseDu kannst auch settings.py verwenden, um Caching, Drosselung, Nebenläufigkeit und mehr zu aktivieren.
max_depth erreicht ist.Die folgenden Funktionen werden noch nicht unterstützt:
Dieses Projekt befindet sich in der frühen Entwicklung. Die Kernkonzepte sind stabil, aber die API und die Funktionen können sich ändern. Bitte melde Probleme – insbesondere Blockaden von Crawls oder unerwartetes Verhalten – und alle Funktionen, die du dir wünschst (keine Garantie, dass sie implementiert werden).
///) erfordern Benutzerdisziplin, um unbegrenzte Expansion (Traversierungsexplosion) zu vermeiden.max_depth sowie XPath-Prädikaten und -Filtern, um den Crawl-Umfang zu begrenzen.Wenn du Hilfe beim Aufbau oder Betrieb von Crawlern/Datenfeeds mit wxpath (Extraktion, Planung, Überwachung, Fehlerbehebung) oder anderen Web-Scraping-Bedürfnissen benötigst, kontaktiere mich bitte unter: [email protected].
Wenn dir wxpath gefällt und du seine Entwicklung unterstützen möchtest, erwäge bitte eine Spende.
wxpath folgt semver: <MAJOR>.<MINOR>.<PATCH>.
Allerdings folgt die Version vor 1.0.0 0.<MAJOR>.<MINOR|PATCH>.
AGPL-3.0