每一个想学习的念头,都有可能是未来的你在向自己求救。

1.写在前面的

本次文章内容需要准备:一台动态 ADSL 拨号服务器,一台普通服务器,一个公网 redis 环境(可以直接部署在前面普通服务器上)。

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

它有个独有的特点,每拨一次号,就获取一个新的IP。也就是它的IP是不固定的,不过既然是拨号上网嘛,速度也是有保障的,用它搭建一个代理,那既能保证可用,又能自由控制拨号切换(抄自知乎)。

借助 ADSL 拨号服务器特点,我们可以获取一个公网 IP,然后借助第三方工具/软件在服务器上搭建 HTTP 代理就行了。关于搭建 HTTP 代理有很多推荐,比如说 Squid。但是 Squid 这些需要配置信息,对于个人来讲有些麻烦,所以在这篇文章里,我们尝试用 gost 来搭建 HTTP 代理(https://github.com/ginuerzh/gost)。

2.拨号服务器基操

在搭建 HTTP 代理之前,还是先说一下拨号服务器常用操作:

  1. pppoe-start # 拨号
  2. pppoe-stop # 断开拨号
  3. pppoe-status # 拨号连接状态

一般购买拨号服务器之后,使用 ssh 连接上使用pppoe-start命令先进行拨号(不同平台命令可能不一样),成功后可以尝试使用curl ip.sb查看当前获取的新 IP:

  1. [root@localhost ~]# curl ip.sb # 检查当前ip
  2. 171.41.86.191
  3. [root@localhost ~]# pppoe-stop # 停止当前拨号
  4. [root@localhost ~]# pppoe-start # 开始拨号
  5. [root@localhost ~]# curl ip.sb # 检查当前ip
  6. 171.41.85.191
  7. [root@localhost ~]#

3.搭建 HTTP 代理

上面提到使用 gost 搭建 HTTP 代理,现从 GITHUB 上下载对应的平台 / 版本的 gost。我这里下载的是:gost-linux-amd64-2.11.1,你们可以根据自己需要下载合适的版本。下载之后解压之后得到一个可执行文件:

  1. [root@localhost ~]# ll
  2. total 31508
  3. -rw-------. 1 root root 1319 Sep 29 2019 anaconda-ks.cfg
  4. -rw-r--r-- 1 root root 8784858 Dec 14 11:50 file.zip
  5. -rw-r--r-- 1 root root 16830464 May 23 2020 gost-linux-amd64 # 就这个这个
  6. -rw-r--r-- 1 root root 6643001 Dec 8 22:46 proxy_client # 等会用
  7. [root@localhost ~]#

确定当前拨号成功后直接执行:

  1. [root@localhost ~]# mv gost-linux-amd64 gost # 重命名(可有可无)
  2. [root@localhost ~]# chmod 777 gost # 修改权限,必须,不然无法执行
  3. [root@localhost ~]# ./gost -L=admin:123456@:8123 # 设置代理认证信息
  4. 2020/12/14 13:52:44 route.go:650: auto://:8123 on [::]:8123

这里可以使用 systemctl 给 gost 做个进程守护,在 /etc/systemd/system/ 路径新建文件 gost.service 内容:

  1. [Unit]
  2. Description=Gost Proxy
  3. After=network.target
  4. Wants=network.target
  5. [Service]
  6. Type=simple
  7. ExecStart=/root/gost -L=http://admin:123456@:8123 -L=socks5://admin:123456@:8125 # socks5 根据自己需要设置
  8. Restart=always
  9. [Install]
  10. WantedBy=multi-user.target

之后可以直接使用 systemctl 命令控制 gost :

  1. systemctl daemon-reload
  2. systemctl enable gost
  3. systemctl restart gost
  4. systemctl status gost

此时测试当前 HTTP 代理是否可用:

  1. ┌──(h1code2LAPTOP-IMECGKD2)-[/mnt/c/Users/h1code2]
  2. └─$ curl -x http://admin:123456@171.41.85.191:8123 http://httpbin.org/ip # 代理测试
  3. {
  4. "origin": "171.41.85.191"
  5. }

现在代理测试通过,接着我们要做的就是需要实时将服务器最新拨号成功后的 IP 通知到爬虫。在这里我们可以把拨号服务器理解成客户端,正式场景肯定不会只有一台(如图:),所以我们还需要使用一个服务器端来记录和分配最近可用的代理 IP。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QA3tFTft-1607998556105)(https://imgkr2.cn-bj.ufileos.com/efa71921-374d-4478-9021-b0ef775f62ec.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=OzSIOdBMM8HLNcrn20kAB%2BbtCyw%3D&Expires=1608013699)]

4.客户端 / 服务端实现

为了简化客户端部署,这里使用 go 语言编写客户端代码,并编译成二进制可执行文件,小伙伴们只需要按照自己的需要增加环境变量即可。这里简单讲一下逻辑,客户端会根据配置的接口信息主动连接服务端,在这个过程中会自动上报当前拨号后的 IP 和端口等信息,服务端响应请求后记录 IP 并返回该客户端是否需要切换 IP 的状态。在上面搭建 HTTP 代理的内容里可以看到与 gost 同级下有个文件 proxy_client,这个就是我们的客户端可执行文件。该程序会读取 7 个环境变量分别是:

  1. api_host # 服务端接口信息host
  2. api_token # 服务端接口验证token
  3. username # 代理验证用户名
  4. password # 代理验证密码
  5. http_port # http/https代理端口
  6. socks5_port # socks5代理端口

根据需要设置环境变量:

  1. export api_host=api.h1code2.cn/api/proxy
  2. export api_token=xfsdfhkjf23rfdwef
  3. export username=admin
  4. export password=123456
  5. export http_port=8123
  6. export socks5_port=8125

之后执行 export 大概是这个样子:

  1. [root@localhost ~]# export
  2. ···
  3. declare -x api_host="api.h1code2.cn/api/proxy"
  4. declare -x api_token="xfsdfhkjf23rfdwef"
  5. declare -x http_port="8123"
  6. declare -x password="123456"
  7. declare -x socks5_port="8125"
  8. declare -x username="admin"

在运行 proxy_client 文件之前我们还需要,简单实现一下服务端的逻辑,这里使用 redis 做了定时缓存等操作,具体逻辑可以自己实现,我这里随便敲的代码渣,作参考就好。

  1. import ujson as json
  2. from pydantic import BaseModel
  3. from fastapi import APIRouter, Request
  4. from db.session_v2 import redis
  5. router = APIRouter()
  6. hash_key = "proxy-node:hash"
  7. redis_client = redis()
  8. class Proxy(BaseModel):
  9. http_port: int
  10. socks5_port: int
  11. username: str
  12. password: str
  13. @router.get("/all_proxy")
  14. def all_proxy():
  15. """
  16. 获取当前所有代理信息
  17. :return:
  18. """
  19. data = {}
  20. nodes = redis_client.hgetall(hash_key)
  21. if not nodes:
  22. return {"message": "success", "code": 200, "data": data}
  23. for key, value in nodes.items():
  24. data[key] = json.loads(value)
  25. return {"message": "success", "code": 200, "data": data}
  26. @router.post("/proxy/{node}/")
  27. def push_proxy_info(node: str, proxy: Proxy, request: Request):
  28. """
  29. 代理信息上报接口
  30. :param node: 客户端节点
  31. :param proxy: 代理信息
  32. :param request:
  33. :return: json 包含代理切换状态
  34. """
  35. switch_state = False
  36. string_key = f"{node}:string"
  37. if redis_client.get(string_key) is None: # 过期
  38. switch_state = True
  39. redis_client.set(string_key, "1", ex=60)
  40. if redis_client.get(string_key) == "0": # 手动切换 / 爬虫程序可控
  41. redis_client.set(string_key, "1", ex=60)
  42. client_host = request.client.host # 当前客户端host:port
  43. # redis_client.hmset(hash_key, dict(proxy)) # 同时设置多值
  44. _dict = dict(proxy)
  45. _dict["host"] = client_host
  46. redis_client.hset(hash_key, key=node, value=json.dumps(_dict))
  47. return {"message": "success", "code": 200, "data": {"switch_state": switch_state}}

启动 fastapi 服务:

  1. (proxy-venv) ┌──(h1code2LAPTOP-IMECGKD2)-[/mnt/d/PycharmProjects/proxy-test]
  2. └─$ uvicorn service.run:app
  3. INFO: Started server process [14291]
  4. INFO: Waiting for application startup.
  5. INFO: Application startup complete.
  6. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
  7. INFO: 171.81.86.81:0 - "POST /api/proxy/node-1/?token=xfsdfhkjf23rfdwef HTTP/1.0" 200 OK
  8. INFO: 171.81.85.51:0 - "POST /api/proxy/node-1/?token=xfsdfhkjf23rfdwef HTTP/1.0" 200 OK
  9. INFO: 171.81.85.51:0 - "POST /api/proxy/node-1/?token=xfsdfhkjf23rfdwef HTTP/1.0" 200 OK

因为我这里代理是运行在本地环境的,使用 frp 做了内网穿透,所以此时 proxy_client 等会通过 api.h1code2.cn/api/proxy/node-1 实际连接的是我们本地的 fastapi 服务,这个并不是必须,你可以直接把 fastapi 接口代码部署公网服务器上。

启动客户端:

  1. [root@localhost ~]# chmod 777 proxy_client # 修改可执行权限
  2. [root@localhost ~]# ./proxy_client
  3. {node:node-1 HttpPort:8123 Socks5Port:8125 Username:admin Password:123456}
  4. switch connect status false 1607958301
  5. switch connect status false 1607958302
  6. switch connect status false 1607958303

客户端程序启动后,可以查看 redis 中已经记录了拨号代理 IP:

  1. 127.0.0.1:6379[14]> hgetall proxy-node:hash
  2. 1) "node-1"
  3. 2) "{\"http_port\":8123,\"socks5_port\":8125,\"username\":\"admin\",\"password\":\"123456\",\"host\":\"171.81.85.51\"}"
  4. 127.0.0.1:6379[14]>

也可以通过我们自己实现的接口查看:

  1. ┌──(h1code2LAPTOP-IMECGKD2)-[/mnt/c/Users/h1code2]
  2. └─$ curl http://api.h1code2.cn/api/all_proxy
  3. {
  4. "message": "success",
  5. "code": 200,
  6. "data": {
  7. "node-1": {
  8. "http_port": 8123,
  9. "socks5_port": 8125,
  10. "username": "admin",
  11. "password": "123456",
  12. "host": "171.81.85.51"
  13. }
  14. }
  15. }

测试一下当前代理:

  1. ┌──(h1code2LAPTOP-IMECGKD2)-[/mnt/c/Users/h1code2]
  2. └─$ curl -x http://admin:123456@171.81.85.51:81233 ip.sb
  3. 171.81.85.51

关于 proxy_client 文件我放置在 https://github.com/h1code2/proxy_client ,更多爬虫 / 逆向相关内容可以进球讨论: