Blog
Como Desarrollar un Script en Python que Busca Apartamentos en Craigslist
- Publicado por: Rafael Fernandez
- Categoría: Analisis de datos Blog
Web scraping es una técnica utilizada por programadores de software para extraer información de sitios web. Usualmente, se simula la navegación de un humano en la World Wide Web ya sea utilizando el protocolo HTTP manualmente, o incrustando un navegador en una aplicación.
El web scraping está relacionado fuertemente con la indexación de la web, la cual se logra utilizando un programa determinado y es una técnica universal adoptada por la mayoría de los motores de búsqueda. Sin embargo, el web scraping se enfoca más en la transformación de datos sin estructura en la web (como el formato HTML) en datos estructurados que pueden ser almacenados y analizados en una base de datos central, en una hoja de cálculo o en alguna otra fuente de almacenamiento. Alguno de los usos del web scraping son la comparación de precios en tiendas, el monitoreo de datos relacionados con el clima de cierta región, la detección de cambios en sitios webs y la integración de datos en sitios webs.
➡ Te invitamos a nuestro Curso Python de Análisis de Datos:
En esta oportunidad se hará uso del web scraping para, mediante la extracción y análisis de datos y listas de páginas inmobiliarias como Craiglist (usada en este tutorial), programar un script que facilite el proceso de búsqueda de arrendamiento de apartamentos en Los Angeles, con filtros muy finos para mostrar el contenido que realmente desea ver el posible nuevo inquilino.
Analizando el problema y dividiéndolo en problemas menores, se concluye que los pasos a seguir para construir el script son los siguientes:
- Extraer los listados de Craigslist.
- Filtrar los listados que no coincidan con los criterios deseados.
- Guardar los resultados para ser revisados posteriormente.
En el resto de la publicación, se presentará cómo se construyó cada pieza y cómo se utilizó el script para encontrar un apartamento.
Extracción de las Listas de Craigslist
El primer paso para construir el script es obtener los listados de Craiglist. Lamentablemente, Craiglist no tiene una API, pero se pueden obtener las publicaciones usando el paquete python-craiglist. Para instalar este paquete ejecuta en tu cmd:
pip install python-craigslist
Las listas de apartamentos de Craigslist para Los Angeles se encuentran en https://losangeles.craigslist.org/. En el código se realiza lo siguiente:
- Se importa CraigslistHousing, una clase de python-craigslist.
- Se inicializa la clase con los siguientes argumentos:
-
-
site: El sitio de Craigslist que será analizado. El sitio es la primera parte de la URL, como https://losangeles.craigslist.org.
-
area: La subárea dentro del sitio que será analizada. El área es la última parte de una URL, como https://losangeles.craigslist.org/lac/. Esta define que solo será consultado el centro de Los Angeles.
-
category: El tipo de listado que se quiere buscar. La categoría es la última parte de una URL de búsqueda, como https://losangeles.craigslist.org/search/lac/apa, que muestra los apartamentos.
-
filters: Cualquier filtro que se quiera aplicar a los resultados.
-
max_price: El precio máximo que se esta dispuesto a pagar.
-
min_price: El precio mínimo a partir de el cual se quiere buscar.
-
-
-
Se obtienen los resultados de Craigslist utilizando el método get_results con los siguientes argumentos:
- limit: El cual limita el numero de resultados que serán obtenidos.
-
newest: Ordena los resultados desde el mas nuevo al mas antiguo.
-
Obtener cada resultado del generador de resultados e imprimirlos.
# Se importa CraigslistHousing como clh from craigslist import CraigslistHousing as clh # Inicializacion de CraigslistHousing cl = clh( site='losangeles', area='lac', category='apa', filters={'max_price': 2000, 'min_price': 1000}) # Obtención de los resultados resultados = cl.get_results(sort_by='newest', limit=10) # Ciclo para imprimir los resultados for resultado in resultados: print(resultado)
De esta forma, ya estará lista la primera parte del script. Ya es posible utilizarlo para buscar apartamentos en Craigslist. Cada uno de los resultados que genera, es un diccionario con los siguientes campos:
{ 'id': '6934750412', 'repost_of': '6904807602', 'name': 'Beautiful 1 bed/1 bath apt in Great Location!', 'url': 'https://losangeles.craigslist.org/lac/apa/d/los-angeles-beautiful-1-bed-1-bath-apt/6934750412.html', 'datetime': '2019-07-29 13:42', 'price': '$1749', 'where': 'Hollywood West Hollywood Los Angeles', 'has_image': True, 'has_map': False, 'geotag': None, 'bedrooms': '1', 'area': '600ft2' }
Una corta descripción de cada campo sería:
- id: Identificador único de la publicación en Craigslist.
- repost_of: Identificador único de la publicación que fue republicada si aplica.
- name: Nombre de la publicación.
- url: Dirección de la publicación en Craigslist.
- datetime: Fecha de la publicación.
- price: Precio del alquiler de la publicación.
- where: Vecindario en el cual se encuentra el apartamento.
- has_image: Si la publicación tiene imagen o no.
- has_map: Si la publicación tiene asociado el mapa de como llegar.
- geotag: Coordenadas de la publicación.
- bedrooms: Numero de habitaciones del apartamento.
- area: Dimensiones del apartamento.
Para obtener los resultados con un formato mas legible, se puede filtrar la información obtenida en cada resultado, dejando solo lo que se considere importante. Esto puede ser:
# Se importa CraigslistHousing como clh from craigslist import CraigslistHousing as clh # Inicializacion de CraigslistHousing cl = clh( site='losangeles', area='lac', category='apa', filters={'max_price': 2000, 'min_price': 1000}) # Obtencion de los resultados results = cl.get_results(sort_by='newest', limit=10) # Items importantes de los resultados items = ["name", "where", "price", "area", "url"] # Ciclo para imprimir los resultados con el formato deseado for result in results: res = "" for item in items: res = res + str(result[item]) + " | " print(res)
De esta forma, la salida obtenida será similar a lo siguiente:
HOME AVAILABLE NOW! MOVE-IN READY! | POMONA | $1150 | None | https://losangeles.craigslist.org/lac/apa/d/walnut-home-available-now-move-in-ready/6945178606.html | $50 DISCOUNT $1345 IF PAID ON OR BEFORE THE 1ST. 1 BED 1 BATH | LYNWOOD | $1395 | 600ft2 | https://losangeles.craigslist.org/lac/apa/d/lynwood-50-discount-1345-if-paid-on-or/6945216397.html | ** Large Studio 1 bath- Hardwood Floors ** | Hollywood | $1495 | None | https://losangeles.craigslist.org/lac/apa/d/large-studio-1-bath-hardwood-floors/6945215618.html |
Se incluyen el nombre de la publicación, la ubicación del apartamento, el precio del alquiler, el área en la que se encuentra el apartamento y el enlace a la publicación.
Filtrar los Resultados Obtenidos
Una vez que se tienen una manera de conseguir publicaciones en Craigslist, hace falta una forma de filtrar los resultados para que aparezcan solamente los que se deseen.
En el proceso de prueba, se incluirán varios vecindarios que pueden ser de interés. Estos son:
- Hollywood
- Wildshire
- Beverly Hills
- Los Angeles (centro de Los Angeles)
- Koreatown
- Little Armenia
Para lograr filtrar por vecindario, es necesario definir los limites del área:
Los límites fueron definidos con BoundingBox. Es necesario especificar la opción “CSV” en la parte inferior izquierda para obtener las coordenadas de los límites. Luego de esto, es necesario crear un diccionario con los limites de la siguiente forma:
limites = {"lac":[ [-118.445649, 34.015294], [-118.213563, 34.105457] ] }
En este diccionario se pueden incluir vecindarios específicos con sus limites superiores e inferiores para tener mas precisión a la hora de hacer la búsqueda. Tomando en cuenta estos límites, se aplica la modificación correspondiente al programa para que cuando haga la búsqueda, filtre los resultados que se encuentran dentro de ellos. Esto sería:
# Lista en donde serán almacenados # los resultados filtrados res_filtrados = [] # Filtro de resultados por ubicación for res in resultados: if res["geotag"] != None: ubic = res["geotag"] for i,limite in limites.items(): if en_lim(ubic,limite): res_filtrados.append(res) else: pass
Desafortunadamente, es muy poco común conseguir publicaciones con sus coordenadas, por lo cual, al aplicar este filtro, se obtendrán muy pocos resultados (o ninguno). Depende de la persona que realiza la publicación el especificar una ubicación, desde donde se pueden calcular las coordenadas. Cuanto más familiarizado se esté con Craigslist, más probable es que la ubicación sea incluida.
Para lograr filtrar los resultados según la ubicación que se desee, será necesario crear una lista con los vecindarios deseados.
VECINDARIOS = ["los angeles", "hollywood", "wilshire", "downtown"]
Luego de esto, hay que proceder a diseñar el filtro para luego implementarlo en el programa.
for res in resultados: for vec in VECINDARIOS: if vec in str(res["where"]).lower(): res_filtrados.append(res)
Para no dejar fuera el filtro por coordenadas, se integran ambos en el programa, dejando como resultado final lo siguiente:
# Se importa CraigslistHousing como clh from craigslist import CraigslistHousing as clh def en_lim(coord, lim): """ Función que verifica si las coordenadas de la vivienda (coord) se encuentran dentro de los limites establecidos (lim). Implementada para filtrar los resultados. """ if lim[0][0] < coord[0] < lim[1][0] and lim[1][1] < coord[1] < lim[0][1]: return True else: return False # Inicializacion de CraigslistHousing cl = clh( site='losangeles', area='lac', category='apa', filters={'max_price': 2000, 'min_price': 1000}) # Obtencion de los resultados resultados = cl.get_results(sort_by='newest', limit=30) # Coordenadas del cuadro de límites limites = {"lac":[ [-118.445649, 34.015294], [-118.213563, 34.105457] ] } # Lista en donde serán almacenados # los resultados filtrados res_filtrados = [] # Lista de vecindarios VECINDARIOS = ["long beach", "los angeles", "hollywood", "wilshire", "downtown"] # Filtro de resultados por ubicación for res in resultados: # Filtro por coordenadas if res["geotag"] != None: ubic = res["geotag"] for i,limite in limites.items(): if en_lim(ubic,limite): res_filtrados.append(res) # Filtro por nombre del vecindario else: for vec in VECINDARIOS: if vec in str(res["where"]).lower(): res_filtrados.append(res) # Items importantes de cada resultado items = ["name", "where", "price", "area", "url"] # Ciclo para imprimir los resultados con el formato deseado for resultado in res_filtrados: res = "" for item in items: res = res + str(resultado[item]) + " | " print(res)
De esta forma, el script estará funcionando perfectamente según lo que le sea especificado. Para que el uso de este programa pueda ser personalizado, se creará un archivo llamado “ajustes.py” en el cual se ingresarán todos los ajustes que el script utilizará. El contenido de este archivo será importado en el programa principal, por lo cual, la ejecución del script, será la misma.
# Sitio de Craigslist que será consultado site = input("Ingrese el sitio de Craigslist que será consultado\n") area = input("Ingrese el area en donde se hará la busqueda\n") # Ajustes de la busqueda min_price = input("Ingrese el precio minimo que está dispuesto a pagar\n") max_price = input("Ingrese el maximo precio que está dispuesto a pagar\n") limit = int(input("Ingrese el numero de resultados que desea obtener\n")) # Vecindarios num = int(input("Ingrese el numero de vecindarios que quiere consultar\n")) VECINDARIOS = [] while num > 0: vec = input("Ingrese el nombre de un vecindario\n") VECINDARIOS.append(vec) num -= 1
Hasta ahora se ha desarrollado un programa que pide al usuario las especificaciones de la búsqueda que se hará para luego hacerla y, una vez que se tienen los resultados, imprimirlos en la terminal. Pero tener los resultados en la terminal no es lo que se desea, ya que se pueden perder fácilmente.
Almacenar los resultados de la Búsqueda
La parte mas complicada del programa ya fue desarrollada. Justo ahora queda la parte final y, quizás, mas sencilla. Para almacenar los resultados obtenidos del programa, se utilizará la función “open” de Python para abrir archivos, la función “write” para escribir los resultados en el archivo y la función “close” para cerrar el archivo. Dependiendo de lo que desee el usuario, el resultado se guardará en un archivo .txt o un archivo .csv llamado. La implementación de esto sería en dos pasos:
- Editar el archivo de ajustes para que el usuario escoja en que formato quiere los resultados:
# Formato en el que se desea obtener los resultados formato = input("Ingrese el formato en el que quiere los resultados (csv/txt)\n")
- Implementar las funciones de apertura/escritura de archivos:
# Ciclo para escribir los resultados con el formato deseado # en el archivo for resultado in res_filtrados: res = "" # Los resultados se guardaran en un archivo .csv if formato == "csv": apts = open("apartamentos.csv", 'a') for item in items: it = "" # Filtro de comas for i in str(resultado[item]): if i != ",": it = it + i res = res + it + "," res = res[:len(res)-1] + "\n" apts.write(res) # Los resultados se guardarán en un archivo .txt elif formato == "txt": apts = open("apartamentos.txt", 'a') res = "" for item in items: res = res + str(resultado[item]) + " | " res = res[:len(res)-3] + "\n" apts.write(res)
La implementación del filtro de comas en la escritura del archivo .csv tiene la finalidad de que el archivo pueda ser consultado sin problemas por programas que puedan leer estos archivos. Por ejemplo, si el archivo es abierto con la librería Pandas, se obtiene un resultado similar al siguiente:
La cual es una forma bastante agradable de consultar los resultados.
Si no se cuenta con un lector de archivos .csv o no se tiene el conocimiento para el manejo de estos archivos, entonces se recomienda el uso del formato .txt.
Si así se requiere, la salida puede ser guardada en archivos de nombre distinto y de tipo distinto (p.ej. “resultados.txt”) cambiando el nombre del archivo que es abierto en el programa principal (scraper.py).
Finalmente se cumplieron los objetivos planteados al inicio. El script:
- Extrae listados de Craigslist.
- Filtra los listados que no coinciden con los criterios deseados.
- Guarda los resultados para ser revisados posteriormente.
Este es el código de los archivos del programa completos:
scraper.py
# Se importa CraigslistHousing como clh from craigslist import CraigslistHousing as clh from ajustes import * print("\nBuscando...") # Inicializacion de CraigslistHousing cl = clh( site=site, area=area, category='apa', filters={'max_price': max_price, 'min_price': min_price}) # Obtencion de los resultados resultados = cl.get_results(sort_by='newest', limit=limit) # Lista en donde serán almacenados los # resultados filtrados res_filtrados = [] # Filtro de resultados por ubicación for res in resultados: for vec in VECINDARIOS: if vec in str(res["where"]).lower(): res_filtrados.append(res) # Items importantes de cada resultado items = ["name", "where", "price", "area", "url"] # Se abre el archivo en el cual se escribirán los resultados # obtenidos del proceso de busqueda # Ciclo para escribir los resultados con el formato deseado # en el archivo for resultado in res_filtrados: res = "" # Los resultados se guardaran en un archivo .csv if formato == "csv": apts = open("apartamentos.csv", 'a') for item in items: it = "" # Filtro de comas for i in str(resultado[item]): if i != ",": it = it + i res = res + it + "," res = res[:len(res)-1] + "\n" apts.write(res) # Los resultados se guardarán en un archivo .txt elif formato == "txt": apts = open("apartamentos.txt", 'a') res = "" for item in items: res = res + str(resultado[item]) + " | " res = res[:len(res)-3] + "\n" apts.write(res) # Se cierra el archivo luego del proceso de escritura apts.close() print("Fin de la ejecución")
ajustes.py
# Sitio de Craigslist que será consultado site = input("Ingrese el sitio de Craigslist que será consultado\n") area = input("Ingrese el area en donde se hará la busqueda\n") # Ajustes de la busqueda min_price = input("Ingrese el precio minimo que está dispuesto a pagar\n") max_price = input("Ingrese el maximo precio que está dispuesto a pagar\n") limit = int(input("Ingrese el numero de resultados que desea obtener\n")) # Vecindarios num = int(input("Ingrese el numero de vecindarios que quiere consultar\n")) VECINDARIOS = [] while num > 0: vec = input("Ingrese el nombre de un vecindario\n") VECINDARIOS.append(vec) num -= 1 # Formato en el que se desea obtener los resultados formato = input("Ingrese el formato en el que quiere los resultados (csv/txt)\n")
Para utilizar el programa se deben tener ambos archivos (“scraper.py” y “ajustes.py”) en el mismo directorio y luego ejecutar “scraper.py”. El programa recibirá los detalles de la búsqueda que se quiere hacer y luego la hará. Los archivos se encuentran en Github para recibir contribuciones y para que puedan ser descargados y utilizados de forma sencilla.