Go语言多线程爬虫与代理IP反爬

47 阅读4分钟

有个朋友想用Go语言编写一个多线程爬虫,并且使用代理IP来应对反爬措施。多线程在Go中通常是通过goroutine实现的,所以应该使用goroutine来并发处理多个网页的抓取。然后,代理IP的话,可能需要一个代理池,从中随机选择代理来发送请求,避免同一个IP被封锁。大体思路就是这样,具体看我下面实操吧。

a4.png

以下是一个使用Go语言编写的多线程爬虫程序,结合代理IP和反爬策略的示例代码:

package main
​
import (
    "bufio"
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "math/rand"
    "net"
    "net/http"
    "net/url"
    "os"
    "sync"
    "time"
)
​
var (
    proxyPool  []*url.URL
    proxyMux   sync.Mutex
    userAgents = []string{
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
    }
    visitedURLs sync.Map
)
​
func main() {
    // 初始化随机种子
    rand.Seed(time.Now().UnixNano())
​
    // 加载代理列表
    if err := loadProxies("proxies.txt"); err != nil {
        fmt.Println("Error loading proxies:", err)
        return
    }
​
    // 创建任务通道
    taskChan := make(chan string, 100)
    
    // 启动worker
    var wg sync.WaitGroup
    workerCount := 10
    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go worker(taskChan, &wg)
    }
​
    // 添加初始任务
    go func() {
        taskChan <- "https://httpbin.org/ip"
        taskChan <- "https://example.com"
    }()
​
    // 等待所有worker完成(实际可能需要其他停止机制)
    wg.Wait()
    close(taskChan)
}
​
func loadProxies(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
​
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        proxyURL, err := url.Parse("http://" + line)
        if err != nil {
            continue
        }
        proxyPool = append(proxyPool, proxyURL)
    }
    return nil
}
​
func worker(taskChan <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
​
    for targetURL := range taskChan {
        // URL去重
        if _, loaded := visitedURLs.LoadOrStore(targetURL, true); loaded {
            continue
        }
​
        // 随机延迟(1-3秒)
        time.Sleep(time.Duration(1+rand.Intn(2)) * time.Second)
​
        // 获取随机代理
        proxy := getRandomProxy()
        if proxy == nil {
            fmt.Println("No available proxies")
            continue
        }
​
        // 创建自定义Transport
        transport := &http.Transport{
            Proxy: http.ProxyURL(proxy),
            DialContext: (&net.Dialer{
                Timeout:   10 * time.Second,
                KeepAlive: 10 * time.Second,
            }).DialContext,
            TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
            MaxIdleConns:          100,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
        }
​
        // 创建HTTP客户端
        client := &http.Client{
            Transport: transport,
            Timeout:   15 * time.Second,
        }
​
        // 创建请求
        req, err := http.NewRequest("GET", targetURL, nil)
        if err != nil {
            fmt.Printf("Error creating request: %v\n", err)
            continue
        }
​
        // 设置随机User-Agent
        req.Header.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
​
        // 发送请求
        resp, err := client.Do(req)
        if err != nil {
            fmt.Printf("Error fetching %s: %v\n", targetURL, err)
            markBadProxy(proxy)
            continue
        }
        defer resp.Body.Close()
​
        // 处理响应
        if resp.StatusCode != http.StatusOK {
            fmt.Printf("Non-200 status code: %d for %s\n", resp.StatusCode, targetURL)
            continue
        }
​
        // 读取响应内容
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("Error reading response body: %v\n", err)
            continue
        }
​
        // 处理页面内容(示例:输出内容长度)
        fmt.Printf("Successfully fetched %s (%d bytes) using proxy %s\n",
            targetURL, len(body), proxy.Host)
​
        // 这里可以添加链接提取和任务添加逻辑
        // parseLinks(body, taskChan)
    }
}
​
func getRandomProxy() *url.URL {
    proxyMux.Lock()
    defer proxyMux.Unlock()
​
    if len(proxyPool) == 0 {
        return nil
    }
    return proxyPool[rand.Intn(len(proxyPool))]
}
​
func markBadProxy(badProxy *url.URL) {
    proxyMux.Lock()
    defer proxyMux.Unlock()
​
    for i, p := range proxyPool {
        if p.String() == badProxy.String() {
            // 移除无效代理
            proxyPool = append(proxyPool[:i], proxyPool[i+1:]...)
            fmt.Printf("Removed bad proxy: %s\n", badProxy.String())
            break
        }
    }
}
​
// 示例链接解析函数(需要根据实际情况实现)
func parseLinks(body []byte, taskChan chan<- string) {
    // 这里可以添加HTML解析逻辑,提取链接并发送到taskChan
    // 注意:需要处理相对路径转换和域名限制
}

关键特性说明:

  1. 代理管理

    • 从文件加载代理列表(格式:ip:port)
    • 随机选择代理进行请求
    • 自动移除失效代理
  2. 反爬策略

    • 随机User-Agent轮换
    • 请求间随机延迟(1-3秒)
    • 自动代理失效检测和移除
    • 支持HTTPS和跳过证书验证
  3. 并发控制

    • 使用带缓冲的channel作为任务队列
    • 可配置的worker数量
    • 自动URL去重(使用sync.Map)
  4. 健壮性设计

    • 连接超时设置(15秒)
    • 自定义Transport参数优化
    • 完善的错误处理

使用说明:

  1. 创建代理文件proxies.txt,格式为每行一个代理地址(ip:port)

  2. 根据需求调整以下参数:

    • workerCount:并发worker数量
    • taskChan缓冲大小
    • 各种超时参数
    • 随机延迟时间范围
  3. 实现parseLinks函数来提取新链接

  4. 添加具体的页面解析逻辑

注意事项:

  1. 代理质量直接影响爬虫效果,建议使用优质代理服务

  2. 根据目标网站的robots.txt和版权政策合理使用

  3. 添加适当的速率限制避免对目标网站造成过大压力

  4. 对于复杂反爬机制可能需要添加更多策略:

    • Cookie管理
    • JavaScript渲染支持
    • 验证码识别
    • 请求头随机化
    • 请求参数随机化

上面代码就是我提供了基础的爬虫框架,大家在使用的时候可以根据具体需求进行扩展和优化,有了这样的代码示例能让你们省很多事情。