利用动态VPS服务器建立自用专属proxy
需求:爬虫需要时不时换ip,但是又不需要那么频繁的切换,所以决定自己用一个动态服务器做自己的代理ip。这时需要准备至少两个服务器:
- 动态VPS支持拨号换ip
- 普通的服务器用来挂爬虫
(其实可以把爬虫也放在VPS上,但是一般VPS的内存CPU硬盘都很小。而性能够的普通服务器又不能换ip,用两个便宜服务器的这种方案是比较省钱的选择)
整个设置过程如下:
设置proxy服务器
首先配置VPS服务器,大流程参考如下链接:轻松获得海量稳定代理!ADSL拨号代理的搭建 (qq.com)
设置adsl
第一步需要先使用pppoe-setup, pppoe-start, pppoe-stop来控制拨号,先让机器连上外网才能正常联网下载安装app,但是可能具体命令要视不同服务器而定。参考:CentOS下配置ADSL 拨号上网CentOS中文站 - 专注Linux技术 (centoschina.cn)
使用pppoe-setup进行设置:第一步按回车,第二步填账号,第三步填网卡,第四步按回车,DNS也直接按回车,第五步填密码,第六步回车,第七步输入0,第八步输入yes,第九步输入y,设置完之后,pppoe-stop,先关闭拨号,然后pppoe-start启动拨号
参考:linux 在shell 下连接pppoe出现/usr/sbin/adsl-start: line 217:怎么办?
安装python3
在centos7上直接源码安装python3,按照以下方式安装
yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make
wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tgz
tar -zxvf Python-3.6.4.tgz
cd Python-3.6.4
./configure && make &&make install
安装tinyproxy
使用源码安装新版本的,能开启密码认证,避免自己的vps沦为网上的免费代理
1:安装gcc(如果前面安装python时安装过,可跳过)
yum install gcc
2:安装TinyProxy,不使用yum安装,直接下载最新的源码安装
wget https://github.com/tinyproxy/tinyproxy/releases/download/1.11.0/tinyproxy-1.11.0.tar.gz
tar -zxvf tinyproxy-1.11.0.tar.gz
cd tinyproxy-1.11.0
3:编译安装
./configure && make &&make install
bash -r
4:创建系统服务
新建tinyproxy.service文件
vi /usr/lib/systemd/system/tinyproxy.service
贴入以下代码
[Unit]
Description=Startup script for the tinyproxy server
After=network.target
[Service]
Type=forking
PIDFile=/usr/local/var/run/tinyproxy/tinyproxy.pid
ExecStart=/bin/bash -c "/usr/local/bin/tinyproxy -c /usr/local/etc/tinyproxy/tinyproxy.conf &"
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
ExecStartPost=/bin/sleep 0.1
[Install]
WantedBy=multi-user.target
5:手动创建log和pid文件
mkdir -p /usr/local/var/run/tinyproxy
mkdir -p /usr/local/var/log/tinyproxy
touch /usr/local/var/log/tinyproxy/tinyproxy.log
touch /usr/local/var/run/tinyproxy/tinyproxy.pid
6:改配置文件/usr/local/etc/tinyproxy/tinyproxy.conf
#这里设置使用的用户名和组
User root
Group root
# 设置爬虫的ip,如果需要本地也连接的话就全部注释掉
# 只采用密码认证的方式,否则本地连本地会被拒绝
Allow xxx.xxx.xxx.xxx
# 设置用户名密码
BasicAuth username password
# 顺便将下面两行取消注释,后面有用到
PidFile "/usr/local/var/run/tinyproxy/tinyproxy.pid"
LogFile "/usr/local/var/log/tinyproxy/tinyproxy.log"
7:启动服务
sudo systemctl enable tinyproxy
service tinyproxy start
service tinyproxy status
之后改一下防火墙设置
firewall-cmd --zone=public --add-port=8888/tcp --permanent
firewall-cmd --reload
这时候拿一台机器(本地或者用要做爬虫的那台机器)试以下命令
curl -x http://user_username:mypassword1234@xx.xx.xx.xx:8888 httpbin.org/get
这里xx的ip是用来拨号的网卡的ip地址,名字一般是ppp0的那个
设置ip地址提取方法
代码参考: Python网络爬虫开发实战,ADSL 拨号代理
1:在centos7上安装anaconda,流程和普通的Linux安装都是一样的。(直接用服务器python3环境的话可跳过)
2:安装redis、requests,直接pip安装即可,注意如果用conda安装redis库的话需要执行
conda install redis
conda install redis-py
3:之后建两个文件,第一个文件是db_setup.py
,需要定制的地方就是REDIS_HOST
和REDIS_PASSWORD
,根据后面redis配置改。
import redis
import random
# Redis数据库安装在爬虫服务器上,他的ip
REDIS_HOST = '111.11.111.111'
# Redis数据库密码, 如无则填None
REDIS_PASSWORD = '11111111'
# Redis数据库端口
REDIS_PORT = 6379
# 代理池键名
PROXY_KEY = 'adsl'
class RedisClient(object):
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, proxy_key=PROXY_KEY):
"""
初始化Redis连接
:param host: Redis 地址
:param port: Redis 端口
:param password: Redis 密码
:param proxy_key: Redis 散列表名
"""
self.db = redis.StrictRedis(host=host, port=port, password=password, decode_responses=True)
self.proxy_key = proxy_key
def set(self, name, proxy):
"""
设置代理
:param name: 主机名称
:param proxy: 代理
:return: 设置结果
"""
return self.db.hset(self.proxy_key, name, proxy)
def get(self, name):
"""
获取代理
:param name: 主机名称
:return: 代理
"""
return self.db.hget(self.proxy_key, name)
def count(self):
"""
获取代理总数
:return: 代理总数
"""
return self.db.hlen(self.proxy_key)
def remove(self, name):
"""
删除代理
:param name: 主机名称
:return: 删除结果
"""
return self.db.hdel(self.proxy_key, name)
def names(self):
"""
获取主机名称列表
:return: 获取主机名称列表
"""
return self.db.hkeys(self.proxy_key)
def proxies(self):
"""
获取代理列表
:return: 代理列表
"""
return self.db.hvals(self.proxy_key)
def random(self):
"""
随机获取代理
:return:
"""
proxies = self.proxies()
if proxies:
ip = random.choice(proxies)
else:
ip = None
return ip
def all(self):
"""
获取字典
:return:
"""
return self.db.hgetall(self.proxy_key)
4:建一个refresh_ip.py
,需要定制的地方是ADSL_CYCLE
控制刷新ip的间隔。
另外在执行这个脚本之前也要确保机器联外网(执行pppoe-start),不然一直连不上爬虫服务器的redis
import re
import time
import requests
from requests.exceptions import ConnectionError, ReadTimeout
from db_setup import RedisClient
import subprocess
import logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] - %(message)s')
logger = logging.getLogger(__name__)
# 拨号网卡
ADSL_IFNAME = 'ppp0'
# 测试URL
TEST_URL = 'http://httpbin.org/get'
# 测试超时时间
TEST_TIMEOUT = 30
# 拨号间隔
ADSL_CYCLE = 43200 # 12 hours
# 拨号出错重试间隔
ADSL_ERROR_CYCLE = 10
# 从数据库删掉旧ip到关闭旧ip之间的冷却时间
COOL_DOWN_CYCLE = 600
# ADSL命令
ADSL_BASH = 'pppoe-stop;pppoe-start'
# 代理运行端口
PROXY_PORT = 8888
# 客户端唯一标识
CLIENT_NAME = 'adsl1'
# proxy的用户名和密码
USERNAME = 'user_username'
PASSWORD = 'mypassword1234'
class Sender():
def get_ip(self, ifname=ADSL_IFNAME):
"""
获取本机IP
:param ifname: 网卡名称
:return:
"""
(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
def test_proxy(self, proxy):
"""
测试代理
:param proxy: 代理
:return: 测试结果
"""
try:
response = requests.get(TEST_URL, proxies={
'http': '{}:{}@{}'.format(USERNAME, PASSWORD, proxy)
}, timeout=TEST_TIMEOUT)
if response.status_code == 200:
return True
except (ConnectionError, ReadTimeout):
return False
def remove_proxy(self):
"""
移除代理
:return: None
"""
self.redis = RedisClient()
self.redis.remove(CLIENT_NAME)
logger.info('Successfully Removed Proxy')
def set_proxy(self, proxy):
"""
设置代理
:param proxy: 代理
:return: None
"""
self.redis = RedisClient()
if self.redis.set(CLIENT_NAME, proxy):
logger.info('Successfully Set Proxy {}'.format(proxy))
def adsl(self):
"""
拨号主进程
:return: None
"""
logger.info('Remove Proxy')
self.remove_proxy()
logger.info('Cooling Down')
time.sleep(COOL_DOWN_CYCLE)
while True:
logger.info('ADSL Dialing Start')
(status, output) = subprocess.getstatusoutput(ADSL_BASH)
if status == 0:
logger.info('Success')
ip = self.get_ip()
if ip:
logger.info('IP: {}'.format(ip))
logger.info('Testing Proxy')
proxy = '{ip}:{port}'.format(ip=ip, port=PROXY_PORT)
if self.test_proxy(proxy):
logger.info('Valid Proxy')
self.set_proxy(proxy)
logger.info('Waiting for Next Dialing')
time.sleep(ADSL_CYCLE)
logger.info('Remove Proxy')
self.remove_proxy()
logger.info('Cooling Down')
time.sleep(COOL_DOWN_CYCLE)
else:
logger.info('Invalid Proxy')
time.sleep(ADSL_ERROR_CYCLE)
else:
logger.info('Get IP Failed, Re Dialing')
time.sleep(ADSL_ERROR_CYCLE)
else:
logger.info('ADSL Dialing Failed, Please Check')
time.sleep(ADSL_ERROR_CYCLE)
if __name__ == '__main__':
sender = Sender()
sender.adsl()
5:但是偶尔有可能在拨号间隙时ip挂掉或者被ban,所以我又简化了一个'manual_refresh.py',可以手动控制切换ip
import re
import time
import requests
from requests.exceptions import ConnectionError, ReadTimeout
from db_setup import RedisClient
import subprocess
import logging
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] - %(message)s')
logger = logging.getLogger(__name__)
# 拨号网卡
ADSL_IFNAME = 'ppp0'
# ADSL命令
ADSL_BASH = 'pppoe-stop;pppoe-start'
# 代理运行端口
PROXY_PORT = 8888
# 客户端唯一标识
CLIENT_NAME = 'adsl1'
class Sender():
def get_ip(self, ifname=ADSL_IFNAME):
"""
获取本机IP
:param ifname: 网卡名称
:return:
"""
(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
def remove_proxy(self):
"""
移除代理
:return: None
"""
self.redis = RedisClient()
self.redis.remove(CLIENT_NAME)
logger.info('Successfully Removed Proxy')
def set_proxy(self, proxy):
"""
设置代理
:param proxy: 代理
:return: None
"""
self.redis = RedisClient()
if self.redis.set(CLIENT_NAME, proxy):
logger.info('Successfully Set Proxy {}'.format(proxy))
def adsl(self):
logger.info('ADSL Start, Remove Proxy, Please wait')
self.remove_proxy()
(status, output) = subprocess.getstatusoutput(ADSL_BASH)
ip = self.get_ip()
logger.info('IP: {}'.format(ip))
proxy = '{ip}:{port}'.format(ip=ip, port=PROXY_PORT)
self.set_proxy(proxy)
if __name__ == '__main__':
sender = Sender()
sender.adsl()
设置爬虫服务器
爬虫服务器除了放爬虫代码以外,还要放一个redis数据库用来接proxy传来的ip,然后用web页面传进爬虫代码解析出来代理ip的地址。
安装redis
参考:如何在 Ubuntu 20.04 上安装和配置 Redis-阿里云开发者社区 (aliyun.com)
首先安装
apt install redis-server
之后可以利用下面指令查看状态
systemctl status redis-server
找到/etc/redis/redis.conf
之后把下面这句的前面加个#号注释掉
bind 127.0.0.1 ::1
找到#requirepass foobared
这句,取消前面的#号,并且把密码改成自己的
requirepass mypassword12345
之后重启服务
systemctl restart redis-server
然后上服务器的安全组或者防火墙什么的里面,把6379这个端口放开。
设置获取ip的web链接
1:安装redis,tornado,之后把上面那个db_setup.py
照搬过来。
2:建立一个文件api.py
,可以改的参数是def serve(port=8425)
可以根据需要改。
import json
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler, Application
from db_setup import RedisClient
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class Server(RequestHandler):
"""
服务器,对接 Redis 并提供 API
"""
def initialize(self, redis):
"""
初始化
:param redis:
:return:
"""
self.redis = redis
def get(self, api=''):
"""
API 列表
:param api:
:return:
"""
if not api:
links = ['random', 'proxies', 'names', 'all', 'count']
self.write('<h4>Welcome to ADSL Proxy API</h4>')
for link in links:
self.write('<a href=' + link + '>' + link + '</a><br>')
if api == 'random':
result = self.redis.random()
if result:
self.write(result)
if api == 'names':
result = self.redis.names()
if result:
self.write(json.dumps(result))
if api == 'proxies':
result = self.redis.proxies()
if result:
self.write(json.dumps(result))
if api == 'all':
result = self.redis.all()
if result:
self.write(json.dumps(result))
if api == 'count':
self.write(str(self.redis.count()))
def serve(port=8425, address='0.0.0.0'):
redis = RedisClient()
application = Application([
(r'/', Server, dict(redis=redis)),
(r'/(.*)', Server, dict(redis=redis)),
])
application.listen(port, address=address)
logger.info(f'API listening on http://{address}:{port}')
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
serve()
3:可以通过在爬虫的脚本里面解析http://0.0.0.0:8425/random
来直接获取到代理ip,然后传给爬虫就行。