Nesta secção, aprenderemos como extrair publicações do mercado dos vendedores e distribuir as tarefas de extração por vários bots para serem executadas simultaneamente.

O objetivo é dividir as tarefas entre os bots porque a maioria dos fóruns impõe limites de taxa ao número de pedidos que pode enviar. Em tornet_forum, não há limitação de taxa para navegar na paginação e pode mover-se entre páginas sem estar conectado.

No entanto, para nos prepararmos para vários mecanismos de proteção, usaremos bots com sessões ativas para extrair. Embora não seja necessário para tornet_forum, sessões iniciadas podem ser necessárias para outros sites de destino que encontrar. Tendo extraído dados desses sites, compreendo os desafios que poderá enfrentar, e esta abordagem prepara-o para qualquer cenário.

Os tópicos desta secção incluem o seguinte:

  1. Modelos de base de dados
  2. Módulos de scraping do marketplace
  3. Rotas de backend do marketplace
  4. Modelo de frontend do marketplace
  5. Testes

Modelos de base de dados

Os seus modelos estão localizados em app/database/models.py. Precisa de 4 modelos para organizar corretamente os dados:

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 funcionalidade, precisamos de vários modelos para fazer tudo o seguinte:

  1. MarketplacePaginationScan:
    • Objetivo: Representa uma configuração de varredura de paginação para extrair dados de um marketplace. Armazena detalhes sobre uma varredura que enumera as páginas de um marketplace, como a URL base e o número máximo de páginas a serem varridas.
    • Campos-chave:
      • id: Identificador único para a varredura.
      • scan_name: Nome único para a varredura de paginação.
      • pagination_url: A URL base usada para paginação.
      • max_page: O número máximo de páginas a serem varridas.
      • batches: Armazena dados em lote serializados (por exemplo, JSON) para processamento de páginas.
  • timestamp: Regista quando a verificação foi criada.
  1. ScanStatus (Enum):

    • Finalidade: Define os estados possíveis de uma verificação pós-verificação, usado para rastrear o estado de um MarketplacePostScan.
    • Valores:
      • RUNNING: A verificação está em curso.
      • COMPLETED: A verificação foi concluída com sucesso.
      • STOPPED: A verificação não está em execução (padrão ou interrompida manualmente).
  2. MarketplacePostScan:

    • Objetivo: Representa uma verificação que recolhe publicações de um marketplace, vinculada a uma verificação de paginação específica. Rastreia os metadados e o estado da verificação.
    • Campos-chave:
      • id: Identificador único para a verificação de publicação.
      • scan_name: Nome único para a verificação de publicação.
      • pagination_scan_name: Faz referência ao MarketplacePaginationScan associado pelo seu scan_name.
      • start_date: Quando a verificação começou.
      • completion_date: Quando a verificação foi concluída (se aplicável).
      • status: Estado atual da verificação (da enumeração ScanStatus).
      • timestamp: Regista quando a verificação foi criada.
  3. MarketplacePost:

    • Finalidade: Armazena publicações individuais recolhidas durante uma MarketplacePostScan. Cada publicação está ligada a uma verificação específica e inclui detalhes sobre a publicação.
    • Campos-chave:
      • id: Identificador único da publicação.
      • scan_id: Faz referência ao MarketplacePostScan ao qual esta publicação pertence.
      • timestamp: Carimbo de data/hora da publicação (como uma string).
      • title: Título da publicação no marketplace.
      • author: Autor da publicação.
      • link: URL da publicação.
      • __table_args__: garante a exclusividade das publicações com base em scan_id e timestamp para evitar duplicatas.

Módulos do scraper do marketplace

Para ver um exemplo de como o scraper do marketplace funciona, abra 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)

Precisamos de duas funções individuais para realizar essas tarefas:

  1. create_pagination_batches(url_template, max_page)
    • Gera lotes de URLs para paginação, criando grupos de 10 URLs de páginas a partir de um modelo de URL fornecido e um número máximo de páginas, retornando-os como uma string JSON.
  2. scrape_posts(session, proxy, useragent, pagination_range, timeout)
    • Extraia detalhes da publicação (título, autor, carimbo de data/hora, link) de uma lista de URLs de páginas da Web usando uma sessão de solicitações, proxy e agente do utilizador, analisando HTML com BeautifulSoup e retorne os dados recolhidos como uma string JSON.

Limitamos os lotes de paginação a 10 páginas porque esse é o limite que definimos. Pode ajustar esse limite, mas se os seus bots acederem a 50 páginas de paginação em apenas alguns segundos, isso poderá acionar o bloqueio da conta.

Mais tarde, usaremos a função scrape_posts para processar intervalos de lotes de paginação, permitindo a extração de publicações de todos os lotes.


Criando o backend do marketplace

O backend pode parecer mais assustador do que as tarefas anteriores. Você pode encontrar o código do backend em app/routes/marketplace.py.

Essa complexidade surge da concorrência, que nos permite distribuir tarefas de scraping de dados por todos os bots disponíveis, aumentando a eficiência, mas adicionando complexidade. Observe que todas as varreduras são executadas em segundo plano, portanto, continuam mesmo se você navegar entre as páginas.

Embora as funcionalidades possam parecer complexas, isso é uma parte natural do processo de aprendizagem. O nosso objetivo é construir um scraper web avançado para a recolha de dados a longo prazo, uma tarefa que é inerentemente sofisticada.

get_pagination_scans

  • Ponto final: GET /api/marketplace-scan/list
  • Objetivo: Recupera todas as varreduras de paginação do banco de dados.
  • Funcionalidade:
    • Consulta a tabela MarketplacePaginationScan para buscar todos os registos.
    • Regista o número de varreduras buscadas.
    • Formata cada varredura em um dicionário compatível com JSON contendo id, scan_name, pagination_url, max_page, batches e timestamp.
    • Retorna um JSONResponse com a lista de varreduras e um código de status 200.
    • Lida com exceções registrando erros e gerando uma HTTPException com um código de status 500 se ocorrer um erro.

enumerate_pages

  • Ponto de extremidade: POST /api/marketplace-scan/enumerate
  • Objetivo: Cria uma nova digitalização de paginação para enumerar páginas para extração.
  • Funcionalidade:
    • Valida se o scan_name fornecido já existe no banco de dados.
    • Chama create_pagination_batches para gerar lotes de URLs com base no pagination_url e max_page fornecidos.
    • Cria um novo registo MarketplacePaginationScan com os detalhes da verificação e armazena os lotes como JSON.
    • Envia o registo para a base de dados e regista a criação.
    • Armazena uma mensagem de sucesso na sessão e retorna um JSONResponse com um código de estado 201.
    • Lida com nomes de varredura duplicados (400), erros de banco de dados (500, com reversão) e outras exceções registrando e levantando HTTPExceptions apropriados.

delete_pagination_scan

  • Ponto final: DELETE /api/marketplace-scan/{scan_id}
  • Objetivo: exclui uma digitalização de paginação pelo seu ID.
  • Funcionalidade:
    • Consulta a tabela MarketplacePaginationScan para a digitalização com o scan_id especificado.
    • Se a varredura não for encontrada, registra um aviso e gera uma HTTPException 404.
    • Exclui a varredura do banco de dados e confirma a transação.
    • Registra a exclusão e armazena uma mensagem de sucesso na sessão.
    • Retorna uma JSONResponse com um código de status 200.
    • Lida com erros registrando-os, revertendo a transação e gerando uma HTTPException 500.

get_post_scans

  • Ponto final: GET /api/marketplace-scan/posts/list
  • Objetivo: Recupera todas as digitalizações de publicações da base de dados.
  • Funcionalidade:
    • Consulta a tabela MarketplacePostScan para obter todos os registos.
    • Regista o número de digitalizações obtidas.
    • Formata cada digitalização num dicionário compatível com JSON com id, scan_name, pagination_scan_name, start_date, completion_date, status e timestamp.
    • Retorna uma JSONResponse com a lista de digitalizações e um código de estado 200.
    • Lida com exceções registrando e gerando uma HTTPException 500.

get_post_scan_status

  • Ponto de extremidade: GET /api/marketplace-scan/posts/{scan_id}/status
  • Objetivo: Recupera o estado de uma verificação de publicação específica pelo seu ID.
  • Funcionalidade:
    • Consulta a tabela MarketplacePostScan para a verificação com o scan_id especificado.
    • Se a verificação não for encontrada, regista um aviso e gera uma HTTPException 404.
    • Regista o estado e retorna uma JSONResponse com o id, scan_name e status (como um valor de string) da verificação com um código de estado 200.
    • Trata as exceções registando-as e gerando uma HTTPException 500.

enumerate_posts

  • Ponto de extremidade: POST /api/marketplace-scan/posts/enumerate
  • Objetivo: Cria uma nova verificação de publicação associada a uma verificação de paginação.
  • Funcionalidade:
    • Valida se o scan_name fornecido já existe.
    • Verifica se o pagination_scan_name referenciado existe na tabela MarketplacePaginationScan.
    • Garante que existem bots ativos com a finalidade SCRAPE_MARKETPLACE e sessões válidas.
    • Cria um novo registo MarketplacePostScan com o scan_name, pagination_scan_name e o estado inicial STOPPED fornecidos.
    • Envia o registo para a base de dados e regista a criação.
    • Armazena uma mensagem de sucesso na sessão e retorna uma JSONResponse com um código de estado 201.
    • Lida com erros para nomes de varredura duplicados (400), varreduras de paginação ausentes (404), nenhum bot ativo (400) ou outros problemas (500, com reversão).

start_post_scan

  • Ponto final: POST /api/marketplace-scan/posts/{scan_id}/start
  • Objetivo: Inicia uma pós-varredura processando lotes de URLs usando bots disponíveis.
  • Funcionalidade:
    • Recupera o MarketplacePostScan por scan_id e verifica se ele existe.
    • Garante que a verificação ainda não está em execução (gera 400 se estiver).
    • Verifica a disponibilidade de bots com a finalidade SCRAPE_MARKETPLACE.
    • Recupera o MarketplacePaginationScan associado e seus lotes.
    • Atualiza o status da verificação para RUNNING, define a start_date e limpa a completion_date.
    • Executa uma tarefa scrape_batches assíncrona para processar lotes simultaneamente:
    • Atribui lotes aos bots disponíveis usando um ThreadPoolExecutor.
    • Cada bot raspa um lote de URLs usando a função scrape_posts, com cookies de sessão e proxy Tor.
    • Lida com erros de análise JSON sanitizando os dados (normalizando Unicode, removendo caracteres de controlo).
    • Guarda publicações únicas na tabela MarketplacePost, evitando duplicados.
    • Regista o progresso e os erros de cada lote.
    • Marca a verificação como COMPLETED em caso de sucesso ou STOPPED em caso de falha.
    • Armazena uma mensagem de sucesso na sessão e devolve uma JSONResponse com um código de estado 200.
    • Lida com erros para varreduras ausentes (404), varreduras em execução (400), sem bots (400), lotes ausentes (400) ou outros problemas (500, com reversão).

delete_post_scan

  • Ponto final: DELETE /api/marketplace-scan/posts/{scan_id}
  • Objetivo: Elimina uma verificação de publicação pelo seu ID.
  • Funcionalidade:
    • Consulta a tabela MarketplacePostScan para a verificação com o scan_id especificado.
    • Se a verificação não for encontrada, regista um aviso e gera uma HTTPException 404.
    • Elimina a verificação da base de dados e confirma a transação.
    • Regista a eliminação e armazena uma mensagem de sucesso na sessão.
    • Retorna uma JSONResponse com um código de estado 200.
    • Trata os erros registando-os, revertendo a transação e gerando uma HTTPException 500.

get_scan_posts

  • Ponto de extremidade: GET /api/marketplace-scan/posts/{scan_id}/posts
  • Objetivo: Recupera todas as publicações associadas a uma verificação de publicação específica.
  • Funcionalidade:
    • Consulta a tabela MarketplacePostScan para verificar se a verificação existe.
    • Se a verificação não for encontrada, regista um aviso e gera uma HTTPException 404.
    • Consulta a tabela MarketplacePost para todas as publicações ligadas ao scan_id.
    • Regista o número de publicações recuperadas.
    • Formata cada publicação num dicionário compatível com JSON com id, timestamp, title, author e link.
    • Retorna um JSONResponse com a lista de publicações e um código de estado 200.
    • Trata exceções registando e gerando uma HTTPException 500.

Esta funcionalidade requer o início manual da recolha a cada poucas horas para verificar se há novas atividades no fórum. A automatização deste processo é evitada para conservar recursos, pois a recolha contínua frequentemente recolheria dados duplicados, levando a um consumo ineficiente de recursos. Portanto, executar varreduras no marketplace sem parar não é a abordagem ideal.

Pela minha vasta experiência, implementar varreduras contínuas a cada poucas horas geralmente não é aconselhável devido à significativa demanda de recursos.

No Módulo 5, implementaremos a recolha contínua de dados, mas, como você descobrirá, esse processo geralmente gera dados duplicados.


Modelo de front-end do marketplace

Para o marketplace, precisamos de um modelo com duas guias, que nos permitam alternar entre vários contentores dentro de uma única página. Em vez de criar duas rotas separadas, usaremos uma rota com guias para simplificar o design.

Embora as guias possam, por vezes, complicar uma aplicação web, neste caso, elas simplificam-na, evitando a necessidade de dois modelos separados, o que aumentaria o tamanho da aplicação. À medida que avançamos, exploraremos vários modelos, mas para esta funcionalidade específica, as guias são suficientes.

O modelo está localizado em app/templates/marketplace.html.

  1. Navegação por separadores para paginação e digitalizações de publicações:

    • Objetivo: Organiza a interface em separadores «Paginação do mercado» e «Publicações do mercado».
    • Interação com o backend: A função openTab() alterna a visibilidade do conteúdo da guia (pagination ou posts) sem chamadas diretas ao backend. Os dados iniciais para ambas as guias (pagination_scans e post_scans) são fornecidos por main.py::marketplace e renderizados usando Jinja2.
  2. Enumeração de digitalização de paginação:

    • Objetivo: Inicia uma nova digitalização de paginação para enumerar as páginas do marketplace.
    • Interação com o backend:
      • O botão “Enumerar páginas” abre um modal (enumerate-modal) com campos para o nome da digitalização, URL de paginação e número máximo de páginas.
      • O envio do formulário envia uma solicitação AJAX POST para /api/marketplace-scan/enumerate (tratada por marketplace_api_router) com os dados do formulário.
      • O backend cria um registo MarketplacePaginationScan, processa a paginação e armazena os resultados. Em caso de sucesso, a página é recarregada para exibir a lista de varreduras atualizada. Erros acionam um alerta com a mensagem de erro.
  3. Enumeração pós-digitalização:

    • Objetivo: Cria uma nova digitalização pós-digitalização com base numa digitalização de paginação existente.
    • Interação com o backend:
      • O botão «Enumerar publicações» abre um modal (enumerate-posts-modal) com campos para o nome da digitalização e uma lista suspensa das digitalizações de paginação existentes (preenchida a partir de pagination_scans).
      • O envio do formulário envia uma solicitação AJAX POST para /api/marketplace-scan/posts/enumerate (tratada por marketplace_api_router) com o nome da verificação e a verificação de paginação selecionada.
      • O backend cria um registo MarketplacePostScan ligado à verificação de paginação escolhida. Se for bem-sucedido, a página é recarregada para atualizar a tabela de verificações de publicações. Os erros acionam um alerta.
  4. Gestão de verificações de publicações:

    • Objetivo: Inicia, visualiza ou elimina verificações de publicações.
    • Interação com o backend:
      • Iniciar: Cada linha de verificação de publicação (não em execução) tem um botão «Iniciar» que envia uma solicitação AJAX POST para /api/marketplace-scan/posts/{scanId}/start (gerido por marketplace_api_router) para iniciar a verificação. Em caso de sucesso, refreshScans() atualiza a tabela.
      • Visualizar: Um botão «Visualizar» abre um modal (view-posts-modal-{scanId}) que busca os dados da publicação por meio de uma solicitação AJAX GET para /api/marketplace-scan/posts/{scanId}/posts, preenchendo uma tabela com os detalhes da publicação (carimbo de data/hora, título, autor, link). Erros acionam um alerta.
      • Eliminar: Um botão «Eliminar» solicita confirmação e envia uma solicitação AJAX DELETE para /api/marketplace-scan/posts/{scanId} para remover a verificação da tabela MarketplacePostScan. Se for bem-sucedido, a página é recarregada. Os erros acionam um alerta.
  5. Visualização e eliminação da digitalização de paginação:

    • Objetivo: Exibe detalhes das digitalizações de paginação e permite a eliminação.
      • Interação com o backend:
      • Visualizar: Cada linha de digitalização de paginação tem um botão «Visualizar» que abre um modal (view-modal-{scanId}) com campos somente de leitura para nome da digitalização, URL, página máxima e lotes (formato JSON). Os dados são pré-carregados de pagination_scans via Jinja2, sem necessidade de chamada adicional ao backend.
      • Eliminar: Um botão «Eliminar» (deleteScan()) solicita confirmação e envia um pedido AJAX DELETE para /api/marketplace-scan/{scanId} para remover a digitalização da tabela MarketplacePaginationScan. Se for bem-sucedido, a página é recarregada. Os erros acionam um alerta.
  6. Atualização da tabela pós-digitalização:

    • Objetivo: atualiza a tabela pós-digitalizações para refletir os status atuais.
    • Interação com o backend:
      • O botão “Atualizar digitalizações” aciona refreshScans(), enviando uma solicitação AJAX GET para /api/marketplace-scan/posts/list (gerenciada por marketplace_api_router).
      • O backend retorna uma lista de registos MarketplacePostScan (ID, nome da verificação, nome da verificação de paginação, datas de início/conclusão, estado). A tabela é atualizada com ícones de estado (por exemplo, Concluído, Em execução, Parado). Os erros acionam um alerta.

Testes

Para iniciar os testes, você precisará configurar os seguintes componentes:

  1. Adicione e ative uma API CAPTCHA a partir do ponto final /manage-api.
  2. Crie pelo menos dois perfis de bot e faça login para recuperar as suas sessões a partir do ponto final /bot-profile.
  3. Obtenha o URL de paginação do marketplace a partir de tornet_forum, por exemplo: http://site.onion/category/marketplace/Sellers?page=1.
  4. Navegue até /marketplace-scan, selecione o separador Marketplace Pagination, clique em Enumerate Pages e preencha os campos da seguinte forma:
    1. Scan Name: Monkey
    2. Pagination URL: http://site.onion/category/marketplace/Sellers?page={page}
    3. Número máximo de paginação: 14 (ajuste com base no número total de páginas de paginação disponíveis).

Quando a verificação for concluída, clique para ver os resultados e um modal será exibido. Abaixo está um exemplo de como os lotes de paginação podem aparecer no 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 publicações no marketplace, siga estas etapas:

  1. Navegue até /marketplace-scan e selecione a guia Publicações do marketplace.
  2. Clique em Enumerar publicações, insira um nome para a verificação, selecione a verificação de paginação chamada Monkey e clique em Iniciar verificação. Isso prepara a verificação, mas não a inicia.
  3. Volte para /marketplace-scan, aceda ao separador Marketplace Posts, localize a sua verificação e clique no botão Start para iniciar a verificação.

Abaixo está um exemplo do resultado da minha configuração:

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

Quando a digitalização começar, observe que é possível alternar entre páginas, e a digitalização continuará a ser executada em 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 

As digitalizações não serão retomadas após reiniciar o sistema ou se sair da aplicação.

Aqui está como o resultado de uma digitalização pode aparecer no seu ecrã:

Publicações do Marketplace copiadas