Implement web crawler with database integration and URL extraction

This commit is contained in:
2024-12-19 23:38:58 +01:00
parent 667c127214
commit abbc23ae46
3 changed files with 153 additions and 8 deletions
Binary file not shown.
+144
View File
@@ -0,0 +1,144 @@
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from sqlalchemy.orm import sessionmaker
from crawler_db_model import WebsPorVisitar, WebsVisitadas, engine
from dotenv import load_dotenv
import os
import hashlib
import re
# Cargar variables de entorno
load_dotenv()
# Crear sesión de base de datos
Session = sessionmaker(bind=engine)
session = Session()
def extract_urls(url):
"""Extrae todas las URLs de una página web."""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
return [], response.status_code
soup = BeautifulSoup(response.text, 'html.parser')
urls = set()
for link in soup.find_all('a', href=True):
full_url = urljoin(url, link['href'])
parsed_url = urlparse(full_url)
# Filtrar URLs inválidas o fuera de dominio
if parsed_url.scheme in ['http', 'https']:
urls.add(full_url)
return list(urls), response.status_code
except Exception as e:
print(f"Error al procesar {url}: {e}")
return [], None
def hash_content(content):
"""Genera un hash SHA-256 del contenido para verificar duplicados."""
return hashlib.sha256(content.encode('utf-8')).hexdigest()
def generate_summary(soup):
"""Genera un resumen más detallado del contenido de la página."""
# Extraer título
titulo = soup.title.string if soup.title else "Sin título"
# Extraer meta descripción
meta_description = soup.find('meta', attrs={'name': 'description'})
descripcion = meta_description['content'] if meta_description else "No hay descripción disponible."
# Extraer encabezados principales
headers = {
'h1': [h1.get_text(strip=True) for h1 in soup.find_all('h1')],
'h2': [h2.get_text(strip=True) for h2 in soup.find_all('h2')],
'h3': [h3.get_text(strip=True) for h3 in soup.find_all('h3')]
}
# Extraer texto de párrafos principales
paragraphs = [p.get_text(strip=True) for p in soup.find_all('p')][:5]
# Resumen estructurado
summary = {
"titulo": titulo,
"descripcion": descripcion,
"encabezados": headers,
"parrafos": paragraphs
}
return summary
def process_url(url):
"""Procesa una URL: extrae datos y las inserta en las tablas correspondientes."""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
urls, status_code = extract_urls(url)
# Obtener datos del sitio
response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
return
soup = BeautifulSoup(response.text, 'html.parser')
dominio = urlparse(url).netloc
summary = generate_summary(soup)
ip = requests.get(f"https://api.ipify.org?domain={dominio}&format=json").json().get('ip', "")
contenido_hash = hash_content(response.text)
# Insertar en WebsVisitadas
visitada = WebsVisitadas(
url=url,
dominio=dominio,
titulo=summary['titulo'],
resumen=f"Descripción: {summary['descripcion']}, Encabezados: {summary['encabezados']}, Párrafos: {summary['parrafos']}",
ip=ip,
codigo_http=status_code,
contenido_hash=contenido_hash
)
session.add(visitada)
# Insertar nuevas URLs en WebsPorVisitar si no están repetidas
for new_url in urls:
if not session.query(WebsPorVisitar).filter_by(url=new_url).first() and \
not session.query(WebsVisitadas).filter_by(url=new_url).first():
session.add(WebsPorVisitar(url=new_url))
session.commit()
except Exception as e:
print(f"Error procesando {url}: {e}")
session.rollback()
def crawl():
"""Crawl principal que procesa URLs hasta agotar la lista de webs por visitar."""
while True:
# Obtener la siguiente URL a visitar
web_por_visitar = session.query(WebsPorVisitar).first()
if not web_por_visitar:
print("No hay más webs por visitar.")
break
url = web_por_visitar.url
print(f"Visitando: {url}")
# Procesar la URL y eliminarla de la lista de pendientes
process_url(url)
session.delete(web_por_visitar)
session.commit()
if __name__ == "__main__":
# URL inicial
url_inicial = "http://example.com"
# Insertar URL inicial si no existe
if not session.query(WebsPorVisitar).filter_by(url=url_inicial).first():
session.add(WebsPorVisitar(url=url_inicial))
session.commit()
# Iniciar el crawler
crawl()
@@ -2,6 +2,8 @@ from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, B
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime
from dotenv import load_dotenv
import os
Base = declarative_base()
@@ -27,18 +29,17 @@ class WebsVisitadas(Base):
contenido_hash = Column(String(64), nullable=True) # Hash del contenido para detectar cambios
es_dinamico = Column(Boolean, default=False) # Si es una página generada dinámicamente
# Cargar variables de entorno desde el archivo .env
load_dotenv()
# Obtener la URL de la base de datos desde las variables de entorno
database_url = os.getenv('DATABASE_URL')
# Crear el motor de base de datos y las tablas
engine = create_engine('postgresql://usuario:password@localhost:5432/nombre_bd')
engine = create_engine(database_url)
Base.metadata.create_all(engine)
# Crear una sesión
Session = sessionmaker(bind=engine)
session = Session()
# Ejemplo de cómo agregar una entrada
if __name__ == "__main__":
nueva_web_por_visitar = WebsPorVisitar(url="http://example.com")
session.add(nueva_web_por_visitar)
session.commit()
print("Nueva URL agregada a 'webs_por_visitar'.")