假设目标网站结构是:列表页 + 内容页
列表页url是规律的,如 http://DOMAIN/list/page/1
计划采集 1-4000 页,每页10条内容,一共40000个内容页。
假设按照深度优先的采集策略,即按照每抓取一个列表页,提取内容页url,紧接着抓取内容页并提取内容,然后入库。
首先第一步,采集入口,我们使用for循环,为每个列表页创建一个线程去采集。
for n:=1;n<=4000;n++ {
go CrawlList(fmt.Sprintf("http://DOMAIN/list/page/%d"), n)
}
CrawlList 来抓取列表页,并创建内容页的抓取请求
func CrawlList(listurl string) {
// http抓取列表页源码, 假设这里封装了个函数, 内部也是异步的,返回一个channel用于接收结果
ch := HtmlGet(listurl)
html <- ch
//提取内容页url,可以使用正则或者 goquery等包 过程省略...
go CrawlContent(contentUrl)
}
func CrawlContent(contentUrl string) {
//抓取内容页源码
ch := HtmlGet(contentUrl)
html <- ch
//提取内容页内容,并入库,过程略
}
大致过程就是这样,现在问题来了,对方站点限制了访问频率,如果并发过高,会被对方防火墙拦截。所以我们首先要对 HtmlGet
增加并发限制,代码大致原理如下
//创建一个带缓冲的channel,容量为10 , 支持10个并发
var conlimit = make(chan bool, 10)
func HtmlGet(addr string) chan string {
conlimit <- true
ch := make(chan string)
go func(){
defer func(){ <-conlimit }()
//http请求过程略,假设内容保存到 res ,最终通过ch提供消费
ch <- res
}()
return ch
}
这样,我们可以针对http请求限制同时最多10个并发。
然而还有一个问题,因为我们是深度采集的,所以,一开始我们创建了4000个goroutine采集列表页,那么每个goroutine中都会调用 GetHtml
函数去抓取列表页内容,等于必须等到所有列表页几乎都请求过,才会轮到内容页的采集,在这之前,所有已经请求成功的列表页都会占着内存,等待内容页采集成功。
所以,在第一步我们也需要限制一下,每次最多采集10个列表页,这样10个列表页抓取成功了,会立即处理下面的内容页,也就是最多 10*10个goroutine,不会占用太多内存。
listconlimit := make(chan bool, 10)
for n:=1;n<=4000;n++ {
listconlimit <- true
go func(n int){
defer func(){ <- listconlimit }()
CrawlList(fmt.Sprintf("http://DOMAIN/list/page/%d"), n)
}(n)
}
注: 以上代码仅视为伪代码,不能直接运行。
-完-