Plantillas generales de spiders¶
La mayoría de los rastreos siguen uno de estos dos patrones: seguir enlaces que coincidan con una regla o recorrer todas las URL de un mapa del sitio. Scrapling incluye plantillas para ambos, para que no tengas que repetir la misma lógica de parse() cada vez.
CrawlSpider y SitemapSpider usan LinkExtractor, que extrae URL de un Response o filtra una URL con matches(). SitemapSpider también analiza el contenido de sitemap.xml y sitemap_index.xml, con o sin compresión gzip.
Puedes usar LinkExtractor directamente dentro de cualquier Spider.parse(). Las plantillas solo te ahorran el trabajo de conectar estas piezas.
CrawlSpider¶
CrawlSpider sigue enlaces de forma automática según las reglas que definas.
from scrapling.spiders import CrawlSpider, CrawlRule, LinkExtractor
class QuotesSpider(CrawlSpider):
name = "blog"
start_urls = ["https://quotes.toscrape.com/"]
def rules(self):
return [
CrawlRule(LinkExtractor(allow=r"/author/"), callback=self.parse_author),
CrawlRule(LinkExtractor(allow=r"/page/\d+/")), # follow pagination, no callback
]
async def parse_author(self, response):
yield {
'.author-title': response.css('.author-title::text').get(),
"birthday": response.css('.author-born-date::text').get(),
"url": response.url,
}
result = QuotesSpider().start()
Un CrawlRule combina un LinkExtractor con tres opciones: callback, un método del spider; priority, para cambiar la prioridad del Request generado; y process_request, un método que modifica cada Request antes de entregarlo. El método parse() predeterminado aplica todas las reglas a cada respuesta y entrega un Request por cada URL que coincida.
Si una regla no tiene callback, las URL que coincidan pasan al parse() predeterminado del spider. Esto es útil para la paginación: puedes extraer los enlaces a las siguientes páginas sin crear otro método para procesarlos.
Orden de rastreo¶
Configura crawl_strategy en CrawlSpider o en sus subclases, incluido ContentSpider, para elegir la siguiente solicitud pendiente:
crawl_strategy |
Orden entre solicitudes con el mismo Request.priority |
|---|---|
"bfs" (predeterminado) |
Primero la solicitud que lleva más tiempo en la cola |
"dfs" |
Primero la última solicitud añadida a la cola |
El Scheduler siempre elige primero las solicitudes con mayor Request.priority. La estrategia solo ordena las que tienen la misma prioridad.
Usa concurrent_requests = 1 para procesar una solicitud a la vez en el orden elegido. Se siguen descartando las URL duplicadas: encontrar una URL de nuevo no cambia la posición de su solicitud en la cola. Con varias solicitudes en paralelo, las páginas pueden terminar en otro orden y los niveles o ramas del rastreo pueden mezclarse.
Mantén el mismo crawl_strategy al reanudar desde un punto de guardado.
Combinar reglas con tu propia lógica¶
Redefine parse() y llama a super().parse(response) para mantener las reglas y añadir tus propios resultados:
class MySpider(CrawlSpider):
def rules(self):
return [CrawlRule(LinkExtractor(allow=r"/posts/"), callback=self.parse_post)]
async def parse(self, response):
yield {"page_url": response.url}
async for req in super().parse(response):
yield req
Modificar solicitudes con process_request¶
def add_priority(self, request, response):
request.priority = 10
return request
def rules(self):
return [CrawlRule(
LinkExtractor(allow=r"/posts/"),
callback=self.parse_post,
process_request=self.add_priority,
)]
SitemapSpider¶
SitemapSpider inicia el rastreo con las URL de sitemap.xml. Usa la misma API rules() que CrawlSpider, así que se configura de una forma similar.
from scrapling.spiders import SitemapSpider, CrawlRule, LinkExtractor
class MySitemap(SitemapSpider):
name = "sm"
sitemap_urls = ["https://example.com/sitemap.xml"]
def rules(self):
return [
CrawlRule(LinkExtractor(allow=r"/posts/"), callback=self.parse_post),
CrawlRule(LinkExtractor(allow=r"/products/"), callback=self.parse_product),
]
async def parse_post(self, response):
yield {"title": response.css("h1::text").get()}
async def parse_product(self, response):
yield {"sku": response.css(".sku::text").get()}
result = MySitemap().start()
Cómo se elige el callback de cada URL¶
Para cada URL del mapa del sitio, SitemapSpider comprueba LinkExtractor.matches(url) de cada regla en orden. Usa la primera regla que coincida y entrega un Request con su callback. Si ninguna coincide y rules() contiene reglas, descarta la URL. Si rules() devuelve una lista vacía, todas las URL pasan al método parse() del spider. Debes redefinir ese método, ya que de forma predeterminada lanza NotImplementedError.
Índices de mapas del sitio¶
Cuando SitemapSpider encuentra un <sitemapindex>, que es un mapa de otros mapas del sitio, recorre cada mapa hijo de forma automática. Para elegir cuáles seguir, asigna un LinkExtractor a sitemap_follow:
class MySitemap(SitemapSpider):
name = "sm"
sitemap_urls = ["https://example.com/sitemap.xml"]
sitemap_follow = LinkExtractor(allow=r"/posts-sitemap-\d+\.xml") # only post sitemaps
Soporte para robots.txt¶
Añade una URL de robots.txt directamente a sitemap_urls. SitemapSpider la detectará, extraerá todos los mapas del sitio que indique y seguirá cada uno:
URL en otros idiomas¶
Configura sitemap_alternate_links = True para que las URL de <xhtml:link rel="alternate" hreflang="..."> también pasen por tus reglas.
XMLFeedSpider¶
XMLFeedSpider recorre los nodos de un feed XML, como RSS, Atom o feeds de productos. Asigna a itertag el nombre del nodo que quieres leer, que es "item" de forma predeterminada, y redefine parse_node(). Este método se llama una vez por cada nodo que coincida:
from scrapling.spiders import XMLFeedSpider
class RSSSpider(XMLFeedSpider):
name = "rss"
start_urls = ["https://example.com/feed.xml"]
itertag = "item"
async def parse_node(self, response, node):
yield {
"title": node.findtext("title"),
"link": node.findtext("link"),
"date": node.findtext("pubDate"),
}
result = RSSSpider().start()
Como los demás callbacks, parse_node() también puede entregar objetos Request. Por ejemplo, puedes usar response.follow(node.findtext("link"), callback=self.parse_post) para recorrer las páginas enlazadas desde el feed.
Cómo se buscan y analizan los nodos¶
Cada nodo que recibe parse_node() es un elemento de lxml al que se le han quitado todos los espacios de nombres. Así puedes usar node.findtext("title"), node.find("thumbnail").get("url") y node.xpath(...), que distingue mayúsculas y minúsculas, sin definir mapas de espacios de nombres. Un itertag simple como "entry" busca nodos por nombre, sin importar su espacio de nombres. Esto sirve para Atom y para la mayoría de los feeds que usan espacios de nombres. Para buscar en uno concreto, usa un itertag con prefijo y define ese prefijo en namespaces:
class ThumbnailSpider(XMLFeedSpider):
name = "thumbs"
start_urls = ["https://example.com/feed.xml"]
itertag = "media:thumbnail"
namespaces = (("media", "http://search.yahoo.com/mrss/"),)
async def parse_node(self, response, node):
yield {"thumbnail": node.get("url")}
Los feeds comprimidos con gzip, ya sea con extensión .xml.gz o con un content-type de gzip, se descomprimen de forma automática con las mismas protecciones del spider de mapas del sitio. Si el XML está mal formado, se registra un aviso en lugar de detener el rastreo.
CSVFeedSpider¶
CSVFeedSpider recorre las filas de un feed CSV. Redefine parse_row(), que recibe cada fila como un diccionario cuyas claves son los nombres de las columnas:
from scrapling.spiders import CSVFeedSpider
class PriceSpider(CSVFeedSpider):
name = "prices"
start_urls = ["https://example.com/products.csv"]
async def parse_row(self, response, row):
yield {"product": row["title"], "price": float(row["price"])}
result = PriceSpider().start()
De forma predeterminada, la primera fila contiene los nombres de las columnas. Si el feed no tiene esa fila, indica los nombres con headers. Usa delimiter/quotechar si el archivo no sigue el formato habitual con comas:
class PriceSpider(CSVFeedSpider):
name = "prices"
start_urls = ["https://example.com/products.csv"]
headers = ["title", "price", "url"]
delimiter = ";"
Los feeds con gzip también se descomprimen de forma automática, igual que con XMLFeedSpider.
ContentSpider¶
ContentSpider se basa en CrawlSpider para recorrer un sitio y convertir cada página en Markdown limpio, listo para usar con un LLM. Entrega un elemento por página y, si lo deseas, guarda cada página en su propio archivo Markdown:
from scrapling.spiders import ContentSpider
class DocsSpider(ContentSpider):
name = "docs"
start_urls = ["https://example.com/docs/"]
allowed_domains = {"example.com"}
output_dir = "docs_markdown"
result = DocsSpider().start()
Debes definir allowed_domains para limitar el rastreo al sitio elegido. La página Crear sistemas RAG explica todas las opciones y cómo usar los resultados en un proceso RAG.
Usar LinkExtractor directamente¶
No necesitas usar las plantillas. LinkExtractor funciona dentro de cualquier Spider:
from scrapling.spiders import Spider, LinkExtractor
class CustomSpider(Spider):
name = "custom"
start_urls = ["https://example.com"]
def __init__(self):
super().__init__()
self._links = LinkExtractor(allow=r"/posts/", deny_domains="ads.example.com")
async def parse(self, response):
for url in self._links.extract(response):
yield response.follow(url, callback=self.parse_post)
async def parse_post(self, response):
yield {"title": response.css("h1::text").get()}
Referencia de LinkExtractor¶
| Argumento | Valor predeterminado | Descripción |
|---|---|---|
allow |
() |
Patrones de URL que se permiten. Si está vacío, se permiten todas. Acepta una cadena, un Pattern compilado o un iterable de cualquiera de los dos |
deny |
() |
Patrones de URL que se descartan. Siempre tienen prioridad sobre allow |
allow_domains |
() |
Nombres de host permitidos. Incluye sus subdominios de forma automática: example.com incluye api.example.com |
deny_domains |
() |
Nombres de host que se descartan |
restrict_css |
() |
Selectores CSS que limitan la extracción a una zona del DOM |
restrict_xpath |
() |
Selectores XPath que limitan la extracción a una zona del DOM |
tags |
("a", "area") |
Etiquetas de los elementos en los que se buscan enlaces |
attrs |
("href",) |
Atributos de esas etiquetas de los que se leen las URL |
canonicalize |
True |
Ordena los parámetros de consulta y normaliza la ruta |
strip |
True |
Elimina los espacios en blanco de los extremos de las URL extraídas |
keep_fragment |
False |
Conserva #fragment al normalizar la URL |
deny_extensions |
IGNORED_EXTENSIONS |
Extensiones que se descartan, como pdf, zip, imágenes o videos |
process |
None |
Función opcional que se aplica a cada URL extraída antes de filtrarla. Devuelve un valor falso para descartarla |
LinkExtractor.extract(response) devuelve una list[str] de URL absolutas, filtradas y sin duplicados.
LinkExtractor.matches(url) devuelve un bool. Aplica los filtros de patrones permitidos y excluidos, dominios y extensiones a una URL. SitemapSpider lo usa para elegir las URL sin necesitar un Response.