可以看到,这里我们执行了停止和开始拨号的命令之后,通过 ifconfig 命令获取的网卡信息的 IP 地址就变化了,所以我们成功实现了 IP 地址的切换。
好,那如果我们要想将这台云主机设置为可以实时变化 IP 的代理服务器的话,主要就有这几件事情:
- 在云主机上运行代理服务软件,使之可以提供 HTTP 代理服务
- 实现云主机定时拨号更换 IP
- 实时获取云主机的代理 IP 和端口信息
接下来我们就来完成这几部分内容吧。
4. 设置代理服务器当前我们使用的云主机使用的是 Linux 的 CentOS 系统,目前它是无法作为一个 HTTP 代理服务器来使用的,因为该云主机上面目前并没有运行相关的代理软件。要想让该云主机提供 HTTP 代理服务,我们需要在其上面安装并运行相关的代理服务。
那什么软件能提供这种代理服务呢?目前业界比较流行的有 Squid 和 TinyProxy,配置完成之后它们会在特定端口上运行一个 HTTP 代理。知道了该云主机当前的 IP 之后,我们就能使用该云主机上 Squid 或 TinyProxy 提供的 HTTP 代理了。
这里我们以 Squid 为例来进行一下配置。
首先我们安装一下 Squid,在 CentOS 的安装命令如下:
sudo yum -y update
yum -y install squid
运行完之后,我们便可以成功安装好 Squid 了。
如果要想启动 Squid,可以运行如下命令:
systemctl start squid
如果想配置开机自动启动,可以运行如下命令:
systemctl enable squid
Squid 成功运行之后,我们可以使用如下命令查看当前 Squid 的运行状态:
systemctl status squid
如图所示,我们可以看到 Squid 就成功运行了:
默认情况下,Squid 会运行在 3128 端口,也就是相当于在云主机的 127.0.0.1:3128 上启动了代理服务,接下来我们可以来测试下 Squid 的代理效果,在该台云主机上运行 curl 命令请求 https://httpbin.org,并配置使用云主机的代理:
curl -x http://127.0.0.1:3128 https://httpbin.org/get
这里 curl 的 -x 参数代表设置 HTTP 代理,由于这是在云主机上运行的,所以代理直接设置为了 http://127.0.0.1:3128。
运行完毕之后,我们再运行下 ifconfig 获取下当前云主机的 IP,运行结果如图所示:
可以看到返回结果的 origin 字段的 IP 就和 ifconfig 获取的 IP 地址是一致的。
接下来,我们在自己本机上(非云主机)运行如下命令测试下代理的连通情况,这里 IP 就需要更换为云主机本身的 IP 了,刚才可以看到云主机当前拨号的 IP 是 106.45.104.166,所以需要运行如下命令:
curl -x http://106.45.104.166:3128 https://httpbin.org/get
然而发现并没有对应的输出结果,代理连接失败。
其实原因在于默认情况下 Squid 并没有开启允许外网访问,我们可以进行 Squid 的相关配置,如更改当前代理运行端口、允许连接的 IP,配置高匿代理等等,这些都需要修改 /etc/squid/squid.conf 文件。
要允许公网访问,最简单的方案就是将 /etc/squid/squid.conf 中的该行:
http_access deny all
修改为:
http_access allow all
意思是允许来自所有 IP 的请求连接。
另外还需要在配置文件的开头 acl 配置的部分添加该行内容:
acl all src 0.0.0.0/0
另外我们还想将 Squid 配置成高度匿名代理,这样目标网站就无从通过一些参数如 X-Forwarded-For 来得知爬虫机本身的 IP 了,所以在配置文件中再添加如下配置:
request_header_access Via deny all
request_header_access X-Forwarded-For deny all
另外有的云主机厂商可能默认封禁了 Squid 的 3128 端口,建议更换一个端口,比如 3328,修改改行:
http_port 3128
修改为:
http_port 3328
修改完配置之后保存配置文件,然后重新启动 Squid 即可:
systemctl restart squid
这时候在本机上(非云主机)重新运行刚才的 curl 命令,同时更改下端口:
curl -x http://106.45.104.166:3328 https://httpbin.org/get
即可得到返回结果:
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.64.1",
"X-Amzn-Trace-Id": "Root=1-60ea8fc0-0701b1494e4680b95889cdb1"
},
"origin": "106.45.104.166",
"url": "https://httpbin.org/get"
}
这时候我们就可以在本机上直接使用云主机的代理了!
5. 动态获取 IP现在我们已经可以执行命令让主机动态切换 IP 了,同时也在主机上搭建好代理服务器了,接下来我们只需要知道拨号后的 IP 就可以使用代理了。
那怎么动态获取拨号主机的 IP 呢?又怎么来维护这些代理呢?怎么保证获取到的代理一定是可用的呢?这时候我们可能想到一些问题:
- 如果我们只有一台拨号云主机并设置了定时拨号的话,那么在拨号的几秒时间内,该云主机提供的代理服务是不可用的。
- 如果我们不用定时拨号的方法,而想要在爬虫端控制拨号云主机的拨号操作的话,爬虫端还需要单独的逻辑来处理拨号和重连的问题,这会带来额外的开销。
综合考虑下来,一个比较好的解决方案是:
- 为了不增加爬虫端的逻辑开销,爬虫端应该无需关心拨号云主机的拨号操作,我们只需要保证爬虫通过某个接口获取到的代理是可用的就行了,拨号云主机的代理的维护逻辑和爬虫是毫不相关的。
- 为了解决一台拨号云主机在拨号时代理不可用的问题,我们需要有多台云主机同时提供代理服务,我们可以将不同云主机的拨号时段错开,当一台云主机正在拨号时,我们可以用其他云主机顶替。
- 为了更加方便地维护和使用代理,我们可以像前文介绍的代理池一样把这些云主机的代理统一维护起来,所有拨号云主机的代理统一存储到一个公共的 Redis 数据库中,可以使用 Redis 的 Hash 存储方式,存好每台云主机和对应代理的映射关系。拨号云主机拨号前将自己对应的代理内容清空,拨号成功之后再将代理更新,这样 Redis 数据库中的代理就一定是实时可用的代理了。
利用这种思路,我们要做的其实就有如下几点:
- 配置一个可以公网访问的 Redis 数据库,每台云主机可以将自己的代理存储到对应的 Redis 数据库中,由该 Redis 数据库维护这些代理。
- 申请多台拨号云主机并按照上文所述配置好 Squid 代理服务,每台云主机设置定时拨号来更换 IP。
- 每台云主机在拨号前删除 Redis 数据库中原来的代理,拨号成功之后测试一下代理的可用性,将最新的代理更新到 Redis 数据库中即可。
OK,接下来我们就来操作一下吧。
由于云主机要进行 Redis 数据库的操作,所以我们可以使用 Python 来实现,所以先在云主机上装下 Python:
yum -y install python3
关于自动拨号、连接 Redis 数据库、获取本机代理、设置 Redis 数据库的操作,我已经写好了一个 Python 的包并发布到 PyPi 了,我们可以直接使用这个包来完成如上的功能,这个包叫做 adslproxy,可以在云主机上使用 pip3 来安装:
pip3 install adslproxy
安装完毕之后,我们可以使用 export 命令设置下环境变量:
export REDIS_HOST=<Redis数据库的地址>
export REDIS_PORT=<Redis数据库的端口>
export REDIS_PASSWORD=<Redis数据库的密码>
export PROXY_PORT=<拨号云主机配置的代理端口>
export DIAL_BASH=<拨号脚本>
export DIAL_IFNAME=<网卡名称>
export CLIENT_NAME=<云主机的唯一标识>
export DIAL_CYCLE=<拨号间隔>
这里 REDIS_HOST、REDIS_PORT、REDIS_PASSWORD 就是远程 Redis 的连接信息,就不再赘述了。PROXY_PORT 就是云主机上代理服务的端口,我们已经设置为了 3328。DIAL_BASH 就是拨号的命令,即 pppoe-stop;pppoe-start,当然该脚本的内容不同的云主机厂商可能不同,以实际为准。DIAL_IFNAME 即拨号云主机上的网卡名称,程序可以通过获取该网卡的信息来获取当前拨号主机的 IP 地址,通过上述操作可以发现,网卡名称叫做 ppp0,当然这个名称也是以实际为准。CLIENT_NAME 就是云主机的唯一标识,用来在 Redis 中存储主机和代理的映射,因为我们有多台云主机,所以不同云主机的名称应该设置为不同的字符串,比如 adsl1、adsl2 等等。
这里我们设置如图所示:
设置好环境变量之后,我们就可以运行 adslproxy 命令来进行拨号了,命令如下:
adslproxy send
运行结果如下:
2021-07-11 15:30:03.062 | INFO | adslproxy.sender.sender:loop:90 - Starting dial...
2021-07-11 15:30:03.063 | INFO | adslproxy.sender.sender:run:99 - Dial started, remove proxy
2021-07-11 15:30:03.063 | INFO | adslproxy.sender.sender:remove_proxy:62 - Removing adsl1...
2021-07-11 15:30:04.065 | INFO | adslproxy.sender.sender:remove_proxy:69 - Removed adsl1 successfully
2021-07-11 15:30:05.373 | INFO | adslproxy.sender.sender:run:111 - Get new IP 106.45.105.33
2021-07-11 15:30:15.552 | INFO | adslproxy.sender.sender:run:120 - Valid proxy 106.45.105.33:3328
2021-07-11 15:30:16.501 | INFO | adslproxy.sender.sender:set_proxy:82 - Successfully set proxy 106.45.105.33:3328
2021-07-11 15:33:36.678 | INFO | adslproxy.sender.sender:loop:90 - Starting dial...
2021-07-11 15:33:36.679 | INFO | adslproxy.sender.sender:run:99 - Dial started, remove proxy
2021-07-11 15:33:36.680 | INFO | adslproxy.sender.sender:remove_proxy:62 - Removing adsl1...
2021-07-11 15:33:37.214 | INFO | adslproxy.sender.sender:remove_proxy:69 - Removed adsl1 successfully
2021-07-11 15:33:38.617 | INFO | adslproxy.sender.sender:run:111 - Get new IP 106.45.105.219
2021-07-11 15:33:48.750 | INFO | adslproxy.sender.sender:run:120 - Valid proxy 106.45.105.219:3328
...
这里我们就可以看到,因为云主机在拨号之后当前代理就会失效了,所以在拨号之前程序先尝试从 Redis 中删除当前云主机的代理。接下来就开始执行拨号操作,拨号成功之后验证一下代理是可用的,然后再将该代理存储到 Redis 数据库中。循环往复运行,我们就达到了定时更换 IP 的效果,同时 Redis 数据库中也是实时可用的代理。
最后按照同样的配置,我们可以购买多台拨号云主机并进行如上同样的设置,这样就有多个稳定的定时更新的代理可用了,Redis 中会实时更新各台云主机的代理,如图所示。