【爬虫】解决封IP问题:1.利用ADSL服务器

ADSL (Asymmetric Digital Subscriber Line ,非对称数字用户环路)是一种新的数据传输方式。它因为上行和下行带宽不对称,因此称为非对称数字用户线环路。它采用频分复用技术把普通的电话线分成了电话、上行和下行三个相对独立的信道,从而避免了相互之间的干扰。

也就是你每拨一次号就换一次IP。

我买的是XX的VPS服务器,这种拨号服务器一搜一堆,但一定要注意可靠。不推荐某家,都差不多,大家自己筛选就好。大多平均一个月100左右。

一、界面

注册购买后进入控制面板,我选择的是Centos 7.1的系统,以下默认为Centos的操作。

根据他给你的账号ssh连接后,ls可以看到一个ppp.sh的文件,执行后,输入你的ADSL账号和密码,这些都在你的云服务器基本信息里。

当你看到一堆Success后,就是成功了。

adls-start

既可拨号成功,如果要断掉,则

adls-stop

所以断线重播的命令就是二者组合起来,先执行adsl-stop再执行adsl-start,每拨一次号,ifocnfig命令观察一下主机的IP,发现主机的IP一直是在变化的,网卡名称叫做ppp0。

或者可以用pppoe,输入pppoe-setup,请按照下图操作,除说明处,其余都可直接敲回车键。

二、设置代理服务器

接下来我们就亲自试验下怎样搭建HTTP代理服务器。

在Linux下搭建HTTP代理服务器,推荐TinyProxy和Squid,配置都非常简单,我们分别配置一下。

Squid

首先利用yum安装squid

yum -y install squid

设置开机启动

chkconfig --level 35 squid on

修改配置文件

vi /etc/squid/squid.conf

修改如下几个部分:

http_access allow !Safe_ports #deny改成allow http_access allow CONNECT !SSL_ports #deny改成allow http_access allow all #deny改成allow

其他的不需要过多配置。

启动squid

sudo service squid start

如此一来配置就完成了。

代理使用的端口是3128

TinyProxy

命令行执行yum安装指令:

yum install -y epel-release yum update -y yum install -y tinyproxy

运行完成之后就可以完成tinyproxy的安装了。

配置TinyProxy

安装完成之后还需要配置一下TinyProxy才可以用作代理服务器,需要编辑配置文件,它一般的路径是/etc/tinyproxy/tinyproxy.conf。

Allow 127.0.0.1

这是被允许连接的主机的IP,如果想任何主机都可以连接,那就直接将它注释即可,所以在这里我们选择直接注释,也就是任何主机都可以使用这台主机作为代理服务器了。设置完成之后重启TinyProxy即可。

service tinyproxy start

验证TinyProxy

好了,这样我们就成功搭建好代理服务器了,首先ifconfig查看下当前主机的IP,比如当前我的主机拨号IP为112.84.118.216,在其他的主机运行测试一下。

比如用curl命令设置代理请求一下httpbin,检测下代理是否生效。

curl -x 112.84.118.216:8888 httpbin.org/get

动态获取IP方法1--flask:

这个方法还需要另一台固定IP的主机或者某个云服务器。在这里我用了另一台有固定IP的蚂蚁云主机。

那么现在的思路就是,拨号VPS定时拨号换IP,然后请求阿里云主机,蚂蚁云主机获取VPS的IP地址即可。

拨号VPS做的事情:

定时拨号,定时请求服务器。

使用bash脚本,然后crontab定时执行。(实际启动的时候有问题,改成了python脚本)

远程服务器:

接收请求,获取remote_addr,保存起来。使用Flask搭建服务器,接收请求。

代码:AutoProxy

功能

由于DDNS生效时间过长,对于爬虫等一些时间要求比较紧迫的项目就不太适用,为此本项目根据DDNS基本原理来实现实时获取ADSL拨号主机IP。

server文件夹是服务器端运行,利用Python的Flask搭建服务器,然后接收ADSL拨号客户机的请求,得到remote_addr,获取客户机拨号后的IP。

项目结构

server

config.py 配置文件。ip 客户端请求后获取的客户端IP,文本保存。main.py Flask主程序,提供两个接口,一个是接收客户端请求,然后将IP保存,另外一个是获取当前保存的IP。

client

crontab 定时任务命令示例。pppoe.sh 拨号脚本,主要是实现重新拨号的几个命令。request.sh 请求服务器的脚本,主要是实现拨号后请求服务器的操作。request.conf 配置文件。

使用

服务器

服务器提供两个功能,record方法是客户机定时请求,然后获取客户机IP并保存。proxy方法是供我们自己用,返回保存的客户机IP,提取代理。

修改配置

修改config.py文件

KEY 是客户端请求服务器时的凭证,在client的request.conf也有相同的配置,二者保持一致即可。NEED_AUTH 在获取当前保存的IP(即代理的IP)的时候,为防止自己的主机代理被滥用,在获取IP的时候,需要加权限验证。AUTH_USER和AUTH_PASSWORD分别是认证用户名密码。PORT默认端口,返回保存的结果中会自动添加这个端口,组成一个IP:PORT的代理形式。

运行

cd server nohup python main.py

ADSL客户机 修改配置

修改reqeust.conf文件

KEY 是客户端请求服务器时的凭证,在server的config.py也有相同的配置,二者保持一致即可。SERVER是服务器项目运行后的地址,一般为;服务器IP>:<服务器端口>/record。如http://120.27.14.24:5000/record。

运行

设置定时任务

crontab -e

输入crontab的实例命令

1*/5 * * * * /var/py/AutoProxy/client/request.sh /var/py/AutoProxy/client/request.conf >> /var/py/AutoProxy/client/request.log

注意修改路径,你的项目在哪里,都统一修改成自己项目的路径。

最前面的*/5是5分钟执行一次。

好了,保存之后,定时任务就会开启。

验证结果

这样一来,访问服务器地址,就可以得到ADSL拨号客户机的IP了。

import requests url = :5000 proxy = requests.get(url, auth=(admin, 123)).text print(proxy)

实例结果:

1116.208.97.22:8888

代理设置

urllib2

import urllib2 proxy_handler = urllib2.ProxyHandler({"http": http:// + proxy}) opener = urllib2.build_opener(proxy_handler) urllib2.install_opener(opener) response = urllib2.urlopen(http://httpbin.org/get) print response.read()

requests

import requests proxies = { http: http:// + proxy, } r = requests.get(http://httpbin.org/get, proxies=proxies) print(r.text)

以上便秒级解决了动态IP解析,自己实现了一遍DDNS,以后就可以直接请求你的主机获取一个最新可用的代理IP了。

动态获取IP方法2:

要实现这个需要两台主机,一台主机就是这台动态拨号VPS主机,另一台是具有固定公网IP的主机。动态VPS主机拨号成功之后就请求远程的固定主机,远程主机获取动态VPS主机的IP,就可以得到这个代理,将代理保存下来,这样拨号主机每拨号一次,远程主机就会及时得到拨号主机的IP,如果有多台拨号VPS,也统一发送到远程主机,这样我们只需要从远程主机取下代理就好了,保准是实时可用,稳定高效的。

整体思路大体是这样子,当然为了更完善一下,我们要做到如下功能:

远程主机:

监听主机请求,获取动态VPS主机IP将VPS主机IP记录下来存入数据库,支持多个客户端检测当前接收到的IP可用情况,如果不可用则删除提供API接口,通过API接口可获取当前可用代理IP

拨号VPS:

定时执行拨号脚本换IP换IP后立即请求远程主机拨号后检测是否拨号成功,如果失败立即重新拨号

远程主机实现

说了这么多,那么我们就梳理一下具体的实现吧,整个项目我们用Python3实现。

数据库

远程主机作为一台服务器,动态拨号VPS会定时请求远程主机,远程主机接收到请求后将IP记录下来存入数据库。

因为IP是一直在变化的,IP更新了之后,原来的IP就不能用了,所以对于一个主机来说我们可能需要多次更新一条数据。另外我们不能仅限于维护一台拨号VPS主机,当然是需要支持多台维护的。在这里我们直接选用Key-Value形式的非关系型数据库存储更加方便,所以在此选用Redis数据库。

既然是Key-Value,Key是什么?Value是什么?首先我们能确定Value就是代理的值,比如112.84.119.67:8888,那么Key是什么?我们知道,这个IP是针对一台动态拨号VPS的,而且这个值会不断地变,所以我们需要有一个不变量Key来唯一标识这台主机,所以在这里我们可以把Key当做主机名称。名称怎么来?自己取就好了,只要每台主机的名字不重复,我们就可以区分出是哪台主机了,这个名字可以在拨号主机那边指定,然后传给远程主机就好了。

所以,在这里数据库我们选用Redis,Key就是拨号主机的名称,可以自己指定,Value就是代理的值。

所以可以写一个操作Redis数据库的类,参考如下:

class RedisClient(object): def __init__(self, host=REDIS_HOST, port=REDIS_PORT): self.db = redis.Redis(host=host, port=port, password=REDIS_PASSWORD) self.proxy_key = PROXY_KEY def key(self, name): return {key}:{name}.format(key=self.proxy_key, name=name) def set(self, name, proxy): return self.db.set(self.key(name), proxy) def get(self, name): return self.db.get(self.key(name)).decode(utf-8)

首先初始化Redis连接,我们可以将Key设计成adsl:vm1这种形式,冒号前面是总的key,冒号后面是主机名称name,这样显得结构更加清晰。

然后指定set()和get()方法,用来存储代理和获取代理。

请求处理

拨号主机会一直向远程主机发送请求,远程主机当然可以获取拨号主机的IP,但是代理端口是无法获得的,我们在拨号主机上设置了TinyProxy或者Squid,但是服务器不知道是在哪个端口开的,所以端口也是需要客户端传给远程主机的。远程主机接收到请求后,将解析得到的IP和端口合并就可以作为完整的代理保存了。

所以现在我们知道拨号主机需要传送给远程主机的信息已经有两个了,一是拨号主机本身的名称,二是代理的端口。

通信秘钥

为了保证远程主机不被恶意的请求干扰,可以设置一个传输秘钥,最简单的方式可以二者共同规定一个秘钥字符串,拨号主机在传送这个字符串,远程主机匹配一下,如果能正确匹配,那就进行下一步的处理,如果不能匹配,那么可能是恶意请求,就忽略这个请求。

当然肯定有更好的加密传输方式,但为了方便起见可以用如上来做。

所以客户机还需要传送一个数据,那就是通信秘钥,一共需要传送三个数据。

所以我们需要架设一个服务器,一直监听客户端的请求,在这里我们用tornado实现。

tornado的安装也非常简单,利用pip安装即可:

pip3 install tornado

定义一个处理拨号主机请求的方法,在这里我们使用post请求,参考如下。

def post(self): token = self.get_body_argument(token, default=None, strip=False) port = self.get_body_argument(port, default=None, strip=False) name = self.get_body_argument(name, default=None, strip=False) if token == TOKEN and port: ip = self.request.remote_ip proxy = ip + : + port print(Receive proxy, proxy) self.redis.set(name, proxy) self.test_proxies() elif token != TOKEN: self.write(Wrong Token) elif not port: self.write(No Client Port)

远程主机获取请求的token,也就是上面我们所说的通信密钥,保证安全。port是拨号机的代理端口,name是拨号主机的名称。然后我们再获取请求的remote_ip,也就是拨号主机的IP。然后将IP和端口拼合就可以得到拨号主机的完整代理信息了,将其存入数据库即可。

代理检测

在远程主机端我们需要做一下代理检测,如果某个代理不可用了,会及时将其去除,以免出现获取到代理后不可用的情况。

注意:在这里在拨号主机端验证是不够的,因为可能突然遇到某个拨号主机宕机的情况,这样拨号主机就不会再向远程主机发送请求,而最后一次得到的代理还会存在于数据库中,所以在远程主机端统一验证比较科学。

验证方式可以定时检测,也可以每收到一次请求检测一次,用获取到的代理来请求某个网站,检测一下是否能访问即可。如果不能,将其从数据库中删除。

API

远程主机已经将拨号主机的IP和端口保存下来了,那也就是说,所有的可用的代理已经在远程主机保存了,我们需要提供一个接口来将代理获取下来。

比如我们可以提供这么几个方法,获取所有代理,获取最新代理,获取随机代理等等。

def all(self): keys = self.keys() proxies = [{name: key, proxy: self.get(key)} for key in keys] return proxies def random(self): items = self.all() return random.choice(items).get(proxy) def list(self): keys = self.keys() proxies = [self.get(key) for key in keys] return proxies def first(self): return self.get(self.keys()[0])

然后用tornado搭建API服务,如果可以的话还可以绑定一个域名,更加便捷,举例如下:

获取随机代理:

获取最新代理:

获取所有代理:

请求接口获取可用代理即可,比如获取一个随机代理:

import requests def get_random_proxy(): try: # 远程主机的服务地址 url = :8000/random return requests.get(url).text except requests.exceptions.ConnectionError: return None

这样我们拿到的IP都是稳定可用的,而且过段时间重新请求取到的IP就会变化,是一直动态变化的高可用代理。

拨号VPS实现

定时拨号

拨号VPS需要每隔一段时间就拨号一次,我们可以直接执行命令行来拨号,那在Python里我们只需要调用一下这个拨号命令就好了。利用subprocess模块调用脚本即可,在这里定义一个变量ADSL_BASH为adsl-stop;adsl-start,这就是拨号的脚本。

import subprocess (status, output) = subprocess.getstatusoutput(ADSL_BASH)

通过getstatusoutput方法可以获取脚本的执行状态和输出结果,如果status为0,则证明拨号成功,然后检测一下拨号接口是否获取了IP地址。

执行ifconfig命令可以获取当前的IP,我这台主机接口名称叫做ppp0,当然网卡名称可以自己指定,所以将ppp0接口的IP提取出来即可。

def get_ip(self, ifname=ADSL_IFNAME): (status, output) = subprocess.getstatusoutput(ifconfig) if status == 0: pattern = re.compile(ifname + .*?inet.*?(\d+\.\d+\.\d+\.\d+).*?netmask, re.S) result = re.search(pattern, output) if result: ip = result.group(1) return ip

如果方法正常返回IP,则证明IP存在,拨号成功,接下来向远程主机发送请求即可,然后sleep一段时间重新再次拨号。如果方法返回的值为空,那证明IP不存在,我们需要重新拨号。

请求远程主机

发送的时候需要携带这么几个信息,一个是通信秘钥,一个是代理端口,另一个是主机的标识符,用requests发送即可。

requests.post(SERVER_URL, data={token: TOKEN, port: PROXY_PORT, name: CLIENT_NAME})

所以整体的思路实现可以写成这样子:

def adsl(self): while True: print(ADSL Start, Please wait) (status, output) = subprocess.getstatusoutput(ADSL_BASH) if status == 0: print(ADSL Successfully) ip = self.get_ip() if ip: print(New IP, ip) try: requests.post(SERVER_URL, data={token: TOKEN, port: PROXY_PORT, name: CLIENT_NAME}) print(Successfully Sent to Server, SERVER_URL) except ConnectionError: print(Failed to Connect Server, SERVER_URL) time.sleep(ADSL_CYCLE) else: print(Get IP Failed) else: print(ADSL Failed, Please Check) time.sleep(1)

这样我们就可以做到定时拨号并向远程主机发送请求了。

代码

Talk is cheap, show me the code! 在这里提供一份完整代码实现,其中client模块是在动态VPS主机运行,server模块在远程主机运行,具体的操作使用可以参考README。

ADSLProxyPool