-
-
Save fernandezpablo85/7ecea67eda79ae474895a07840fb56b6 to your computer and use it in GitHub Desktop.
[[source]] | |
name = "pypi" | |
url = "https://pypi.org/simple" | |
verify_ssl = true | |
[dev-packages] | |
[packages] | |
requests = "*" | |
beautifulsoup4 = "*" | |
lxml = "*" | |
[requires] | |
python_version = "3.7" |
import requests | |
from bs4 import BeautifulSoup | |
from hashlib import sha1 | |
from urllib.parse import urlparse | |
from dataclasses import dataclass | |
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", | |
} | |
@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"), | |
] | |
def _main(): | |
for url in urls: | |
res = requests.get(url) | |
ads = list(extract_ads(url, res.text)) | |
seen, unseen = split_seen_and_unseen(ads) | |
print("{} seen, {} unseen".format(len(seen), len(unseen))) | |
for u in unseen: | |
notify(u) | |
mark_as_seen(unseen) | |
def extract_ads(url, text): | |
uri = urlparse(url) | |
parser = next(p for p in parsers if uri.hostname in p.website) | |
return parser.extract_links(text) | |
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() | |
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) | |
def mark_as_seen(unseen): | |
with open("seen.txt", "a+") as f: | |
ids = ["{}\n".format(u["id"]) for u in unseen] | |
f.writelines(ids) | |
if __name__ == "__main__": | |
_main() |
Llegué acá por tuiter y me pareció genial! Gracias.
Consulta: qué ventaja tiene pensar el Parser como una clase en lugar de armar todo el programa con funciones? No vengo de la programacion y en gral me resulta mas intuitivo pensar en funciones que en clases.
Llegué acá por tuiter y me pareció genial! Gracias.
Consulta: qué ventaja tiene pensar el Parser como una clase en lugar de armar todo el programa con funciones? No vengo de la programacion y en gral me resulta mas intuitivo pensar en funciones que en clases.
Buenas. Absolutamente nada, se puede hacer con una función (que recibiría el body + los 2 argumentos que pasas en el constructor). Lo tenía hecho así pero lo cambié para probar (y capaz ayudar a que gente conozca) el concepto de dataclass
que me pareció piola
Hola! Un amigo me compartió tu blogpost hace unas semanas, está genial la idea. Decidí reimplementarlo en Ruby (porque me es más familiar) y agregarle algunas cosas, como que guarde los IDs en Drive para que sea independiente de la máquina donde se corre. Implementé también un sistema de puntaje que anda bastante mal 😆. Lo comparto por si a alguien le interesa =) https://github.com/lipusal/apts
Muchisimas gracias por esto! Para los proximos que se pasen, Zonaprops ya no te deja acceder al catálogo mediante el uso de requests.get()
pero usando cloudscraper anda joya!
CloudScraper me da este error:
cloudscraper.exceptions.CloudflareChallengeError: Detected a Cloudflare version 2 Captcha challenge, This feature is not available in the opensource (free) version.
¿Hay que cambiar de Scraper o se puede usar ingresando algun token gratuito?
CloudScraper me da este error: cloudscraper.exceptions.CloudflareChallengeError: Detected a Cloudflare version 2 Captcha challenge, This feature is not available in the opensource (free) version.
¿Hay que cambiar de Scraper o se puede usar ingresando algun token gratuito?
En su momento busqué algo gratuito y no encontré nada pero si resolves el captcha por tu cuenta (podés hacer que te aparezca ingresando usando Tor posiblemente), te agrega una cookie que indica que sos humano. Podés agregar esa cookie a la request y debería funcionar. Esto lo hice hace mucho tiempo con otra pág y funcionó, espero que tengas la misma suerte!
De paso te dejo este repo mío que tiene otros scrapers aparte del de Zonaprop y otras cosas que pueden servirte.
https://github.com/mauroeparis/scrappdept
genial Pablo. A ver si lo adapto para busquedas de cualquier cosa. For example ahora busco macbooks usadas.