用golang实现一个代理池(代码片段)

DilonWu DilonWu     2022-10-28     441

关键词:

背景

写爬虫的时候总会遇到爬取速度过快而被封IP的情况,这个时候就需要使用代理了。在https://github.com/henson/ProxyPool
的启发下,决定自己实现一个代理池。项目已经开源在github。

https://github.com/AceDarkkinght/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对网页元素进行选择和使用正则表达式匹配我们需要的信息。直接上代码吧。
// github.com\AceDarkkinght\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\AceDarkkinght\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\AceDarkkinght\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"`
  • scheduler
    scheduler负责完成一些初始化的工作以及调度collector任务。不同的任务在不同的goroutine中运行,goroutine之间通过channel进行通信。
// github.com\AceDarkkinght\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)
    
  • server
    server启动了一个服务器,提供API
  • storage
    storage提供了存储相关的interface和实现。
// github.com\AceDarkkinght\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的相关文档和教程在我参考的是:

https://segmentfault.com/a/1190000010098668

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

  • util
    util实现了一些通用方法,例如取一个随机的user-agent,具体就不展开了。
  • verifier

    verifier负责验证collector拿到的ip是否可用,可用的入库,不可用的就从数据库中删除。

    配置

    collector是通过配置文件驱动的。配置文件是:

github.com\AceDarkkinght\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\d2)|([1-9]?\d)))\.)3(?:25[0-5]|2[0-4]\d|((1\d2)|([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写项目如果有什么不足和错误希望大家多多包涵和指出。如果你有疑问和更好的建议也欢迎大家一起探讨~

golang实现正/反向代理服务(代码片段)

1概念1.1正向代理一种客户端代理技术,用于帮助客户端访问无法直接访问的网络资源,并隐藏客户端IP,常见的场景有***、浏览器HTTP代理1.2反向代理一种服务端代理技术,用于隐藏真实服务端节点,并实现负载均衡、缓存、安... 查看详情

python爬虫实战-基于代理池的高并发爬虫(代码片段)

...网站API爬取十亿级别的JSON数据。代理池有两种方式能够实现爬虫对代理池的充分利用:搭建一个TunnelProxy服务器维护代理池在爬虫项目内部自动切换代理所谓TunnelProxy实际上是将切换代理的操作交给了代理服务器,很多市面上的... 查看详情

设计模式这样玩泰简单(golang版)-中介模式(代码片段)

场景老板:如果我们现在要实现一个群通知功能,群中的所有成员都能通知其他人,你觉得用什么设计模式比较好你:好的老板,那就使用中介模式方案中介模式,指的是用一个对象来封装一批成员对象,并暴露同一的接口,这个接口可以... 查看详情

go并发-对象池实现(代码片段)

...子,需要对象时从池子取,用完了再还回去。在golang中通过维持一个带缓冲channel很容易完成这一功能,天然具有并发访问安全性。简单实现如下typeConnectPoolstruct conn 查看详情

go并发-对象池实现(代码片段)

...子,需要对象时从池子取,用完了再还回去。在golang中通过维持一个带缓冲channel很容易完成这一功能,天然具有并发访问安全性。简单实现如下typeConnectPoolstruct con 查看详情

go并发-对象池实现(代码片段)

...子,需要对象时从池子取,用完了再还回去。在golang中通过维持一个带缓冲channel很容易完成这一功能,天然具有并发访问安全性。简单实现如下typeConnectPoolstruct con 查看详情

用golang实现的一个小型http框架(代码片段)

用golang实现的一个小型http框架支持接口访问funcmain() hw:=Cheetah.Hwdefault() hw.Get("/a",func(elp*Cheetah.Elp) elp.RendenTemper("a.html") ) hw.Post("/hello",func(elp*Cheetah.Elp) elp.PrintString("helloword") ) hw.Run("127.0.0.1",1231)仓... 查看详情

如何判断golang接口是否实现?(代码片段)

...乎有了本文。主要探讨两个问题:1.利用编译来判断Golang接口是否实现2.延伸出的make和new的区别正文1.利用编译来判断Golang接口是否实现看了一个底层通用链接池的库,有这么一行代码:var_Pooler=new(WeightedRoundRobin) 查看详情

搭建免费代理池(代码片段)

搭一个免费的代理池#https://github.com/jhao104/proxy_pool#收费的:提供给你一个接口,每掉一次这个接口,获得一个代理#免费:用爬虫爬取,免费代理,放到我的库中,flask,django搭一个服务(删除代理,自动测试代理可用性),每次... 查看详情

scrapy实现ip代理池(代码片段)

首先需要在ip代理的网站爬取有用的ip,保存到数据库中importrequestsfromscrapy.selectorimportSelectorimportpymysqlconn=pymysql.connect(host=‘127.0.0.1‘,user=‘root‘,passwd=‘root‘,db=‘mysql18_text‘,charset=‘utf8‘)cursor=conn.curs 查看详情

设计模式代理模式实现连接池

1.连接池创建Connection的过程是很耗时的,为了保证Conection能够重用。应该将Connection进行池管理。使用静态工厂方法管理一个唯一的连接:/***用静态工厂方法管理一个唯一的可重用的连接*/publicclassConnUtils{privateConnUtils(){}privatestati... 查看详情

使用urllib--用户代理池(代码片段)

为什么要建立用户代理池?如果我们用浏览器伪装,仅用一个浏览器标识,如果对方服务器的反爬手段高,我们这一个一直访问很容易被抓到,这时候我们可以建一个用户代理池,随机进行访问,增加我们的成功率话不多说上代... 查看详情

flask开发系列之flask+redis实现ip代理池(代码片段)

Flask开发系列之Flask+redis实现IP代理池 6.11-6.15号完善...简易实现版importrequestsimportreimporttimeimportredisfrombloom_filterimportBloomFilterimportastpool=redis.ConnectionPool(host=‘localhost‘,password=‘xxx‘,por 查看详情

golang实现http代理和反向代理(代码片段)

正向代理packagemainimport("fmt""io""net""net/http""strings")typePxystructfunc(p*Pxy)ServeHTTP(rwhttp.ResponseWriter,req*http.Request)fmt.Printf("Receivedrequest%s%s%s",req.Method,req.Host,req.RemoteAddr)transport:=http.DefaultTransport//step1outReq:=new(http.Request)*outReq=*req//thisonlydoessha... 查看详情

[爬虫]一个易用的ip代理池(代码片段)

一个易用的IP代理池-stand写爬虫时常常会遇到各种反爬虫手段,封IP就是比较常见的反爬策略遇到这种情况就需要用到代理IP,好用的代理通常需要花钱买,而免费的代理经常容易失效,所以就需要自己搭建IP代理池,来获取免费高效的... 查看详情

grpc的简单用例(golang实现)(代码片段)

这个用例的逻辑很简单,服务器运行一个管理个人信息的服务,提供如下的四个服务:(1)添加一个个人信息  注:对应于UnaryRPCs,客户端发送单一消息给服务器,服务器返回单一消息(2)添加多个个人信息  注:对应于ClientstreamingRPCs,客... 查看详情

golang结构体嵌套和用结构体实现模拟“继承”(代码片段)

什么是结构体嵌套一个结构体中可以嵌套包含另一个结构体或结构体指针结构体嵌套packagemainimport"fmt"//桌子结构体typetablestruct materialstring shapeint comcommon//嵌套结构体//被嵌套的结构体定义typecommonstruct yearintfuncmain() //嵌套结... 查看详情

golang工人池(代码片段)

查看详情