As medidas anti-bot são muito comuns, aparecendo em sites importantes e até mesmo em algumas plataformas de crimes cibernéticos para evitar spam ou abuso. Um sistema anti-bot avançado é o Netacea, embora seja menos usado em sites de crimes cibernéticos, de acordo com as minhas observações. Para sites mais sofisticados, pode ser necessário controlar programaticamente um navegador da web para desktop, em vez de um navegador sem interface gráfica, como o Playwright. Uma abordagem a explorar é o Hidden VNC para automação do navegador, que permite simular interações do rato e do teclado.

Como os sites não podem aceder diretamente ao seu sistema, eles ainda podem detectar navegadores sem interface gráfica ou extensões de navegador. No entanto, automatizar interações através do seu sistema operativo muitas vezes pode passar despercebido, pois imita o comportamento genuíno do utilizador de forma mais eficaz. Embora essa técnica esteja além do escopo deste curso, é uma opção que pode investigar independentemente. A maioria dos sites de crimes cibernéticos implementa limitação de taxa com base em endereços IP e CAPTCHAs para bloquear bots.

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

  1. Limitação de taxa
  2. Proibições de IP
  3. Contorno de CAPTCHA
  4. Bloqueios de conta

Limitação de taxa

A maioria dos sites tem a limitação de taxa ativada para determinados pontos finais. O objetivo da limitação de taxa é impedir que sejam enviados pedidos excessivos para uma página da Web. Do ponto de vista de um programador, eis como a limitação de taxa é configurada para uma rota:

limiter = Limiter(
	get_remote_address,
	app=app,
	default_limits=["500 per day", "200 per hour"],
	storage_uri="memory://"
)

@app.route('/post/<post_type>/<int:post_id>')
@limiter.limit("30 per minute")
@login_required
def post_detail(post_type, post_id):

Para referência, estou a executar o tornet_forum localmente sem o tor.

Os limites padrão são 500 pedidos de um IP para todas as outras rotas por dia e 200 pedidos por hora. Mas para /api/post, o limite é de 30 pedidos por minuto, portanto, após 30 pedidos, não será possível enviar mais pedidos. Essa rota é usada para mostrar detalhes de uma publicação. A URL tem o seguinte aspecto:

http://127.0.0.1:5000/post/announcements/272

Aqui está um exemplo em Python para mostrar o que acontece quando o limite é excedido:

import requests

url = "http://127.0.0.1:5000/post/announcements/272"
cookies = {
    "session": ".eJwlzjkOwjAQAMC_uKbwru098hmUvQRtQirE30FiXjDvdq8jz0fbXseVt3Z_RtsaLq_dS6iPEovRuyIyhhGoxMppFG5CJEAEy3cuZ9LivWN0nWwyLCZS6tI5h66oWdA9hBADWN14xyrJ6LkmFFj5yjKsMVjbL3Kdefw30D5fvlgvjw.aHE7pQ.HyexRW6P3g7njbSz53vZj77gmMU"
}

for reqnum in range(31):
    response = requests.get(url, cookies=cookies)
    print(f"Request number: {reqnum} | HTTP Status code: {response.status_code}")

Saída:

-> % python3 rate_limit_test.py
Request number: 0 | HTTP Status code: 200
Request number: 1 | HTTP Status code: 200
Request number: 2 | HTTP Status code: 200
Request number: 3 | HTTP Status code: 200
Request number: 4 | HTTP Status code: 200
Request number: 5 | HTTP Status code: 200
Request number: 6 | HTTP Status code: 200
Request number: 7 | HTTP Status code: 200
Request number: 8 | HTTP Status code: 200
Request number: 9 | HTTP Status code: 200
Request number: 10 | HTTP Status code: 200
Request number: 11 | HTTP Status code: 200
Request number: 12 | HTTP Status code: 200
Request number: 13 | HTTP Status code: 200
Request number: 14 | HTTP Status code: 200
Request number: 15 | HTTP Status code: 200
Request number: 16 | HTTP Status code: 200
Request number: 17 | HTTP Status code: 200
Request number: 18 | HTTP Status code: 200
Request number: 19 | HTTP Status code: 200
Request number: 20 | HTTP Status code: 200
Request number: 21 | HTTP Status code: 200
Request number: 22 | HTTP Status code: 200
Request number: 23 | HTTP Status code: 200
Request number: 24 | HTTP Status code: 200
Request number: 25 | HTTP Status code: 200
Request number: 26 | HTTP Status code: 200
Request number: 27 | HTTP Status code: 200
Request number: 28 | HTTP Status code: 200
Request number: 29 | HTTP Status code: 200
Request number: 30 | HTTP Status code: 429

Em cenários reais, normalmente não é possível aceder ao código-fonte de uma aplicação, portanto, determinar o limite de taxa, como o número de solicitações permitidas por minuto ou 10 minutos, requer tentativa e erro. Embora os scripts possam automatizar esse processo, ele continua sendo tedioso.

Agora que sabemos que o limite de taxa é acionado após 30 solicitações, podemos enviar 29 solicitações, pausar por 60 segundos, enviar outras 29 solicitações e repetir esse ciclo:

import requests
import time

REQUEST_COUNT = 29
SLEEP_DURATION = 60

url = "http://127.0.0.1:5000/post/announcements/272"
cookies = {
    "session": ".eJwlzjkOwjAQAMC_uKbwru098hmUvQRtQirE30FiXjDvdq8jz0fbXseVt3Z_RtsaLq_dS6iPEovRuyIyhhGoxMppFG5CJEAEy3cuZ9LivWN0nWwyLCZS6tI5h66oWdA9hBADWN14xyrJ6LkmFFj5yjKsMVjbL3Kdefw30D5fvlgvjw.aHE7pQ.HyexRW6P3g7njbSz53vZj77gmMU"
}

batch_num = 1
while True:
    print(f"Starting batch {batch_num}")
    for reqnum in range(REQUEST_COUNT):
        response = requests.get(url, cookies=cookies)
        print(f"Batch {batch_num} | Request number: {reqnum + 1} | HTTP Status code: {response.status_code}")
    
    print(f"Batch {batch_num} completed. Sleeping for {SLEEP_DURATION} seconds...")
    time.sleep(SLEEP_DURATION)
    batch_num += 1

Saída:

-> % python3 rate_limit_test.py 
Starting batch 1
Batch 1 | Request number: 1 | HTTP Status code: 200
Batch 1 | Request number: 2 | HTTP Status code: 200
Batch 1 | Request number: 3 | HTTP Status code: 200
Batch 1 | Request number: 4 | HTTP Status code: 200
Batch 1 | Request number: 5 | HTTP Status code: 200
Batch 1 | Request number: 6 | HTTP Status code: 200
Batch 1 | Request number: 7 | HTTP Status code: 200
Batch 1 | Request number: 8 | HTTP Status code: 200
Batch 1 | Request number: 9 | HTTP Status code: 200
Batch 1 | Request number: 10 | HTTP Status code: 200
Batch 1 | Request number: 11 | HTTP Status code: 200
Batch 1 | Request number: 12 | HTTP Status code: 200
Batch 1 | Request number: 13 | HTTP Status code: 200
Batch 1 | Request number: 14 | HTTP Status code: 200
Batch 1 | Request number: 15 | HTTP Status code: 200
Batch 1 | Request number: 16 | HTTP Status code: 200
Batch 1 | Request number: 17 | HTTP Status code: 200
Batch 1 | Request number: 18 | HTTP Status code: 200
Batch 1 | Request number: 19 | HTTP Status code: 200
Batch 1 | Request number: 20 | HTTP Status code: 200
Batch 1 | Request number: 21 | HTTP Status code: 200
Batch 1 | Request number: 22 | HTTP Status code: 200
Batch 1 | Request number: 23 | HTTP Status code: 200
Batch 1 | Request number: 24 | HTTP Status code: 200
Batch 1 | Request number: 25 | HTTP Status code: 200
Batch 1 | Request number: 26 | HTTP Status code: 200
Batch 1 | Request number: 27 | HTTP Status code: 200
Batch 1 | Request number: 28 | HTTP Status code: 200
Batch 1 | Request number: 29 | HTTP Status code: 200
Batch 1 completed. Sleeping for 60 seconds...
Starting batch 2
Batch 2 | Request number: 1 | HTTP Status code: 200
Batch 2 | Request number: 2 | HTTP Status code: 200
Batch 2 | Request number: 3 | HTTP Status code: 200
Batch 2 | Request number: 4 | HTTP Status code: 200
Batch 2 | Request number: 5 | HTTP Status code: 200
Batch 2 | Request number: 6 | HTTP Status code: 200
Batch 2 | Request number: 7 | HTTP Status code: 200
Batch 2 | Request number: 8 | HTTP Status code: 200
Batch 2 | Request number: 9 | HTTP Status code: 200
Batch 2 | Request number: 10 | HTTP Status code: 200
Batch 2 | Request number: 11 | HTTP Status code: 200
Batch 2 | Request number: 12 | HTTP Status code: 200
Batch 2 | Request number: 13 | HTTP Status code: 200
Batch 2 | Request number: 14 | HTTP Status code: 200
Batch 2 | Request number: 15 | HTTP Status code: 200
Batch 2 | Request number: 16 | HTTP Status code: 200
Batch 2 | Request number: 17 | HTTP Status code: 200
Batch 2 | Request number: 18 | HTTP Status code: 200
Batch 2 | Request number: 19 | HTTP Status code: 200
Batch 2 | Request number: 20 | HTTP Status code: 200
Batch 2 | Request number: 21 | HTTP Status code: 200
Batch 2 | Request number: 22 | HTTP Status code: 200
Batch 2 | Request number: 23 | HTTP Status code: 200
Batch 2 | Request number: 24 | HTTP Status code: 200
Batch 2 | Request number: 25 | HTTP Status code: 200
Batch 2 | Request number: 26 | HTTP Status code: 200
Batch 2 | Request number: 27 | HTTP Status code: 200
Batch 2 | Request number: 28 | HTTP Status code: 200
Batch 2 | Request number: 29 | HTTP Status code: 200
Batch 2 completed. Sleeping for 60 seconds...

Lembre-se de que aqui estamos apenas a enviar pedidos para um URL, mas no mundo real, estará a enumerar centenas de publicações, mas o processo é o mesmo.


Bloqueios de IP

Os bloqueios de IP eram comuns no passado, mas agora são menos frequentes, limitando-se principalmente a determinados sistemas de gestão de conteúdos. Não os encontrei em sites de cibercrime, mas este curso irá ensinar-lhe como contorná-los para o preparar para qualquer cenário.

Para sites típicos, um IP banido pode ser contornado usando uma VPN ou proxy. No entanto, a extração de dados em grande escala requer a rotação de centenas de proxies, como proxies residenciais ou de centros de dados.

O meu site preferido para comprar proxies é: https://decodo.com

É uma plataforma confiável que uso para caça a bugs, com atendimento ao cliente ágil. Em 11 de julho de 2025, o custo de 100 proxies era de US$ 3,80:

Decodo Datacenter Proxies

É bastante barato. Depois de comprar os proxies, eis como pode utilizá-los:

import requests

REQUEST_COUNT = 29
PROXY_CHANGE_INTERVAL = 5

proxies_list = [
    "http://sp96rgc8yz:[email protected]:10001",
    "http://sp96rgc8yz:[email protected]:10002",
    "http://sp96rgc8yz:[email protected]:10003",
    "http://sp96rgc8yz:[email protected]:10004",
    "http://sp96rgc8yz:[email protected]:10005",
    "http://sp96rgc8yz:[email protected]:10006"
]

url = "http://127.0.0.1:5000/post/announcements/272"
cookies = {
    "session": ".eJwlzjkOwjAQAMC_uKbwru098hmUvQRtQirE30FiXjDvdq8jz0fbXseVt3Z_RtsaLq_dS6iPEovRuyIyhhGoxMppFG5CJEAEy3cuZ9LivWN0nWwyLCZS6tI5h66oWdA9hBADWN14xyrJ6LkmFFj5yjKsMVjbL3Kdefw30D5fvlgvjw.aHE7pQ.HyexRW6P3g7njbSz53vZj77gmMU"
}

proxy_index = 0
request_count = 0

while True:
    current_proxy = proxies_list[proxy_index % len(proxies_list)]
    proxies = {
        "http": current_proxy,
        "https": current_proxy
    }
    
    response = requests.get(url, cookies=cookies, proxies=proxies)
    print(f"Request number: {request_count + 1} | Proxy: {current_proxy} | HTTP Status code: {response.status_code}")
    
    request_count += 1
    # Change proxy every 5 requests
    if request_count % PROXY_CHANGE_INTERVAL == 0:
        proxy_index += 1

O programa opera em um loop infinito while True, selecionando um proxy da lista usando uma operação módulo (proxy_index % len(proxies_list)) para voltar ao primeiro proxy após o último. Ele aplica o proxy selecionado aos protocolos HTTP e HTTPS. Após cada cinco solicitações, ele incrementa proxy_index para mudar para o próximo proxy, reiniciando a partir do primeiro proxy quando o fim de proxies_list é alcançado.


Contorno de CAPTCHA

CAPTCHAs são provavelmente o tópico mais envolvente deste módulo e provavelmente a razão pela qual muitos de vocês estão aqui. Assim como analisámos as páginas da Web antes de as extrair, podemos estudar como os CAPTCHAs funcionam antes de os contornar.

Para compreender os CAPTCHAs, visite a página de login onde eles normalmente aparecem:

Fórum de login da Tornet

Recarregue a página várias vezes para observar:

  • Quantos caracteres cada CAPTCHA usa? 6 caracteres

  • Que tipos de caracteres são usados (minúsculas, maiúsculas, misturadas, números)? Todas as letras maiúsculas e números

  • Qual é o tamanho da imagem do CAPTCHA? Faça o download, abra no Chrome e observe que tem 200 pixels de largura por 60 pixels de altura

  • Qual é a resolução máxima ao redimensionar a imagem do CAPTCHA? Redimensionar melhora a legibilidade dos caracteres

Aqui está uma versão redimensionada do CAPTCHA de login: CAPTCHA de login redimensionado

Não está mais claro e fácil de ler? Veja como eu solicito ao ChatGPT o3 para extrair o texto com precisão: ChatGPT o1 CAPTCHA Bypass

Esse processo pode demorar, às vezes 49 segundos ou até um minuto, mas o OCR do ChatGPT o3 é completo, embora ocasionalmente inconsistente. Opcionalmente, pode analisar o CAPTCHA várias vezes e selecionar o resultado com a pontuação de confiança mais alta.

Felizmente, os CAPTCHAs são normalmente usados apenas em páginas de login e registo na maioria dos fóruns, por isso nem sempre é necessário contorná-los extensivamente. No nosso site principal de web scraper, quando uma conta de bot é adicionada, fazemos login automaticamente, contornamos o CAPTCHA e criamos uma sessão. Se a sessão expirar, fazemos um novo login usando as credenciais. Se o login falhar devido a um CAPTCHA incorreto, tentamos novamente até obter sucesso.

Uma limitação do uso do o3 é que seu modelo de API OpenAI não suporta processamento de imagens. Exploraremos modelos com suporte a imagens e acesso à API em seções posteriores.

Nas próximas seções, aprenderá como usamos o modelo gpt-4.1 para realizar tentativas contínuas de login, normalmente com sucesso após cinco tentativas consecutivas, como descobrirá mais adiante.


Bloqueio de conta

Os bloqueios ou proibições de contas são normalmente manuais, mas às vezes podem ser automatizados. Muitos fóruns de crimes cibernéticos combatem os golpistas colocando em uma lista negra nomes de utilizadores específicos. Por exemplo, publicar um nome de utilizador do Telegram como @BluePig pode acionar um banimento automático.

Observei isso com frequência, portanto, alternar entre várias contas nem sempre resolve o problema. No entanto, se você encontrar bloqueios automáticos, alternar entre contas é uma estratégia viável.

Alguns sites empregam sistemas de registo automático que alertam os administradores sobre atividades suspeitas, como solicitações excessivas ou comportamento potencialmente malicioso.

No nosso principal scraper da web, implementaremos a rotação de contas usando várias contas de bot para evitar bloqueios. Embora os sites de teste deste curso não tenham mecanismos de bloqueio de conta, ensinaremos como contornar essas proteções para prepará-lo para cenários do mundo real.

O código Python a seguir demonstra a alternância entre duas contas e a impressão da página de perfil de cada conta como uma prova de conceito para a rotação de contas.

Quando está conectado como DarkHacker, a página de perfil exibe Você está conectado como DarkHacker. Esta mensagem não aparece ao visitar as páginas de perfil de outros utilizadores, confirmando o seu estado de login. Abaixo está como verificamos isso programaticamente, alternando entre contas:

import requests


# JSON structure for profiles
profiles = {
    "url": "http://127.0.0.1:5000/profile/",
    "users": {
        "CyberGhost": {
            "cookie": "session=.eJwtzrkRwjAQAMBeFBNIJ-keN-PRfWNSG0cMvUPAVrDvsucZ11G213nHo-xPL1uBabksGWtPVu-1CgCBKzZhnzEU3ZQRuSG2aYvSCCVpVfAqg5S7-gAMmTJGl-k5slVzRgBvJKa0IJPDa8zRsmnajFTI3knKL3Jfcf435fMFvlsvkA.aHFScA.HYZ0jgZ5eb06WP5SzPnnf6pISJo"
        },
        "DarkHacker": {
            "cookie": "session=.eJwlzjkOwjAQAMC_uKbwru098hmUvQRtQirE30FiXjDvdq8jz0fbXseVt3Z_RtsaLq_dS6iPEovRuyIyhhGoxMppFG5CJEAEy3cuZ9LivWN0nWwyLCZS6tI5h66oWdA9hBADWN14xyrJ6LkmFFj5yjKsMVjbL3Kdefw30D5fvlgvjw.aHFR-A.mcu8L_CTLdZrz2254OEzZhsqpbQ"
        }
    }
}

# Function to fetch profile and check for login string
def fetch_profile(username, cookie):
    url = f"{profiles['url']}{username}"
    headers = {'Cookie': cookie}
    try:
        response = requests.get(url, headers=headers)
        # Check for the login confirmation string
        login_string = f"You are logged in as {username}"
        if login_string in response.text:
            print(f"Confirmation: '{login_string}' found in the response for user: {username}.")
        else:
            print(f"Confirmation: '{login_string}' NOT found in the response.")
    except requests.RequestException as e:
        print(f"Error fetching profile for {username}: {e}")

# Fetch profiles for both users
for username, data in profiles['users'].items():
    fetch_profile(username, data['cookie'])

Saída:

-> % python3 profile_rotation.py
Confirmation: 'You are logged in as CyberGhost' found in the response for user: CyberGhost.
Confirmation: 'You are logged in as DarkHacker' found in the response for user: DarkHacker.

Pode melhorar a experiência de aprendizagem enumerando as publicações com cada conta, mas a abordagem atual é suficiente. Alternar entre contas é uma estratégia eficaz para evitar ser sinalizado por atividade suspeita, uma vez que todos os pedidos enviados para o site são registados.