En el trabajo hace unos días nos surgió la necesidad de presentar en un mapa cierto tipo de publicaciones, ya que uno de nuestros equipos estaba buscando locales para expandirnos. Así es cómo me encomendaron la tarea de obtener información de varios sitios y dejarlos disponibles en nuestro software de mapeo.
Hoy vamos a ver cómo obtener datos de publicaciones de mercadolibre, para su posterior utilización.
Mercadolibre nos provee de manera pública una url con una api REST lista para usar. La API de mercadolibre devuelve resultados en formato .json,
En este ejemplo vamos a desarrollar una búsqueda por categoría, y no por consulta, además, solo buscaremos las publicaciones que se realizaron ese día.
Empecemos.
La url de mercadolibre es la siguiente:
https://api.mercadolibre.com/sites/MLA/search?category=MLA79242&_PublishedToday_YES
Analicemosla un poco
https://api.mercadolibre.com/sites/MLA/search?category=MLA79242&_PublishedToday_YES
Es la url principal, para identificarla del sitio general, no comienza con www sino que comienza con api.
https://api.mercadolibre.com/sites/MLA/search?category=MLA79242&_PublishedToday_YES
Este código de categoría específicamente es para Locales (tanto alquiler como venta).
https://api.mercadolibre.com/sites/MLA/search?category=MLA79242&_PublishedToday_YES
Un filtro de mercadolibre que filtra solo los publicados hoy.
Estos filtros y otros dos que utilizaremos en este tutorial, incluyendo los códigos de categorías, etc, se pueden conseguir jugando con el sitio común de mercadolibre y agregandole filtros, luego verificando la url generada podemos inferir lo que necesitemos.
Por ejemplo si quisiera ver Celulares, necesito conseguir el código de categoría correspondiente, y eso lo hago en dos pasos:
Una vez cargada la página, inspeccionamos el HTML del sitio buscando “category”
Vamos a encotnrar algo asi:
Y asi sabemos que el codigo de categoría es MLA1051
Una vez obtenida esta información, podemos armar la url.
Arquitectura del scrapper
Vamos a armar minimo dos clases, una clase “categoría”, que va a tener dentro informacion propia de la categoria, incluyendo el método adaptar, que adapta los datos de la url al formato de esa categoría.
Una segunda clase será la de mercadolibreAPI, facilitandonos asi la operacion de busqueda, etc.
Antes de seguir, estas son las librerías de python que vamos a utilizar:
1 2 3 |
pip install <a href="http://www.numpy.org/">numpy</a> pip install <a href="https://pandas.pydata.org/">pandas</a> pip install <a href="https://mkleehammer.github.io/pyodbc/">pyodbc</a> |
Empecemos a armar nuestra clase, cómo esta clase va a aceptar objetos categoría, tenemos que crear el método acorde que permita agregarselo, y también agreguemos la búsqueda.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class inmueble: mercadolibre_id = 'MLA79242' tabla = 'mercadolibre_inmueble' # Utilizada para la carga en SQL Server columnas = ['titulo', 'producto_latitud', 'producto_longitud', 'url' 'tipo_propiedad'] # Conversion de json a dataframe y rearmado de datos para que sea formato tabla def adapt(self, items): data = pd.DataFrame(items) data = data.groupby(['id']).first().reset_index() # Elimina duplicados largo = data.shape[0] for i in range(0, largo): if(i % 100 == 0): print("Procesando: " + str(i) + " de " + str(largo)) try: data.loc[i,'fecha'] = strftime("%Y_%m_%d", gmtime()) data.loc[i,'producto_latitud'] = data.loc[i,'location']['latitude'] data.loc[i,'producto_longitud'] = data.loc[i,'location']['longitude'] data.loc[i,'url'] = data.loc[i,'permalink'] dataAttr = pd.DataFrame(data.loc[i,'attributes']) if(dataAttr.loc[dataAttr['id']=='PROPERTY_TYPE']['value_name'].count() > 0): data.loc[i,'tipo_propiedad'] = dataAttr.loc[dataAttr['id']=='PROPERTY_TYPE']['value_name'].item() else: data.loc[i,'tipo_propiedad'] = 'DESCONOCIDO' except Exception as e: print('Error adaptando: ' + str(i) + ' - ' + str(data.loc[i,'mercadolibre_id']) + ' - ' + ' -- %s' % e) pass data = data.fillna('') # Los nulos los completamos con un string vacio return data[self.columnas] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class mercadolibreAPI: objeto = None ml_url = 'http://api.mercadolibre.com/sites/MLA/search?category=' items = [] def search_config(self, objeto): self.objeto = objeto() def request_get(self, url): return requests.get(url).json() def search(self): url = ml_url + self.objeto.mercadolibre_id + '&_PublishedToday_YES' print("Buscando: " + url) paginators = round(self.request_get(url)['paging']['total']/50)+1 for offset in range(0,paginators): url = ml_url + self.objeto.mercadolibre_id + '&_PublishedToday_YES&limit=50&offset=' + str(offset*50) jsdata = self.request_get(url) if(jsdata is not None): self.items = self.items + jsdata['results'] self.adapt() def adapt(self): o = self.objeto self.items = o.adapt(self.items) ml = mercadolibreAPI() ml.search_config(inmueble) ml.search() |
Vamos paso a paso.
def request_get(self, url):
lee y descarga utilizando la librería requests de python.
def search(self):
Primero armamos la url utilizando propiedades del objeto agregado en
ml.search_config(inmueble)
(ya vamos a ver en detalle ese objeto), luego ejecutamos la consulta REST, y le pedimos que nos devuelva el valor del elemento ‘paging’ y dentro de ese elemento le pedimos el valor ‘total’, si vas a la dirección desde el navegador, vas a verlo representado como en este ejemplo:
paginators = round(self.request_get(url)['paging']['total']/50)+1
la consulta que hacemos a la api pide 50 resultados por vez, y lo que vamos a ir haciendo es mover el offset de a 50, así, la primer consulta pide del 0 al 50, la segunda del 50 al 100, etc.
1 2 3 4 |
for offset in range(0,paginators): url = ml_url + self.objeto.mercadolibre_id + '&_PublishedToday_YES&limit=50&offset=' + str(offset*50) jsdata = self.request_get(url) if(jsdata is not None): self.items = self.items + jsdata['results'] |
Una vez conocida la cantidad de consultas que debemos realizar, hacemos un for y rearmamos la url para solicitar (offset=??). Vamos agregando los elementos de cada consulta al atributo “items” de la clase, que contiene todos los elementos que luego vamos a procesar.
def adapt(self):
Solo llama al método de la clase categoría.
Analicemos ahora la clase “inmueble”
1 2 |
columnas = ['fecha', 'producto_latitud', 'producto_longitud', 'url', 'tipo_propiedad'] |
Estas columnas son las columnas de salida que definí necesitaba para el modelo pueden cambiar para el tuyo, y seguramente cambiaran para cada categoría.
def adapt(self, items):
recibe una lista de resultados json para procesar
1 2 |
data = pd.DataFrame(items) data = data.groupby(['id']).first().reset_index() # Elimina duplicados |
Transformamos los resultados recibidos en un dataframe de pandas, y luego eliminamos los duplicados.
Este es el resultado final:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
''' SCRAPPER MERCADOLIBRE - API --------------------------- Version: 2.0 Autor: Juan Jose Sisti Ultima Revision: 2018-03-26 Descripcion: Bajada de datos desde mercadolibre, utilizando el acceso via API POST que proveen. El proceso esta preparado para descargar dos tipos de elementos, inmuebles y vehiculos Se puede configurar modo DEBUG (set_debug(true)) para solo procesar los primeros elementos, esto tambien activa el modo "verbose" con salida detallada Requisitos de instalacion: - Python 3.x - Libreriras (incluye los comandos de instalacion) pip install numpy pip install pandas pip install pyodbc ''' import requests #Bajada de datos URL import json #La API utiliza Json para transmitir datos import pandas as pd import re #RegExp import pyodbc #Conexion a SQL Server from time import gmtime, strftime #Para obtener la fecha actual class inmueble: mercadolibre_id = 'MLA79242' tabla = 'mercadolibre_inmueble' # Utilizada para la carga en SQL columnas = ['fecha', 'producto_latitud', 'producto_longitud', 'url', 'tipo_propiedad'] # Conversion de json a dataframe y rearmado de datos para que sea formato tabla def adapt(self, items): data = pd.DataFrame(items) data = data.groupby(['id']).first().reset_index() # Elimina duplicados largo = data.shape[0] for i in range(0, largo): if(i % 100 == 0): print("Procesando: " + str(i) + " de " + str(largo)) try: # La fecha es la fecha de ejecucion, como este es un proceso diario, la necesitamos # para poder identificar las diferentes corridas data.loc[i,'fecha'] = strftime("%Y_%m_%d", gmtime()) data.loc[i,'mercadolibre_id'] = data.loc[i,'id'] data.loc[i,'producto_latitud'] = data.loc[i,'location']['latitude'] data.loc[i,'producto_longitud'] = data.loc[i,'location']['longitude'] data.loc[i,'url'] = data.loc[i,'permalink'] dataAttr = pd.DataFrame(data.loc[i,'attributes']) if(dataAttr.loc[dataAttr['id']=='PROPERTY_TYPE']['value_name'].count() > 0): data.loc[i,'tipo_propiedad'] = dataAttr.loc[dataAttr['id']=='PROPERTY_TYPE']['value_name'].item() else: data.loc[i,'tipo_propiedad'] = 'DESCONOCIDO' except Exception as e: print('Error adaptando: ' + str(i) + ' - ' + str(data.loc[i,'mercadolibre_id']) + ' - ' + ' -- %s' % e) pass data = data.fillna('') # Los nulos los completamos con un string vacio return data[self.columnas] class mercadolibreAPI: debug = False # Limita el procesamiento a los primeros elementos del primer indice, habilida la salida por pantalla de mensajes query = None objeto = None ml_url = 'http://api.mercadolibre.com/sites/MLA/search?category=' items = [] pd.options.display.float_format = '{:.2f}'.format def set_debug(self, debug): self.debug = debug if(self.debug): print("Modo Debug ENCENDIDO") def request_get(self, url): if(self.debug): print("Procesando url: ", url) try: return requests.get(url).json() except: return None def search(self, objeto): self.objeto = objeto() url = self.ml_url + self.objeto.mercadolibre_id + '&_PublishedToday_YES' print("Buscando: " + url) if(self.debug): paginators = 5 else: paginators = round(self.request_get(url)['paging']['total']/50)+1 # Limito un poco los paginators, solo me traigo las primeras XX paginas #if(paginators>200): paginators = 200 for offset in range(0,paginators): url = self.ml_url + self.objeto.mercadolibre_id + '&_PublishedToday_YES&limit=50&offset=' + str(offset*50) jsdata = self.request_get(url) if(jsdata is not None): self.items = self.items + jsdata['results'] self.adapt() def adapt(self): o = self.objeto self.items = o.adapt(self.items) def export(self, tipo = 'csv'): if tipo.lower() == 'sql': self.export_sql() elif tipo.lower() == 'csv': self.export_csv() else: print("No existe el metodo de exportacion: " + tipo.lower()) def export_sql(self): ## No implementado return None def export_csv(self): archivo = "indice_" + self.objeto.mercadolibre_id + ".csv" if(self.debug): print("Guardando archivo", archivo) self.items.to_csv(archivo, sep=";", decimal=",") ml = mercadolibreAPI() ml.set_debug(False) ml.search(inmueble) ml.export() print("Fin") |