En esta sección, aprenderemos a extraer publicaciones del mercado de vendedores y distribuir las tareas de extracción entre varios bots para que se ejecuten simultáneamente.
El objetivo es dividir las tareas entre los bots porque la mayoría de los foros imponen límites de velocidad en el número de solicitudes que se pueden enviar. En tornet_forum, no hay límites de velocidad para navegar por la paginación y se puede pasar de una página a otra sin necesidad de iniciar sesión.
Sin embargo, para prepararnos para diversos mecanismos de protección, utilizaremos bots con sesiones activas para el rastreo. Aunque no es necesario para tornet_forum, es posible que las sesiones iniciadas sean necesarias para otros sitios de destino con los que te encuentres. Habiendo rastreado datos de dichos sitios, entiendo los retos a los que te puedes enfrentar, y este enfoque te prepara para cualquier escenario.
Los temas de esta sección incluyen lo siguiente:
- Modelos de base de datos
- Módulos de scraping de Marketplace
- Rutas de backend de Marketplace
- Plantilla de frontend de Marketplace
- Pruebas
Modelos de base de datos
Tus modelos se encuentran en app/database/models.py. Necesitas 4 modelos para organizar correctamente los datos:
class MarketplacePaginationScan(Base):
__tablename__ = "marketplace_pagination_scans"
id = Column(Integer, primary_key=True, index=True)
scan_name = Column(String, nullable=False)
pagination_url = Column(String, nullable=False)
max_page = Column(Integer, nullable=False)
batches = Column(Text, nullable=True)
timestamp = Column(DateTime, default=datetime.utcnow)
class ScanStatus(enum.Enum):
RUNNING = "running"
COMPLETED = "completed"
STOPPED = "stopped"
class MarketplacePostScan(Base):
__tablename__ = "marketplace_post_scans"
id = Column(Integer, primary_key=True, index=True)
scan_name = Column(String, nullable=False, unique=True)
pagination_scan_name = Column(String, ForeignKey("marketplace_pagination_scans.scan_name"), nullable=False)
start_date = Column(DateTime(timezone=True), default=datetime.utcnow)
completion_date = Column(DateTime(timezone=True), nullable=True)
status = Column(Enum(ScanStatus), default=ScanStatus.STOPPED, nullable=False)
timestamp = Column(DateTime, default=datetime.utcnow)
class MarketplacePost(Base):
__tablename__ = "marketplace_posts"
id = Column(Integer, primary_key=True, index=True)
scan_id = Column(Integer, ForeignKey("marketplace_post_scans.id"), nullable=False)
timestamp = Column(String, nullable=False)
title = Column(String, nullable=False)
author = Column(String, nullable=False)
link = Column(String, nullable=False)
__table_args__ = (UniqueConstraint('scan_id', 'timestamp', name='uix_scan_timestamp'),)
Para esta funcionalidad, necesitamos varios modelos que realicen todas las siguientes tareas:
-
MarketplacePaginationScan:- Finalidad: Representa una configuración de exploración de paginación para extraer datos de un mercado. Almacena detalles sobre una exploración que enumera las páginas de un mercado, como la URL base y el número máximo de páginas que se pueden explorar.
- Campos clave:
id: Identificador único del escaneo.scan_name: Nombre único del escaneo de paginación.pagination_url: URL base utilizada para la paginación.max_page: Número máximo de páginas que se pueden escanear.batches: Almacena datos de lotes serializados (por ejemplo, JSON) para procesar páginas.timestamp: Registra cuándo se creó el escaneo.
-
ScanStatus (Enum):- Propósito: Define los estados posibles de un escaneo posterior, utilizado para rastrear el estado de un
MarketplacePostScan. - Valores:
RUNNING: El escaneo está actualmente en curso.COMPLETED: El escaneo ha finalizado correctamente.STOPPED: El escaneo no se está ejecutando (por defecto o detenido manualmente).
- Propósito: Define los estados posibles de un escaneo posterior, utilizado para rastrear el estado de un
-
MarketplacePostScan:- Propósito: Representa un escaneo que recopila publicaciones de un mercado, vinculado a un escaneo de paginación específico. Realiza un seguimiento de los metadatos y el estado del escaneo.
- Campos clave:
id: Identificador único de la exploración de publicaciones.scan_name: Nombre único de la exploración de publicaciones.pagination_scan_name: Hace referencia a laMarketplacePaginationScanasociada por suscan_name.start_date: Fecha de inicio de la exploración.completion_date: Fecha de finalización de la exploración (si procede).status: Estado actual del escaneo (de la enumeraciónScanStatus).timestamp: Registra cuándo se creó el escaneo.
-
MarketplacePost:- Propósito: Almacena las publicaciones individuales recopiladas durante un
MarketplacePostScan. Cada publicación está vinculada a un escaneo específico e incluye detalles sobre la publicación. - Campos clave:
id: Identificador único de la publicación.scan_id: Hace referencia alMarketplacePostScanal que pertenece esta publicación.timestamp: Marca de tiempo de la publicación (como cadena).title: Título de la publicación del marketplace.author: Autor de la publicación.link: URL de la publicación.__table_args__: Garantiza la unicidad de las publicaciones basándose enscan_idytimestamppara evitar duplicados.
- Propósito: Almacena las publicaciones individuales recopiladas durante un
Módulos del rastreador del mercado
Para ver un ejemplo de cómo funciona el rastreador del mercado, abre app/scrapers/marketplace_scraper.py.
import json
import requests
from bs4 import BeautifulSoup
import logging
# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def create_pagination_batches(url_template, max_page):
"""
Given a web URL with max pagination number, this function returns batches of 10 pagination ranges.
"""
if max_page < 1:
return json.dumps({})
all_urls = [url_template.format(page=page) for page in range(max_page, 0, -1)]
batch_size = 10
batches = {f"{i//batch_size + 1}": all_urls[i:i + batch_size] for i in range(0, len(all_urls), batch_size)}
return json.dumps(batches)
def scrape_posts(session, proxy, useragent, pagination_range, timeout=30):
"""
Given a list of web pages, it scraps all post details from every pagination page.
"""
posts = {}
headers = {'User-Agent': useragent}
proxies = {'http': proxy, 'https': proxy} if proxy else None
for url in pagination_range:
logger.info(f"Scraping URL: {url}")
try:
response = session.get(url, headers=headers, proxies=proxies, timeout=timeout)
logger.info(f"Response status code: {response.status_code}")
response.raise_for_status()
# Log response size and snippet
logger.debug(f"Response size: {len(response.text)} bytes")
logger.debug(f"Response snippet: {response.text[:200]}...")
soup = BeautifulSoup(response.text, 'html.parser')
table = soup.select_one('table.table-dark tbody')
if not table:
logger.error(f"No table found on {url}")
continue
table_rows = table.select('tr')
logger.info(f"Found {len(table_rows)} table rows on {url}")
for row in table_rows[:10]:
try:
title = row.select_one('td:nth-child(1)').text.strip()
author = row.select_one('td:nth-child(2) a').text.strip()
timestamp = row.select_one('td:nth-child(3)').text.strip()
link = row.select_one('td:nth-child(5) a')['href']
logger.info(f"Extracted post: timestamp={timestamp}, title={title}, author={author}, link={link}")
posts[timestamp] = {
'title': title,
'author': author,
'link': link
}
except AttributeError as e:
logger.error(f"Error parsing row on {url}: {e}")
continue
except requests.RequestException as e:
logger.error(f"Error scraping {url}: {e}")
continue
logger.info(f"Total posts scraped: {len(posts)}")
return json.dumps(posts)
if __name__ == "__main__":
# Create a proper requests.Session and set the cookie
session = requests.Session()
session.cookies.set('session', '.eJwlzsENwzAIAMBd_O4DbINNlokAg9Jv0ryq7t5KvQnuXfY84zrK9jrveJT9ucpWbA0xIs5aZ8VM5EnhwqNNbblWVlmzMUEH9MkDmwZQTwkFDlqhkgounTm9Q7U0nYQsw6MlmtKYqBgUpAMkuJpnuEMsYxtQfpH7ivO_wfL5AtYwMDs.aH1ifQ.uRrB1FnMt3U_apyiWitI9LDnrGE')
proxy = "socks5h://127.0.0.1:49075"
useragent = "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"
pagination_range = [
"http://y5extjdmtegzt6n6qe3titrmgjvff4hiualgzy7n2jrahbmfkggbmqqd.onion/category/marketplace/Sellers?page=1",
"http://y5extjdmtegzt6n6qe3titrmgjvff4hiualgzy7n2jrahbmfkggbmqqd.onion/category/marketplace/Sellers?page=2",
"http://y5extjdmtegzt6n6qe3titrmgjvff4hiualgzy7n2jrahbmfkggbmqqd.onion/category/marketplace/Sellers?page=3"
]
timeout = 30
result = scrape_posts(session, proxy, useragent, pagination_range, timeout)
print(result)
Necesitamos dos funciones individuales para realizar estas tareas:
- create_pagination_batches(url_template, max_page)
- Genera lotes de URL para la paginación creando grupos de 10 URL de página a partir de una plantilla de URL dada y un número máximo de páginas, devolviéndolos como una cadena JSON.
- scrape_posts(session, proxy, useragent, pagination_range, timeout)
- Extrae los detalles de las publicaciones (título, autor, marca de tiempo, enlace) de una lista de URL de páginas web utilizando una sesión de solicitudes, un proxy y un agente de usuario, analizando el HTML con
BeautifulSoupy devolviendo los datos recopilados como una cadena JSON.
- Extrae los detalles de las publicaciones (título, autor, marca de tiempo, enlace) de una lista de URL de páginas web utilizando una sesión de solicitudes, un proxy y un agente de usuario, analizando el HTML con
Limitamos los lotes de paginación a 10 páginas porque es el umbral que hemos establecido. Puedes ajustar este límite, pero si tus bots acceden a 50 páginas de paginación en solo unos segundos, podría activarse el bloqueo de la cuenta.
Más adelante, utilizaremos la función scrape_posts para procesar rangos de lotes de paginación, lo que permitirá extraer las publicaciones de todos los lotes.
Creación del backend del marketplace
El backend puede parecer más intimidante que nuestras tareas anteriores. Puedes encontrar el código del backend en app/routes/marketplace.py.
Esta complejidad surge de la concurrencia, que nos permite distribuir las tareas de scraping de datos entre todos los bots disponibles, lo que aumenta la eficiencia pero añade complejidad. Ten en cuenta que todos los escaneos se ejecutan en segundo plano, por lo que continúan incluso si navegas entre páginas.
Aunque las funcionalidades pueden parecer complejas, esto es parte natural del proceso de aprendizaje. Nuestro objetivo es crear un web scraper avanzado para la recopilación de datos a largo plazo, una tarea que es intrínsecamente sofisticada.
get_pagination_scans
- Punto final:
GET /api/marketplace-scan/list - Propósito: Recupera todos los escaneos de paginación de la base de datos.
- Funcionalidad:
- Consulta la tabla
MarketplacePaginationScanpara obtener todos los registros. - Registra el número de escaneos obtenidos.
- Formatea cada exploración en un diccionario compatible con JSON que contiene
id,scan_name,pagination_url,max_page,batchesytimestamp. - Devuelve una
JSONResponsecon la lista de exploraciones y un código de estado 200. - Gestiona las excepciones registrando los errores y generando una
HTTPExceptioncon un código de estado 500 si se produce un error.
- Consulta la tabla
enumerate_pages
- Punto final:
POST /api/marketplace-scan/enumerate - Finalidad: Crea un nuevo escaneo de paginación para enumerar las páginas que se van a extraer.
- Funcionalidad:
- Valida que el
scan_nameproporcionado no existe ya en la base de datos. - Llama a
create_pagination_batchespara generar lotes de URL basados en elpagination_urly elmax_pageproporcionados. - Crea un nuevo registro
MarketplacePaginationScancon los detalles del escaneo y almacena los lotes como JSON. - Confirma el registro en la base de datos y registra la creación.
- Almacena un mensaje de éxito en la sesión y devuelve una
JSONResponsecon un código de estado 201. - Gestiona los nombres de escaneo duplicados (400), los errores de la base de datos (500, con reversión) y otras excepciones registrando y generando las
HTTPExceptions adecuadas.
- Valida que el
delete_pagination_scan
- Punto final:
DELETE /api/marketplace-scan/{scan_id} - Propósito: Elimina un escaneo de paginación por su ID.
- Funcionalidad:
- Consulta la tabla
MarketplacePaginationScanpara buscar el escaneo con elscan_idespecificado. - Si no se encuentra el escaneo, registra una advertencia y genera una excepción
HTTPException404. - Elimina el escaneo de la base de datos y confirma la transacción.
- Registra la eliminación y almacena un mensaje de éxito en la sesión.
- Devuelve una respuesta
JSONResponsecon un código de estado 200. - Gestiona los errores registrándolos, revirtiendo la transacción y generando una excepción
HTTPException500.
- Consulta la tabla
get_post_scans
- Punto final:
GET /api/marketplace-scan/posts/list - Finalidad: recupera todos los escaneos publicados de la base de datos.
- Funcionalidad:
- Consulta la tabla
MarketplacePostScanpara recuperar todos los registros. - Registra el número de escaneos recuperados.
- Formatea cada escaneo en un diccionario compatible con JSON con
id,scan_name,pagination_scan_name,start_date,completion_date,statusytimestamp. - Devuelve una
JSONResponsecon la lista de escaneos y un código de estado 200. - Gestiona las excepciones registrándolas y generando una
HTTPException500.
- Consulta la tabla
get_post_scan_status
- Punto final:
GET /api/marketplace-scan/posts/{scan_id}/status - Finalidad: Recupera el estado de un escaneo publicado específico por su ID.
- Funcionalidad:
- Consulta la tabla
MarketplacePostScanpara buscar el escaneo con elscan_idespecificado. - Si no se encuentra el escaneo, registra una advertencia y genera una excepción
HTTPException404. - Registra el estado y devuelve una respuesta
JSONResponsecon elid, elscan_namey elstatus(como valor de cadena) del escaneo con un código de estado 200. - Gestiona las excepciones registrándolas y generando una excepción HTTP 500.
- Consulta la tabla
enumerate_posts
- Punto final:
POST /api/marketplace-scan/posts/enumerate - Finalidad: Crea una nueva publicación de escaneo asociada a un escaneo de paginación.
- Funcionalidad:
- Valida que el
scan_nameproporcionado no exista ya. - Comprueba si el
pagination_scan_nameal que se hace referencia existe en la tablaMarketplacePaginationScan. - Se asegura de que hay bots activos con el propósito
SCRAPE_MARKETPLACEy sesiones válidas. - Crea un nuevo registro
MarketplacePostScancon elscan_name, elpagination_scan_namey el estado inicialSTOPPEDproporcionados. - Confirma el registro en la base de datos y registra la creación.
- Almacena un mensaje de éxito en la sesión y devuelve una
JSONResponsecon un código de estado 201. - Gestiona los errores por nombres de escaneo duplicados (400), escaneos de paginación que faltan (404), bots inactivos (400) u otros problemas (500, con reversión).
- Valida que el
start_post_scan
- Punto final:
POST /api/marketplace-scan/posts/{scan_id}/start - Propósito: Inicia un escaneo posterior procesando lotes de URL utilizando los bots disponibles.
- Funcionalidad:
- Recupera el
MarketplacePostScanporscan_idy comprueba si existe. - Se asegura de que el escaneo no se esté ejecutando ya (genera un error 400 si es así).
- Verifica la disponibilidad de bots con el propósito
SCRAPE_MARKETPLACE. - Recupera el
MarketplacePaginationScanasociado y sus lotes. - Actualiza el estado del escaneo a
RUNNING, establece lastart_datey borra lacompletion_date. - Ejecuta una tarea asíncrona
scrape_batchespara procesar los lotes simultáneamente: - Asigna los lotes a los bots disponibles utilizando un
ThreadPoolExecutor. - Cada bot extrae un lote de URL utilizando la función
scrape_posts, con cookies de sesión y proxy Tor. - Gestiona los errores de análisis JSON limpiando los datos (normalizando Unicode, eliminando caracteres de control).
- Guarda las publicaciones únicas en la tabla «MarketplacePost», evitando duplicados.
- Registra el progreso y los errores de cada lote.
- Marca el escaneo como «COMPLETED» si se realiza correctamente o «STOPPED» si falla.
- Almacena un mensaje de éxito en la sesión y devuelve una
JSONResponsecon un código de estado 200. - Gestiona los errores por escaneos perdidos (404), escaneos en ejecución (400), ausencia de bots (400), lotes perdidos (400) u otros problemas (500, con retroceso).
- Recupera el
delete_post_scan
- Punto final:
DELETE /api/marketplace-scan/posts/{scan_id} - Finalidad: Elimina un escaneo posterior por su ID.
- Funcionalidad:
- Consulta la tabla
MarketplacePostScanpara buscar el escaneo con elscan_idespecificado. - Si no se encuentra el escaneo, registra una advertencia y genera una
HTTPException404. - Elimina el escaneo de la base de datos y confirma la transacción.
- Registra la eliminación y almacena un mensaje de éxito en la sesión.
- Devuelve una
JSONResponsecon un código de estado 200. - Gestiona los errores registrándolos, revirtiendo la transacción y generando una excepción HTTP 500.
- Consulta la tabla
get_scan_posts
- Punto final:
GET /api/marketplace-scan/posts/{scan_id}/posts - Finalidad: Recupera todas las publicaciones asociadas a un escaneo de publicación específico.
- Funcionalidad:
- Consulta la tabla
MarketplacePostScanpara verificar que el escaneo existe. - Si no se encuentra el escaneo, registra una advertencia y genera una excepción
HTTPException404. - Consulta la tabla
MarketplacePostpara obtener todas las publicaciones vinculadas alscan_id. - Registra el número de publicaciones recuperadas.
- Formatea cada publicación en un diccionario compatible con JSON con
id,timestamp,title,authorylink. - Devuelve una
JSONResponsecon la lista de publicaciones y un código de estado 200. - Gestiona las excepciones registrándolas y generando una
HTTPException500.
- Consulta la tabla
Esta funcionalidad requiere la iniciación manual del rastreo cada pocas horas para comprobar si hay nueva actividad en el foro. Se evita automatizar este proceso para conservar los recursos, ya que el rastreo continuo a menudo recopilaría datos duplicados, lo que provocaría un consumo ineficiente de recursos. Por lo tanto, ejecutar escaneos del mercado sin parar no es el enfoque óptimo.
Según mi amplia experiencia, no es recomendable implementar escaneos continuos cada pocas horas debido a la gran cantidad de recursos que requieren.
En el módulo 5, implementaremos el rastreo continuo de datos, pero, como descubrirás, este proceso a menudo genera datos duplicados.
Plantilla del frontend del mercado
Para el mercado, necesitamos una plantilla con dos pestañas, que nos permitan cambiar entre varios contenedores dentro de una misma página. En lugar de crear dos rutas separadas, utilizaremos una sola ruta con pestañas para simplificar el diseño.
Aunque las pestañas a veces pueden complicar una aplicación web, en este caso la simplifican al evitar la necesidad de dos plantillas separadas, lo que aumentaría el tamaño de la aplicación. A medida que avancemos, exploraremos varias plantillas, pero para esta funcionalidad específica, las pestañas son suficientes.
La plantilla se encuentra en app/templates/marketplace.html.
-
Navegación por pestañas para paginación y escaneos de publicaciones:
- Propósito: Organiza la interfaz en las pestañas «Paginación del mercado» y «Publicaciones del mercado».
- Interacción con el backend: La función
openTab()alterna la visibilidad del contenido de la pestaña (paginationoposts) sin llamadas directas al backend. Los datos iniciales para ambas pestañas (pagination_scansypost_scans) son proporcionados pormain.py::marketplacey se representan utilizando Jinja2.
-
Enumeración de escaneo de paginación:
- Finalidad: Inicia un nuevo escaneo de paginación para enumerar las páginas del mercado.
- Interacción con el backend:
- El botón «Enumerar páginas» abre un modal (
enumerate-modal) con campos para el nombre del escaneo, la URL de paginación y el número máximo de páginas. - El envío del formulario envía una solicitud AJAX POST a
/api/marketplace-scan/enumerate(gestionada pormarketplace_api_router) con los datos del formulario. - El backend crea un registro
MarketplacePaginationScan, procesa la paginación y almacena los resultados. Si se realiza correctamente, la página se recarga para mostrar la lista de escaneos actualizada. Los errores activan una alerta con el mensaje de error.
- El botón «Enumerar páginas» abre un modal (
-
Enumeración posterior al escaneo:
- Objetivo: crea un nuevo escaneo posterior basado en un escaneo de paginación existente.
- Interacción con el backend:
- El botón «Enumerar publicaciones» abre un modal (
enumerate-posts-modal) con campos para el nombre del escaneo y un menú desplegable con los escaneos de paginación existentes (rellenado desdepagination_scans). - El envío del formulario envía una solicitud AJAX POST a
/api/marketplace-scan/posts/enumerate(gestionada pormarketplace_api_router) con el nombre del escaneo y el escaneo de paginación seleccionado. - El backend crea un registro
MarketplacePostScanvinculado al escaneo de paginación elegido. Si se realiza correctamente, la página se recarga para actualizar la tabla de escaneos publicados. Los errores activan una alerta.
- El botón «Enumerar publicaciones» abre un modal (
-
Gestión de escaneos publicados:
- Objetivo: Inicia, visualiza o elimina escaneos publicados.
- Interacción con el backend:
- Inicio: Cada fila de escaneo de publicaciones (no en ejecución) tiene un botón «Iniciar» que envía una solicitud AJAX POST a
/api/marketplace-scan/posts/{scanId}/start(gestionado pormarketplace_api_router) para iniciar el escaneo. Si se realiza correctamente,refreshScans()actualiza la tabla. - Ver: Un botón «Ver» abre un modal (
view-posts-modal-{scanId}) que recupera los datos de la publicación mediante una solicitud AJAX GET a/api/marketplace-scan/posts/{scanId}/posts, rellenando una tabla con los detalles de la publicación (marca de tiempo, título, autor, enlace). Los errores activan una alerta. - Eliminar: El botón «Eliminar» solicita confirmación y envía una solicitud AJAX DELETE a
/api/marketplace-scan/posts/{scanId}para eliminar el escaneo de la tablaMarketplacePostScan. Si se realiza correctamente, la página se recarga. Los errores activan una alerta.
- Inicio: Cada fila de escaneo de publicaciones (no en ejecución) tiene un botón «Iniciar» que envía una solicitud AJAX POST a
-
Visualización y eliminación de escaneos de paginación:
- Finalidad: muestra los detalles de los escaneos de paginación y permite su eliminación.
- Interacción con el backend:
- Ver: Cada fila de escaneos paginados tiene un botón «Ver» que abre un modal (
view-modal-{scanId}) con campos de solo lectura para el nombre del escaneo, la URL, la página máxima y los lotes (en formato JSON). Los datos se precargan desdepagination_scansa través de Jinja2, sin necesidad de llamadas adicionales al backend. - Eliminar: Un botón «Eliminar» (
deleteScan()) solicita confirmación y envía una solicitud AJAX DELETE a/api/marketplace-scan/{scanId}para eliminar el escaneo de la tablaMarketplacePaginationScan. Si se realiza correctamente, la página se recarga. Los errores activan una alerta.
- Ver: Cada fila de escaneos paginados tiene un botón «Ver» que abre un modal (
-
Actualización de la tabla de escaneos posteriores:
- Objetivo: Actualiza la tabla de escaneos posteriores para reflejar los estados actuales.
- Interacción con el backend:
- El botón «Actualizar escaneos» activa
refreshScans(), que envía una solicitud AJAX GET a/api/marketplace-scan/posts/list(gestionada pormarketplace_api_router). - El backend devuelve una lista de registros
MarketplacePostScan(ID, nombre del escaneo, nombre de la paginación del escaneo, fechas de inicio/finalización, estado). La tabla se actualiza con insignias de estado (por ejemplo, Completado, En ejecución, Detenido). Los errores activan una alerta.
- El botón «Actualizar escaneos» activa
Pruebas
Para comenzar las pruebas, deberá configurar los siguientes componentes:
- Añada y active una API CAPTCHA desde el punto final
/manage-api. - Cree al menos dos perfiles de bot e inicie sesión para recuperar sus sesiones desde el punto final
/bot-profile. - Obtenga la URL de paginación del marketplace desde
tornet_forum, por ejemplo:http://site.onion/category/marketplace/Sellers?page=1. - Vaya a
/marketplace-scan, seleccione la pestañaMarketplace Pagination, haga clic enEnumerate Pagesy rellene los campos como se indica a continuación:- Scan Name:
Monkey - Pagination URL:
http://site.onion/category/marketplace/Sellers?page={page} - Número máximo de paginación: 14 (ajústelo en función del número total de páginas de paginación disponibles).
- Scan Name:
Una vez completado el escaneo, haga clic para ver los resultados y se mostrará un modal. A continuación se muestra un ejemplo de cómo pueden aparecer los lotes de paginación en formato JSON:
"{\"1\": [\"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=14\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=13\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=12\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=11\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=10\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=9\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=8\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=7\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=6\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=5\"], \"2\": [\"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=4\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=3\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=2\", \"http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=1\"]}"
Para enumerar las publicaciones en el marketplace, siga estos pasos:
- Vaya a
/marketplace-scany seleccione la pestañaPublicaciones del marketplace. - Haga clic en
Enumerar publicaciones, introduzca un nombre para el escaneo, seleccione el escaneo de paginación denominadoMonkeyy haga clic enIniciar escaneo. Esto prepara el escaneo, pero no lo inicia. - Vuelve a «/marketplace-scan», ve a la pestaña «Publicaciones del mercado», busca tu escaneo y haz clic en el botón «Iniciar» para comenzar el escaneo.
A continuación se muestra un ejemplo del resultado de mi configuración:
2025-07-21 19:49:41,140 - INFO - Found 3 active bots for scan ID 6: ['DarkHacker', 'CyberGhost', 'ShadowV']
2025-07-21 19:49:41,141 - INFO - Starting post scan tyron (ID: 6) with 2 batches: ['1', '2']
2025-07-21 19:49:41,148 - INFO - Post scan tyron (ID: 6) status updated to RUNNING
2025-07-21 19:49:41,149 - INFO - Assigning batch 1 to bot DarkHacker (ID: 1)
2025-07-21 19:49:41,150 - INFO - Bot DarkHacker (ID: 1) starting batch 1 (10 URLs)
2025-07-21 19:49:41,150 - INFO - Scraping URL: http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=20
2025-07-21 19:49:41,151 - INFO - Assigning batch 2 to bot CyberGhost (ID: 2)
2025-07-21 19:49:41,151 - INFO - Bot CyberGhost (ID: 2) starting batch 2 (10 URLs)
2025-07-21 19:49:41,151 - INFO - Scraping URL: http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=10
2025-07-21 19:49:41,152 - INFO - Launching 2 concurrent batch tasks
INFO: 127.0.0.1:34646 - "POST /api/marketplace-scan/posts/6/start HTTP/1.1" 200 OK
2025-07-21 19:49:41,158 - INFO - Fetched 6 post scans
INFO: 127.0.0.1:34646 - "GET /api/marketplace-scan/posts/list HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /manage-api HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /api/manage-api/list HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /proxy-gen HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /api/proxy-gen/list HTTP/1.1" 200 OK
2025-07-21 19:49:46,794 - INFO - Response status code: 200
2025-07-21 19:49:46,804 - INFO - Found 10 table rows on http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=20
2025-07-21 19:49:46,804 - INFO - Extracted post: timestamp=2025-07-19 07:04:10, title=OFFER:, author=DarkHacker, link=/post/marketplace/1901
2025-07-21 19:49:46,805 - INFO - Extracted post: timestamp=2025-07-19 06:33:54, title=Avoid “anonssh” , ssh pack had only 2 live hosts, author=N3tRunn3r, link=/post/marketplace/588
2025-07-21 19:49:46,805 - INFO - Extracted post: timestamp=2025-07-19 05:56:53, title=Access to Northern Trust Realty, US, author=DarkHacker, link=/post/marketplace/1532
2025-07-21 19:49:46,806 - INFO - Extracted post: timestamp=2025-07-19 05:20:53, title=FOR SALE:, author=GhostRider, link=/post/marketplace/2309
2025-07-21 19:49:46,806 - INFO - Extracted post: timestamp=2025-07-19 04:24:04, title=Custom RAT builder crashed on open, author=ShadowV, link=/post/marketplace/968
2025-07-21 19:49:46,806 - INFO - Extracted post: timestamp=2025-07-19 03:35:29, title=Private obfuscator for Python tools, author=GhostRider, link=/post/marketplace/1845
2025-07-21 19:49:46,806 - INFO - Extracted post: timestamp=2025-07-19 03:23:21, title="RootedShells" panel has backconnect, author=ZeroByte, link=/post/marketplace/1829
2025-07-21 19:49:46,806 - INFO - Extracted post: timestamp=2025-07-19 03:09:27, title=RDP seller "skylinesupply" giving same IP to 4 people, author=N3tRunn3r, link=/post/marketplace/1710
2025-07-21 19:49:46,807 - INFO - Extracted post: timestamp=2025-07-19 02:39:40, title=FOR SALE: DA access into Lakewood Public Services, author=ShadowV, link=/post/marketplace/972
2025-07-21 19:49:46,807 - INFO - Extracted post: timestamp=2025-07-19 02:37:10, title=4k cracked Apple IDs, author=DarkHacker, link=/post/marketplace/1154
2025-07-21 19:49:46,807 - INFO - Scraping URL: http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/category/marketplace/Sellers?page=19
2025-07-21 19:49:46,995 - INFO - Response status code: 200
--- snip ---
--- snip ---
--- snip ---
2025-07-21 19:49:56,643 - INFO - Total posts scraped: 100
2025-07-21 19:49:56,643 - INFO - Bot CyberGhost completed batch 2, found 100 posts
2025-07-21 19:49:56,696 - INFO - Bot DarkHacker saved batch 1 posts to database for scan ID 6
2025-07-21 19:49:56,704 - INFO - Bot CyberGhost saved batch 2 posts to database for scan ID 6
2025-07-21 19:49:56,710 - INFO - Post scan tyron (ID: 6) completed successfully
Una vez que comience el escaneo, observe que puede cambiar entre páginas y el escaneo continuará ejecutándose en segundo plano:
2025-07-21 19:49:41,152 - INFO - Launching 2 concurrent batch tasks
INFO: 127.0.0.1:34646 - "POST /api/marketplace-scan/posts/6/start HTTP/1.1" 200 OK
2025-07-21 19:49:41,158 - INFO - Fetched 6 post scans
INFO: 127.0.0.1:34646 - "GET /api/marketplace-scan/posts/list HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /manage-api HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /api/manage-api/list HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /proxy-gen HTTP/1.1" 200 OK
INFO: 127.0.0.1:34646 - "GET /api/proxy-gen/list HTTP/1.1" 200 OK
2025-07-21 19:49:46,794 - INFO - Response status code: 200
2025-07-21 19:49:46,804 - INFO - Found 10 table rows on
Los escaneos no se reanudarán después de reiniciar el sistema o si cierras la aplicación.
Así es como podría verse el resultado de un escaneo en tu dispositivo:
