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

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

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

go 复制代码
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渲染支持
    • 验证码识别
    • 请求头随机化
    • 请求参数随机化

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

相关推荐
DanB2444 分钟前
Java笔记4
java·开发语言·笔记
Dddle11 小时前
C++:this指针
java·c语言·开发语言·c++
studyer_domi1 小时前
Matlab 234-锂电池充放电仿真
开发语言·matlab
yuanpan1 小时前
.net/C#进程间通信技术方案总结
开发语言·c#·.net
吃面不喝汤661 小时前
破解 Qt QProcess 在 Release 模式下的“卡死”之谜
开发语言·qt
@十八子德月生1 小时前
8天Python从入门到精通【itheima】-1~5
大数据·开发语言·python·学习
jiunian_cn2 小时前
【c++】异常详解
java·开发语言·数据结构·c++·算法·visual studio
martian6652 小时前
信创生态核心技术栈:数据库与中间件
开发语言·中间件·系统架构·系统安全·创业创新
Bl_a_ck2 小时前
开发环境(Development Environment)
开发语言·前端·javascript·typescript·ecmascript
每天一个秃顶小技巧2 小时前
02.Golang 切片(slice)源码分析(一、定义与基础操作实现)
开发语言·后端·python·golang