用golang 實現一個代理池

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

背景

寫爬蟲的時候總會遇到爬取速度過快而被封IP的情況,這個時候就需要使用代理了。在https://github.com/henson/ProxyPool
的啟發下,決定自己實現一個代理池。項目已經開源在github。

https://github.com/AceDarkknight/GoProxyCollector

開發環境

windows 7,Go 1.8.4

資料來源

http://www.xicidaili.com
http://www.89ip.cn
http://www.kxdaili.com/
https://www.kuaidaili.com
http://www.ip3366.net/
http://www.ip181.com/
http://www.data5u.com
https://proxy.coderbusy.com

項目結構

目錄 作用
collector 收集器,抓取各個網站的代理
result 表示抓取的結果
scheduler 負責任務調度,包括啟動collector和入庫
server 啟動一個web服務,提供取結果的API
storage 儲存結果,通過介面可以使用別的資料庫
util 一些常用的工具方法
verifier ip的驗證與入庫出庫

實現

  • collector
    collector 支援兩種模式,分別是使用goquery對網頁元素進行選擇和使用Regex匹配我們需要的資訊。直接上代碼吧。
// github.com\AceDarkknight\GoProxyCollector\collector\selectorCollector.gofunc (c *SelectorCollector) Collect(ch chan<- *result.Result) {    // 退出前關閉channel。    defer close(ch)    response, _, errs := gorequest.New().Get(c.currentUrl).Set("User-Agent", util.RandomUA()).End()        /* 省略部分代碼 */    // 有些網站不是UTF-8編碼的,需要進行轉碼。    var decoder mahonia.Decoder    if c.configuration.Charset != "utf-8" {        decoder = mahonia.NewDecoder(c.configuration.Charset)    }    // 使用goquery。    doc, err := goquery.NewDocumentFromReader(response.Body)    if err != nil {        seelog.Errorf("parse %s error:%v", c.currentUrl, err)        return    }    // 大部分代理網站的代理列表都放在一個table裡,先選出table再迴圈裡面的元素。    selection := doc.Find(c.selectorMap["table"][0])    selection.Each(func(i int, sel *goquery.Selection) {        var (            ip       string            port     int            speed    float64            location string        )        // 我們需要的資訊的名字和路徑存在collectorConfig.xml。        nameValue := make(map[string]string)        for key, value := range c.selectorMap {            if key != "table" {                var temp string                if len(value) == 1 {                    temp = sel.Find(value[0]).Text()                } else if len(value) == 2 {                    temp, _ = sel.Find(value[0]).Attr(value[1])                }                // 轉碼.                if temp != "" {                    if decoder != nil {                        temp = decoder.ConvertString(temp)                    }                    nameValue[key] = temp                }            }        }        /* 省略部分代碼 */        // 過濾一些不合格結果        if ip != "" && port > 0 && speed >= 0 && speed < 3 {            r := &result.Result{                Ip:       ip,                Port:     port,                Location: location,                Speed:    speed,                Source:   c.currentUrl}            // 把合格結果放進channel            ch <- r        }    })}// github.com\AceDarkknight\GoProxyCollector\collector\regexCollector.gofunc (c *RegexCollector) Collect(ch chan<- *result.Result) {    response, bodyString, errs := gorequest.New().Get(c.currentUrl).Set("User-Agent", util.RandomUA()).End()        /* 省略部分代碼 */    // 用正則匹配。    regex := regexp.MustCompile(c.selectorMap["ip"])    ipAddresses := regex.FindAllString(bodyString, -1)    if len(ipAddresses) <= 0 {        seelog.Errorf("can not found correct format ip address in url:%s", c.currentUrl)        return    }    for _, ipAddress := range ipAddresses {        temp := strings.Split(ipAddress, ":")        if len(temp) == 2 {            port, _ := strconv.Atoi(temp[1])            if port <= 0 {                continue            }            r := &result.Result{                Ip:     temp[0],                Port:   port,                Source: c.currentUrl,            }            ch <- r        }    }}
  • result
    result很簡單,只是用來表示collector爬取的結果。
// github.com\AceDarkknight\GoProxyCollector\result\result.gotype Result struct {    Ip       string  `json:"ip"`    Port     int     `json:"port"`    Location string  `json:"location,omitempty"`    Source   string  `json:"source"`    Speed    float64 `json:"speed,omitempty"`}
  • scheduler
    scheduler負責完成一些初始化的工作以及調度collector任務。不同的任務在不同的goroutine中運行,goroutine之間通過channel進行通訊。
// github.com\AceDarkknight\GoProxyCollector\scheduler\scheduler.gofunc Run(configs *collector.Configs, storage storage.Storage) {    /* 省略部分代碼 */    for {        var wg sync.WaitGroup        for _, configuration := range configs.Configs {            wg.Add(1)            go func(c collector.Config) {                // 防止死結。                defer wg.Done()                // 處理panic。                defer func() {                    if r := recover(); r != nil {                        seelog.Criticalf("collector %s occur panic %v", c.Name, r)                    }                }()                col := c.Collector()                done := make(chan bool, 1)                go func() {                    runCollector(col, storage)                    // 完成時發送訊號。                    done <- true                }()                // 設定timeout防止goroutine已耗用時間過長。                select {                case <-done:                    seelog.Debugf("collector %s finish.", c.Name)                case <-time.After(7 * time.Minute):                    seelog.Errorf("collector %s time out.", c.Name)                }            }(configuration)        }        // 等待所有collector完成。        wg.Wait()        seelog.Debug("finish once, sleep 10 minutes.")        time.Sleep(time.Minute * 10)    }}
  • server
    server啟動了一個伺服器,提供API
  • storage
    storage提供了儲存相關的interface和實現。
// github.com\AceDarkknight\GoProxyCollector\storage\storage.gotype Storage interface {    Exist(string) bool    Get(string) []byte    Delete(string) bool    AddOrUpdate(string, interface{}) error    GetAll() map[string][]byte    Close()    GetRandomOne() (string, []byte)}

目前項目的資料都是儲存在boltdb。github上面關於boltdb的簡介如下:

Bolt is a pure Go key/value store inspired by Howard Chu's LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don't require a full database server such as Postgres or MySQL.
Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That's it.

考慮到代理池的資料量比較小,而且當初的想法是實現一個開箱即用的代理池,選擇boltdb這樣的嵌入式資料庫顯然是比使用MySQL和MongoDB更加簡單、便捷。當然,如果以後需要使用不同的資料庫時,只需要實現storage的介面即可。使用boltdb的相關文檔和教程在我參考的是:

https://segmentfault.com/a/1190000010098668

https://godoc.org/github.com/boltdb/bolt

  • util
    util實現了一些通用方法,例如取一個隨機的user-agent,具體就不展開了。
  • verifier

    verifier負責驗證collector拿到的ip是否可用,可用的入庫,停用就從資料庫中刪除。

    配置

    collector是通過設定檔驅動的。設定檔是:

github.com\AceDarkknight\GoProxyCollector\collectorConfig.xml

舉個例子:

<config name="coderbusy">    <urlFormat>https://proxy.coderbusy.com/classical/https-ready.aspx?page=%s</urlFormat>    <urlParameters>1,2</urlParameters>    <collectType>0</collectType>    <charset>utf-8</charset>    <valueNameRuleMap>        <item name="table" rule=".table tr:not(:first-child)"/>        <item name="ip" rule="td:nth-child(2)" attribute="data-ip"/>        <item name="port" rule=".port-box"/>        <item name="location" rule="td:nth-child(3)"/>        <item name="speed" rule="td:nth-child(10)"/>    </valueNameRuleMap></config><config name="89ip">    <urlFormat>http://www.89ip.cn/tiqv.php?sxb=&amp;tqsl=20&amp;ports=&amp;ktip=&amp;xl=on&amp;submit=%CC%E1++%C8%A1</urlFormat>    <collectType>1</collectType>    <charset>utf-8</charset>    <valueNameRuleMap>        <item name="ip" rule="((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))):[1-9]\d*"/>    </valueNameRuleMap></config>
  • name是collector的名字,主要作用是方便調試和出錯時查問題。
  • urlFormat和urlParameters用來拼接出需要爬取的網址。urlParameters可以為空白。例如上面第一個配置就是告訴爬蟲要爬的網站是:

    https://proxy.coderbusy.com/classical/https-ready.aspx?page=1

    https://proxy.coderbusy.com/classical/https-ready.aspx?page=2

  • collectType表示是用哪個collector,0代表selectorCollector,1代表regexCollector。
  • charset表示網站用的是哪種編碼。預設編碼是UTF-8,如果設定錯了可能會拿不到想要的資料。
  • valueNameRuleMap表示需要的點的規則。對於使用selectorCollector的網站,大部分結果通過table表示,所以table是必須的,其他點根據不同網站配置即可。相關rule的配置可以參考goquery的文檔:

    https://github.com/PuerkitoBio/goquery

結語

關於項目的介紹到這裡就差不多了,新手第一次用go寫項目如果有什麼不足和錯誤希望大家多多包涵和指出。如果你有疑問和更好的建議也歡迎大家一起探討~

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.