使用Golang构建多线程爬虫框架,核心是利用其原生的goroutine和channel特性实现高效的并发调度,同时合理设计任务分配逻辑保证爬虫的稳定性和抓取效率。Golang的轻量级协程相比传统线程占用资源更少,适合处理大量网络请求场景,这也是选择它构建爬虫框架的主要原因。

核心模块设计
一个基础的多线程爬虫框架需要包含以下几个核心模块:
- 任务队列:存储待抓取的URL和对应的抓取规则,支持动态添加任务
- 工作协程池:控制并发数量,避免同时发起过多请求被目标站点封禁
- 去重模块:防止重复抓取同一个URL,减少无效请求
- 结果处理器:负责解析抓取到的内容,保存需要的数据
- 调度器:协调各个模块的工作,实现任务的分配和异常回收
任务分配实现逻辑
任务分配的核心是通过channel实现任务的分发和回收,首先定义任务结构体:
// 定义抓取任务结构体
type CrawlTask struct {
URL string // 待抓取的URL
Headers map[string]string // 请求头
Depth int // 抓取深度,用于控制爬取层级
}
接下来实现任务队列和调度逻辑,使用两个channel分别传递待处理任务和结果:
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
// 任务队列,使用channel实现
var taskQueue = make(chan CrawlTask, 100)
// 结果队列,传递抓取后的结果
var resultQueue = make(chan string, 100)
// 等待组,用于等待所有工作协程完成
var wg sync.WaitGroup
// 去重集合,使用map实现简单去重
var visited = make(map[string]bool)
var visitedLock sync.Mutex
// 添加任务到队列,同时做去重判断
func addTask(task CrawlTask) {
visitedLock.Lock()
defer visitedLock.Unlock()
// 如果URL已经访问过则跳过
if visited[task.URL] {
return
}
visited[task.URL] = true
taskQueue <- task
}
// 工作协程,负责执行抓取任务
func worker(id int) {
defer wg.Done()
for task := range taskQueue {
fmt.Printf("工作协程%d开始抓取URL:%sn", id, task.URL)
// 模拟网络请求耗时
time.Sleep(time.Millisecond * 500)
// 发起HTTP请求
resp, err := http.Get(task.URL)
if err != nil {
fmt.Printf("抓取URL%s失败:%vn", task.URL, err)
continue
}
resp.Body.Close()
// 将结果发送到结果队列
resultQueue <- fmt.Sprintf("协程%d抓取%s成功,状态码:%d", id, task.URL, resp.StatusCode)
}
}
// 结果处理协程,负责处理抓取结果
func resultHandler() {
for res := range resultQueue {
fmt.Println("处理结果:", res)
}
}
func main() {
// 启动3个工作协程,控制并发数为3
workerNum := 3
wg.Add(workerNum)
for i := 0; i < workerNum; i++ {
go worker(i)
}
// 启动结果处理协程
go resultHandler()
// 添加初始任务
initTasks := []CrawlTask{
{URL: "http://ipipp.com/test1", Depth: 0},
{URL: "http://ipipp.com/test2", Depth: 0},
{URL: "http://ipipp.com/test3", Depth: 0},
{URL: "http://ipipp.com/test4", Depth: 0},
{URL: "http://ipipp.com/test5", Depth: 0},
}
for _, task := range initTasks {
addTask(task)
}
// 等待所有任务处理完成,这里简单等待3秒模拟任务处理周期
time.Sleep(time.Second * 3)
// 关闭任务队列,通知工作协程退出
close(taskQueue)
// 等待所有工作协程完成
wg.Wait()
// 关闭结果队列
close(resultQueue)
fmt.Println("所有任务处理完成")
}
并发控制注意事项
在实际使用中需要注意以下几点:
- 工作协程数量不是越多越好,需要根据目标站点的反爬策略和自身机器性能调整,一般建议控制在10-50之间
- 请求时需要设置合理的超时时间,避免单个请求卡住导致协程阻塞
- 可以添加请求重试机制,对于网络波动导致的失败请求进行有限次数的重试
- 如果抓取的是外部站点,建议添加请求间隔,避免对目标站点造成过大压力
框架扩展方向
上述是基础的多线程爬虫框架实现,还可以根据需求扩展更多功能:
- 添加代理IP池,实现请求IP的轮换,降低被封禁的概率
- 支持自定义解析规则,通过回调函数处理不同页面的内容提取逻辑
- 增加任务持久化能力,避免程序重启后任务丢失
- 添加监控模块,统计抓取成功率、耗时等指标,方便排查问题