Hace un tiempo estaba buscando departamento para alquilar y me frustraba entrar periódicamente a las páginas de propiedades sólo para encontrarme una y otra vez con los mismos inmuebles.
Buscar un departamento es una tarea tediosa y estresante, por eso, se me ocurrió dar vuelta el problema y transformarlo en algo divertido: crear un bot que busque las propiedades y avise vía chat cuando aparece una nueva.
El código es relativamente sencillo, menos de 100 líneas de python, pero de todos modos vamos a seguirlo paso por paso para ver qué es lo que hace.
URLS y Main
Primero notamos que tenemos un set
con las URLs de búsqueda:
urls = {
"https://www.argenprop.com/departamento-alquiler-barrio-chacarita-hasta-25000-pesos-orden-masnuevos",
"https://www.zonaprop.com.ar/departamento-alquiler-villa-crespo-con-terraza-orden-publicado-descendente.html",
}
Estas son simplemente URLs con resultados de búsqueda de los sitios soportados por nuestro script. En este caso departamentos en Chacarita hasta 25.000 pesos y en Villa Crespo con terraza (sin límite de precio). Ambas en distintos portales de búsqueda. Es importante que los resultados esten ordenados por "más nuevos"
La función principal (main
) define los pasos a seguir:
for url in urls: # 1
res = requests.get(url) # 2
ads = list(extract_ads(url, res.text)) # 3
seen, unseen = split_seen_and_unseen(ads) # 4
print("{} seen, {} unseen".format(len(seen), len(unseen)))
for u in unseen: # 5
notify(u)
mark_as_seen(unseen) # 6
- Para cada una de nuestras URLs definidas previamente
- Ir a buscar los resultados
- Extraer los links de los ads
- Dividir links en vistos y no vistos
- Para los no vistos, notificar por mensaje
- Marcar los no vistos como vistos
De acá nos enfocamos en las partes más interesantes:
Extraer links de los ads
Cada página tiene su estructura propia, para eso definimos la dataclass Parser
:
@dataclass
class Parser:
website: str
link_regex: str
def extract_links(self, contents: str):
soup = BeautifulSoup(contents, "lxml")
ads = soup.select(self.link_regex)
for ad in ads:
href = ad["href"]
_id = sha1(href.encode("utf-8")).hexdigest()
yield {"id": _id, "url": "{}{}".format(self.website, href)}
parsers = [
Parser(website="https://www.zonaprop.com.ar", link_regex="a.go-to-posting"),
Parser(website="https://www.argenprop.com", link_regex="div.listing__items div.listing__item a"),
Parser(website="https://inmuebles.mercadolibre.com.ar", link_regex="li.results-item .rowItem.item a"),
]
Como vemos, cada Parser
actúa sobre un dominio en particular de los que soportamos, porque cada página tiene su estructura de HTML propia.
Con beautifulsoup podemos usar CSS queries para movernos por el markup y obtener los links de los avisos que nos interesan.
Una vez extraídos los devolvemos junto con un id
que generamos hasheando la URL del aviso (esto será útil para marcar los ya vistos).
Recordar Links vistos
El bot no sería muy útil si reportara todo el tiempo lo mismo, deberíamos buscar una forma de "recordar" qué avisos ya envíamos.
Una forma sencilla es un archivo de texto seen.txt
con los id
s de los avisos reportados:
def split_seen_and_unseen(ads):
history = get_history()
seen = [a for a in ads if a["id"] in history]
unseen = [a for a in ads if a["id"] not in history]
return seen, unseen
def get_history():
try:
with open("seen.txt", "r") as f:
return {l.rstrip() for l in f.readlines()}
except:
return set()
También agregamos una función que nos permita distinguir los que ya mandamos (seen) de los nuevos (unseen).
Notificaciones
def notify(ad):
bot = "YOUR_BOT_ID"
room = "YOUR_CHAT_ROOM_ID"
url = "https://api.telegram.org/bot{}/sendMessage?chat_id={}&text={}".format(bot, room, ad["url"])
r = requests.get(url)
Luego, usando un simple request de HTTP posteamos la url del aviso a un grupo de Telegram.
Gracias a la función de "preview" de Telegram, los ads se muestran con una imagen de la propiedad:
Por último sólo nos queda agregar los avisos notificados a seen.txt
y listo.
Hosting
Ahora sólo necesitamos una máquina donde correrlo periódicamente. Por suerte AWS tiene un free tier que nos brinda una máquina básica por un mes, gratis.
Si bien la virtual no es muy poderosa, para este simple script basta y sobra. Yo lo tengo corriendo con un cronjob que lo ejecuta cada 5 minutos:
# m h dom mon dow command
*/5 * * * * /home/ubuntu/propfinder/search_props.sh
Conclusión
Con 100 líneas de scripting pudimos hacer un scrapper multi-sitio, que nos envía propiedades por chat como máximo a 5 minutos de ser publicadas.
Mucho más divertido (y menos frustrante) que meterse periódicamente a los distintos sitios a ver si hay alguna novedad.
Top comments (3)
Pregunta de inexperto : No vi que hayas usado alguna linea para indicarle a la website que supuestamente estás accediendo desde un browser común. Te bloquearon la IP o algo si es que lo dejaste corriendo desde tu propia compu? Mande un mail a argenprop y zona prop y me tiraron que supuestamente eso te hacen y además en los "términos y condiciones" de la website había algo como "no usar de la manera que no está intencionada para usarla" . Nada, si no hay problema a mi me da lo mismo xq cualquiera... Si estuviera usando lo recolectado para armar una website, digamos, "clon" lo entiendo, no da... pero para uso personal ....
Hola, espectacular, felicitaciones! una pregunta, sabes como haher para que funcione zonaprop? no me anda el parseo
Muchas gracias de antemano!
Para el objeto Parser de ZonaProp, usá este link_regex:
"h2.sc-i1odl-10 a.sc-i1odl-11.hDkIKM"
El que estaba anteriormente ("a.go-to-posting") por lo visto era de una versión anterior de la página.
Espero que te sirva, abrazo!