Volver al blog

Construye un Rastreador de Precios de Vuelos con la API de Scraping Sin Estrés

Sophia Martinez
Sophia Martinez

Specialist in Anti-Bot Strategies

14-Jul-2026

Resumen:

  • Un seguimiento de vuelos es una ruta y una fecha, no una URL. Las tarifas aéreas no tienen una única página canónica como un SKU minorista; el mismo viaje es una combinación de departure_id, arrival_id, outbound_date y (para un viaje redondo) return_date. El pipeline a continuación consulta esa combinación directamente a través del actor scraper.google.flights de Scrapeless Scraping API, en lugar de renderizar y raspar la propia interfaz de Google Flights.
  • El tipo de viaje es un parámetro de primera clase, no una variante de URL. data_type cambia la misma solicitud entre un solo sentido, viaje redondo y multi-ciudad, y multi-ciudad toma su propio array de piernas multi_city_json. Un seguimiento de precios minoristas nunca necesita esta dimensión; un seguimiento de vuelos sí la necesita desde el primer día.
  • Google Flights envía su propia señal de volatilidad. El objeto price_insights de la respuesta lleva lowest_price y una clasificación de price_level (bajo, típico, alto) junto a las ofertas individuales en best_flights y other_flights. Eso permite que el pipeline envíe alertas sobre "esta es una buena tarifa en este momento", no solo "esto es más barato que la última vez".
  • La historia se organiza por ruta y fecha, agregada una vez por verificación. Un registro JSONL con un registro por cada combinación de (departure_id, arrival_id, outbound_date, return_date) es suficiente para calcular un mínimo móvil y detectar una caída; no se requiere base de datos hasta que la lista de seguimiento crezca más allá de un puñado de rutas.
  • Una caída, o un cambio de price_level a bajo, activa un webhook. La decisión de alerta combina la comparación numérica con la propia señal cualitativa de Google y luego publica una vez en el endpoint que la recibe.
  • Un programador ejecuta el ciclo; nada de eso necesita estar en funcionamiento. Cron, Task Scheduler o un temporizador sin servidor ejecuta la verificación a intervalos y sale; el pipeline no lleva ningún proceso de larga duración.
  • Gratis para empezar. Las nuevas cuentas de Scrapeless incluyen créditos gratuitos de Scraping API; regístrate en app.scrapeless.com.

Introducción: una tarifa es un objetivo en movimiento con dos dimensiones, no una

Un precio minorista es un único número asociado a una única página. Una tarifa de vuelo es una función de una ruta y una fecha, y la misma ruta puede tener precios diferentes para cada fecha de salida dentro de la misma búsqueda. Esa es la razón por la que existe la función de seguimiento de precios de vuelos de Google: las aerolíneas reincorporan continuamente el inventario de asientos contra la demanda, y un comprador que chequea una vez y reserva está adivinando sobre el tiempo. El rastreador de Google envía un resumen cuando la ruta rastreada se mueve; no le entrega al lector datos en bruto, un webhook o una manera de combinar esa señal con cualquier otra cosa que el lector ya esté ejecutando.

Esta publicación construye un pequeño pipeline que realiza ese trabajo programáticamente, sobre una ruta específica y una ventana de fechas en lugar de la interfaz de Google Flights. La API de Scraping de Scrapeless envía un actor diseñado específicamente para esta superficie — scraper.google.flights — que acepta una combinación de ruta y fecha y devuelve datos estructurados de tarifas: ofertas individuales, una aerolínea y duración por oferta, y la propia lectura de nivel de precio de Google sobre la tarifa. El pipeline consulta a ese actor, extrae las señales de tarifas, las agrega a un registro histórico organizado por ruta y fecha, decide si la lectura vale la pena una alerta y activa un webhook cuando lo es. Un programador ejecuta la verificación a intervalos.

El actor devolvió una respuesta de disabled actor en vivo para la cuenta utilizada para investigar esta publicación; una brecha en el plan de nivel, no un pipeline roto, y esa brecha está documentada en el punto que importa en lugar de ser ignorada. Cada otra etapa a continuación se prueba contra Python real y ejecutado.

Lo Que Puedes Hacer Con Esto

  • Seguimientos de tarifas personales. Rastrea una ruta específica y una ventana de fechas y recibe notificaciones solo cuando el precio realmente se mueve en una dirección útil.
  • Compras con fechas flexibles. Ejecuta la misma ruta a través de un rango de fechas candidatas y compara price_insights.price_level a través del rango para encontrar la ventana más barata, no solo la fecha única más barata.
  • Monitoreo de viajes corporativos. Un escritorio de viajes puede supervisar rutas recurrentes de viaje o de clientes y señalar cuando la ruta de un itinerario reservado ha caído, lo que provoca una nueva reserva.
  • Seguimiento de itinerarios multi-ciudad. multi_city_json permite que la misma solicitud represente un viaje de múltiples tramos, por lo que un itinerario con tres o cuatro segmentos es una sola llamada en lugar de tres o cuatro seguimientos de un solo sentido separados cosidos a mano.
  • Conjuntos de datos de historial de tarifas. El registro de solo anexar también funciona como una serie temporal por ruta, útil para análisis de tendencias posteriores o para alimentar una heurística de "reserva ahora o espera".

Por Qué Scrapeless Scraping API para Esto

Scrapeless Scraping API expone actores específicos del sitio — scraper.<site> — que devuelven JSON estructurado para un objetivo dado en lugar de una página renderizada que un llamador tiene que analizar. Para datos de vuelos específicamente, eso significa:

  • Una solicitud de ruta y fecha, no una sesión de navegador. La llamada es un POST autenticado con un cuerpo JSON; no hay página que renderizar, ningún selector que anclar y ninguna sesión que mantener activa.
  • Tipo de viaje integrado en la forma de la solicitud. data_type cubre viajes de ida (2), viajes de ida y vuelta (1), y múltiples ciudades (3, con multi_city_json) como parámetros documentados, no como lógica de raspado separada por caso.
  • Una clasificación de precios que el propio sitio objetivo calcula. price_insights.price_level es el propio juicio de Google sobre si la tarifa es baja, típica o alta para la ruta — la canalización puede apoyarse en eso en lugar de adivinar un umbral desde cero.
  • Una solicitud en lugar de una pila de distribución. Los datos de tarifas aéreas llegan a un llamador a través de una mezcla de GDS y alimentaciones directas de aerolíneas — el tipo de distribución fragmentada para la que existe el estándar de Nueva Capacidad de Distribución de IATA para estandarizar en el lado de la industria aérea. Una única llamada de actor estructurada elude esa pila por completo para un seguimiento de precios de solo lectura.

Obtén una clave API de plan gratuito en app.scrapeless.com. La página del producto API de Raspado cubre la familia de actores que esta canalización llama, y la forma de la solicitud para este actor específico se describe en la guía de solicitudes de la API de Google Flights.

Requisitos Previos

  • Python 3.10 o superior, además del paquete requests (pip install requests).
  • Una cuenta de Scrapeless y una clave API, leídas de la variable de entorno SCRAPELESS_API_KEY. Confirma que el actor scraper.google.flights esté habilitado para el plan de la cuenta antes de dirigir tráfico de producción hacia él — consulta la nota bajo Fetch, a continuación.
  • Un endpoint de webhook para recibir alertas (Slack, Discord, o cualquier relé que acepte un POST JSON).
  • Un host capaz de cron, el Programador de tareas de Windows, o un programador sin servidor para ejecutar la verificación de forma periódica.

Canalización de un Vistazo

La canalización consta de seis etapas conectadas en línea recta: fetch → discover → extract → transform → store → decide → alert, con un programador impulsando todo esto de forma periódica.

  1. Fetch — POST una ruta, una fecha (o ventana de fechas), y un tipo de viaje a scraper.google.flights.
  2. Discover — leer el sobre del tipo de viaje: cuál de best_flights, other_flights y price_insights fue realmente poblado por la respuesta.
  3. Extract — obtener el precio de la oferta más barata y la clasificación price_level de Google de ese sobre.
  4. Transform — normalizar la lectura en un único registro canónico clave por ruta y fecha.
  5. Store — agregar el registro a un registro de historial.
  6. Decide y alert — comparar con el historial y activar un webhook cuando la lectura valga la pena resaltar.

Fetch: Consultar una Ruta y una Ventana de Fechas

La solicitud es un único POST autenticado. El nombre del actor va en actor; los parámetros de ruta y fecha van en input. data_type es 1 para un viaje de ida y vuelta y 2 para un viaje de ida; un viaje de ida y vuelta también lleva return_date.

python Copy
import os
import requests

API_URL = "https://api.scrapeless.com/api/v1/scraper/request"


def fetch_route(departure_id: str, arrival_id: str, outbound_date: str,
                 return_date: str | None = None, gl: str = "us", hl: str = "en") -> dict:
    payload = {
        "actor": "scraper.google.flights",
        "input": {
            "departure_id": departure_id,
            "arrival_id": arrival_id,
            "data_type": 1 if return_date else 2,   # 1 = viaje de ida y vuelta, 2 = viaje de ida
            "outbound_date": outbound_date,
            "gl": gl,
            "hl": hl,
        },
    }
    if return_date:
        payload["input"]["return_date"] = return_date

    headers = {"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]}
    response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
    response.raise_for_status()
    return response.json()

outbound_date y return_date siguen la forma de calendario sencilla YYYY-MM-DD definida por RFC 3339 para el intercambio de fecha y hora en internet — sin componente horario, ya que una búsqueda de tarifas se vincula a un día de salida, no a un instante de salida. Un itinerario de múltiples ciudades reemplaza los campos de un solo tramo con data_type: 3 y un array multi_city_json, un objeto por tramo, cada uno con su propio departure_id, arrival_id, y date.
Brecha de requisitos previos: ejecutar la llamada anterior contra una ruta real devolvió un HTTP 400 en vivo para la cuenta utilizada para verificar esta tubería: {"code": 14002, "message": "actor deshabilitado: scraper.google.flights"}. Ese mensaje es distinto del error que devuelve un nombre de actor no reconocido ("actor no válido: <nombre>" — confirmado al solicitar deliberadamente algunas variantes inventadas), lo que significa que el actor en sí es real y está catalogado; está restringido del plan gratuito utilizado aquí en lugar de estar ausente. La mecánica de solicitud subyacente de la cuenta no está en duda: el actor hermano scraper.google.search devolvió un HTTP 200 real con resultados orgánicos en la misma clave y el mismo punto final durante esta investigación. Confirme que el actor está habilitado para el plan objetivo (GET /api/v1/me informa sobre el plan y el balance de crédito) antes de generar tráfico de producción en él, y trate todo desde Discover en adelante como si estuviera ejecutándose contra la forma de respuesta documentada del actor en lugar de una página capturada en vivo en esta sesión.

Descubrir: Leer el sobre del tipo de viaje

Una búsqueda de ida y vuelta o de solo ida devuelve dos arreglos de ofertas — best_flights y other_flights — además de un objeto price_insights. Una búsqueda de múltiples ciudades devuelve la estructura equivalente por itinerario en lugar de por pierna única. Antes de extraer cualquier cosa, la tubería solo necesita saber cuál de esas claves pobló realmente la respuesta, ya que una ruta estrecha o una fecha extraña pueden regresar con other_flights vacío y solo best_flights llevando ofertas.

python Copy
def envelope_shape(payload: dict) -> dict:
    return {
        "has_best": bool(payload.get("best_flights")),
        "has_other": bool(payload.get("other_flights")),
        "has_insights": bool(payload.get("price_insights")),
    }


if __name__ == "__main__":
    sample = {"best_flights": [{"price": 312}], "other_flights": [], "price_insights": {}}
    print(envelope_shape(sample))

Ejecutado contra una respuesta estrecha que solo pobló best_flights, envelope_shape devuelve {'has_best': True, 'has_other': False, 'has_insights': False} — exactamente el caso que la etapa de extracción a continuación debe tolerar.

Esta es una verificación de cordura de una línea, pero es importante: extraer una "oferta más barata" de un sobre que nunca pobló best_flights produce un None en lugar de un número incorrecto, siempre que la etapa de extracción verifique ambos arreglos en lugar de suponer que uno está siempre presente.

Extraer: Sacar las señales de tarifas de la respuesta

La etapa de extracción extrae la oferta más barata de ambos arreglos y los campos de price_insights junto a ellos. La instalación a continuación refleja la forma de respuesta documentada para esta familia de actores — best_flights y other_flights como arreglos de {flights: [...], price, layovers} objetos, y price_insights llevando lowest_price, price_level, y un typical_price_range — ya que la llamada en vivo es la brecha anotada anteriormente en lugar de una página capturada en esta sesión.

python Copy
# Instalación ilustrativa que coincide con la forma de respuesta documentada (ver el
# nota de brecha previa bajo Fetch) — no es una captura en vivo.
FIXTURE_RESPONSE = {
    "best_flights": [
        {
            "flights": [{
                "departure_airport": {"id": "JFK", "time": "2026-08-10 08:00"},
                "arrival_airport": {"id": "LAX", "time": "2026-08-10 11:24"},
                "airline": "Example Air",
                "flight_number": "EX 101",
                "duration": 384,
                "travel_class": "Economy",
            }],
            "price": 312,
            "layovers": 0,
        }
    ],
    "other_flights": [
        {"flights": [{"airline": "Sample Airways", "flight_number": "SA 220"}], "price": 349, "layovers": 1}
    ],
    "price_insights": {
        "lowest_price": 289,
        "price_level": "low",
        "typical_price_range": [295, 430],
    },
}


def extract_fares(payload: dict) -> dict:
    best = payload.get("best_flights", [])
    other = payload.get("other_flights", [])
    insights = payload.get("price_insights", {})

    cheapest_offer = min([*best, *other], key=lambda offer: offer["price"], default=None)

    return {
        "cheapest_price": cheapest_offer["price"] if cheapest_offer else None,
        "cheapest_layovers": cheapest_offer["layovers"] if cheapest_offer else None,
        "offer_count": len(best) + len(other),
        "price_insights_lowest": insights.get("lowest_price"),
        "price_level": insights.get("price_level"),
        "typical_price_range": insights.get("typical_price_range"),
    }


if __name__ == "__main__":
    print(extract_fares(FIXTURE_RESPONSE))

Ejecutado contra la instalación, extract_fares devuelve:

text Copy
{'cheapest_price': 312, 'cheapest_layovers': 0, 'offer_count': 2,
 'price_insights_lowest': 289, 'price_level': 'low', 'typical_price_range': [295, 430]}

Tenga en cuenta la diferencia entre cheapest_price (la oferta individual más baja que se ha devuelto) y price_insights_lowest (el mínimo propio de Google para la ruta). Rara vez coinciden exactamente: price_insights refleja una visión más amplia sobre la ruta que las ofertas específicas en esta única respuesta, por lo que mantenga ambos campos en lugar de colapsarlos en un solo número.

Transformar: Normalizar en un Registro de Ruta-Fecha

La identidad de un seguimiento de vuelos es la tupla de (departure_id, arrival_id, outbound_date, return_date), no una URL. La etapa de transformación construye esa clave junto con una forma de registro canónica para que el almacenamiento y la comparación nunca tengan que volver a derivarla.

python Copy
from datetime import datetime, timezone


def to_record(route: dict, fares: dict) -> dict:
    return {
        "departure_id": route["departure_id"],
        "arrival_id": route["arrival_id"],
        "outbound_date": route["outbound_date"],
        "return_date": route.get("return_date"),
        "trip_type": "round_trip" if route.get("return_date") else "one_way",
        "price": fares["cheapest_price"],
        "price_level": fares["price_level"],
        "currency": "USD",
        "checked_at": datetime.now(timezone.utc).strftime("%d-%b-%Y %H:%M UTC"),
    }


def route_key(record: dict) -> str:
    return f"{record['departure_id']}-{record['arrival_id']}:{record['outbound_date']}:{record.get('return_date')}"


if __name__ == "__main__":
    sample_route = {"departure_id": "JFK", "arrival_id": "LAX",
                     "outbound_date": "2026-08-10", "return_date": "2026-08-17"}
    sample_fares = {"cheapest_price": 312, "price_level": "low"}
    record = to_record(sample_route, sample_fares)
    print(record)
    print(route_key(record))

Al ejecutarlo contra la ruta de referencia (JFK a LAX, 10 de agosto de ida, 17 de agosto de regreso) y las tarifas extraídas, to_record devuelve un registro con price: 312, price_level: 'low', trip_type: 'round_trip', y route_key devuelve JFK-LAX:2026-08-10:2026-08-17. Un seguimiento de un solo trayecto en la misma ruta y fecha de ida produce una clave distinta en el momento en que return_date está ausente, que es el punto: dos tipos de viaje en la misma ruta son dos seguimientos diferentes, no uno.

Almacenar: Un Registro Solo de Adición Claveado por Ruta y Fecha

El almacenamiento es el mismo patrón JSONL de solo adición que utiliza un seguimiento de precios detallista, con una diferencia: los filtros de búsqueda se basan en la clave de ruta-fecha en lugar de una URL.

python Copy
import json

HISTORY_FILE = "flight_history.jsonl"


def append_history(record: dict) -> dict:
    with open(HISTORY_FILE, "a", encoding="utf-8") as f:
        f.write(json.dumps(record) + "\n")
    return record


def load_history(key: str) -> list[dict]:
    rows = []
    try:
        with open(HISTORY_FILE, encoding="utf-8") as f:
            for line in f:
                row = json.loads(line)
                if route_key(row) == key:
                    rows.append(row)
    except FileNotFoundError:
        pass
    return rows


if __name__ == "__main__":
    open(HISTORY_FILE, "w").close()  # comienza esta demostración desde un registro vacío
    sample_record = {"departure_id": "JFK", "arrival_id": "LAX", "outbound_date": "2026-08-10",
                      "return_date": "2026-08-17", "trip_type": "round_trip",
                      "price": 312, "price_level": "low", "currency": "USD",
                      "checked_at": "13-Jul-2026 14:28 UTC"}
    append_history(sample_record)
    append_history(sample_record)
    with open(HISTORY_FILE, encoding="utf-8") as f:
        print(f"{len(f.readlines())} línea(s) en {HISTORY_FILE} después de dos comprobaciones")

Al agregar el mismo registro dos veces y recargar por clave, se devuelven ambas filas en orden de inserción, confirmando que el registro acumula en lugar de sobrescribir: la misma garantía que un archivo de solo adición brinda a un seguimiento con clave de URL, solo que filtrado por una clave diferente. Pasada una cantidad de rutas, cambie el archivo por una pequeña tabla SQLite con (departure_id, arrival_id, outbound_date, return_date) como clave compuesta; la forma de lectura y escritura se mantiene igual.

Decidir: Comparar Contra el Historial y la Propia Señal de Precio de Google

Un seguimiento de precios detallista tiene una única regla de decisión: si el nuevo precio está por debajo del más bajo visto hasta ahora. Un seguimiento de tarifas tiene una segunda señal disponible — price_level — por lo que la etapa de decisión verifica ambas: una caída numérica genuina por debajo del mínimo vigente, o una primera lectura que ya cae en price_level: "low".

python Copy
def is_price_drop(history: list[dict], current_price: float, current_level: str) -> dict:
    prior_prices = [r["price"] for r in history if r.get("price") is not None]
    previous_low = min(prior_prices) if prior_prices else None

    numeric_drop = (
        current_price is not None
        and previous_low is not None
        and current_price < previous_low
    )
    level_signal = current_level == "low"

    return {
        "dropped": numeric_drop,
        "worth_alerting": numeric_drop or (level_signal and previous_low is None),
        "current": current_price,
        "previous_low": previous_low,
```json
"nivel_precio": nivel_actual,
    }


if __name__ == "__main__":
    historia = [{"precio": 349}, {"precio": 340}]
    print(es_caída_precio(historia, precio_actual=312, nivel_actual="bajo"))

Contra dos lecturas anteriores de 349 y 340, una nueva lectura de 312 con nivel_precio: "bajo" devuelve {'caído': True, 'vale_la_alerta': True, 'actual': 312, 'bajo_anterior': 340, 'nivel_precio': 'bajo'}. El campo vale_la_alerta es deliberadamente más amplio que caído: también se activa en la primera lectura de una ruta si la propia clasificación de Google ya la considera baja, ya que una comparación de bajo en funcionamiento no tiene nada con qué comparar aún, pero la señal cualitativa sí. Esa distinción refleja algo que investigación sobre la fijación dinámica de precios offline en los mercados de billetes de avión trata como una característica estructural de la categoría: el inventario de asientos de avión se vuelve a previad contra la demanda y la capacidad restante, no contra un calendario de ventas fijo, por lo que una única instantánea ya puede ser informativa sin un historial de precios con que compararla.

Alerta: Disparar un Webhook en una Caída

La mecánica de alerta es la misma que usa un reloj de venta al por menor — una sola llamada HTTP, sin cola, sin intermediario — con el mensaje construido a partir de la identidad de la ruta y la fecha en lugar de un nombre de producto.

python Copy
def enviar_alerta(etiqueta_ruta: str, decisión: dict, url_webhook: str) -> int:
    mensaje = (
        f"Vigilancia de tarifas: {etiqueta_ruta}\n"
        f"Ahora ${decisión['actual']} (era ${decisión['bajo_anterior']}), "
        f"nivel_precio de Google={decisión['nivel_precio']}"
    )
    respuesta = requests.post(url_webhook, json={"texto": mensaje}, timeout=15)
    respuesta.raise_for_status()
    return respuesta.status_code

raise_for_status() señala inmediatamente un punto final de webhook roto en lugar de dejar que un relay mal configurado consuma cada alerta en silencio. El cuerpo {"texto": mensaje} coincide con la forma común de webhook entrante de Slack/Discord; ajusta el JSON a lo que el punto final receptor espera.

Programar: Consultar con una Cadencia Límite por el Viaje

Conectar la obtención y la alerta en una sola función permite que una sola llamada comprobar_ruta() la ejecute un programador:

python Copy
def comprobar_ruta(id_salida, id_llegada, fecha_salida, fecha_regreso=None, url_webhook=None):
    carga_util = obtener_ruta(id_salida, id_llegada, fecha_salida, fecha_regreso)
    tarifas = extraer_tarifas(carga_util)
    ruta = {"id_salida": id_salida, "id_llegada": id_llegada,
             "fecha_salida": fecha_salida, "fecha_regreso": fecha_regreso}
    registro = agregar_historia(a_registrar(ruta, tarifas))
    decisión = es_caída_precio(cargar_historia(clave_ruta(registro)), registro["precio"], registro["nivel_precio"])
    if decisión["vale_la_alerta"] and url_webhook:
        enviar_alerta(f"{id_salida}-{id_llegada} {fecha_salida}", decisión, url_webhook)
    return registro, decisión

Una ejecución diaria es suficiente para la mayoría de los relojes personales; un escritorio de viajes corporativos que rastrea varias rutas recurrentes puede ejecutarse con más frecuencia. A diferencia de un SKU minorista, un reloj de vuelo también tiene un final natural: una vez que pasa la fecha de salida, la ruta deja de ser una búsqueda activa y la historia para ella se termina. Una entrada en crontab es el controlador más sencillo en Linux o macOS, utilizando la misma especificación de tiempo crontab POSIX que cualquier trabajo programado en la plataforma usa:

bash Copy
# crontab -e — verificar dos veces al día, 07:00 y 19:00
0 7,19 * * * cd /opt/vigilancia-tarifas && /usr/bin/python3 verificar.py >> vigilancia.log 2>&1

El Programador de tareas de Windows ejecuta el mismo comando en un disparador equivalente, y un temporizador sin servidor también funciona: el script no mantiene estado más allá del archivo de historia, por lo que una invocación sin estado le conviene. Para una lista de vigilancia de varias rutas, itera comprobar_ruta() sobre la lista y mantén la concurrencia moderada para que los límites de tasa de la clave API se mantengan cómodos.

Lo Que Obtienes de Vuelta

Cada verificación agrega un registro al log de historia, indexado por ruta y fecha en lugar de por URL:

json Copy
// El esquema refleja exactamente lo que to_record()/append_history() escriben.
// Los valores de los campos son ilustrativos, coincidiendo con el fixture utilizado anteriormente — no es una lectura activa.
{
  "id_salida": "JFK",
  "id_llegada": "LAX",
  "fecha_salida": "2026-08-10",
  "fecha_regreso": "2026-08-17",
  "tipo_viaje": "ida_y_vuelta",
  "precio": 312,
  "nivel_precio": "bajo",
  "moneda": "USD",
  "verificado_en": "13-Jul-2026 15:01 UTC"
}

Algunos puntos que vale la pena conocer antes de ejecutar esto a gran escala:

  • precio y precio_informes_bajo pueden divergir. El primero es la oferta más barata que esta respuesta específica devolvió; el segundo es la lectura más amplia de Google sobre la ruta. Almacena ambos si el caso de uso posterior se preocupa por la diferencia.

  • La cifra almacenada debería ser la tarifa total, no una tarifa base. Los transportistas de EE. UU. que publicitan una tarifa están obligados por la regla federal de publicidad de tarifas completas a cotizar el precio entero que un pasajero paga, no una tarifa base reducida; trata cualquier campo que el pipeline almacene como precio de la misma manera, de modo que una comparación a través de los cheques nunca sea una tarifa base un día y un total completo al siguiente.

  • El multi-city cambia la forma, no el pipeline. data_type: 3 y multi_city_json producen una respuesta a nivel de itinerario en lugar de una respuesta de un solo segmento; extiende to_record para incorporar el precio de cada segmento en un total en lugar de tratar el itinerario como una tarifa plana.

  • gl y hl afectan la moneda y el idioma, no solo la redacción. Ancla ambos por watch de la misma manera que un watch minorista ancla proxy_country, de modo que una comparación a través de los cheques se mantenga en el mismo mercado.

  • Un precio nullable no es un cero. Trata un cheapest_price que falta como None, exactamente como ya lo hace la etapa de extracción: una respuesta ocasional extraña nunca debe inyectar un falso cero en el mínimo en curso.

Conclusión: una ruta, una fecha, una decisión

El pipeline se reduce a la misma forma que cualquier otro watch de precios: obtener, extraer, almacenar, decidir, alertar, programar, con la identidad de un "watch" redefinida para la categoría: un par de ruta y fecha en lugar de una URL, un parámetro de tipo de viaje en lugar de una única forma de solicitud, y una segunda señal proporcionada por el objetivo (price_level) disponible junto a la comparación numérica simple. Ampliar la lista de vigilancia a más rutas, más fechas o un itinerario multi-city reutiliza cada etapa sin cambios; solo los parámetros pasados a Fetch cambian.

¿Listo para construir su pipeline de datos impulsado por IA?

Únete a la comunidad para intercambiar notas con desarrolladores que construyen pipelines de datos en Scrapeless: Discord · Telegram.

Regístrate en app.scrapeless.com para obtener créditos gratuitos de la API de Scraping, confirma que el actor scraper.google.flights está habilitado en el plan de la cuenta y adapta las etapas anteriores a las rutas que necesita la lista de vigilancia. Consulta precios para detalles del plan, y la documentación de la API de Scraping para la referencia del actor actual.

FAQ

Q: ¿Es scraper.google.flights un actor real y documentado?
Sí. Un nombre de actor deliberadamente inventado devuelve "invalid actor: <name>"; solicitar scraper.google.flights devuelve "disabled actor: scraper.google.flights" en su lugar, un error distinto que solo se aplica a un actor reconocido y catalogado. Está limitado en algunos planes en lugar de no existir; confirma que está habilitado para el plan de la cuenta objetivo antes de centrar el tráfico de producción en él.

Q: ¿Por qué clave la historia por ruta y fecha en lugar de una URL, como lo hace un watch minorista?
Porque una tarifa de vuelo no tiene una sola página canónica como lo hace un SKU minorista. La misma ruta tiene precios diferentes según la fecha de salida, la fecha de regreso y el tipo de viaje, por lo que la identidad de un "watch" tiene que ser esa combinación, no una sola URL.

Q: ¿Qué añade price_insights.price_level sobre una simple comparación de "más barato que la última vez"?
Es la propia clasificación de Google de si la tarifa actual es baja, típica o alta para la ruta, calculada a partir de datos más allá de la única respuesta que el pipeline acaba de recibir. Combinarlo con la comparación de mínimo en curso permite que la primera lectura de una ruta sea procesable, no solo sus segundas y posteriores.

Q: ¿Puede esto manejar un itinerario multi-city?
Sí, a nivel de solicitud — data_type: 3 con un array de multi_city_json de segmentos es una forma de parámetro documentada para esta familia de actores. Ampliar to_record para incorporar un itinerario de múltiples segmentos en un total comparable es la única pieza de lógica adicional que un watch multi-city necesita más allá de lo que este post construye.

Q: ¿Con qué frecuencia debería ejecutarse la verificación?
Una ejecución diaria o dos veces al día se adapta a la mayoría de los watches de tarifa personal. Un watch de vuelo también tiene un alto natural: una vez que pasa la fecha de salida, la ruta ya no es una búsqueda activa, a diferencia de un SKU minorista que se puede observar indefinidamente.

Q: ¿Puede esto funcionar sin un agente de IA?
Sí. El Python en Fetch a través de Schedule funciona de principio a fin por sí mismo: llamar, extraer, almacenar, decidir, alertar, y dejar que un programador impulse la cadencia. Un agente es una forma conveniente de impulsar la selección de ruta y fecha en lenguaje natural, pero el pipeline en sí no necesita más que Python y un programador.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar