JavaScript é uma linguagem de programação muito legal, mas hoje não falaremos sobre ela.
Há muito tempo que penso em aprender Go; essa linguagem me atraiu pela facilidade de trabalhar com threads, ótimas capacidades de networking, alta velocidade e estilo lacônico. Acredito que escolher esta linguagem como primeira língua seja uma péssima ideia, pois a maioria dos recursos de aprendizagem pressupõe uma introdução à programação em geral, sem focar nos detalhes. A documentação oficial de introdução será bastante clara se você já trabalhou com linguagens de programação semelhantes a C. Você pode começar por ele, e embora alguns conceitos sejam implementados de forma diferente do seu idioma, no geral tudo é explicado muito legal e todo o treinamento é claro - Tour of Go - https://go.dev/tour/list . Além disso, posso recomendar o curso:

https://forumupload.ru/uploads/001b/c9/09/2/t597078.png

https://youtu.be/un6ZyFkqFKo

Ótimo professor, ele também tem um curso simples sobre protocolos de rede . A seguir, você pode visitar o canal - https://www.youtube.com/@nikolay_tuzov , há muitos projetos Go lá.
Neste artigo tentarei descrever o código do ponto de vista de iniciantes, mas, ao mesmo tempo, o artigo contém o código para um analisador/verificador de proxy funcional e, se você estiver interessado no projeto, encontrará o código fonte aqui.
Para começar, baixe e instale do site oficial do Go - https://go.dev/doc/install , verifique se tudo foi instalado corretamente com o comando go version no terminal. Trabalharemos em VS Code, antes de começar adicionamos uma extensão para um trabalho confortável - https://marketplace.visualstudio.com/it … =golang.Go
Portanto, nossa tarefa é obter dados de sites que contenham listas de proxies gratuitos. Obviamente, muitos deles não funcionarão em geral ou para um recurso específico. Em seguida, precisamos verificar sua capacidade de se conectar ao recurso desejado.
Vamos decompor o problema. O plano de trabalho é o seguinte:
Obtemos uma lista de proxies de um recurso. Nesta fase, enviamos o resultado para o terminal.
Recebemos listas de muitos recursos e removemos duplicatas. Pegamos a lista de recursos do arquivo e salvamos o resultado no arquivo.
Aprendemos a acessar recursos remotos utilizando os proxies recebidos, descartando tentativas malsucedidas.
O primeiro site do qual receberemos dados será https://free-proxy-list.net/ . Os dados nos quais estou interessado são ipAddress:port.
Como em essência o software será universal e será possível adicionar links adicionais para sites onde os proxies estão localizados, precisamos de uma forma universal de obter informações e, claro, as expressões regulares serão assim.
Vamos para o IDE:

package main

import (
    "fmt"
    "io"
    "net/http"
    "regexp"
)

func main() {

    resp, err := http.Get("https://free-proxy-list.net/")
    if err != nil {
        fmt.Println("Error on page fetch:", err)
        return
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading the page content:", err)
        return
    }

    re := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+:\d+`)

    proxies := re.FindAllString(string(body), -1)

    fmt.Println("Found Proxies:")
    for _, proxy := range proxies {
        fmt.Println(proxy)
    }
}

Importamos pacotes. Precisaremos de fmt para enviar um log dos dados recebidos para o terminal. O pacote io fornece interfaces básicas para operações de E/S. Neste caso, é utilizado para ler a resposta de uma solicitação HTTP. Usamos o pacote net/http para fazer solicitações HTTP e processar respostas , e o pacote regexp fornece funcionalidade para trabalhar com expressões regulares.
O código é bem simples. Primeiro, fazemos uma solicitação HTTP GET para a URL especificada, retornando uma resposta e um erro, se houver. Em seguida, tratamos do erro potencial, encerrando o programa se ocorrer. Depois que a solicitação for concluída com êxito, defer é usado para adiar a conclusão da operação de fechamento do corpo da resposta ( resp.Body.Close() ) até que main() seja concluído e otimize os recursos. defer é uma palavra-chave exclusiva para a linguagem Go. Você pode descobrir mais sobre isso neste artigo  link para o artigo https://www.digitalocean.com/community/ … troduction  Em seguida, lemos o corpo da resposta (esse é o HTML que você verá se pressionar Ctrl+U no Chrome) usando io.ReadAll() . O resultado da leitura é gravado na variável body e qualquer erro que possa ocorrer durante o processo de leitura é verificado da mesma forma que uma solicitação GET. A seguir, criamos uma expressão regular usando regexp.MustCompile() que será usada para procurar endereços IP e portas no texto da página. Já recomendei o livro "Aprendendo Expressões Regulares" - Ben Forta , continua excelente. A lógica neste caso é muito simples: um endereço IP é uma sequência de números, geralmente limitada pelo número de dígitos, mas em geral é um certo número de dígitos, um ponto final e assim por diante quatro vezes, depois dois pontos e novamente números. O caractere “d+” indica uma ou mais ocorrências de um dígito decimal (0-9). Portanto, a expressão regular fica assim: \\d+\\.\\d+\\.\\d+\\.\\d+:\\d+ . Chame o método re.FindAllString(string(body), -1) para encontrar todas as correspondências com a expressão regular. Ele retorna uma fatia de linhas contendo os proxies encontrados. string(body) é uma conversão do conteúdo do corpo da resposta, que é inicialmente representado como uma fatia de bytes, em uma string. -1 é um parâmetro que informa ao método FindAllString() para encontrar todas as correspondências da expressão regular na string. Iteramos todos os elementos da fatia de proxies usando um loop for e os enviamos para o console.

https://forumupload.ru/uploads/001b/c9/09/2/t214926.png

Ótimo, vamos continuar. Neste momento, carregamos o link para o site. Para dimensionar a aplicação, precisamos colocar os links em um arquivo do qual possamos retirá-los e, se necessário, adicionar novos. Também precisamos salvar os resultados fora da saída do console, neste caso faremos isso em um arquivo de texto.
Vou criar um arquivo links.txt na raiz do projeto e colocar lá por enquanto apenas um link para https://free-proxy-list.net/ . Vamos para o Código VS.

package main

import (
    "bufio"
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
)

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error opening the file:", err)
        return
    }
    defer file.Close()

    outFile, err := os.Create("notChecks.txt")
    if err != nil {
        fmt.Println("Error creating the output file:", err)
        return
    }
    defer outFile.Close()

    writer := bufio.NewWriter(outFile)

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()

        resp, err := http.Get(url)
        if err != nil {
            fmt.Println("Error on page fetch:", err)
            continue
        }
        defer resp.Body.Close()

        body, err := io.ReadAll(resp.Body)
        if err != nil {
            fmt.Println("Error reading the page content:", err)
            continue
        }

        re := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+:\d+`)
        proxies := re.FindAllString(string(body), -1)

        for _, proxy := range proxies {
            fmt.Fprintln(writer, proxy)
        }
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading the file:", err)
    }

    writer.Flush()
}

Conectamos vários pacotes adicionais - bufio e os . bufio fornece funcionalidade para leitura e gravação de dados em buffer, e os fornece funcionalidade para trabalhar com o sistema operacional.
Primeiro, abrimos o arquivo "links.txt" ao qual adicionamos links antecipadamente e tratamos os erros normalmente. Como serão proxies não verificados, escolhi o nome do arquivo onde serão salvos, “notChecks.txt”. Portanto, posteriormente no código criamos esse arquivo, e caso já exista, simplesmente sobrescrevemos seu conteúdo. use bufio.NewWriter() para criar um gravador que será usado para gravar em "notChecks.txt". No loop for scanner.Scan() , percorremos cada linha do arquivo "links.txt". , executamos uma solicitação HTTP ( http.Get ) (url) ), assim como fizemos anteriormente, a lógica de tratamento de erros será um pouco diferente, não devemos parar o programa. links é problemático por algum motivo, ainda queremos obter um resultado de nove outros, então usamos continue e registramos o erro. Novamente, iteramos pelos elementos dos proxies slice , mas desta vez usamos fmt.Fprintln(writer. , proxy) para gravar no arquivo Depois de terminarmos de ler todos os URLs do arquivo, verificamos se há erros e os encontramos na saída do console. O registro é bastante claro e ajuda a descartar links desnecessários. Em geral, se quisermos mais dados, também devemos salvá-los em um arquivo separado, acrescentando tempo de verificação. O uso dewriter.Flush() garante que todos os dados sejam gravados no arquivo antes do encerramento do programa.
Verificamos o programa - está tudo bem. Eu adiciono outro link https://proxyspace.pro/socks4.txt e obtenho o resultado:

https://forumupload.ru/uploads/001b/c9/09/2/t25785.png

Como é provável que os endereços IP se sobreponham entre recursos, precisamos eliminar duplicatas. Lembrei-me imediatamente de uma estrutura de dados chamada tabela hash. Como minha primeira linguagem de programação foi JavaScript, pensei imediatamente em uma estrutura de dados definida, mas Go não tem uma. No entanto, podemos criar um conjunto a partir do mapa.

package main

import (
    "bufio"
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
)

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error opening the file:", err)
        return
    }
    defer file.Close()

    outFile, err := os.Create("notChecks.txt")
    if err != nil {
        fmt.Println("Error creating the output file:", err)
        return
    }
    defer outFile.Close()

    writer := bufio.NewWriter(outFile)

    scanner := bufio.NewScanner(file)

    uniqueProxies := make(map[string]bool)

    for scanner.Scan() {
        url := scanner.Text()

        resp, err := http.Get(url)
        if err != nil {
            fmt.Println("Error on page fetch:", err)
            continue
        }
        defer resp.Body.Close()

        body, err := io.ReadAll(resp.Body)
        if err != nil {
            fmt.Println("Error reading the page content:", err)
            continue
        }

        re := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+:\d+`)
        proxies := re.FindAllString(string(body), -1)

        for _, proxy := range proxies {
            if _, exists := uniqueProxies[proxy]; !exists {
                uniqueProxies[proxy] = true
                fmt.Fprintln(writer, proxy)
            }
        }
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading the file:", err)
    }

    writer.Flush()
}

Então, vamos criar um mapa uniqueProxies := make(map[string]bool) . Deixe-me lembrá-lo de que os mapas em Go são um conjunto de pares de valores-chave. Neste caso, as chaves serão strings (endereços proxy) e os valores serão booleanos. No loop for _, proxy := range proxies , percorremos os endereços e verificamos se existe algum no mapa uniqueProxies . se _, existe := uniqueProxies[proxy]; !exists – acessa o valor do mapa usando a chave proxy . Se o endereço do proxy não estiver no mapa, a variável existe será falsa e a condição !exists será verdadeira. uniqueProxies[proxy] = true - se o endereço do proxy não existir no mapa, nós o adicionamos atribuindo um valor booleano como true à chave do proxy para marcá-lo como presente.
Eu verifico o código inserindo dois links idênticos em uma linha e certifico-me de que o resultado foi bem-sucedido😀
Como nem todos os proxies que recebemos funcionarão, precisamos verificar sua capacidade de conexão. Vamos prosseguir para a criação de um verificador no IDE. Crie um novo arquivo em Go.

package main

import (
    "bufio"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "strings"
    "sync"
    "time"
)

func main() {
    file, err := os.Open("notChecks.txt")
    if err != nil {
        fmt.Println("Error opening the file:", err)
        return
    }
    defer file.Close()
    scanner := bufio.NewScanner(file)
    var ips []string
    var ports []string
    for scanner.Scan() {
        proxyStr := scanner.Text()
        parts := strings.Split(proxyStr, ":")
        if len(parts) != 2 {
            fmt.Println("Proxy format not valid:", proxyStr)
            continue
        }
        ips = append(ips, parts[0])
        ports = append(ports, parts[1])
    }
    var wg sync.WaitGroup
    concurrentRequests := 10
    semaphore := make(chan struct{}, concurrentRequests)
    validFile, err := os.Create("validProxy.txt")
    if err != nil {
        fmt.Println("Error creating the output file:", err)
        return
    }
    defer validFile.Close()
    for i := range ips {
        wg.Add(1)
        go func(ip, port string) {
            defer wg.Done()
            semaphore <- struct{}{}
            valid := checkProxy(ip, port)
            if valid {
                fmt.Printf("Valid Proxy %s:%s\n", ip, port)
                validFile.WriteString(ip + ":" + port + "\n")
            } else {
                fmt.Printf("Invalid Proxy %s:%s\n", ip, port)
            }
            <-semaphore
        }(ips[i], ports[i])
    }
    wg.Wait()
    fmt.Println("Check completed. Valid proxies have been saved to the file validProxy.txt.")
}

func checkProxy(ip, port string) bool {
    client := http.Client{
        Timeout: 5 * time.Second,
    }
    proxyURL := fmt.Sprintf("http://%s:%s", ip, port)
    parsed, err := url.Parse(proxyURL)
    if err != nil {
        fmt.Println("Error while parsing URL:", err)
        return false
    }
    transport := &http.Transport{
        Proxy: http.ProxyURL(parsed),
    }
    client.Transport = transport
    req, err := http.NewRequest("GET", "http://google.com", nil)
    if err != nil {
        fmt.Println("Error while creating a request:", err)
        return false
    }
    resp, err := client.Do(req)
    if err != nil {
        return false
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        return false
    }
    return true
}

Vamos começar a analisar o código. Estamos adicionando vários novos pacotes:
net/url - para trabalhar com URLs e criar um proxy no formato “ http://ip :port”
strings - para trabalhar com strings
sync - serão usados ​​para sincronizar goroutines e aguardar sua conclusão
time - para trabalhar com o tempo e definir um timeout ao fazer solicitações HTTP através de um proxy. Vamos começar analisando a lógica da
função checkProxy . Esta função é diretamente responsável por verificar o proxy, leva um endereço IP e uma porta como entrada. retorna um valor booleano que determina a validade. A validação ocorre através de
uma solicitação ao recurso e verificação do status do código de resposta, esta é uma boa forma de verificar universalmente. .com ”, mas pode ser substituído por qualquer um com o qual você planeja trabalhar. Um método alternativo de verificação pode ser um serviço como - https://httpbin.org/ip , que retorna o IP do qual você acessou. que retornou o código de status 200 OK será válido.
Para acessar através de um proxy, criamos um cliente HTTP com timeout de 5 segundos, portanto, qualquer solicitação que seja executada através deste cliente e não seja concluída em 5 segundos será. abortado e será considerado inválido. A seguir, formamos a URL do servidor proxy no formato “ http://ip :port” usando o ip e a porta passados ​​​​para a função . Criamos um Transporte, que define os parâmetros de conexão, neste caso é um proxy, depois declaramos um cliente utilizando o transporte. Uma solicitação HTTP Get é criada para o site do Google, se houver um erro durante a criação, retornamos false e exibimos o log. A seguir enviamos a solicitação através do cliente, em caso de erro (por exemplo, por timeout), retornamos falso. Em seguida, a função verifica o código de status. Se o código não for 200 OK, retornamos false novamente . Em todos os outros casos, retornamos verdadeiro.
Vejamos a função principal. Abra o arquivo 'notChecks.txt'. Criamos um scanner para ler um arquivo linha por linha. Inicializamos dois arrays ips e ports , que armazenarão os endereços IP e portas dos servidores proxy, respectivamente. A seguir, por meio de um loop, dividimos tudo nesses dois arrays para depois passá-los para a função de verificação. Parece-me que esta está longe de ser a solução ideal e talvez valha a pena pensar em otimizá-la. O loop também verifica a presença de dois pontos e, se a linha não o contiver, é considerado inválido. É então criado um grupo de espera sync.WaitGroup , que será usado para aguardar a conclusão de todas as goroutines. Um canal de semáforo também é criadopara controlar o número de goroutines em execução simultaneamente. Existem dez deles neste código. Vamos criar um arquivo para gravação de proxies válidos “validProxy.txt”. Itera pela lista de proxies. Para cada endereço IP e porta na lista de proxy, uma nova goroutine é criada. A goroutine é adicionada ao grupo de espera. Dentro da goroutine é executada a função checkProxy , que verifica o proxy e grava o resultado no arquivo "validProxy.txt". Antes de executar uma solicitação, ele espera que haja espaço no canal do semáforo para que mais de 10 solicitações não sejam executadas ao mesmo tempo. wg.Wait() - aguarda a conclusão de todas as goroutines. Após a conclusão da verificação, exibimos uma mensagem no console

https://forumupload.ru/uploads/001b/c9/09/2/t84497.png

Você provavelmente já deve ter notado um erro, ou seja, esqueci completamente de levar em consideração o tipo de proxy. Acessar o protocolo SOCKS por HTTP é a solução errada. Percebi esse erro apenas neste estágio e comecei a pensar em como corrigi-lo depois de escrever o código. A maioria dos sites de distribuição de proxy indicará o tipo. O problema é que escrever uma expressão regular que colete essas informações universalmente é bastante problemático. Portanto, raciocinamos a partir da posição de que queremos uma solução universal e não sabemos que tipo obtemos como entrada. Em geral, decidi adicionar uma verificação para o protocolo SOCKS e, se obtivermos um resultado inválido, passá-lo para verificação via HTTP. Obviamente, esta não é a solução ideal, mas funciona mesmo assim. A verificação de SOCKS ocorrerá por TCP. Estudei a especificação e concluí que a verificação TCP deveria ser uma solução universal tanto para SOCKS5 quanto para SOCKS4, uma vez que a conectividade TCP é suportada por todas as versões do protocolo.
Vamos passar para o código:

package main

import (
    "bufio"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "strings"
    "sync"
    "time"

    "golang.org/x/net/proxy"
)

func main() {
    file, err := os.Open("notChecks.txt")
    if err != nil {
        fmt.Println("Error opening the file:", err)
        return
    }
    defer file.Close()
    scanner := bufio.NewScanner(file)
    var ips []string
    var ports []string
    for scanner.Scan() {
        proxyStr := scanner.Text()
        parts := strings.Split(proxyStr, ":")
        if len(parts) != 2 {
            fmt.Println("Proxy format not valid:", proxyStr)
            continue
        }
        ips = append(ips, parts[0])
        ports = append(ports, parts[1])
    }
    var wg sync.WaitGroup
    concurrentRequests := 10
    semaphore := make(chan struct{}, concurrentRequests)
    validFile, err := os.Create("validProxy.txt")
    if err != nil {
        fmt.Println("Error creating the output file:", err)
        return
    }
    defer validFile.Close()
    for i := range ips {
        wg.Add(1)
        go func(ip, port string) {
            defer wg.Done()
            semaphore <- struct{}{}
            valid, proxyType := checkProxyWithSocks(ip, port)
            if !valid {
                valid, proxyType = checkProxy(ip, port)
            }
            if valid {
                fmt.Printf("✅ Valid %s Proxy %s:%s\n", proxyType, ip, port)
                validFile.WriteString(ip + ":" + port + "\n")
            } else {
                fmt.Printf("❌ Invalid Proxy %s:%s\n", ip, port)
            }
            <-semaphore
        }(ips[i], ports[i])
    }
    wg.Wait()
    fmt.Println("Check completed. Valid proxies have been saved to the file validProxy.txt.")
}

func checkProxyWithSocks(ip, port string) (bool, string) {
    dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)
    if err != nil {
        fmt.Println("Error while creating SOCKS5 proxy:", err)
        return false, ""
    }

    httpTransport := &http.Transport{
        Dial: dialer.Dial,
    }

    client := &http.Client{
        Transport: httpTransport,
        Timeout:   5 * time.Second,
    }

    req, err := http.NewRequest("GET", "http://google.com", nil)
    if err != nil {
        fmt.Println("Error while creating a request:", err)
        return false, ""
    }

    resp, err := client.Do(req)
    if err != nil {
        return false, "SOCKS5"
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        return false, "SOCKS5"
    }
    return true, "SOCKS5"
}

func checkProxy(ip, port string) (bool, string) {
    client := http.Client{
        Timeout: 5 * time.Second,
    }
    proxyURL := fmt.Sprintf("http://%s:%s", ip, port)
    parsed, err := url.Parse(proxyURL)
    if err != nil {
        fmt.Println("Error while parsing URL:", err)
        return false, ""
    }
    transport := &http.Transport{
        Proxy: http.ProxyURL(parsed),
    }
    client.Transport = transport
    req, err := http.NewRequest("GET", "http://google.com", nil)
    if err != nil {
        fmt.Println("Error while creating a request:", err)
        return false, ""
    }
    resp, err := client.Do(req)
    if err != nil {
        return false, "HTTP"
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        return false, "HTTP"
    }
    return true, "HTTP"
}

Para trabalhar com o protocolo SOCKS, adicionei o pacote " golang.org/x/net/proxy ". A função principal primeiro verifica usando a função checkProxyWithSocks . Se retornar false , passamos para checkProxy . Se conseguirmos true , imprimimos o resultado no console. Nesta versão alterei um pouco o log, adicionando emoji para separação visual, além de indicar o tipo de proxy no caso de um válido. A função checkProxy não mudou e ainda verifica a validade via HTTP. Vamos dar uma olhada na função checkProxyWithSocks :

dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)

Primeiro, ele cria um cliente proxy SOCKS5 usando a função proxy.SOCKS5 . O primeiro argumento é o protocolo TCP, o segundo é o endereço do servidor proxy. nil indica que nenhuma autenticação é necessária e proxy.Direct indica que não há necessidade de usar uma cadeia de proxy. Então tudo acontece da mesma forma que na verificação do HTTP: criamos um transporte, um cliente, formamos uma solicitação, enviamos e recebemos uma resposta, processamos erros e verificamos o status da resposta, fechamos o corpo da resposta e retornamos o resultado.

https://forumupload.ru/uploads/001b/c9/09/2/t488103.png

Vamos resumir. O design e a decomposição são importantes e o desenvolvimento em Go é divertido e fácil. 🙂
Na verdade, ainda há algum trabalho a ser feito aqui. No entanto, este projeto cumpre o seu propósito. Estou tendo problemas para obter proxies de vários sites e atualmente estou trabalhando para entender por que isso está acontecendo (todos os links do links.txt estão funcionando). Eu também adicionaria mais logs ao console sobre o processo.
Combinei tudo em uma função e fiz algumas alterações em relação ao que está descrito no artigo:
Os proxies válidos são salvos em dois arquivos dependendo do tipo.
Criamos variáveis ​​para arquivos, link no qual verificamos a conexão, solicitações simultâneas e timeout de conexão.
Você pode baixar o projeto aqui (em forma de código-fonte, o arquivo está nos arquivos anexos, sem senha).
Ficarei feliz em receber críticas ao código de participantes mais experientes. Sei que muitas pessoas aqui usam Go e também ficarei feliz se meu programa for útil para você. Tenho certeza que este não será o último artigo sobre Go.🙂