Neste módulo, exploraremos como o tornet_scraper funciona. Já aprendeu a extrair dados dos fóruns tornet e clearnet, mas para simplificar este processo, precisamos de um web scraper avançado equipado com funcionalidades para a recolha de dados em grande escala.
Este tópico pode parecer desafiante para alguns, mas na era da IA, em que grande parte da programação pode ser automatizada, e com este curso a aproveitar a IA para a defesa cibernética, construir uma aplicação web para scraping de dados está agora mais acessível.
Todas as aplicações web dependem de uma pilha de tecnologia, uma coleção de ferramentas de software usadas para desenvolver diferentes componentes da aplicação. Por exemplo, sites como o Facebook apresentam uma interface de utilizador (front-end) e uma lógica do lado do servidor (back-end) que torna as páginas acessíveis. Em alguns casos, o front-end e o back-end podem usar a mesma linguagem de programação. Por exemplo, aplicações web baseadas em JavaScript podem usar Vue para o front-end e Nuxt para o back-end, simplificando o desenvolvimento, uma vez que ambos são escritos em JavaScript.
Para os nossos fins, precisamos de uma pilha de tecnologia escalável e compatível com API que possa personalizar de forma independente. Este módulo não aprofundará conceitos de programação, pois pode copiar e colar o código na sua ferramenta de IA preferida para obter uma explicação personalizada que se adapte ao seu estilo de aprendizagem.
Se preferir ignorar este módulo e começar com um projeto funcional, pode fazê-lo, mas perderá a oportunidade de aprender como o projeto funciona, e será apenas mais uma ferramenta. O projeto final está disponível aqui:
https://github.com/CyberMounties/tornet_scraper
Os tópicos desta secção incluem o seguinte:
- Como funcionam os modelos
- Como funciona o
main.py - Como funcionam as bases de dados
- Como funcionam as rotas
- Como funcionam os scrapers
- Como funcionam os serviços
Como funcionam os modelos
As aplicações web modernas utilizam modelos, tais como um modelo padrão ou base, para definir elementos que aparecem em todas as páginas, como um menu de navegação ou uma barra de navegação. Embora seja possível copiar e colar a barra de navegação em todas as páginas, quaisquer alterações exigiriam a atualização de cada página individualmente.
Para simplificar isso, usamos um motor de modelos. Em aplicações web Python, o Jinja2 permite-nos criar um modelo base com blocos de conteúdo para um design consistente e eficiente.
Abra app/templates/base.html:
<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="en" data-theme="nord">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %} - Tornet Scraper</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body class="min-h-screen bg-base-200">
<!-- Navbar -->
<div class="navbar bg-base-100 shadow-xl">
<div class="navbar-start">
<a class="btn btn-ghost text-xl">Tornet Scraper</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><a href="/" class="btn btn-ghost">Dashboard</a></li>
<li><a href="/proxy-gen" class="btn btn-ghost">Proxy Gen</a></li>
<li><a href="/manage-api" class="btn btn-ghost">API Management</a></li>
<li><a href="/bot-profile" class="btn btn-ghost">Bot Profile</a></li>
<li><a href="/marketplace-scan" class="btn btn-ghost">Marketplace Scan</a></li>
<li><a href="/posts-scans" class="btn btn-ghost">Posts Scans</a></li>
<li><a href="/watchlist" class="btn btn-ghost">Watchlist</a></li>
</ul>
</div>
<div class="navbar-end">
<div class="dropdown dropdown-end lg:hidden">
<label tabindex="0" class="btn btn-ghost">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-52">
<li><a href="/">Dashboard</a></li>
<li><a href="/proxy-gen">Proxy Gen</a></li>
<li><a href="/manage-api">API Management</a></li>
<li><a href="/bot-profile">Bot Profile</a></li>
<li><a href="/marketplace-scan">Marketplace Scan</a></li>
<li><a href="/posts-scans" class="btn btn-ghost">Posts Scans</a></li>
<li><a href="/watchlist" class="btn btn-ghost">Watchlist</a></li>
</ul>
</div>
</div>
</div>
<!-- Main content -->
<main class="container mx-auto p-4 mt-6 bg-base-300 rounded-box">
<!-- Flash Messages Container -->
<div class="mb-4" id="flash-messages">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.category | default('info') }} shadow-lg mb-4 flex justify-between items-center flash-message">
<div>
<span>{{ message.text }}</span>
</div>
<button class="btn btn-sm btn-circle btn-ghost" onclick="this.parentElement.remove()">✕</button>
</div>
{% endfor %}
{% endif %}
</div>
{% block content %}
{% endblock %}
</main>
<script>
// Automatically remove flash messages after 5 seconds
document.addEventListener('DOMContentLoaded', function() {
function removeFlashMessages() {
const flashMessages = document.querySelectorAll('.flash-message');
flashMessages.forEach(function(message) {
// Skip if already fading
if (message.style.opacity === '0') return;
setTimeout(function() {
message.style.transition = 'opacity 0.5s ease';
message.style.opacity = '0';
setTimeout(function() {
message.remove();
}, 500);
}, 5000);
});
}
removeFlashMessages();
const flashContainer = document.querySelector('#flash-messages');
if (flashContainer) {
const observer = new MutationObserver(function() {
removeFlashMessages();
});
observer.observe(flashContainer, { childList: true });
}
});
</script>
</body>
</html>
O modelo base.html é um modelo base Jinja2 para fornecer um layout consistente para todas as páginas. Inclui uma barra de navegação, mensagens flash e espaços reservados para título e conteúdo que os modelos filhos podem substituir. Abaixo está uma explicação técnica de como funciona e o seu mecanismo de extensão.
-
Estrutura HTML:
- Define um documento HTML5 padrão com um atributo
data-theme=“nord”para o estilo DaisyUI. - Inclui recursos externos: CSS DaisyUI, CSS Tailwind e jQuery para estilo e interatividade.
- Define um documento HTML5 padrão com um atributo
-
Bloco de título:
- A tag
<title>contém um bloco Jinja2{% block title %}{% endblock %} - Tornet Scraper. - Os modelos filhos substituem o bloco
titlepara definir um título específico da página, que é acrescentado com “ - Tornet Scraper”. - Exemplo: Um modelo filho com
{% block title %}Dashboard{% endblock %}resulta em<title>Dashboard - Tornet Scraper</title>.
- A tag
-
Barra de navegação:
- Uma barra de navegação responsiva com um logótipo (“Tornet Scraper”) e links de navegação (Painel, Proxy Gen, etc.).
- Em ecrãs grandes (
lg:flex), os links são apresentados horizontalmente; em ecrãs mais pequenos, um menu suspenso (ativado por um ícone de hambúrguer) mostra os links verticalmente.
-
Mensagens flash:
- Um
<div id="flash-messages">apresenta mensagens enviadas a partir do backend (por exemplo, notificações de sucesso ou erro). - Usa Jinja2:
{% if messages %}percorremessages(uma lista de objetos comtextecategory). - Cada mensagem é estilizada como um
alertDaisyUI com uma classe dinâmica (alert-{{ message.category }}, padrão parainfo). - Um botão Fechar (
✕) remove a mensagem ao clicar. - O JavaScript desvanece e remove automaticamente as mensagens flash após 5 segundos usando uma transição CSS (
opacity). - Um
MutationObservergarante que novas mensagens flash (adicionadas dinamicamente) também sejam removidas automaticamente.
- Um
-
Bloco de conteúdo:
- A secção
<main>contém um espaço reservado{% block content %}{% endblock %}. - Os modelos filhos substituem este bloco para inserir conteúdo específico da página no contentor principal, estilizado com classes Tailwind/DaisyUI (
container,mx-auto, etc.).
- A secção
-
Extensão em modelos filhos:
- Os modelos filhos usam
{% extends ‘base.html’ %}para herdar a estrutura base. - Eles substituem
{% block title %}para o título da página e{% block content %}para o conteúdo principal. - Isso renderiza o layout completo
base.htmlcom “Painel” no título e o conteúdo especificado na seção<main>.
- Os modelos filhos usam
Esta configuração garante uma interface de utilizador consistente, títulos dinâmicos, navegação reutilizável e tratamento de mensagens flash intuitivo em todas as páginas.
dashboard.html
O ficheiro localizado em app/templates/dashboard.html fornece um exemplo claro de como ele estende base.html. Embora este ficheiro não seja estritamente necessário, criei o hábito de incluí-lo. Abaixo está o código contido nele:
<!-- app/templates/dashboard.html -->
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<h1 class="text-3xl font-bold text-base-content mb-4">Dashboard</h1>
{% endblock %}
Repare como ele estende base.html?
O conteúdo dentro de {% block content %} é inserido no contentor de conteúdo principal definido em base.html:
<!-- Main content -->
<main class="container mx-auto p-4 mt-6 bg-base-300 rounded-box">
{% block content %}
{% endblock %}
</main>
O título também é definido:
{% block title %}Dashboard{% endblock %}
Em base.html, a estrutura do título é:
<title>{% block title %}{% endblock %} - Tornet Scraper</title>
Ao visualizar o código-fonte da página do painel, o título aparece como:
<title>Dashboard - Tornet Scraper</title>
Em vez de reescrever o título para cada página, pode defini-lo em base.html e, em seguida, definir apenas o nome da página para todas as outras páginas.
Como funciona o main.py
Pode encontrar o main.py em app/main.py.
O ficheiro main.py é o núcleo de uma aplicação web FastAPI, definindo rotas, modelos e interações com a base de dados para uma interface de web scraper. Abaixo está uma explicação técnica das variáveis globais, routers incluídos e funções de tratamento de rotas.
Variáveis globais
-
app:- Finalidade: A instância principal da aplicação FastAPI.
- Detalhes: Inicializa a estrutura FastAPI para lidar com pedidos e respostas HTTP.
-
BASE_DIR:- Finalidade: Armazena o caminho absoluto do diretório raiz do projeto.
- Detalhes: Derivado usando
os.pathpara localizar o diretórioapp/templatespara modelos Jinja2.
-
TEMPLATES_DIR:- Objetivo: Especifica o diretório para modelos Jinja2.
- Detalhes: definido como
app/templatesdentro da raiz do projeto para renderizar modelos HTML.
-
templates:- Finalidade: instância do motor de modelos Jinja2.
- Detalhes: configurado para carregar modelos de
TEMPLATES_DIRpara renderizar HTML dinâmico.
Roteadores incluídos
-
proxy_gen_router:- Finalidade: Regista rotas para a funcionalidade de geração de proxy.
- Detalhes: Importado de
app.routes.proxy_gen, trata de pontos finais relacionados com proxy.
-
manage_api_router:- Finalidade: Regista rotas para gestão de API.
- Detalhes: Importado de
app.routes.manage_api, gere chaves e configurações da API.
-
bot_profile_router:- Finalidade: Regista rotas para gestão de perfis de bots.
- Detalhes: Importado de
app.routes.bot_profile, trata da configuração dos bots.
-
marketplace_api_router:- Finalidade: Regista rotas para a verificação do marketplace.
- Detalhes: Importado de
app.routes.marketplace, gere a recuperação de dados do marketplace.
-
posts_api_router:- Finalidade: Regista rotas para a verificação de publicações.
- Detalhes: Importado de
app.routes.posts, trata das operações de dados relacionadas com publicações.
-
watchlist_api_router:- Finalidade: Regista rotas para a gestão da lista de observação.
- Detalhes: Importado de
app.routes.watchlist, gere itens da lista de observação e digitalizações.
Funções do manipulador de rotas
-
dashboard:- Objetivo: Renderiza a página do painel.
- Parâmetros principais:
request: ObjetoRequestdo FastAPI para contexto de modelo.db: SQLAlchemySessionpara acesso ao banco de dados (viaDepends(get_db)).
- Retorna:
TemplateResponserenderizandodashboard.htmlcom mensagens exibidas da sessão.
-
proxy_gen:- Objetivo: Exibe a página de geração de proxies com uma lista de proxies.
- Parâmetros-chave:
request: objeto FastAPIRequest.db: SQLAlchemySessionpara consultar proxies.
- Retorna:
TemplateResponserenderizandoproxy_gen.htmlcom dados do proxy (nome do contêiner, IP, nó de saída do Tor, carimbo de data/hora, status de execução) e mensagens em flash.
-
manage_api:- Objetivo: Mostra a página de gerenciamento da API com detalhes da API.
- Parâmetros principais:
request: objetoRequestdo FastAPI.db:Sessiondo SQLAlchemy para consultar APIs.
- Retorna:
TemplateResponserenderizandomanage_api.htmlcom dados da API (ID, nome, provedor, chave, modelo, tokens máximos, prompt, carimbo de data/hora, status ativo) e mensagens instantâneas.
-
bot_profile:- Objetivo: Exibe a página de gerenciamento do perfil do bot.
- Parâmetros-chave:
request: objeto FastAPIRequest.db: SQLAlchemySessionpara consultar perfis de bots e o URL onion mais recente.
- Retorna:
TemplateResponserenderizandobot_profile.htmlcom dados do perfil do bot (ID, nome de utilizador, palavra-passe mascarada, finalidade, proxy Tor, carimbo de data/hora), URL onion mais recente e mensagens exibidas.
-
marketplace:- Objetivo: Renderiza a página de varredura do marketplace com paginação e dados da varredura de publicações.
- Parâmetros principais:
request: objetoRequestdo FastAPI.db:Sessiondo SQLAlchemy para consultar varreduras.
- Retorna:
TemplateResponserenderizandomarketplace.htmlcom varreduras paginadas, varreduras de postagem e mensagens exibidas.
-
posts_scans:- Finalidade: Exibe a página de visão geral das varreduras de postagem.
- Parâmetros principais:
request: objeto FastAPIRequest.db: SQLAlchemySessionpara acesso ao banco de dados.
- Retorna:
TemplateResponserenderizandoposts_scans.htmlcom mensagens instantâneas.
-
posts_scan_result:- Objetivo: Mostra os resultados de uma verificação de postagem específica.
- Parâmetros principais:
scan_id: ID inteiro da verificação.request: objeto FastAPIRequest.db: SQLAlchemySessionpara acesso ao banco de dados.name: nome opcional da verificação (string, padrão vazio).
- Retorna:
TemplateResponserenderizandoposts_scan_result.htmlcom ID da verificação, nome e mensagens exibidas.
-
watchlist:- Objetivo: Renderiza a página de visão geral da lista de observação.
- Parâmetros principais:
request: objeto FastAPIRequest.db: SQLAlchemySessionpara acesso ao banco de dados.
- Retorna:
TemplateResponserenderizandowatchlist.htmlcom mensagens exibidas.
-
watchlist_profile:- Objetivo: Exibe o perfil de um item específico da lista de observação e as varreduras associadas.
- Parâmetros principais:
target_id: ID inteiro do item da lista de observação.request: objeto FastAPIRequest.db: SQLAlchemySessionpara consultar o item da lista de observação e as varreduras.- Retorna:
TemplateResponserenderizandowatchlist_profile.htmlcom o item da lista de observação, os dados da varredura e as mensagens exibidas; geraHTTPException(404 para item ausente, 500 para erros do servidor).
Como funcionam as bases de dados
Na nossa aplicação FastAPI, db.py gere as configurações do motor da base de dados, enquanto models.py define o esquema e as tabelas da base de dados. A utilização de um ficheiro db.py separado permite alternar facilmente entre tipos de bases de dados. Embora tornet_scraper utilize SQLite3 para prototipagem e testes, não é ideal para a recolha de dados em grande escala. Para ambientes de produção, recomendamos a transição para um banco de dados mais robusto, como o PostgreSQL.
Você também pode implementar lógica em db.py para alternar dinamicamente entre o SQLite3 e um servidor de banco de dados com base em condições ou no ambiente (por exemplo, desenvolvimento, teste ou produção).
db.py
O ficheiro db.py configura a ligação à base de dados e a gestão de sessões para uma aplicação FastAPI utilizando SQLAlchemy. Abaixo encontra-se uma explicação concisa dos seus componentes e funcionalidades.
Pode encontrar db.py em app/database/db.py.
- Finalidade:
- Configura uma ligação à base de dados SQLite, cria tabelas e fornece uma dependência para sessões de base de dados em rotas FastAPI.
-
Variáveis globais:
BASE_DIR:- Objetivo: Define o diretório raiz do projeto.
- Detalhes: Usa
Path(__file__).resolve().parent.parent.parentpara calcular o caminho absoluto para a raiz do projeto, garantindo que o ficheiro da base de dados esteja localizado corretamente.
DATABASE_URL:- Finalidade: Especifica a string de conexão com o banco de dados.
- Detalhes: Configurado para SQLite como
sqlite:///{BASE_DIR}/tornet_scraper.db, apontando para um ficheirotornet_scraper.dbna raiz do projeto.
engine:- Finalidade: motor SQLAlchemy para interações com a base de dados.
- Detalhes: criado com
create_engine(DATABASE_URL, connect_args={“check_same_thread”: False})para lidar com ligações SQLite. O argumentocheck_same_threadpermite que o SQLite seja usado num ambiente FastAPI multithread.
SessionLocal:- Finalidade: Fábrica para criar sessões de base de dados.
- Detalhes: Configurado com
sessionmaker(autocommit=False, autoflush=False, bind=engine)para criar sessões vinculadas ao motor, com controlo manual de commit e flush.
-
Funções:
get_db:- Finalidade: Fornece uma sessão de base de dados para rotas FastAPI.
- Parâmetros-chave: Nenhum (função de dependência).
- Retorna: Produz uma sessão SQLAlchemy (
db) e garante que ela seja fechada após o uso. - Detalhes: Usado como uma dependência FastAPI (via
Depends(get_db)) para injetar uma sessão em manipuladores de rota. O blocotry/finallygarante que a sessão seja encerrada, evitando fugas de recursos.
init_db:- Objetivo: Inicializa a base de dados criando tabelas.
- Parâmetros-chave: Nenhum.
- Retorna: Nenhum.
- Detalhes: Importa
Basedemodels.pye chamaBase.metadata.create_all(bind=engine)para criar todas as tabelas definidas (por exemplo,proxies,apis) no banco de dados SQLite, caso elas não existam.
-
Utilização:
- Objetivo: Habilita operações de banco de dados na aplicação.
- Detalhes:
init_dbé chamado emmain.pypara configurar o esquema do banco de dados na inicialização.get_dbé usado em manipuladores de rotas (por exemplo,/proxy-gen,/manage-api) para consultar ou modificar dados em tabelas definidas emmodels.py. A sessão garante a integridade transacional e a limpeza adequada dos recursos.
models.py
O ficheiro models.py define o esquema da base de dados para uma aplicação FastAPI utilizando SQLAlchemy, especificando tabelas, colunas e relações. Abaixo está uma explicação concisa dos seus componentes e funcionalidades.
Pode encontrar models.py em app/database/models.py.
-
Visão geral do SQLAlchemy:
- Objetivo: O SQLAlchemy é uma biblioteca ORM (mapeamento objeto-relacional) para Python, que permite a interação com um banco de dados relacional usando objetos Python.
- Detalhes: Mapeia classes Python para tabelas de banco de dados, permitindo operações CRUD por meio de sintaxe orientada a objetos. Usa
declarative_basepara criar uma classe base (Base) para definições de modelo.
-
Colunas:
- Objetivo: Representam campos em uma tabela de banco de dados.
- Detalhes: Definidas usando a classe
Columndo SQLAlchemy, especificando atributos como tipo de dados (por exemplo,Integer,String,DateTime), restrições (por exemplo,primary_key,unique,nullable) e padrões (por exemplo,datetime.utcnow). As colunas armazenam dados para cada registo numa tabela.
-
Tabelas:
- Objetivo: Representar tabelas de bases de dados que armazenam dados estruturados.
- Detalhes: Cada classe (por exemplo,
Proxy,APIs) herda deBasee define uma tabela através de__tablename__. As tabelas incluem colunas e restrições opcionais (por exemplo,UniqueConstraintpara combinações únicas de campos). Exemplos incluemproxies,apis,bot_profiles, etc.
-
Relações:
- Finalidade: Definem associações entre tabelas para consultas relacionais.
- Detalhes: Estabelecidas usando colunas
ForeignKey(por exemplo,MarketplacePost.scan_idrefere-se amarketplace_post_scans.id). As relações permitem unir tabelas, como ligarMarketplacePostaMarketplacePostScanatravés descan_id. O SQLAlchemy trata estas como referências de objetos em consultas.
-
Enums:
- Objetivo: Definir vocabulários controlados para campos específicos.
- Detalhes: usa
enum.Enumdo Python (por exemplo,BotPurpose,ScanStatus) para restringir os valores das colunas a opções predefinidas (por exemplo,SCRAPE_MARKETPLACE,RUNNING). Armazenado como strings no banco de dados por meio deEnum.
-
Componentes principais:
Base: A classe base do SQLAlchemy para todos os modelos, usada para gerar esquemas de tabelas.- Definições de tabelas: Classes como
Proxy,APIs,BotProfile, etc., definem tabelas para armazenar detalhes de proxy, configurações de API, perfis de bot, URLs de cebola, varreduras de marketplace, publicações e listas de observação. - Restrições: Restrições exclusivas (por exemplo,
UniqueConstraintemMarketplacePost) garantem a integridade dos dados, impedindo registros duplicados com base em combinações específicas de colunas. - Carimbos de data/hora: Muitas tabelas incluem uma coluna
timestamp(padrãodatetime.utcnow) para rastrear os horários de criação/atualização dos registros.
-
Utilização:
- Objetivo: Os modelos são usados pela aplicação FastAPI para interagir com a base de dados.
- Detalhes: As rotas
main.pyconsultam esses modelos (por meio de sessões SQLAlchemy) para recuperar ou armazenar dados, que são então passados para modelos Jinja2 para renderização ou processados na lógica da API. Por exemplo, os dadosProxysão consultados na rota/proxy-genpara exibir detalhes do proxy.
Como funcionam as rotas
No desenvolvimento de API, é fundamental organizar os pontos finais da API de forma eficaz, pois não é prático encaminhar todas as solicitações através de um único ponto final.
Por exemplo, o formato /api/posts/get-post indica que /api/posts/* é dedicado a operações relacionadas com publicações, tais como visualizar, eliminar ou modificar publicações. Todas as chamadas API relacionadas são prefixadas com /api/posts/.
Dado que a nossa aplicação inclui funcionalidades como geração de proxy, gestão de bots, scraping de marketplace, scraping de publicações, monitorização de ameaças e muito mais, é essencial organizar os pontos finais de forma adequada.
Pode encontrar todas as rotas em app/routes/*.py, que contém seis ficheiros Python. Abaixo está um exemplo de bot_profile.py:
# app/routes/bot_profile.py
import logging
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.database.models import BotProfile, OnionUrl, BotPurpose, APIs
from app.database.db import get_db
from typing import Optional
from app.services.tornet_forum_login import login_to_tor_website
from app.services.gen_random_ua import gen_desktop_ua
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
bot_profile_router = APIRouter(prefix="/api/bot-profile", tags=["API", "Bot Profile Management"])
# Pydantic models for validation
class BotProfileCreate(BaseModel):
username: str
password: str
purpose: str
tor_proxy: Optional[str] = None
session: Optional[str] = None
class BotProfileUpdate(BaseModel):
username: Optional[str] = None
password: Optional[str] = None
purpose: Optional[str] = None
tor_proxy: Optional[str] = None
user_agent: Optional[str] = None
session: Optional[str] = None
class OnionUrlCreate(BaseModel):
url: str
# Get all bot profiles
@bot_profile_router.get("/list")
async def get_bot_profiles(db: Session = Depends(get_db)):
try:
profiles = db.query(BotProfile).all()
return [
{
"id": p.id,
"username": p.username,
"password": "********",
"actual_password": p.password,
"purpose": p.purpose.value,
"tor_proxy": p.tor_proxy,
"has_session": bool(p.session and len(p.session) > 0),
"session": p.session,
"user_agent": p.user_agent,
"timestamp": p.timestamp.isoformat()
} for p in profiles
]
except Exception as e:
logger.error(f"Error fetching bot profiles: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
Utilizamos o registo para rastrear eventos dentro da aplicação. O registo é fundamental para compreender o que está a acontecer na sua aplicação web; sem ele, estará a operar às cegas. Embora os registos possam ser guardados num ficheiro, normalmente evito fazê-lo durante o desenvolvimento local, mas recomendo-o para ambientes de produção.
No entanto, o aspeto mais crítico é a criação de routers:
bot_profile_router = APIRouter(prefix="/api/bot-profile", tags=["API", "Bot Profile Management"])
Este router estabelece um prefixo de ponto final, /api/bot-profile, que todos os pontos finais relacionados irão utilizar.
Por exemplo, a função seguinte recupera todos os perfis de bot da tabela BotProfile:
@bot_profile_router.get("/list")
async def get_bot_profiles(db: Session = Depends(get_db)):
Quando utiliza @bot_profile_router.get(“/list”), o ponto final é prefixado com /api/bot-profile, resultando em /api/bot-profile/list.
Esta estrutura permite uma organização eficiente de centenas ou milhares de rotas API. O bot_profile_router é importado para main.py e registado.
Como funcionam os scrapers
Todos os scrapers são armazenados em app/scrapers/*.py.
Esses ficheiros são normalmente importados como módulos em outras partes da base de código. Por exemplo, dentro de marketplace_scraper.py, a função create_pagination_batches recebe como entrada uma URL de página web, como:
http://site.url/pagination?page=1
Ele cria um lote de paginações como este:
http://site.url/pagination?page=1
http://site.url/pagination?page=2
http://site.url/pagination?page=3
--- snip ---
http://site.url/pagination?page=10
Esta abordagem é eficaz para a distribuição de tarefas, como atribuir cada lote de 10 páginas de paginação a um único bot para scraping.
Como funcionam os serviços
Todos os serviços estão localizados em app/services/*.py.
Os serviços também são usados como módulos, mas têm uma finalidade distinta, lidando com tarefas como:
- Gerenciar o Docker para proxies
- Gerar proxies
- Efetuar logins
- Ignorar CAPTCHAs
Como aprenderá mais adiante, usamos contentores Docker para criar proxies Tor localmente. Quando um proxy não é mais necessário, ele deve ser excluído. Para verificar se os proxies estão em execução, pode usar uma função como container_running de container_status.py para verificar o estado do contentor Docker.