Code算法收集

爬虫和IP代理

2018-07-21  本文已影响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的服务器交流。这里网关就是一种正向代理。同时这种代理除了转发请求响应还可以有其他功能:

客户端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池是很重要的。那么要怎么做呢?

自己抓取

我随便一搜发现网上有很多提供免费代理的地方,甚至一些代理商为了避免爬虫还提供了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的程序(也就是爬虫程序):

这些都只是我的想法,虽然实现起来也很简单,但是不是真有效果,我现在也不清楚,我手头也没有资源让我这样去测试,只能说有机会我再深入一下。其实说到底还是看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
上一篇下一篇

猜你喜欢

热点阅读