背景
写爬虫的时候总会遇到爬取速度过快而被封IP的情况,这个时候就需要使用代理了。在https://github.com/henson/ProxyPool
的启发下,决定自己实现一个代理池。项目已经开源在github。
2018.03.29更新
- go 版本升级为1.9.4,使用新版本的sync.Map 提高并发读的效率
开发环境
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对网页元素进行选择和使用正则表达式匹配我们需要的信息。直接上代码吧。
// 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。
nameva lue := 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)
}
nameva lue[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
}
}
}
result
result很简单,只是用来表示collector爬取的结果。
// github.com\AceDarkknight\GoProxyCollector\result\result.go
type Result struct {
Ip string `json:"ip"`
Port int `json:"port"`
Location stri