
wxpath - rastreo web declarativo con XPath; un Lenguaje de Consulta Web (WQL)
NUEVO: TUI – Interfaz de terminal interactiva (impulsada por Textual) para probar expresiones wxpath y exportar datos.

Requiere Python 3.10+.
pip install wxpath
# For TUI support:
pip install "wxpath[tui]"
# Immediately launch the TUI via uv:
uvx --from "wxpath[tui]" wxpath-tui
wxpath es un rastreador web declarativo donde el recorrido se expresa directamente en XPath. En lugar de escribir bucles de rastreo imperativos, wxpath te permite describir qué seguir y qué extraer en una sola expresión. wxpath ejecuta esa expresión de forma concurrente, en un orden similar a primero en anchura, y transmite los resultados a medida que se descubren.
Esta expresión obtiene una página, extrae enlaces y los transmite concurrentemente, sin necesidad de un bucle de rastreo:
import wxpath
expr = "url('https://quotes.toscrape.com')//a/@href"
for link in wxpath.wxpath_async_blocking_iter(expr):
print(link)
Al introducir el operador url(...) y la sintaxis ///, el motor de wxpath puede realizar rastreo y extracción web recursivos (o paginados):
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)
La mayoría de los raspadores web te obligan a escribir primero el flujo de control del rastreo y luego la extracción.
wxpath unifica esos dos pasos en uno:
Extrae jerarquías JSON limpias y estructuradas directamente del grafo: alimenta tus LLMs con señal, no con ruido. Consulta Integración con LangChain para más detalles.
wxpath es determinista (léase: no impulsado por LLMs). Aunque no podemos garantizar que la red sea estable, podemos garantizar que el recorrido lo es.
La documentación ya está disponible aquí.
url(...) y ///url(...) explicadosimport wxpath
from wxpath.settings import CRAWLER_SETTINGS
# Custom headers for politeness; necessary for some sites (e.g., Wikipedia)
CRAWLER_SETTINGS.headers = {'User-Agent': 'my-app/0.4.0 (contact: [email protected])'}
# Crawl, extract fields, build a knowledge graph
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)
Nota: Algunos sitios (incluyendo Wikipedia) pueden bloquear solicitudes sin las cabeceras adecuadas.
Consulta Avanzado: Configuración del motor y rastreador para establecer un User-Agent personalizado.
La expresión anterior hace lo siguiente:
https://en.wikipedia.org/wiki/Expression_language.<main> que empiezan con /wiki/ y no contienen dos puntos (:).url(...) y ///url(...) explicadosurl(...) es un operador personalizado que obtiene el contenido de la URL especificada por el usuario o generada internamente y lo devuelve como un lxml.html.HtmlElement para su posterior procesamiento con XPath.///url(...) indica un rastreo profundo. Le dice al motor que continúe siguiendo enlaces hasta el max_depth especificado. A diferencia de saltos repetidos con url(), permite que una sola expresión describa una exploración más profunda del grafo. ADVERTENCIA: Úsalo con cuidado y con restricciones (mediante max_depth o predicados XPath) para evitar explosión del recorrido.Consulta DESIGN.md para más detalles sobre el diseño del lenguaje. Allí verás los conceptos centrales y el diseño del lenguaje desde cero.
wxpath evalúa una expresión como una lista de pasos de recorrido y extracción (internamente llamados Segment).
url(...) crea tareas de rastreo, ya sea estáticamente (mediante una URL fija) o dinámicamente (mediante una URL derivada de la expresión XPath). Las URLs se deduplican globalmente, con el mejor esfuerzo, no por profundidad.
Los segmentos XPath operan sobre documentos obtenidos (a través de las operaciones url(...) inmediatamente anteriores).
///url(...) indica rastreo profundo: procede en orden similar a primero en anchura hasta max_depth.
Los resultados se entregan tan pronto como están listos.
wxpath es ante todo asyncio/aiohttp, proporcionando una API asíncrona para rastrear y extraer datos.
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 también proporciona una API asíncrona-dentro-de-síncrona, que permite rastrear varias páginas concurrentemente manteniendo la simplicidad del código síncrono. Esto es especialmente útil para rastreos en entornos estrictamente síncronos (es decir, fuera de un bucle de eventos asyncio) donde el rendimiento es importante.
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 respeta robots.txt por defecto mediante el constructor WXPathEngine(..., robotstxt=True).
La API de Python de wxpath devuelve objetos estructurados.
Dependiendo de la expresión, los resultados pueden incluir:
lxml.* y lxml.html.*elementpath.datatypes.* (para características de XPath 3.1)WxStr (valores de cadena con procedencia)La CLI aplana estos objetos en JSON simple para su visualización. La API de Python conserva la estructura por defecto.
wxpath utiliza la librería elementpath para proporcionar soporte para XPath 3.1, permitiendo características avanzadas de XPath como mapas, arrays y más. Esto permite escribir consultas XPath más potentes.
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 proporciona una barra de progreso (mediante tqdm) para seguir el avance del rastreo. Es especialmente útil para rastreos de larga duración.
Actívala configurando engine.run(..., progress=True), o pasando progress=True a cualquiera de las funciones wxpath_async*(...).
items = wxpath.wxpath_async_blocking("...", progress=True)
> 100%|██████████████████████████████████████████████████████████▎| 469/471 [00:05<00:00, 72.00it/s, depth=2, yielded=457]
wxpath proporciona una interfaz de línea de comandos (CLI) para experimentar rápidamente y ejecutar expresiones wxpath directamente desde el terminal.
El siguiente ejemplo demuestra cómo rastrear Wikipedia comenzando desde la página "Expression language", extraer enlaces a otras páginas wiki y recuperar campos específicos de cada página enlazada.
NOTA: Debido a la naturaleza cambiante del contenido web, la salida puede variar con el tiempo.
> 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}
Opciones de línea de comandos:
--depth <depth> Profundidad máxima de rastreo
--verbose [true|false] Proporciona información superficial de la CLI
--debug [true|false] Proporciona salida detallada del tiempo de ejecución e información
--concurrency <concurrency> Número de descargas concurrentes
--concurrency-per-host <concurrency> Número de descargas concurrentes por host
--header "Key:Value" Añade una cabecera personalizada (ej., 'Key:Value'). Se puede usar varias veces.
--respect-robots [true|false] (Por defecto: True) Respeta robots.txt
--cache [true|false] (Por defecto: False) Persiste los resultados del rastreo en una base de datos local
wxpath proporciona una interfaz de terminal (TUI) para pruebas interactivas de expresiones y extracción de datos.
Consulta Inicio rápido de TUI para más detalles.
wxpath opcionalmente persiste los resultados del rastreo en una base de datos local. Esto es especialmente útil cuando se rastrea un gran número de URLs y se decide pausar el rastreo, cambiar expresiones de extracción, o necesitar reiniciarlo.
wxpath soporta dos motores: sqlite y redis. SQLite es ideal para rastreos a pequeña escala con un solo trabajador (es decir, engine.crawler.concurrency == 1). Redis es ideal para rastreos a gran escala con múltiples trabajadores. Recibirás una advertencia si min(engine.crawler.concurrency, engine.crawler.per_host) > 1 al usar el motor sqlite.
Para usarlo, debes instalar la dependencia opcional correspondiente:
pip install wxpath[cache-sqlite]
pip install wxpath[cache-redis]
Una vez instalada la dependencia, debes habilitar la caché:
from wxpath.settings import SETTINGS
# To enable caching; sqlite is the default
SETTINGS.http.client.cache.enabled = True
# For redis backend
SETTINGS.http.client.cache.enabled = True
SETTINGS.http.client.cache.backend = "redis"
SETTINGS.http.client.cache.redis.address = "redis://localhost:6379/0"
# Run wxpath as usual
items = list(wxpath_async_blocking_iter('...', max_depth=1, engine=engine))
Consulta settings.py para más detalles sobre los ajustes.
wxpath soporta un sistema de hooks conectables que permite modificar el comportamiento de rastreo y extracción. Puedes registrar hooks para preprocesar URLs, postprocesar HTML, filtrar valores extraídos y más. Los hooks se ejecutarán en el orden en que se registren. Los hooks pueden afectar al rendimiento.
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
NOTA: Los hooks pueden ser síncronos o asíncronos, pero todos los hooks en un proyecto deben seguir el mismo estilo. Mezclar hooks síncronos y asíncronos no está soportado y puede causar comportamientos inesperados.
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) es un hook integrado que escribe los datos extraídos en un archivo JSON delimitado por nuevas líneas. Es útil para almacenar resultados en un formato estructurado que puede procesarse fácilmente después.
from wxpath import hooks
hooks.register(hooks.JSONLWriter)
Requiere Python 3.10+.
pip install wxpath
Para persistencia/caché, wxpath soporta los siguientes motores:
pip install wxpath[cache-sqlite]
pip install wxpath[cache-redis]
Consulta EXAMPLES.md para más ejemplos de uso.
Consulta COMPARISONS.md para comparaciones con otras herramientas de raspado web.
Puedes modificar el comportamiento del motor y el rastreador de la siguiente manera:
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])", # Sites like Wikipedia will appreciate this
},
)
# If `crawler` is not specified, a default Crawler will be created with
# the provided concurrency, per_host, and respect_robots values, or with defaults.
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 = FalseTambién puedes usar settings.py para habilitar caché, limitación de velocidad, concurrencia y más.
max_depth.Las siguientes características aún no están soportadas:
Este proyecto está en desarrollo temprano. Los conceptos centrales son estables, pero la API y las características pueden cambiar. Por favor, reporta problemas – en particular, rastreos bloqueados o comportamientos inesperados – y cualquier característica que te gustaría ver (sin garantía de que se implementen).
///) requieren disciplina del usuario para evitar expansión ilimitada (explosión del recorrido).max_depth, y predicados y filtros XPath para limitar el alcance del rastreo.Si deseas ayuda para construir u operar rastreadores / fuentes de datos con wxpath (extracción, planificación, monitoreo, corrección de roturas) u otras necesidades de raspado web, contáctame en: [email protected].
Si te gusta wxpath y deseas apoyar su desarrollo, considera donar.
wxpath sigue semver: <MAJOR>.<MINOR>.<PATCH>.
Sin embargo, antes de 1.0.0 se sigue 0.<MAJOR>.<MINOR|PATCH>.
AGPL-3.0