Go与Python爬虫对比及模板实现

发布于:2025-07-04 ⋅ 阅读:(16) ⋅ 点赞:(0)

go语言和Python语言都可选作用来爬虫项目,因为python经过十几年的累积,各种库是应有尽有,学习也相对比较简单,相比GO起步较晚还是有很大优势的,么有对比就没有伤害,所以我利用一个下午,写个Go爬虫,虽说运行起来没啥问题,但是之间出错的概率太高了,没有完备的模版套用得走很多弯路,这就是为啥go没有python受欢迎的原因。

在这里插入图片描述

为何Go爬虫远没有Python爬虫流行?

1、历史生态差距

  • Python爬虫生态成熟(Scrapy、BeautifulSoup、Requests等库已有10+年积累)
  • Go生态起步较晚(Colly等主流库2017年后才出现)

2、开发效率差异

  • Python动态类型适合快速试错:response.json()直接解析动态数据
  • Go需预定义结构体:type Result struct{ Title string json:“title” }

3、学习曲线陡峭

  • Python同步代码直观:requests.get() -> BeautifulSoup()
  • Go并发模型复杂:需掌握goroutine/channel/sync等概念

4、数据处理短板

  • Python有Pandas/NumPy等成熟数据处理库
  • Go缺乏同级别数据分析工具链

5、社区惯性

  • 90%爬虫教程使用Python编写
  • Stack Overflow爬虫问题Python占比超80%

废话不多说,看我直接上代码。

Go爬虫通用模板(带高级特性)

package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/gocolly/colly"
	"github.com/gocolly/colly/debug"
	"golang.org/x/time/rate"
)

// 配置结构体
type Config struct {
	StartURLs        []string
	AllowedDomains   []string
	Parallelism      int
	RequestTimeout   time.Duration
	RotateUserAgents bool
	ProxyList        []string
	OutputFile       string
	RateLimit        int // 每秒请求数
}

// 爬取结果
type ScrapeResult struct {
	URL   string
	Title string
	Data  map[string]string
}

func main() {
	// 配置示例
	cfg := Config{
		StartURLs:        []string{"https://example.com"},
		AllowedDomains:   []string{"example.com"},
		Parallelism:      5,
		RequestTimeout:   30 * time.Second,
		RotateUserAgents: true,
		ProxyList:        []string{"http://proxy1:8080", "socks5://proxy2:1080"},
		OutputFile:       "results.json",
		RateLimit:        10,
	}

	// 运行爬虫
	results := runCrawler(cfg)

	// 处理结果 (示例输出)
	fmt.Printf("爬取完成! 共获取%d条数据\n", len(results))
	for _, res := range results {
		fmt.Printf("URL: %s\nTitle: %s\n\n", res.URL, res.Title)
	}
}

func runCrawler(cfg Config) []ScrapeResult {
	// 初始化收集器
	c := colly.NewCollector(
		colly.AllowedDomains(cfg.AllowedDomains...),
		colly.Async(true),
		colly.Debugger(&debug.LogDebugger{}),
	)

	// 配置并发
	c.Limit(&colly.LimitRule{
		DomainGlob:  "*",
		Parallelism: cfg.Parallelism,
		RandomDelay: 2 * time.Second, // 随机延迟防封禁
	})

	// 设置超时
	c.SetRequestTimeout(cfg.RequestTimeout)

	// 配置代理轮询
	if len(cfg.ProxyList) > 0 {
		proxySwitcher := setupProxySwitcher(cfg.ProxyList)
		c.SetProxyFunc(proxySwitcher)
	}

	// 配置限流器
	limiter := rate.NewLimiter(rate.Limit(cfg.RateLimit), 1)
	c.OnRequest(func(r *colly.Request) {
		limiter.Wait(context.Background())
	})

	// 随机User-Agent
	if cfg.RotateUserAgents {
		c.OnRequest(func(r *colly.Request) {
			r.Headers.Set("User-Agent", randomUserAgent())
		})
	}

	// 结果存储
	var (
		results []ScrapeResult
		mu      sync.Mutex
	)

	// 核心解析逻辑
	c.OnHTML("html", func(e *colly.HTMLElement) {
		result := ScrapeResult{
			URL:   e.Request.URL.String(),
			Title: e.DOM.Find("title").Text(),
			Data:  make(map[string]string),
		}

		// 示例:提取所有<h2>标签内容
		e.DOM.Find("h2").Each(func(i int, s *goquery.Selection) {
			result.Data[fmt.Sprintf("heading_%d", i)] = s.Text()
		})

		// 示例:提取元数据
		if desc, exists := e.DOM.Find(`meta[name="description"]`).Attr("content"); exists {
			result.Data["description"] = desc
		}

		// 线程安全写入
		mu.Lock()
		results = append(results, result)
		mu.Unlock()
	})

	// 链接发现
	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		link := e.Attr("href")
		absoluteURL := e.Request.AbsoluteURL(link)
		
		// URL过滤规则
		if shouldCrawl(absoluteURL, cfg.AllowedDomains) {
			e.Request.Visit(absoluteURL)
		}
	})

	// 错误处理
	c.OnError(func(r *colly.Response, err error) {
		log.Printf("请求失败 %s: %v", r.Request.URL, err)
		// 自动重试逻辑
		if r.StatusCode == 429 { // 触发限流
			time.Sleep(10 * time.Second)
			r.Request.Retry()
		}
	})

	// 启动任务
	for _, u := range cfg.StartURLs {
		c.Visit(u)
	}

	// 等待完成
	c.Wait()
	return results
}

// 高级功能函数实现
func setupProxySwitcher(proxies []string) func(*http.Request) (*url.URL, error) {
	var proxyIndex int
	return func(r *http.Request) (*url.URL, error) {
		proxy := proxies[proxyIndex%len(proxies)]
		proxyIndex++
		return url.Parse(proxy)
	}
}

func randomUserAgent() string {
	agents := []string{
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
		"Googlebot/2.1 (+http://www.google.com/bot.html)",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/605.1.15",
	}
	return agents[time.Now().UnixNano()%int64(len(agents))]
}

func shouldCrawl(rawURL string, allowedDomains []string) bool {
	u, err := url.Parse(rawURL)
	if err != nil {
		return false
	}

	// 跳过非HTTP
	if !strings.HasPrefix(u.Scheme, "http") {
		return false
	}

	// 检查域名白名单
	domainAllowed := false
	for _, domain := range allowedDomains {
		if strings.HasSuffix(u.Hostname(), domain) {
			domainAllowed = true
			break
		}
	}
	if !domainAllowed {
		return false
	}

	// 过滤静态资源
	staticExt := []string{".jpg", ".png", ".css", ".js", ".svg", ".gif"}
	for _, ext := range staticExt {
		if strings.HasSuffix(u.Path, ext) {
			return false
		}
	}

	// 自定义过滤规则 (示例:排除登录页面)
	if regexp.MustCompile(`/(login|signin)`).MatchString(u.Path) {
		return false
	}

	return true
}

模板核心优势

1、企业级功能集成

  • 代理轮询:支持HTTP/SOCKS5代理池
  • 智能限流:令牌桶算法控制请求频率
  • 动态UA:自动切换User-Agent
  • 错误恢复:429状态码自动重试

2、反爬对抗设计

c.Limit(&colly.LimitRule{
  RandomDelay: 2 * time.Second, // 随机延迟
})

// TLS配置跳过证书验证(应对某些反爬)
c.WithTransport(&http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
})

3、资源控制

// 内存保护:限制爬取深度
c.MaxDepth = 3

// 防止循环:URL去重
c.URLFilters = append(c.URLFilters, regexp.MustCompile(`^https?://`))

4、数据管道扩展

// 添加数据库写入
c.OnScraped(func(r *colly.Response) {
  saveToDB(r.Ctx.Get("result"))
})

适用场景建议

场景 推荐语言 原因
快速原型验证 Python 交互式开发,调试便捷
大规模数据采集 Go 高并发性能,内存控制优秀
复杂JS渲染 Python Playwright/Selenium支持更成熟
分布式爬虫系统 Go 天然并发支持,部署资源节省
简单数据抓取 Python 代码简洁,开发速度快

上面我们已经了解了go和python爬虫的优劣势,主要Python在爬虫领域的统治地位源于其极致的开发效率,而Go在需要高性能、高可靠性的生产环境中逐渐崭露头角。随着Go生态完善(如Rod无头浏览器库),其爬虫应用正在快速增长。但是相对来说python爬虫还是能让更多人接受的。