En este módulo, exploraremos cómo funciona «tornet_scraper». Ya has aprendido a extraer datos de los foros «tornet» y «clearnet», pero para agilizar este proceso, necesitamos un rastreador web avanzado equipado con funciones para el rastreo de datos a gran escala.

Este tema puede parecer difícil para algunos, pero en la era de la IA, en la que gran parte de la programación se puede automatizar, y con este curso que aprovecha la IA para la ciberdefensa, crear una aplicación web para la extracción de datos es ahora más accesible.

Todas las aplicaciones web se basan en una pila tecnológica, una colección de herramientas de software que se utilizan para desarrollar los diferentes componentes de la aplicación. Por ejemplo, sitios web como Facebook cuentan con una interfaz de usuario (front-end) y una lógica del lado del servidor (back-end) que hace que las páginas sean accesibles. En algunos casos, el front-end y el back-end pueden utilizar el mismo lenguaje de programación. Por ejemplo, las aplicaciones web basadas en JavaScript pueden utilizar Vue para el front-end y Nuxt para el back-end, lo que simplifica el desarrollo, ya que ambos están escritos en JavaScript.

Para nuestros fines, necesitamos una pila tecnológica escalable y compatible con API que se pueda personalizar de forma independiente. Este módulo no profundizará en los conceptos de programación, ya que puedes copiar y pegar el código en tu herramienta de IA preferida para obtener una explicación personalizada que se adapte a tu estilo de aprendizaje.

Si prefieres saltarte este módulo y empezar con un proyecto práctico, puedes hacerlo, pero te perderás el aprendizaje del funcionamiento del proyecto, que será simplemente otra herramienta más. El proyecto final está disponible aquí:

https://github.com/CyberMounties/tornet_scraper

Los temas de esta sección incluyen lo siguiente:

  1. Cómo funcionan las plantillas
  2. Cómo funciona main.py
  3. Cómo funcionan las bases de datos
  4. Cómo funcionan las rutas
  5. Cómo funcionan los scrapers
  6. Cómo funcionan los servicios

Cómo funcionan las plantillas

Las aplicaciones web modernas utilizan plantillas, como una plantilla predeterminada o básica, para definir los elementos que aparecen en todas las páginas, como un menú de navegación o una barra de navegación. Aunque se podría copiar y pegar la barra de navegación en todas las páginas, cualquier cambio requeriría actualizar cada página individualmente.

Para agilizar este proceso, utilizamos un motor de plantillas. En las aplicaciones web de Python, Jinja2 nos permite crear una plantilla base con bloques de contenido para lograr un diseño coherente y eficiente.

Abre app/templates/base.html:

<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="en" data-theme="nord">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %} - Tornet Scraper</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css" />
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body class="min-h-screen bg-base-200">
    <!-- Navbar -->
    <div class="navbar bg-base-100 shadow-xl">
        <div class="navbar-start">
            <a class="btn btn-ghost text-xl">Tornet Scraper</a>
        </div>
        <div class="navbar-center hidden lg:flex">
            <ul class="menu menu-horizontal px-1">
                <li><a href="/" class="btn btn-ghost">Dashboard</a></li>
                <li><a href="/proxy-gen" class="btn btn-ghost">Proxy Gen</a></li>
                <li><a href="/manage-api" class="btn btn-ghost">API Management</a></li>
                <li><a href="/bot-profile" class="btn btn-ghost">Bot Profile</a></li>
                <li><a href="/marketplace-scan" class="btn btn-ghost">Marketplace Scan</a></li>
                <li><a href="/posts-scans" class="btn btn-ghost">Posts Scans</a></li>
                <li><a href="/watchlist" class="btn btn-ghost">Watchlist</a></li>
            </ul>
        </div>
        <div class="navbar-end">
            <div class="dropdown dropdown-end lg:hidden">
                <label tabindex="0" class="btn btn-ghost">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                    </svg>
                </label>
                <ul tabindex="0" class="menu menu-sm dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-52">
                    <li><a href="/">Dashboard</a></li>
                    <li><a href="/proxy-gen">Proxy Gen</a></li>
                    <li><a href="/manage-api">API Management</a></li>
                    <li><a href="/bot-profile">Bot Profile</a></li>
                    <li><a href="/marketplace-scan">Marketplace Scan</a></li>
                    <li><a href="/posts-scans" class="btn btn-ghost">Posts Scans</a></li>
                    <li><a href="/watchlist" class="btn btn-ghost">Watchlist</a></li>
                </ul>
            </div>
        </div>
    </div>

    <!-- Main content -->
    <main class="container mx-auto p-4 mt-6 bg-base-300 rounded-box">
        <!-- Flash Messages Container -->
        <div class="mb-4" id="flash-messages">
            {% if messages %}
                {% for message in messages %}
                    <div class="alert alert-{{ message.category | default('info') }} shadow-lg mb-4 flex justify-between items-center flash-message">
                        <div>
                            <span>{{ message.text }}</span>
                        </div>
                        <button class="btn btn-sm btn-circle btn-ghost" onclick="this.parentElement.remove()">✕</button>
                    </div>
                {% endfor %}
            {% endif %}
        </div>

        {% block content %}
        {% endblock %}
    </main>

    <script>
        // Automatically remove flash messages after 5 seconds
        document.addEventListener('DOMContentLoaded', function() {
            function removeFlashMessages() {
                const flashMessages = document.querySelectorAll('.flash-message');
                flashMessages.forEach(function(message) {
                    // Skip if already fading
                    if (message.style.opacity === '0') return;
                    setTimeout(function() {
                        message.style.transition = 'opacity 0.5s ease';
                        message.style.opacity = '0';
                        setTimeout(function() {
                            message.remove();
                        }, 500);
                    }, 5000);
                });
            }
            removeFlashMessages();
            const flashContainer = document.querySelector('#flash-messages');
            if (flashContainer) {
                const observer = new MutationObserver(function() {
                    removeFlashMessages();
                });
                observer.observe(flashContainer, { childList: true });
            }
        });
    </script>
</body>
</html>

La plantilla base.html es una plantilla base de Jinja2 que proporciona un diseño coherente para todas las páginas. Incluye una barra de navegación, mensajes flash y marcadores de posición para el título y el contenido que las plantillas secundarias pueden sobrescribir. A continuación se ofrece una explicación técnica de cómo funciona y su mecanismo de extensión.

  1. Estructura HTML:

    • Define un documento HTML5 estándar con un atributo data-theme=«nord» para el estilo DaisyUI.
    • Incluye recursos externos: CSS de DaisyUI, CSS de Tailwind y jQuery para el estilo y la interactividad.
  2. Bloque de título:

    • La etiqueta <title> contiene un bloque Jinja2 {% block title %}{% endblock %} - Tornet Scraper.
    • Las plantillas secundarias anulan el bloque title para establecer un título específico de la página, al que se añade « - Tornet Scraper».
    • Ejemplo: una plantilla secundaria con {% block title %}Dashboard{% endblock %} da como resultado <title>Dashboard - Tornet Scraper</title>.
  3. Barra de navegación:

    • Una barra de navegación adaptativa con un logotipo («Tornet Scraper») y enlaces de navegación (Panel de control, Generador de proxies, etc.).
    • En pantallas grandes (lg:flex), los enlaces se muestran horizontalmente; en pantallas más pequeñas, un menú desplegable (activado por un icono de hamburguesa) muestra los enlaces verticalmente.
  4. Mensajes flash:

    • Un <div id="flash-messages"> muestra los mensajes enviados desde el backend (por ejemplo, notificaciones de éxito o error).
    • Utiliza Jinja2: {% if messages %} recorre messages (una lista de objetos con text y category).
    • Cada mensaje tiene el estilo de una alerta de DaisyUI con una clase dinámica (alert-{{ message.category }}, cuyo valor predeterminado es info).
    • Un botón de cierre () elimina el mensaje al hacer clic.
    • JavaScript desvanece y elimina automáticamente los mensajes flash después de 5 segundos utilizando una transición CSS (opacity).
    • Un MutationObserver garantiza que los nuevos mensajes flash (añadidos dinámicamente) también se eliminen automáticamente.
  5. Bloque de contenido:

    • La sección <main> contiene un marcador de posición {% block content %}{% endblock %}.
    • Las plantillas secundarias anulan este bloque para insertar contenido específico de la página en el contenedor principal, con el estilo de las clases Tailwind/DaisyUI (container, mx-auto, etc.).
  6. Extensión en plantillas secundarias:

    • Las plantillas secundarias utilizan {% extends “base.html” %} para heredar la estructura base.
    • Anulan {% block title %} para el título de la página y {% block content %} para el contenido principal.
    • Esto renderiza el diseño completo de base.html con «Dashboard» en el título y el contenido especificado en la sección <main>.

Esta configuración garantiza una interfaz de usuario coherente, títulos dinámicos, navegación reutilizable y un manejo de mensajes flash fácil de usar en todas las páginas.

dashboard.html

El archivo ubicado en app/templates/dashboard.html proporciona un ejemplo claro de cómo se extiende base.html. Aunque este archivo no es estrictamente necesario, he adquirido el hábito de incluirlo. A continuación se muestra el código que contiene:

<!-- app/templates/dashboard.html -->
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}

{% block content %}
<h1 class="text-3xl font-bold text-base-content mb-4">Dashboard</h1>
{% endblock %}

¿Te fijas cómo se extiende «base.html»? El contenido dentro de «{% block content %}» se inserta en el contenedor de contenido principal definido en «base.html»:

    <!-- Main content -->
    <main class="container mx-auto p-4 mt-6 bg-base-300 rounded-box">
        {% block content %}
        {% endblock %}
    </main>

El título también se define:

{% block title %}Dashboard{% endblock %}

En «base.html», la estructura del título es:

<title>{% block title %}{% endblock %} - Tornet Scraper</title>

Al ver el código fuente de la página del panel de control, el título aparece como:

<title>Dashboard - Tornet Scraper</title>

En lugar de reescribir el título en cada página, puedes definirlo en base.html y luego solo definir el nombre de la página para todas las demás páginas.


Cómo funciona «main.py»

Puedes encontrar «main.py» en «app/main.py».

El archivo «main.py» es el núcleo de una aplicación web FastAPI, ya que define las rutas, las plantillas y las interacciones con la base de datos para una interfaz de scraping web. A continuación se ofrece una explicación técnica de las variables globales, los enrutadores incluidos y las funciones de controlador de rutas.

Variables globales

  1. app:

    • Propósito: La instancia principal de la aplicación FastAPI.
    • Detalles: Inicializa el marco FastAPI para gestionar las solicitudes y respuestas HTTP.
  2. BASE_DIR:

    • Propósito: Almacena la ruta absoluta del directorio raíz del proyecto.
    • Detalles: Se deriva utilizando os.path para localizar el directorio app/templates para las plantillas Jinja2.
  3. TEMPLATES_DIR:

    • Propósito: Especifica el directorio para las plantillas Jinja2.
    • Detalles: Se establece en app/templates dentro de la raíz del proyecto para renderizar plantillas HTML.
  4. templates:

    • Propósito: Instancia del motor de plantillas Jinja2.
    • Detalles: Configurado para cargar plantillas desde TEMPLATES_DIR para renderizar HTML dinámico.

Enrutadores incluidos

  1. proxy_gen_router:

    • Propósito: Registra rutas para la funcionalidad de generación de proxy.
    • Detalles: Importado desde app.routes.proxy_gen, gestiona los puntos finales relacionados con el proxy.
  2. manage_api_router:

    • Propósito: Registra rutas para la gestión de API.
    • Detalles: Importado desde app.routes.manage_api, gestiona las claves y la configuración de la API.
  3. bot_profile_router:

    • Finalidad: Registra rutas para la gestión de perfiles de bots.
    • Detalles: Importado desde app.routes.bot_profile, gestiona la configuración de los bots.
  4. marketplace_api_router:

    • Finalidad: Registra rutas para el escaneo del mercado.
    • Detalles: Importado desde app.routes.marketplace, gestiona la recuperación de datos del mercado.
  5. posts_api_router:

    • Finalidad: Registra rutas para el escaneo de publicaciones.
    • Detalles: Importado desde app.routes.posts, gestiona las operaciones de datos relacionadas con las publicaciones.
  6. watchlist_api_router:

    • Finalidad: Registra rutas para la gestión de la lista de seguimiento.
    • Detalles: Importado desde app.routes.watchlist, gestiona los elementos de la lista de seguimiento y los escaneos.

Funciones del controlador de rutas

  1. dashboard:

    • Propósito: Renderiza la página del panel de control.
    • Parámetros clave:
      • request: Objeto Request de FastAPI para el contexto de la plantilla.
      • db: Session de SQLAlchemy para acceder a la base de datos (a través de Depends(get_db)).
    • Devuelve: TemplateResponse que renderiza dashboard.html con mensajes flash de la sesión.
  2. proxy_gen:

    • Propósito: Muestra la página de generación de proxies con una lista de proxies.
    • Parámetros clave:
      • request: objeto Request de FastAPI.
      • db: Session de SQLAlchemy para consultar proxies.
    • Devuelve: TemplateResponse que renderiza proxy_gen.html con datos de proxy (nombre del contenedor, IP, nodo de salida de Tor, marca de tiempo, estado de ejecución) y mensajes flash.
  3. manage_api:

    • Propósito: Muestra la página de gestión de API con los detalles de la API.
    • Parámetros clave:
      • request: Objeto Request de FastAPI.
      • db: Session de SQLAlchemy para consultar las API.
    • Devuelve: TemplateResponse que renderiza manage_api.html con datos de la API (ID, nombre, proveedor, clave, modelo, tokens máximos, mensaje, marca de tiempo, estado activo) y mensajes flash.
  4. bot_profile:

    • Propósito: Muestra la página de gestión del perfil del bot.
    • Parámetros clave:
      • request: objeto FastAPI Request.
      • db: SQLAlchemy Session para consultar perfiles de bots y la última URL onion.
    • Devuelve: TemplateResponse que renderiza bot_profile.html con datos del perfil del bot (ID, nombre de usuario, contraseña enmascarada, propósito, proxy Tor, marca de tiempo), la última URL onion y mensajes flash.
  5. marketplace:

    • Propósito: Renderiza la página de escaneo del mercado con paginación y datos de escaneo de publicaciones.
    • Parámetros clave:
      • request: Objeto Request de FastAPI.
      • db: Session de SQLAlchemy para consultar escaneos.
    • Devuelve: TemplateResponse que muestra marketplace.html con escaneos paginados, escaneos publicados y mensajes mostrados.
  6. posts_scans:

    • Propósito: Muestra la página de resumen de escaneos publicados.
    • Parámetros clave:
      • request: objeto Request de FastAPI.
      • db: SQLAlchemy Session para acceder a la base de datos.
    • Devuelve: TemplateResponse que renderiza posts_scans.html con mensajes flash.
  7. posts_scan_result:

    • Propósito: Muestra los resultados de un escaneo de publicaciones específico.
    • Parámetros clave:
      • scan_id: ID entero del escaneo.
      • request: objeto Request de FastAPI.
      • db: Session de SQLAlchemy para acceder a la base de datos.
      • name: nombre opcional del escaneo (cadena, por defecto vacío).
    • Devuelve: TemplateResponse que renderiza posts_scan_result.html con el ID del escaneo, el nombre y los mensajes emergentes.
  8. watchlist:

    • Propósito: Representa la página de resumen de la lista de seguimiento.
    • Parámetros clave:
      • request: Objeto Request de FastAPI.
      • db: Session de SQLAlchemy para acceder a la base de datos.
    • Devuelve: TemplateResponse que representa watchlist.html con mensajes flash.
  9. watchlist_profile:

    • Propósito: Muestra el perfil de un elemento específico de la lista de seguimiento y los escaneos asociados.
    • Parámetros clave:
      • target_id: ID entero del elemento de la lista de seguimiento.
      • request: objeto Request de FastAPI.
      • db: Session de SQLAlchemy para consultar el elemento de la lista de seguimiento y los escaneos.
    • Devuelve: TemplateResponse que renderiza watchlist_profile.html con el elemento de la lista de seguimiento, los datos del escaneo y los mensajes flash; lanza una HTTPException (404 si falta el elemento, 500 si hay errores del servidor).

Cómo funcionan las bases de datos

En nuestra aplicación FastAPI, db.py gestiona las configuraciones del motor de la base de datos, mientras que models.py define el esquema y las tablas de la base de datos. El uso de un archivo db.py independiente permite cambiar fácilmente entre tipos de bases de datos. Aunque tornet_scraper utiliza SQLite3 para la creación de prototipos y las pruebas, no es ideal para el scraping de datos a gran escala. Para entornos de producción, recomendamos pasar a una base de datos más robusta, como PostgreSQL.

También puedes implementar lógica en db.py para cambiar dinámicamente entre SQLite3 y un servidor de base de datos en función de las condiciones o el entorno (por ejemplo, desarrollo, pruebas o producción).

db.py

El archivo db.py configura la conexión a la base de datos y la gestión de sesiones para una aplicación FastAPI que utiliza SQLAlchemy. A continuación se ofrece una explicación concisa de sus componentes y funcionalidad.

Puede encontrar db.py en app/database/db.py.

  1. Propósito:

    • Configura una conexión a la base de datos SQLite, crea tablas y proporciona una dependencia para las sesiones de la base de datos en las rutas FastAPI.
  2. Variables globales:

    • BASE_DIR:
      • Propósito: Define el directorio raíz del proyecto.
      • Detalles: Utiliza Path(__file__).resolve().parent.parent.parent para calcular la ruta absoluta a la raíz del proyecto, asegurando que el archivo de la base de datos se encuentra en la ubicación correcta.
    • DATABASE_URL:
      • Propósito: Especifica la cadena de conexión a la base de datos.
      • Detalles: Configurado para SQLite como sqlite:///{BASE_DIR}/tornet_scraper.db, apuntando a un archivo tornet_scraper.db en la raíz del proyecto.
    • engine:
      • Propósito: Motor SQLAlchemy para interacciones con la base de datos.
      • Detalles: Creado con create_engine(DATABASE_URL, connect_args={«check_same_thread»: False}) para gestionar las conexiones SQLite. El argumento check_same_thread permite utilizar SQLite en un entorno FastAPI multihilo.
    • SessionLocal:
      • Finalidad: Fábrica para crear sesiones de base de datos.
      • Detalles: Configurado con sessionmaker(autocommit=False, autoflush=False, bind=engine) para crear sesiones vinculadas al motor, con control manual de confirmación y vaciado.
  3. Funciones:

    • get_db:
      • Propósito: Proporciona una sesión de base de datos para las rutas FastAPI.
      • Parámetros clave: Ninguno (función de dependencia).
      • Devuelve: Devuelve una sesión SQLAlchemy (db) y garantiza que se cierre después de su uso.
      • Detalles: Se utiliza como dependencia FastAPI (a través de Depends(get_db)) para inyectar una sesión en los controladores de rutas. El bloque try/finally garantiza que la sesión se cierre, evitando fugas de recursos.
    • init_db:
      • Propósito: Inicializa la base de datos creando tablas.
      • Parámetros clave: Ninguno.
      • Devuelve: Ninguno.
      • Detalles: Importa Base desde models.py y llama a Base.metadata.create_all(bind=engine) para crear todas las tablas definidas (por ejemplo, proxies, apis) en la base de datos SQLite si no existen.
  4. Uso:

    • Propósito: Habilita las operaciones de la base de datos en la aplicación.
    • Detalles: init_db se llama en main.py para configurar el esquema de la base de datos al inicio. get_db se utiliza en los controladores de rutas (por ejemplo, /proxy-gen, /manage-api) para consultar o modificar datos en las tablas definidas en models.py. La sesión garantiza la integridad de las transacciones y la limpieza adecuada de los recursos.

models.py

El archivo models.py define el esquema de la base de datos para una aplicación FastAPI utilizando SQLAlchemy, especificando tablas, columnas y relaciones. A continuación se ofrece una explicación concisa de sus componentes y funcionalidad.

Puede encontrar models.py en app/database/models.py.

  1. Descripción general de SQLAlchemy:

    • Propósito: SQLAlchemy es una biblioteca ORM (mapeo objeto-relacional) para Python que permite la interacción con una base de datos relacional utilizando objetos Python.
    • Detalles: Mapea clases de Python a tablas de bases de datos, lo que permite operaciones CRUD a través de una sintaxis orientada a objetos. Utiliza declarative_base para crear una clase base (Base) para las definiciones de modelos.
  2. Columnas:

    • Propósito: Representan campos en una tabla de base de datos.
    • Detalles: Se definen utilizando la clase Column de SQLAlchemy, especificando atributos como el tipo de datos (por ejemplo, Integer, String, DateTime), restricciones (por ejemplo, primary_key, unique, nullable) y valores predeterminados (por ejemplo, datetime.utcnow). Las columnas almacenan los datos de cada registro de una tabla.
  3. Tablas:

    • Finalidad: Representar tablas de bases de datos que almacenan datos estructurados.
    • Detalles: Cada clase (por ejemplo, Proxy, APIs) hereda de Base y define una tabla mediante __tablename__. Las tablas incluyen columnas y restricciones opcionales (por ejemplo, UniqueConstraint para combinaciones únicas de campos). Algunos ejemplos son proxies, apis, bot_profiles, etc.
  4. Relaciones:

    • Finalidad: Definen asociaciones entre tablas para consultas relacionales.
    • Detalles: Se establecen mediante columnas ForeignKey (por ejemplo, MarketplacePost.scan_id hace referencia a marketplace_post_scans.id). Las relaciones permiten unir tablas, como vincular MarketplacePost a MarketplacePostScan a través de scan_id. SQLAlchemy las maneja como referencias de objetos en las consultas.
  5. Enums:

    • Propósito: definir vocabularios controlados para campos específicos.
    • Detalles: Utiliza enum.Enum de Python (por ejemplo, BotPurpose, ScanStatus) para restringir los valores de las columnas a opciones predefinidas (por ejemplo, SCRAPE_MARKETPLACE, RUNNING). Se almacenan como cadenas en la base de datos mediante Enum.
  6. Componentes clave:

    • Base: La clase base de SQLAlchemy para todos los modelos, utilizada para generar esquemas de tablas.
    • Definiciones de tablas: Clases como Proxy, APIs, BotProfile, etc., definen tablas para almacenar detalles de proxies, configuraciones de API, perfiles de bots, URL de onion, escaneos de marketplaces, publicaciones y listas de seguimiento.
    • Restricciones: Las restricciones únicas (por ejemplo, UniqueConstraint en MarketplacePost) garantizan la integridad de los datos al evitar registros duplicados basados en combinaciones específicas de columnas.
    • Marcas de tiempo: Muchas tablas incluyen una columna timestamp (por defecto datetime.utcnow) para realizar un seguimiento de las horas de creación/actualización de los registros.
  7. Uso:

    • Finalidad: La aplicación FastAPI utiliza los modelos para interactuar con la base de datos.
    • Detalles: Las rutas main.py consultan estos modelos (a través de sesiones SQLAlchemy) para recuperar o almacenar datos, que luego se pasan a las plantillas Jinja2 para su representación o se procesan en la lógica de la API. Por ejemplo, los datos Proxy se consultan en la ruta /proxy-gen para mostrar los detalles del proxy.

Cómo funcionan las rutas

En el desarrollo de API, es fundamental organizar los puntos finales de la API de forma eficaz, ya que no es práctico enrutar todas las solicitudes a través de un único punto final.

Por ejemplo, el formato /api/posts/get-post indica que /api/posts/* está dedicado a operaciones relacionadas con las publicaciones, como ver, eliminar o modificar publicaciones. Todas las llamadas a la API relacionadas tienen el prefijo /api/posts/.

Dado que nuestra aplicación incluye funciones como la generación de proxies, la gestión de bots, el scraping de mercados, el scraping de publicaciones, la supervisión de amenazas y mucho más, es esencial organizar correctamente los puntos finales.

Puedes encontrar todas las rutas en app/routes/*.py, que contiene seis archivos Python. A continuación se muestra un ejemplo de bot_profile.py:

# app/routes/bot_profile.py
import logging
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.database.models import BotProfile, OnionUrl, BotPurpose, APIs
from app.database.db import get_db
from typing import Optional
from app.services.tornet_forum_login import login_to_tor_website
from app.services.gen_random_ua import gen_desktop_ua


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


bot_profile_router = APIRouter(prefix="/api/bot-profile", tags=["API", "Bot Profile Management"])


# Pydantic models for validation
class BotProfileCreate(BaseModel):
    username: str
    password: str
    purpose: str
    tor_proxy: Optional[str] = None
    session: Optional[str] = None


class BotProfileUpdate(BaseModel):
    username: Optional[str] = None
    password: Optional[str] = None
    purpose: Optional[str] = None
    tor_proxy: Optional[str] = None
    user_agent: Optional[str] = None
    session: Optional[str] = None


class OnionUrlCreate(BaseModel):
    url: str


# Get all bot profiles
@bot_profile_router.get("/list")
async def get_bot_profiles(db: Session = Depends(get_db)):
    try:
        profiles = db.query(BotProfile).all()
        return [
            {
                "id": p.id,
                "username": p.username,
                "password": "********",
                "actual_password": p.password,
                "purpose": p.purpose.value,
                "tor_proxy": p.tor_proxy,
                "has_session": bool(p.session and len(p.session) > 0),
                "session": p.session,
                "user_agent": p.user_agent,
                "timestamp": p.timestamp.isoformat()
            } for p in profiles
        ]
    except Exception as e:
        logger.error(f"Error fetching bot profiles: {str(e)}")
        raise HTTPException(status_code=500, detail="Internal server error")

Utilizamos el registro para realizar un seguimiento de los eventos dentro de la aplicación. El registro es fundamental para comprender lo que está sucediendo en su aplicación web; sin él, estará operando a ciegas. Aunque los registros se pueden guardar en un archivo, yo suelo evitarlo durante el desarrollo local, pero lo recomiendo para entornos de producción.

Sin embargo, el aspecto más crítico es la creación de enrutadores:

bot_profile_router = APIRouter(prefix="/api/bot-profile", tags=["API", "Bot Profile Management"])

Este enrutador establece un prefijo de punto final, /api/bot-profile, que utilizarán todos los puntos finales relacionados.

Por ejemplo, la siguiente función recupera todos los perfiles de bot de la tabla BotProfile:

@bot_profile_router.get("/list")
async def get_bot_profiles(db: Session = Depends(get_db)):

Cuando utilizas @bot_profile_router.get(«/list»), el punto final se antepone con /api/bot-profile, lo que da como resultado /api/bot-profile/list.

Esta estructura permite organizar de forma eficiente cientos o miles de rutas API. El bot_profile_router se importa a main.py y se registra.


Cómo funcionan los scrapers

Todos los scrapers se almacenan en app/scrapers/*.py.

Estos archivos suelen importarse como módulos en otras partes del código base. Por ejemplo, dentro de marketplace_scraper.py, la función create_pagination_batches toma como entrada la URL de una página web, como por ejemplo:

http://site.url/pagination?page=1

Crea un lote de paginaciones como este:

http://site.url/pagination?page=1
http://site.url/pagination?page=2
http://site.url/pagination?page=3
--- snip ---
http://site.url/pagination?page=10

Este enfoque es eficaz para la distribución de tareas, como asignar cada lote de 10 páginas de paginación a un solo bot para su scraping.


Cómo funcionan los servicios

Todos los servicios se encuentran en app/services/*.py.

Los servicios también se utilizan como módulos, pero tienen una finalidad distinta, ya que se encargan de tareas como:

  1. Gestionar Docker para los proxies
  2. Generar proxies
  3. Realizar inicios de sesión
  4. Omitir CAPTCHAs

Como aprenderás más adelante, utilizamos contenedores Docker para crear proxies Tor localmente. Cuando un proxy ya no es necesario, debe eliminarse. Para verificar si los proxies se están ejecutando, puedes utilizar una función como container_running de container_status.py para comprobar el estado del contenedor Docker.