In diesem Abschnitt lernen wir, wie man Beiträge aus dem Marktplatz der Verkäufer scrapt und die Scraping-Aufgaben auf mehrere Bots verteilt, damit sie gleichzeitig ausgeführt werden können.

Das Ziel ist es, die Aufgaben auf die Bots aufzuteilen, da die meisten Foren eine Begrenzung der Anzahl der Anfragen, die Sie senden können, vorgeben. In „tornet_forum“ gibt es keine Begrenzung für die Navigation zwischen den Seiten, und Sie können zwischen den Seiten wechseln, ohne angemeldet zu sein.

Um jedoch auf verschiedene Schutzmechanismen vorbereitet zu sein, verwenden wir Bots mit aktiven Sitzungen für das Scraping. Obwohl dies für „tornet_forum“ nicht erforderlich ist, können angemeldete Sitzungen für andere Zielseiten, auf die Sie stoßen, notwendig sein. Da ich selbst schon Daten von solchen Seiten gescrapt habe, verstehe ich die Herausforderungen, denen Sie begegnen könnten, und dieser Ansatz rüstet Sie für jedes Szenario.

Die Themen dieses Abschnitts umfassen Folgendes:

  1. Datenbankmodelle
  2. Marktplatz-Scraper-Module
  3. Marktplatz-Backend-Routen
  4. Marktplatz-Frontend-Vorlage
  5. Testen

Datenbankmodelle

Ihre Modelle befinden sich in app/database/models.py. Sie benötigen 4 Modelle, um die Daten ordnungsgemäß zu organisieren:

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'),)

Für diese Funktionalität benötigen wir mehrere Modelle, um alle folgenden Aufgaben auszuführen:

  1. MarketplacePaginationScan:

    • Zweck: Stellt eine Paginierungsscan-Konfiguration zum Scraping eines Marktplatzes dar. Es speichert Details zu einem Scan, der die Seiten eines Marktplatzes auflistet, wie z. B. die Basis-URL und die maximale Anzahl der zu scannenden Seiten.
    • Schlüsselfelder:
      • id: Eindeutige Kennung für den Scan.
      • scan_name: Eindeutiger Name für den Paginierungsscan.
      • pagination_url: Die Basis-URL, die für die Paginierung verwendet wird.
      • max_page: Die maximale Anzahl der zu scannenden Seiten.
      • batches: Speichert serialisierte Batch-Daten (z. B. JSON) für die Verarbeitung von Seiten.
      • timestamp: Zeichnet auf, wann der Scan erstellt wurde.
  2. ScanStatus (Enum):

    • Zweck: Definiert die möglichen Zustände eines Post-Scans, die zur Verfolgung des Status eines MarketplacePostScan verwendet werden.
    • Werte:
      • RUNNING: Der Scan wird derzeit ausgeführt.
      • COMPLETED: Der Scan wurde erfolgreich abgeschlossen.
      • STOPPED: Der Scan wird nicht ausgeführt (Standard oder manuell angehalten).
  3. MarketplacePostScan:

    • Zweck: Stellt einen Scan dar, der Beiträge aus einem Marktplatz sammelt und mit einem bestimmten Paginierungsscan verknüpft ist. Er verfolgt die Metadaten und den Status des Scans.
    • Schlüsselfelder:
      • id: Eindeutige Kennung für den Post-Scan.
      • scan_name: Eindeutiger Name für den Post-Scan.
      • pagination_scan_name: Verweist anhand des scan_name auf den zugehörigen MarketplacePaginationScan.
      • start_date: Zeitpunkt, zu dem der Scan gestartet wurde.
      • completion_date: Zeitpunkt, zu dem der Scan abgeschlossen wurde (falls zutreffend).
      • status: Aktueller Status des Scans (aus der Aufzählung ScanStatus).
      • timestamp: Zeichnet auf, wann der Scan erstellt wurde.
  4. MarketplacePost:

    • Zweck: Speichert einzelne Beiträge, die während eines MarketplacePostScan gesammelt wurden. Jeder Beitrag ist mit einem bestimmten Scan verknüpft und enthält Details zum Beitrag.
    • Schlüsselfelder:
      • id: Eindeutige Kennung für den Beitrag.
      • scan_id: Verweist auf den MarketplacePostScan, zu dem dieser Beitrag gehört.
      • timestamp: Zeitstempel des Beitrags (als Zeichenfolge).
      • title: Titel des Marktplatzbeitrags.
      • author: Autor des Beitrags.
      • link: URL zum Beitrag.
      • __table_args__: Stellt die Eindeutigkeit der Beiträge anhand von scan_id und timestamp sicher, um Duplikate zu vermeiden.

Marktplatz-Scraper-Module

Um ein Beispiel für die Funktionsweise des Marktplatz-Scrapers zu sehen, öffnen Sie 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)

Wir benötigen zwei separate Funktionen, um diese Aufgaben auszuführen:

  1. create_pagination_batches(url_template, max_page)
    • Generiert Batches von URLs für die Paginierung, indem aus einer vorgegebenen URL-Vorlage und einer maximalen Seitenanzahl Gruppen von 10 Seiten-URLs erstellt und als JSON-Zeichenkette zurückgegeben werden.
  2. scrape_posts(session, proxy, useragent, pagination_range, timeout)
    • Kratzt Post-Details (Titel, Autor, Zeitstempel, Link) aus einer Liste von Webseiten-URLs unter Verwendung einer Requests-Sitzung, eines Proxys und eines User-Agents, parst HTML mit BeautifulSoup und gibt die gesammelten Daten als JSON-Zeichenkette zurück.

Wir beschränken die Paginierungs-Batches auf 10 Seiten, da dies der von uns festgelegte Schwellenwert ist. Sie können diese Grenze anpassen, aber wenn Ihre Bots in wenigen Sekunden auf 50 Paginierungsseiten zugreifen, kann dies zu einer Sperrung des Kontos führen.

Später werden wir die Funktion scrape_posts verwenden, um Bereiche von Paginierungs-Batches zu verarbeiten, wodurch das Scraping von Beiträgen aus allen Batches ermöglicht wird.


Erstellen des Marktplatz-Backends

Das Backend mag auf den ersten Blick schwieriger erscheinen als unsere bisherigen Aufgaben. Den Backend-Code finden Sie in app/routes/marketplace.py.

Diese Komplexität ergibt sich aus der Parallelität, die es uns ermöglicht, die Daten-Scraping-Aufgaben auf alle verfügbaren Bots zu verteilen, was die Effizienz erhöht, aber auch die Komplexität erhöht. Beachten Sie, dass alle Scans im Hintergrund ausgeführt werden, sodass sie auch dann fortgesetzt werden, wenn Sie zwischen den Seiten navigieren.

Die Funktionen mögen komplex erscheinen, aber das ist ein natürlicher Teil des Lernprozesses. Unser Ziel ist es, einen fortschrittlichen Web-Scraper für die langfristige Datenerfassung zu entwickeln, eine Aufgabe, die von Natur aus anspruchsvoll ist.

get_pagination_scans

  • Endpunkt: GET /api/marketplace-scan/list
  • Zweck: Ruft alle Paginierungsscans aus der Datenbank ab.
  • Funktionalität:
    • Abfrage der Tabelle MarketplacePaginationScan, um alle Datensätze abzurufen.
    • Protokollierung der Anzahl der abgerufenen Scans.
    • Formatierung jedes Scans in ein JSON-kompatibles Wörterbuch mit den Feldern id, scan_name, pagination_url, max_page, batches und timestamp enthält.
    • Gibt eine JSONResponse mit der Liste der Scans und einem Statuscode 200 zurück.
    • Behandelt Ausnahmen, indem Fehler protokolliert und bei Auftreten eines Fehlers eine HTTPException mit dem Statuscode 500 ausgelöst wird.

enumerate_pages

  • Endpunkt: POST /api/marketplace-scan/enumerate
  • Zweck: Erstellt einen neuen Paginierungsscan, um Seiten für das Scraping aufzulisten.
  • Funktionalität:
    • Überprüft, ob der angegebene scan_name bereits in der Datenbank vorhanden ist.
    • Ruft create_pagination_batches auf, um basierend auf der angegebenen pagination_url und max_page Batches von URLs zu generieren.
    • Erstellt einen neuen MarketplacePaginationScan mit den Scan-Details und speichert die Batches als JSON.
    • Überträgt den Datensatz in die Datenbank und protokolliert die Erstellung.
    • Speichert eine Erfolgsmeldung in der Sitzung und gibt eine JSONResponse mit dem Statuscode 201 zurück.
    • Behandelt doppelte Scan-Namen (400), Datenbankfehler (500, mit Rollback) und andere Ausnahmen durch Protokollierung und Auslösen entsprechender HTTPExceptions.

delete_pagination_scan

  • Endpunkt: DELETE /api/marketplace-scan/{scan_id}
  • Zweck: Löscht einen Paginierungsscan anhand seiner ID.
  • Funktionalität:
    • Fragt die Tabelle MarketplacePaginationScan nach dem Scan mit der angegebenen scan_id ab.
    • Wenn der Scan nicht gefunden wird, protokolliert sie eine Warnung und löst eine 404 HTTPException aus.
    • Löscht den Scan aus der Datenbank und führt die Transaktion aus.
    • Protokolliert die Löschung und speichert eine Erfolgsmeldung in der Sitzung.
    • Gibt eine JSONResponse mit dem Statuscode 200 zurück.
    • Behandelt Fehler durch Protokollierung, Zurücksetzen der Transaktion und Auslösen einer 500 HTTPException.

get_post_scans

  • Endpunkt: GET /api/marketplace-scan/posts/list
  • Zweck: Ruft alle Post-Scans aus der Datenbank ab.
  • Funktionalität:
    • Abfrage der Tabelle MarketplacePostScan, um alle Datensätze abzurufen.
    • Protokollierung der Anzahl der abgerufenen Scans.
    • Formatierung jedes Scans in ein JSON-kompatibles Wörterbuch mit id, scan_name, pagination_scan_name, start_date, completion_date, status und timestamp.
    • Gibt eine JSONResponse mit der Liste der Scans und einem Statuscode 200 zurück.
    • Behandelt Ausnahmen durch Protokollierung und Auslösen einer 500 HTTPException.

get_post_scan_status

  • Endpunkt: GET /api/marketplace-scan/posts/{scan_id}/status
  • Zweck: Ruft den Status eines bestimmten Post-Scans anhand seiner ID ab.
  • Funktionalität:
    • Fragt die Tabelle MarketplacePostScan nach dem Scan mit der angegebenen scan_id ab.
    • Wenn der Scan nicht gefunden wird, protokolliert eine Warnung und löst eine 404 HTTPException aus.
    • Protokolliert den Status und gibt eine JSONResponse mit der id, dem scan_name und dem status des Scans (als Zeichenfolgenwert) mit einem Statuscode 200 zurück.
    • Behandelt Ausnahmen durch Protokollierung und Auslösen einer 500 HTTPException.

enumerate_posts

  • Endpunkt: POST /api/marketplace-scan/posts/enumerate
  • Zweck: Erstellt einen neuen Post-Scan, der mit einem Paginierungs-Scan verknüpft ist.
  • Funktionalität:
    • Überprüft, ob der angegebene scan_name bereits existiert.
    • Überprüft, ob der referenzierte pagination_scan_name in der Tabelle MarketplacePaginationScan existiert.
    • Stellt sicher, dass aktive Bots mit dem Zweck SCRAPE_MARKETPLACE und gültigen Sitzungen vorhanden sind.
    • Erstellt einen neuen MarketplacePostScan-Datensatz mit dem angegebenen scan_name, pagination_scan_name und dem anfänglichen Status STOPPED.
    • Speichert den Datensatz in der Datenbank und protokolliert die Erstellung.
    • Speichert eine Erfolgsmeldung in der Sitzung und gibt eine „JSONResponse“ mit dem Statuscode 201 zurück.
    • Behandelt Fehler für doppelte Scan-Namen (400), fehlende Paginierungsscans (404), keine aktiven Bots (400) oder andere Probleme (500, mit Rollback).

start_post_scan

  • Endpunkt: POST /api/marketplace-scan/posts/{scan_id}/start
  • Zweck: Startet einen Post-Scan, indem Batches von URLs mit verfügbaren Bots verarbeitet werden.
  • Funktionalität:
    • Ruft den MarketplacePostScan anhand der scan_id ab und überprüft, ob er vorhanden ist.
    • Stellt sicher, dass der Scan nicht bereits ausgeführt wird (löst 400 aus, wenn dies der Fall ist).
    • Überprüft die Verfügbarkeit von Bots mit dem Zweck SCRAPE_MARKETPLACE.
    • Ruft den zugehörigen MarketplacePaginationScan und seine Stapel ab.
    • Aktualisiert den Scan-Status auf RUNNING, legt das start_date fest und löscht das completion_date.
    • Führt eine asynchrone scrape_batches-Aufgabe aus, um Batches gleichzeitig zu verarbeiten:
    • Weist Batches mithilfe eines ThreadPoolExecutor verfügbaren Bots zu.
    • Jeder Bot scrapt einen Batch von URLs mithilfe der Funktion scrape_posts, mit Sitzungscookies und Tor-Proxy.
    • Behandelt JSON-Parsing-Fehler durch Bereinigung der Daten (Normalisierung von Unicode, Entfernen von Steuerzeichen).
    • Speichert eindeutige Beiträge in der Tabelle „MarketplacePost“, um Duplikate zu vermeiden.
    • Protokolliert den Fortschritt und Fehler für jeden Batch.
    • Markiert den Scan bei Erfolg als „COMPLETED“ und bei Fehler als „STOPPED“.
    • Speichert eine Erfolgsmeldung in der Sitzung und gibt eine „JSONResponse“ mit dem Statuscode 200 zurück.
    • Behandelt Fehler für fehlende Scans (404), laufende Scans (400), keine Bots (400), fehlende Stapel (400) oder andere Probleme (500, mit Rollback).

delete_post_scan

  • Endpunkt: DELETE /api/marketplace-scan/posts/{scan_id}
  • Zweck: Löscht einen Post-Scan anhand seiner ID.
  • Funktionalität:
    • Fragt die Tabelle MarketplacePostScan nach dem Scan mit der angegebenen scan_id ab.
    • Wenn der Scan nicht gefunden wird, protokolliert er eine Warnung und löst eine 404 HTTPException aus.
    • Löscht den Scan aus der Datenbank und führt die Transaktion aus.
    • Protokolliert die Löschung und speichert eine Erfolgsmeldung in der Sitzung.
    • Gibt eine JSONResponse mit dem Statuscode 200 zurück.
    • Behandelt Fehler durch Protokollierung, Zurücksetzen der Transaktion und Auslösen einer 500 HTTPException.

get_scan_posts

  • Endpunkt: GET /api/marketplace-scan/posts/{scan_id}/posts
  • Zweck: Ruft alle Beiträge ab, die mit einem bestimmten Beitragsscan verknüpft sind.
  • Funktionalität:
    • Fragt die Tabelle MarketplacePostScan ab, um zu überprüfen, ob der Scan vorhanden ist.
    • Wenn der Scan nicht gefunden wird, protokolliert sie eine Warnung und löst eine 404 HTTPException aus.
    • Abfrage der Tabelle MarketplacePost nach allen Beiträgen, die mit der scan_id verknüpft sind.
    • Protokollierung der Anzahl der abgerufenen Beiträge.
    • Formatierung jedes Beitrags in ein JSON-kompatibles Wörterbuch mit id, timestamp, title, author und link.
    • Gibt eine „JSONResponse“ mit der Liste der Beiträge und einem Statuscode 200 zurück.
    • Behandelt Ausnahmen durch Protokollierung und Auslösen einer 500 „HTTPException“.

Diese Funktion erfordert die manuelle Initiierung des Scrapings alle paar Stunden, um nach neuen Aktivitäten im Forum zu suchen. Eine Automatisierung dieses Prozesses wird vermieden, um Ressourcen zu sparen, da durch kontinuierliches Scraping häufig doppelte Daten erfasst würden, was zu einer ineffizienten Ressourcennutzung führen würde. Daher ist die ununterbrochene Ausführung von Marktplatz-Scans nicht der optimale Ansatz.

Aus meiner umfangreichen Erfahrung ist die Implementierung kontinuierlicher Scans alle paar Stunden aufgrund des erheblichen Ressourcenbedarfs generell nicht zu empfehlen.

In Modul 5 werden wir kontinuierliches Daten-Scraping implementieren, aber wie Sie feststellen werden, erzeugt dieser Prozess oft doppelte Daten.


Marktplatz-Frontend-Vorlage

Für den Marktplatz benötigen wir eine Vorlage mit zwei Registerkarten, mit denen wir innerhalb einer einzigen Seite zwischen mehreren Containern wechseln können. Anstatt zwei separate Routen zu erstellen, verwenden wir eine Route mit Registerkarten, um das Design zu optimieren.

Registerkarten können eine Webanwendung manchmal komplizieren, in diesem Fall vereinfachen sie sie jedoch, da keine zwei separaten Vorlagen erforderlich sind, die die App unnötig aufblähen würden. Im weiteren Verlauf werden wir mehrere Vorlagen untersuchen, aber für diese spezielle Funktionalität sind Registerkarten ausreichend.

Die Vorlage befindet sich unter app/templates/marketplace.html.

  1. Registerkarten-Navigation für Paginierung und Post-Scans:

    • Zweck: Organisiert die Oberfläche in die Registerkarten „Marketplace Pagination” (Marktplatz-Paginierung) und „Marketplace Posts” (Marktplatz-Beiträge).
    • Backend-Interaktion: Die Funktion openTab() schaltet die Sichtbarkeit des Tab-Inhalts (pagination oder posts) ohne direkte Backend-Aufrufe um. Die Anfangsdaten für beide Tabs (pagination_scans und post_scans) werden von main.py::marketplace bereitgestellt und mit Jinja2 gerendert.
  2. Aufzählung der Paginierungsscans:

    • Zweck: Startet einen neuen Paginierungsscan, um Marktplatzseiten aufzulisten.
    • Backend-Interaktion:
      • Die Schaltfläche „Seiten auflisten“ öffnet ein Modal (enumerate-modal) mit Feldern für den Scan-Namen, die Paginierungs-URL und die maximale Seitenanzahl.
      • Mit dem Absenden des Formulars wird eine AJAX-POST-Anfrage mit den Formulardaten an /api/marketplace-scan/enumerate (verarbeitet von marketplace_api_router) gesendet.
      • Das Backend erstellt einen MarketplacePaginationScan-Datensatz, verarbeitet die Paginierung und speichert die Ergebnisse. Bei Erfolg wird die Seite neu geladen, um die aktualisierte Scanliste anzuzeigen. Fehler lösen eine Warnmeldung mit der Fehlermeldung aus.
  3. Aufzählung nach dem Scan:

    • Zweck: Erstellt einen neuen Post-Scan basierend auf einem vorhandenen Paginierungs-Scan.
    • Backend-Interaktion:
      • Die Schaltfläche „Beiträge auflisten” öffnet ein Modal (enumerate-posts-modal) mit Feldern für den Scan-Namen und einer Dropdown-Liste der vorhandenen Paginierungsscans (aus pagination_scans).
      • Mit dem Absenden des Formulars wird eine AJAX-POST-Anfrage an /api/marketplace-scan/posts/enumerate (verarbeitet von marketplace_api_router) mit dem Scan-Namen und dem ausgewählten Paginierungsscan gesendet.
      • Das Backend erstellt einen MarketplacePostScan-Datensatz, der mit dem ausgewählten Paginierungsscan verknüpft ist. Bei Erfolg wird die Seite neu geladen, um die Tabelle mit den Post-Scans zu aktualisieren. Fehler lösen eine Warnmeldung aus.
  4. Post-Scan-Verwaltung:

    • Zweck: Startet, zeigt an oder löscht Post-Scans.
    • Backend-Interaktion:
      • Start: Jede Post-Scan-Zeile (nicht ausgeführt) verfügt über eine Schaltfläche „Start“, die eine AJAX-POST-Anfrage an /api/marketplace-scan/posts/{scanId}/start (verarbeitet von marketplace_api_router) sendet, um den Scan zu starten. Bei Erfolg aktualisiert `refreshScans()“ die Tabelle.
      • Anzeigen: Eine Schaltfläche „Anzeigen“ öffnet ein Modal (view-posts-modal-{scanId}), das Post-Daten über eine AJAX-GET-Anfrage an /api/marketplace-scan/posts/{scanId}/posts abruft und eine Tabelle mit Post-Details (Zeitstempel, Titel, Autor, Link) füllt. Fehler lösen eine Warnmeldung aus.
      • Löschen: Eine Schaltfläche „Löschen“ fordert zur Bestätigung auf und sendet eine AJAX-DELETE-Anfrage an /api/marketplace-scan/posts/{scanId}, um den Scan aus der Tabelle MarketplacePostScan zu entfernen. Bei Erfolg wird die Seite neu geladen. Fehler lösen eine Warnmeldung aus.
  5. Anzeigen und Löschen von Paginierungsscans:

    • Zweck: Zeigt Details zu Paginierungsscans an und ermöglicht deren Löschung.
    • Backend-Interaktion:
      • Anzeigen: Jede Zeile eines Paginierungsscans verfügt über eine Schaltfläche „Anzeigen“, die ein Modalfenster (view-modal-{scanId}) mit schreibgeschützten Feldern für den Scan-Namen, die URL, die maximale Seitenanzahl und die Batches (im JSON-Format). Die Daten werden über Jinja2 aus pagination_scans vorab geladen, sodass kein zusätzlicher Backend-Aufruf erforderlich ist.
      • Löschen: Eine Schaltfläche „Löschen“ (deleteScan()) fordert zur Bestätigung auf und sendet eine AJAX-DELETE-Anfrage an /api/marketplace-scan/{scanId}, um den Scan aus der Tabelle MarketplacePaginationScan zu entfernen. Bei Erfolg wird die Seite neu geladen. Fehler lösen eine Warnmeldung aus.
  6. Aktualisierung der Post-Scan-Tabelle:

    • Zweck: Aktualisiert die Post-Scan-Tabelle, um den aktuellen Status widerzuspiegeln.
    • Backend-Interaktion:
      • Die Schaltfläche „Scans aktualisieren“ löst refreshScans() aus und sendet eine AJAX-GET-Anfrage an /api/marketplace-scan/posts/list (verarbeitet von marketplace_api_router).
      • Das Backend gibt eine Liste der MarketplacePostScan-Datensätze zurück (ID, Scan-Name, Paginierungs-Scan-Name, Start-/Abschlussdatum, Status).. Die Tabelle wird mit Status-Badges aktualisiert (z. B. „Abgeschlossen“, „Läuft“, „Gestoppt“). Fehler lösen eine Warnmeldung aus.

Testen

Um mit dem Testen zu beginnen, müssen Sie die folgenden Komponenten konfigurieren:

  1. Fügen Sie eine CAPTCHA-API aus dem Endpunkt /manage-api hinzu und aktivieren Sie sie.
  2. Erstellen Sie mindestens zwei Bot-Profile und melden Sie sich an, um deren Sitzungen vom Endpunkt „/bot-profile“ abzurufen.
  3. Rufen Sie die URL für die Marktplatz-Paginierung aus „tornet_forum“ ab, zum Beispiel: „http://site.onion/category/marketplace/Sellers?page=1“.
  4. Navigieren Sie zu „/marketplace-scan“, wählen Sie die Registerkarte „Marketplace Pagination“ (Marktplatz-Paginierung), klicken Sie auf „Enumerate Pages“ (Seiten auflisten) und füllen Sie die Felder wie folgt aus:
    1. Scan Name (Scan-Name): „Monkey“
    2. Pagination URL (Paginierungs-URL): „http://site.onion/category/marketplace/Sellers?page={page}“
    3. Maximale Anzahl der Seiten: 14 (an die Gesamtzahl der verfügbaren Seiten anpassen).

Sobald der Scan abgeschlossen ist, klicken Sie auf , um die Ergebnisse anzuzeigen. Daraufhin wird ein Modal angezeigt. Nachfolgend finden Sie ein Beispiel dafür, wie Paginierungsstapel im JSON-Format angezeigt werden können:

"{\"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\"]}"

Um Beiträge im Marktplatz aufzulisten, gehen Sie wie folgt vor:

  1. Navigieren Sie zu /marketplace-scan und wählen Sie die Registerkarte Marketplace Posts.
  2. Klicken Sie auf Enumerate Posts, geben Sie einen Scan-Namen ein, wählen Sie den Paginierungs-Scan mit dem Namen Monkey und klicken Sie auf Start Scan. Dadurch wird der Scan vorbereitet, aber nicht gestartet.
  3. Kehren Sie zu „/marketplace-scan“ zurück, gehen Sie zur Registerkarte „Marketplace Posts“, suchen Sie Ihren Scan und klicken Sie auf die Schaltfläche „Start“, um den Scan zu starten.

Nachfolgend finden Sie ein Beispiel für die Ausgabe meiner Konfiguration:

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

Sobald der Scan gestartet ist, können Sie zwischen den Seiten wechseln, während der Scan im Hintergrund weiterläuft:

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 

Der Scan wird nach einem Neustart des Systems oder beim Beenden der Anwendung nicht fortgesetzt.

So könnte das Ergebnis eines Scans auf Ihrer Seite aussehen:

Marketplace posts scraping