-
Notifications
You must be signed in to change notification settings - Fork 0
4 Implementación: Requests
Nota: Todo el código de este trabajo está hecho en Python3. Lo he elegido debido a su versatilidad, rendimiento y, más importante, simpleza de cara a las funcionalidades a implementar.
En flight_search/requests
se ubican esbozos de código de cómo buscar
vuelos utilizando requests
. Hablo de esbozos puesto que más adelante
veremos que surgieron varios problemas.
requests
es el paquete de Python más recomendado para realizar
peticiones HTTP. Su facilidad en los argumentos de entrada
para los métodos GET
y POST
, así como en el retorno de dichas
funciones (que es el recurso pedido en texto plano), son motivos
para implementar una spider con requests
.
Desde el punto de vista de nuestro trabajo, usar requests
para
buscar un vuelo es "canónico" por diversos motivos:
- Introduciríamos el mínimo de datos necesarios para obtener información de un vuelo.
- Podríamos elegir si enviar las cookies o no añadiéndolas como argumento
al
GET
o alPOST
. - Solo pasamos por los dominios justos y necesarios, sin terceros que se encarguen del marketing.
En el papel se ve bastante bien la planificación del trabajo, pero la realidad es que nos encontramos con un primer problema al intentar sintetizar nuestro tráfico con Iberia y Ryanair.
Intentando empezar la fase de implementación con Iberia, vemos que
la URL clave es https://www.iberia.com/web/dispatchSearchFormHOME.do
.
La diferencia con Ryanair es el hecho de que se hace request de dicha
URL con un POST
. Esto no supondría problema si no fuera por lo bastante
"modelo de caja negra" que es dicha petición. Nos encontramos con una serie
de argumentos indescifrables, parecidos a cuando analizábamos algunas cookies
(consultar flight_search/requests/iberia_params.txt
).
A continuación se muestran las funciones mediante las que realizábamos la
petición POST
, cuyos parámetros se leían de un .txt
(incluido en el
apéndice el ejemplo de Iberia):
def simple_post(url,params):
"""
Attempts to send a post http request with the params given,
which is a dictionary of pairs variable_name : value.
Returns the request response, otherwise None.
"""
try:
with closing(requests.post(url,params)) as resp:
if is_good_response(resp):
return resp.content
else:
return None
except RequestException as e:
log_error('Error during requests to {0} : {1}'.format(url, str(e)))
return None
def print_req(req):
print('{}\n{}\n{}\n\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
def read_params(path):
params = {}
with open(path,'r') as f:
for line in f:
if(len(line.split())==1):
key = line.split()[0]
val = None
else:
(key, val) = line.split()
params[key] = val
return params
print(simple_get("https://www.iberia.com/web/dispatchSearchFormHOME.do"))
Código 1: Funciones de POST con requests
Por no poder entender el funcionamiento de los POSTS de Iberia, nos vemos incapacitados para:
- Obtener el
.html
con la información del vuelo a buscar, en lugar de uno que nos informa del error y con información predeterminada. - Saber manipular los parámetros de búsqueda como el que rellena el formulario de la página web.
Por ello, nos vemos obligados a abandonar la investigación con Iberia
y cualquier otra aerolínea que requiera de POSTS siempre y cuando
la dificultad de sus parámetros de entrada sea de este nivel. Abandonar
Iberia sería en el caso de proseguir con requests
, claro.
Por otro lado, mostramos en esta subsección la simpleza de realizar un GET con Ryanair.
Si, por ejemplo, queremos buscar vuelos desde Madrid a Palma de Mallorca
el 30 de Mayo de 2019, la URL del GET
es:
https://www.ryanair.com/es/es/booking/home/MAD/PMI/2019-05-30/1/0/0/0?Discount=0
Es fácil ver que para poder elegir otros vuelos tan solo de cambiar los
códigos de aeropuerto de origen (MAD
) y destino (PMI
), así como la fecha
en formato YYYY-MM-DD
. Los unos y ceros a la derecha de la fecha indican
el número de distintos pasajeros para los que buscamos vuelo.
A continuación mostramos las funciones implementadas para GET
de la
URL dada:
def simple_get(url):
"""
Attempts to get the content at `url` by making an HTTP GET request.
If the content-type of response is some kind of HTML/XML, return the
text content, otherwise return None.
"""
try:
with closing(get(url, stream=True)) as resp:
if is_good_response(resp):
return resp.content
else:
return None
except RequestException as e:
log_error('Error during requests to {0} : {1}'.format(url, str(e)))
return None
url =
"https://www.ryanair.com/es/es/booking/home/MAD/PMI/2019-05-30/1/0/0/0?Discount=0"
print(simple_get(url))
Código 2: Funciones de GET con requests
No hay función alguna de modificar la URL según los destinos y la fecha (de momento) debido al hecho de que nos encontramos con un segundo y último problema al ejecutar este código.
El segundo problema de requests
en nuestra investigación es de tal calibre
que hace finiquitar este camino de abordaje al problema. Es decir, no se
van a sintetizar los procesos de búsqueda con requests.
Dicho problema aparece cuando un host analiza las cabeceras de una petición
de forma que rechace paquetes inadecuados. No especificar el navegador,
fechas actuales, idioma, etc. son signos para dichos hosts de poder sufrir
cualquier tipo de ataque (dado que es más probable que el tráfico sea
sintético). Rechazar paquetes inadecuados puede traducirse en enviar
.html
de errores, y ello, por supuesto, no contiene información alguna
sobre nuestras búsquedas.
Resulta que Ryanair parece aplicar este tipo de análisis en el tráfico que intercambia con nosotros y, con ello, nos responde con páginas predeterminadas. Podríamos intentar analizar qué headers son necesarios y cuáles no, pero hacer esto supone:
- Alejarnos más de poder intercambiar tráfico canónico para poder manipularlo.
- Demasiado tiempo analizando parámetros opacos sin la certeza de que vaya a funcionar.
Aparte, pese a que algunas veces hemos conseguido que el host nos aceptara, se
añade otra dificultad: Un GET
de scripts embebidos es sinónimo de no
recibir los datos que queremos (puesto que dicho script no tiene navegador
donde ejecutarse). En el caso de las aerolíneas, scripts que muestran resultados
de búsqueda es el recurso más utilizado.
Para completar nuestra araña web basada en requests
, solo faltaba
parsear los .html
obtenidos con una librería como BeautifulSoup
.
En flight_search/requests
se adjuntan dos .html
, uno de Expedia donde no se
hace uso de estos filtros y, con ello, si parseamos con grep
, por ejemplo,
encontramos los precios de distintos vuelos. El otro .html
es uno
de Ryanair, con el cual vemos que no se obtiene ningún dato de precios
buscados.
En definitiva, crear una araña web con requests
es simple cuanto más simple
sea el host de la página web. Para nuestra investigación, es claro que nos
daría demasiadas dificultades llevar adelante la implementación con este
paquete por estos motivos:
- Las peticiones POSTS que tengan parámetros difíciles de analizar serán imposibles de sintetizar a nuestro antojo.
- Cualquier petición en general hacia un host "exigente" con las cabeceras intercambiadas aumenta considerablemente la dificultad en conseguir generar nuestro propio tráfico.
Aparte, señalar que tenemos imposible desde requests
poder comunicarnos desde
IP "anónima" (o al menos variable para escapar del IP tracking), luego se
está despreciando un factor importante para la discriminación perfecta.