寫爬蟲的時候總會遇到爬取速度過快而被封IP的狀況,這個時候就須要使用代理了。在https://github.com/henson/ProxyPool
的啓發下,決定本身實現一個代理池。項目已經開源在github。php
windows 7,Go 1.8.4github
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.comweb
目錄 | 做用 |
---|---|
collector | 收集器,抓取各個網站的代理 |
result | 表示抓取的結果 |
scheduler | 負責任務調度,包括啓動collector和入庫 |
server | 啓動一個web服務,提供取結果的API |
storage | 存儲結果,經過接口可使用別的數據庫 |
util | 一些經常使用的工具方法 |
verifier | ip的驗證與入庫出庫 |
// github.com\AceDarkknight\GoProxyCollector\collector\selectorCollector.go func (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.go func (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 } } }
// github.com\AceDarkknight\GoProxyCollector\result\result.go type Result struct { Ip string `json:"ip"` Port int `json:"port"` Location string `json:"location,omitempty"` Source string `json:"source"` Speed float64 `json:"speed,omitempty"` }
// github.com\AceDarkknight\GoProxyCollector\scheduler\scheduler.go func 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) } }
// github.com\AceDarkknight\GoProxyCollector\storage\storage.go type 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的相關文檔和教程在我參考的是:json
http://www.javashuo.com/article/p-tggqzyzd-cz.htmlsegmentfault
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=&tqsl=20&ports=&ktip=&xl=on&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>
urlFormat和urlParameters用來拼接出須要爬取的網址。urlParameters能夠爲空。例如上面第一個配置就是告訴爬蟲要爬的網站是:
https://proxy.coderbusy.com/classical/https-ready.aspx?page=1
https://proxy.coderbusy.com/classical/https-ready.aspx?page=2
valueNameRuleMap表示須要的點的規則。對於使用selectorCollector的網站,大部分結果經過table表示,因此table是必須的,其餘點根據不一樣網站配置便可。相關rule的配置能夠參考goquery的文檔:
關於項目的介紹到這裏就差很少了,新手第一次用go寫項目若是有什麼不足和錯誤但願你們多多包涵和指出。若是你有疑問和更好的建議也歡迎你們一塊兒探討~