1. 代理池

github找一个proxy pool轮子,后面爬虫要用

2. http库的选用

我之前一直使用go-colly框架进行爬虫,后来发现了未知的严重的内存泄漏bug,改换自带的net/http库就没有内存泄漏的问题。而且colly能实现的爬虫,net/http库实现起来也毫无压力。所以这里推荐使用net/http库,高并发NIO且占用内存小。

net/http常用几点:代理池,get,post raw和json,header设置,cookie设置,session,user agent推荐go-colly自带的生成器,超时设置,失败重试次数(得上层逻辑实现)。

一个使用示例:

proxyFunc := func(*http.Request) (*url.URL, error) {
    resp, err := http.Get(proxyURL)
    if err != nil {
        return nil, err
    }
    ip, _ := ioutil.ReadAll(resp.Body)
    _ = resp.Body.Close()
    return url.Parse("http://" + string(ip))
}
client := &http.Client{Timeout: time.Second * 5}
client.Transport = &http.Transport{
    Proxy: proxyFunc,
}
// ……
resp, err := client.Post(crawlURL, "application/json", strings.NewReader(requestData))
if err != nil {
    continue
}
body, _ := ioutil.ReadAll(resp.Body)
_ = resp.Body.Close()
// ……

3. 协程池的实现

go虽然原生支持高并发,但无脑开协程会让内存一下子吃满,所以并发爬虫一定要写协程池,我最常用有两种写法,也是go官网推荐的两种写法。

第一种,起固定数目个协程:

poolSize := 128

recv := make(chan string, poolSize)
wg := sync.WaitGroup{}

for i := 0; i < maxGoRoutine; i++ {
    wg.Add(1)
    go func() {
        for x := range recv {
            time.Sleep(time.Millisecond * 20)
            // ……
        }
        wg.Done()
    }()
}

for _, x := range xxx {
    recv <- x
}

close(recv)
wg.Wait()

第二种,semaphore:

poolSize := 128

sem := make(chan struct{}, poolSize)
defer close(sem)
wg := sync.WaitGroup{}

for _, x := range xxx {
    sem <- struct{}{}
    wg.Add(1)
    x := x // 这里也可以直接传参给go func
    go func() {
        defer func() {
            <-sem
            wg.Done()
        }()
        // ……
    }()
}
wg.Wait()

4. 爬取内容存储

由于爬取的东西基本用于本地分析,这里推荐一些嵌入式单机数据库。

  1. go语言实现的leveldb,无需任何依赖(只需导入一个go包)的高性能单机kv存储引擎
  2. boltdb,同样是基于go语言实现的kv存储引擎,区别于leveldb,leveldb基于LSM树,boltdb基于B+树。
  3. sqlite3,众所周知的单机关系型数据库。

5. 关于爬虫的一些技巧

  1. 用浏览器抓包,用fiddler/charles抓包,用js fetch/postman模拟请求。
  2. 浏览器查看元素,copy path,查看network,copy request。
  3. 浏览器断点调试js,分析request是如何构造的以及response是如何解析的。