爬虫和IP代理

作者: 小温侯 | 来源:发表于2018-07-21 00:07 被阅读202次

    工作原理

    之前在写代理的时候写错了一些内容,搞混了网络层和应用层的概念,深感惭愧。今天着重收集了一些资料,同时也温习一下网络知识。

    关于TCP/IP的五层架构:物理层、数据链路层、网络层、传输层和应用层。像我之前说的那个IP切换的过程发生在网络层,而代理是工作在传输层(SOCKS代理)和应用层(其他代理)的。因为网络各层的透明性,即应用层只知道应用层,它不知道也不care下面四层的存在,所以我以为通过检查MAC地址来反爬虫应该是不合理的。

    现在说说代理的工作模式,假设你从你的电脑上访问一个网站,HTTP报文会被封装成IP包(有可能被分割)然后经过不知道多少路由最终被传到目标网站所在的服务器,这个过程不重要,这里我们只需要从应用层这个角度看这个过程。

    首先,最简单的过程就是请求+响应:

                      请求                 
                ------------------>         
      客户端                         服务器  
                <-----------------           
                      响应                   
    

    代理服务器可以进行很多种协议的代理:HTTP,FTP,RTSP,POP3等作用在应用层的协议,它们都是先了各自不同的功能并工作在不同的端口号上;另外SOCKS代理工作在传输层,它又分为SOCKS4和SOCKS5,前者支支持TCP协议,后者则支持TCP和UDP协议。

    但是随着互联网的发展,衍伸出几种不同的需求:

    正向代理

    因为IPv4地址(所谓的公网IP)数量短缺,很多公司会自建一个内网并用192.或者10. 开头的小网IP来管理公司内部的网络,同时架设一台或者多台网关,所有的内网主机都会把请求发送到网关,而网关会有一个公网IP,因此它可以和互联网内其他同样带有公网IP的服务器交流。这里网关就是一种正向代理。同时这种代理除了转发请求响应还可以有其他功能:

    • 加速网络:游戏加速器,当然这点还要配合其他强力的技术。
    • 缓存作用:代理服务器有时会缓存一些常用网站的内容,如果我把整个WIKI下载到本地服务器,这样如果内网有人要访问wiki,就只要直接访问本地的一个服务器,而不用使用互联网了,这样能减少网络的使用率。
    • 权限控制:这个很好理解,代理服务器也可以拒绝某些内网主机的请求以达到控制内网主机上网的目的。除此之外,还可以控制带宽,黑白上网名单之类的,就不都说了。
    • 隐藏访问者信息:这个也好理解,对于服务器来说,访问它的是代理服务器,内网主机的身份被隐藏了。
    客户端1  |
             |
    客户端2  |<------->   代理服务器  <-------------------------> 服务器
             |
        ...     |
    

    反向代理

    如果互联网中有一个服务器提供商特别热门,比如说每天都要访问的搜索网站;那么如果服务商只提供一台服务器用来响应来自互联网的海量请求,这样服务器的有可能崩溃。因此通常情况下,服务商会放置一台代理服务器用来接收这些请求,同时也会安置很多真正的相同的服务器位于后端,在代理服务器收到请求后,它会根据某种策略把请求转发到服务器中的某一台,而对于客户端来说,它以为自己访问的就是真正的服务器。真正的服务器在收到请求后,可以向代理服务器返回请求再由代理服务器返回给客户端,也可以直接向客户端返回请求,这取决于你的转发策略,这个过程就是负载均衡, 也是反向代理的一种重要形式。

                                                            |  服务器1
                                                            |
    客户端   <-----------------------> 代理服务器 <-------->   服务器2
                                                            |           
                                                            |  .....
    

    透明代理

    如果一台有公网IP的客户端,要访问另一个有公网IP的服务端,中间使用了同样有公网IP的代理服务器呢?这就是透明代理。其实我觉得这个名字有一点的误导性,如果我们称如下图中的模式为其他代理(用以区分上述两种代理),那么透明代理只是这种代理的一种类型。但是既然大家都称之为透明代理,这里就不改了。

    客户端 <----------------> 代理服务器 <-------------------> 客户端
    

    我们约定数据流从客户端到服务端的方向为正向,如果代理的存在是用来隐藏客户端,我们就认为它是正向代理;如果是用来隐藏服务端,则认为是反向代理。但是仅凭这两点还是无法区分正向代理和透明代理,私以为区别这两者的关键在于有没有小网IP的存在。不过我觉得硬要区分这几个也没什么意义,最关键的是还是要弄懂这几种模式的工作方式。

    这里要展开说一下这种代理。从具体的实现细节来看,在报文传输过程中,与IP相关的Header字段有三个:

    REMOTE_ADDR
    HTTP_VIA 
    HTTP_X_FORWARDED_FOR
    

    这三个字段都不是HTTP的标准字段,也就是说在传输过程中可以携带也可以不携带,我们约定是如果报文经过代理服务器,则代理服务器需要在报文的Header中添加这三个字段,用来记录报文经过了哪些代理服务器。这几个字段在如果用本地抓包很难抓得到(不是不可以)。但是有些时候,我们并不想让服务器知道我们使用了代理服务器,此时,代理服务器就会根据不同的需求主动调整这几个字段。

    在正常情况下,这三个值为:

    REMOTE_ADDR = Client_IP
    HTTP_VIA = None
    HTTP_X_FORWARDED_FOR = None
    

    透明代理

    REMOTE_ADDR = Last Proxy IP
    HTTP_VIA = Proxy IP
    HTTP_X_FORWARDED_FOR = Client_IP, Proxy1 IP, Proxy2 IP....
    

    这是符合互联网约定的写法。

    普通匿名代理

    REMOTE_ADDR = Last Proxy IP
    HTTP_VIA = Proxy IP
    HTTP_X_FORWARDED_FOR = Proxy1 IP, Proxy2 IP....
    

    它可以隐藏你的真实IP。

    高等匿名代理

    REMOTE_ADDR = Proxy IP
    HTTP_VIA = None
    HTTP_X_FORWARDED_FOR = None
    

    注意看,这种情况下和正常访问是没有区别的,代理IP就相当于正常访问里的客户端IP。

    混淆代理

    还有更刺激的。

    REMOTE_ADDR = Last Proxy IP
    HTTP_VIA = Proxy IP
    HTTP_X_FORWARDED_FOR = Randon IP(s)
    

    和VPN、SSH代理的区别

    透明代理可以帮助客户端绕过一些限制,比如说访问一下客户端IP不允许访问的服务器。除了使用代理,还有两种技术可以达到这个目的:VPN和SSH隧道。

    VPN和代理不同的地方在于,它会首先构建一个专有的两端通讯线路,然后分配给两端虚拟身份(IP, mac),之后所有的报文都会被封装在VPN特有的数据格式中,等数据包到达线路的另一头后再进行解封, 之后在根据解封后的报文格式进行对应的处理,比如所如果解封后发现是HTTP报文,就转发到80端口。这个过程不一定是加密的。目前VPN的协议大概有三种:IP sec, MPLS和SSL,具体是什么我就不细说了,理论上说VPN的安全性最好。

    SSH代理只是socks代理的一种特殊方式,它利用了ssh的端口转发功能,客户端首先和代理服务器建立SSH连接(SSH也有客户端和服务端),应用程序会把报文发到ssh客户端(一般是同一台机器),由其转发到ssh服务端(也就是代理服务器),其实SSH就是一个特殊的socks代理。

    总之,三种方式都可以实现翻墙,也各有各自的优缺点,毕竟互联网嘛,everything is tradeoff.

    服务器识别代理

    从服务器的角度出发,你可能并不像有爬虫每天,甚至每分钟使用不同的代理向你发送海量的请求。这时候你就需要一个能够识别代理机制。这点很难,尤其是客户端使用的高匿代理的时候,但是仍然有一些蛛丝马迹可以讨论一下。参考[4]提供了一下常用的方法:

    • 反向探测扫描IP是不是开通了80,8080等代理服务器经常开通的端口,显然一个普通的用户IP不太可能开通如上的端口。
    • HTTP头部的X_Forwarded_For:开通了HTTP代理的IP可以通过此法来识别是不是代理IP;如果带有XFF信息,该IP是代理IP无疑。
    • Keep-alive报文:如果带有Proxy-Connection的Keep-alive报文,该IP毫无疑问是代理IP。
    • 查看IP上端口:如果一个IP有的端口大于10000,那么该IP大多也存在问题,普通的家庭IP开这么大的端口几乎是不可能的。

    构建自己的代理池

    如果要做爬虫,不可避免的终会有被反爬虫制裁的一天,因此构建自己的代理IP池是很重要的。那么要怎么做呢?

    自己抓取

    我随便一搜发现网上有很多提供免费代理的地方,甚至一些代理商为了避免爬虫还提供了API让你获取它的免费IP。然而它们提供的IP大多数其实是不能用的,所以我还是比较推荐自己买一些代理IP,不过练习一下抓取总没坏处。目前我所知道的代理IP提供商有:西刺免费代理IP, 米扑代理, 快代理; 还有两个国外的:Free Proxy List, spys.one。因为这不是我主要获取代理IP的手段,我就爬一个意思一下,这里是爬西刺代理的代码:

    from urllib import request
    from urllib import error
    from bs4 import BeautifulSoup
    import chardet
    import time
    import re
    
    ip_lists = []
    domain = "http://www.xicidaili.com"
    
    def get_iplists(target):
        try:
            # 构建请求
            req = request.Request(target)
            req.add_header("User-Agent","Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36")
            response = request.urlopen(req)
            html = response.read()
    
            # 用bs4解析
            soup = BeautifulSoup(html, 'lxml')
    
            # 提取我们需要的信息
            all_trs = soup.table.find_all('tr')[1:]
    
            # 这里只提取了IP:PORT
            for tr in all_trs:
                td = tr.find_all('td')
                ip_lists.append(td[1].string + ':' + td[2].string)
    
        except error.URLError as e:
            if hasattr(e,"code"):
                print(e.code)
            if hasattr(e,"reason"):
                print(e.reason)
    
        # 尝试返回下一页的地址
        next_page = soup.find_all('a', class_='next_page')
        if next_page:
            next_url = next_page[0].get('href')
            return (domain + next_url)
        else:
            return None
    
    def writeIP():
        if ip_lists:
            file = open("ip.txt", "w+", encoding="UTF-8")
    
            for ip in ip_lists:
                file.write(ip.strip() + '\n')
    
            file.close()
    
        else:
            print ("ip list is empty.")
    
    if __name__ == "__main__":
        num = 500
        page = num // 100
    
        # 这里获取500个
        next_url = "http://www.xicidaili.com/nn"
        while (next_url is not None) and (page > 0):
            next_url = get_iplists(next_url)
            page = page - 1
            time.sleep(1)
    
        # 写入文件
        writeIP()
    

    其他几个代理的网页结构都差不多(核心都是一张表格),就不多说了,这段代码最终会生成一个ip.txt文件,里面包含500个代理IP地址。

    利用API

    这个说白了就是一个带特定参数的请求,比如我用的大象代理,获取500个IP的请求地址是:http://tvp.daxiangdaili.com/ip/?tid=order_number&num=500&delay=5&category=2,保存为IP_API.txt文件。

    验证IP的有效性

    我们需要判断获取到的IP是不是有效,思路大概有两个:

    1. 访问一个网站,检查其返回的状态码是不是200。
    2. 访问一个可以返回IP的网站,确保返回的值和你所用的代理IP的值是一样的。

    私以为能满足第一条的就能算有效的IP了,毕竟IP数量众多,同时在之后使用这些IP时同样会有部分的IP失效,这些都属于可以接受的“损失”。但是编写代码的时候还有几点需要考虑:

    1. 如果检测的IP不能用,一般来说要等10s程序才能返回404或者400,这个过程太长了。
    2. 并发检测

    之前用urllib库写了一个代理代码,后来用了下requests库,觉得写代理的话后者比前者简单很多,这里提供一个用requests库的方法。注意这个库是第三方库,要安装:

    from requests import get
    from bs4 import BeautifulSoup
    
    def getIP():
        return "222.163.28.35:8908"
    
    try:
        url = "https://www.baidu.com/s?wd=ip"
        ip = getIP()
        proxies = {"http": "http://"+ip, "https": "https://"+ip}
        url_content = get(url,
                            proxies = proxies,
                            timeout = 20,
                            headers = {
                                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                                'Accept-Encoding': 'gzip, deflate, compress',
                                'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,ru;q=0.4',
                                'Cache-Control': 'max-age=0',
                                'Connection': 'keep-alive',
                                'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36'
                            })
        # 只校验status code
        if int(url_content.status_code) == int(200):
            print ('True')
        
        # 校验返回的IP
        soup = BeautifulSoup(url_content.text, 'html.parser')
        myip = soup.find_all('div', class_="c-row")[0].find_all('div')[3].get_text()[5:]
        
        if ip.split(':')[0] == myip:
            print ('True')
     
    except BaseException as e:
        print (e)
    

    程序运行下来感觉检验IP其实挺耗时间的。如果你使用第一种方法抓取免费IP的,那就肯定要检验,因为这样抓取回来的IP大多数是不能用的。如果我采用的是第二种方法呢?似乎代理商能够保证IP的有效性。那IPPOOL到底应该怎么做呢?

    1. 它必须有能力收集足够多的IP,比如说用户要初始5000个IP,先从ip.txt里读取(文件、数据库都无所谓),如果不满5000个,再去抓满5000个,用户用完以后将剩下的IP写回存储介质里。
    2. 提供getIP()接口时,必须要能满足用户的要求,用户要几个就提供几个。
    3. 在给出IP的时候,POOL概率性的选择是不是要检验这个IP的有效性,这个值是动态的,怎么确定?
      • 它可以和请求的数量挂钩,请求数越多概率值越低
      • 然后我们要确定一个初始值,就是如果不检验,有效IP的概率是多少?这个可以通过抽样计算,比如说抽10组每组20个IP,检验其有效性计算有效IP的比例;也可以从网站上获取,有的代理网站能保证提供给你的IP有效率,比如说99%。个人觉得前者靠谱点,毕竟IP放久了是会失效的。
      • 计算的话,x + (N - x) * p1 > N*p2, 其中x是有效的IP的数量,N是总IP数,p1是上一条里所提的概率值,p2是你的POOL想要提供的有效率,最后x/N就是需要检查的概率值。
      • 假设N=5000, p1 = 0.98, p2 = 0.99,最后x/N = 0.5 也就说每提供2个IP需要检验一次。

    至于使用POOL的程序(也就是爬虫程序):

    • 如果URL数量大,要考虑多线程
    • 考虑多久换IP,Header的信息要随机
    • 其他爬取策略

    这些都只是我的想法,虽然实现起来也很简单,但是不是真有效果,我现在也不清楚,我手头也没有资源让我这样去测试,只能说有机会我再深入一下。其实说到底还是看IP池的质量,如果p1的值是0.99, x就等于0,也就是不需要检验。

    参考

    1. 代理服务器的分类
    2. 代理IP的高匿、匿名、透明有何区别
    3. 正向代理、反向代理、透明代理以及CDN的区别
    4. Wireshark抓包分析/TCP/Http/Https及代理IP的识别
    5. RFC-Forwarded HTTP Extension
    6. X-Forwarded-For
    7. Proxy、SSH 和VPN 的区别
    8. SSH隧道翻墙的原理和实现
    9. Github - IPProxy

    相关文章

      网友评论

        本文标题:爬虫和IP代理

        本文链接:https://www.haomeiwen.com/subject/dhycmftx.html