├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── adslproxy ├── __init__.py ├── __version__.py ├── checker │ ├── __init__.py │ └── checker.py ├── cmd.py ├── db.py ├── sender │ ├── __init__.py │ └── sender.py ├── server │ ├── __init__.py │ └── server.py └── settings.py ├── deployment-checker.yml ├── deployment-server.yml ├── docker-compose.yml ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | /.idea 134 | 135 | env.sh -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | WORKDIR /app 3 | RUN pip3 install adslproxy 4 | CMD ["adslproxy", "serve"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 bexidcom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 拨号主机设置 2 | 3 | 首先配置好代理,如使用 Squid,运行在 3128 端口,并设置好用户名和密码。 4 | 5 | 配置好代理之后,即可使用本工具定时拨号并发送至 Redis。 6 | 7 | ### 安装 ADSLProxy 8 | 9 | ``` 10 | pip3 install -U adslproxy 11 | ``` 12 | 13 | ### 设置环境变量 14 | 15 | ``` 16 | # Redis 数据库地址、端口和密码 17 | export REDIS_HOST= 18 | export REDIS_PORT= 19 | export REDIS_PASSWORD= 20 | # 拨号云主机配置的代理端口 21 | export PROXY_PORT= 22 | # 拨号云主机的代理用户名,无认证则留空 23 | export PROXY_USERNAME= 24 | # 拨号云主机配置的代理密码,无认证则留空 25 | export PROXY_PASSWORD= 26 | ``` 27 | 28 | ### 执行 29 | 30 | ``` 31 | adslproxy send 32 | ``` 33 | 34 | 运行结果: 35 | 36 | 运行结果: 37 | 38 | ``` 39 | 2020-04-13 01:39:30.811 | INFO | adslproxy.sender.sender:loop:90 - Starting dial... 40 | 2020-04-13 01:39:30.812 | INFO | adslproxy.sender.sender:run:99 - Dial Started, Remove Proxy 41 | 2020-04-13 01:39:30.812 | INFO | adslproxy.sender.sender:remove_proxy:62 - Removing adsl1... 42 | 2020-04-13 01:39:30.893 | INFO | adslproxy.sender.sender:remove_proxy:69 - Removed adsl1 successfully 43 | 2020-04-13 01:39:37.034 | INFO | adslproxy.sender.sender:run:108 - Get New IP 113.128.9.239 44 | 2020-04-13 01:39:42.221 | INFO | adslproxy.sender.sender:run:117 - Valid proxy 113.128.9.239:3389 45 | 2020-04-13 01:39:42.458 | INFO | adslproxy.sender.sender:set_proxy:82 - Successfully Set Proxy 46 | 2020-04-13 01:43:02.560 | INFO | adslproxy.sender.sender:loop:90 - Starting dial... 47 | 2020-04-13 01:43:02.561 | INFO | adslproxy.sender.sender:run:99 - Dial Started, Remove Proxy 48 | 2020-04-13 01:43:02.561 | INFO | adslproxy.sender.sender:remove_proxy:62 - Removing adsl1... 49 | 2020-04-13 01:43:02.630 | INFO | adslproxy.sender.sender:remove_proxy:69 - Removed adsl1 successfully 50 | 2020-04-13 01:43:08.770 | INFO | adslproxy.sender.sender:run:108 - Get New IP 113.128.31.230 51 | 2020-04-13 01:43:13.955 | INFO | adslproxy.sender.sender:run:117 - Valid proxy 113.128.31.230:3389 52 | 2020-04-13 01:43:14.037 | INFO | adslproxy.sender.sender:set_proxy:82 - Successfully Set Proxy 53 | 2020-04-13 01:46:34.216 | INFO | adslproxy.sender.sender:loop:90 - Starting dial... 54 | 2020-04-13 01:46:34.217 | INFO | adslproxy.sender.sender:run:99 - Dial Started, Remove Proxy 55 | 2020-04-13 01:46:34.217 | INFO | adslproxy.sender.sender:remove_proxy:62 - Removing adsl1... 56 | 2020-04-13 01:46:34.298 | INFO | adslproxy.sender.sender:remove_proxy:69 - Removed adsl1 successfully 57 | ``` 58 | 59 | 此时有效代理就会发送到 Redis。 60 | 61 | -------------------------------------------------------------------------------- /adslproxy/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | def version(): 4 | """ 5 | get version from version file 6 | :return: 7 | """ 8 | from adslproxy import __version__ 9 | return __version__.version() 10 | -------------------------------------------------------------------------------- /adslproxy/__version__.py: -------------------------------------------------------------------------------- 1 | VERSION = (3, 0, 3) 2 | 3 | __version__ = '.'.join(map(str, VERSION)) 4 | 5 | version = lambda: __version__ 6 | -------------------------------------------------------------------------------- /adslproxy/checker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python3WebSpider/AdslProxy/4d1a242d58ee9d8acdbeca9b48e7390ca7243409/adslproxy/checker/__init__.py -------------------------------------------------------------------------------- /adslproxy/checker/checker.py: -------------------------------------------------------------------------------- 1 | import time 2 | from requests import ReadTimeout 3 | from adslproxy.db import RedisClient 4 | import requests 5 | from adslproxy import settings 6 | from collections import defaultdict 7 | from loguru import logger 8 | 9 | 10 | class Checker(object): 11 | 12 | def __init__(self): 13 | self.db = RedisClient() 14 | self.counts = defaultdict(int) 15 | 16 | def check(self, proxy): 17 | """ 18 | 测试代理,返回测试结果 19 | :param proxy: 代理 20 | :return: 测试结果 21 | """ 22 | try: 23 | response = requests.get(settings.TEST_URL, proxies={ 24 | 'http': 'http://' + proxy, 25 | 'https': 'https://' + proxy 26 | }, timeout=settings.TEST_TIMEOUT) 27 | logger.debug(f'Using {proxy} to test {settings.TEST_URL}...') 28 | if response.status_code == 200: 29 | return True 30 | except (ConnectionError, ReadTimeout): 31 | return False 32 | 33 | def run(self): 34 | """ 35 | 测试一轮 36 | :return: 37 | """ 38 | proxies = self.db.all() 39 | logger.info(f'Try to get all proxies {proxies}') 40 | for name, proxy in proxies.items(): 41 | # 检测无效 42 | if not self.check(proxy): 43 | logger.info(f'Proxy {proxy} invalid') 44 | self.counts[proxy] += 1 45 | else: 46 | logger.info(f'Proxy {proxy} valid') 47 | count = self.counts.get(proxy) or 0 48 | logger.debug(f'Count {count}, TEST_MAX_ERROR_COUNT {settings.TEST_MAX_ERROR_COUNT}') 49 | if count >= settings.TEST_MAX_ERROR_COUNT: 50 | self.db.remove(name) 51 | 52 | def loop(self): 53 | """ 54 | 循环测试 55 | :return: 56 | """ 57 | while True: 58 | logger.info('Check for infinite') 59 | self.run() 60 | logger.info(f'Tested, sleeping for {settings.TEST_CYCLE}s...') 61 | time.sleep(settings.TEST_CYCLE) 62 | 63 | 64 | def check(loop=True): 65 | """ 66 | check proxies 67 | :param loop: 68 | :return: 69 | """ 70 | if not loop: 71 | settings.TEST_MAX_ERROR_COUNT = 1 72 | checker = Checker() 73 | checker.loop() if loop else checker.run() 74 | 75 | 76 | if __name__ == '__main__': 77 | check() 78 | -------------------------------------------------------------------------------- /adslproxy/cmd.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from adslproxy import version 3 | import sys 4 | from adslproxy.checker.checker import check 5 | from adslproxy.sender.sender import send 6 | from adslproxy.server.server import serve 7 | 8 | optional_title = 'optional arguments' 9 | 10 | 11 | def str2bool(v): 12 | """ 13 | convert string to bool 14 | :param v: 15 | :return: 16 | """ 17 | if isinstance(v, bool): 18 | return v 19 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 20 | return True 21 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 22 | return False 23 | return True 24 | 25 | 26 | class CapitalisedHelpFormatter(argparse.HelpFormatter): 27 | def __init__(self, prog): 28 | super(CapitalisedHelpFormatter, self).__init__(prog, 29 | indent_increment=2, 30 | max_help_position=30, 31 | width=200) 32 | self._action_max_length = 20 33 | 34 | def add_usage(self, usage, actions, groups, prefix=None): 35 | if prefix is None: 36 | prefix = 'Usage: ' 37 | return super(CapitalisedHelpFormatter, self).add_usage( 38 | usage, actions, groups, prefix) 39 | 40 | class _Section(object): 41 | 42 | def __init__(self, formatter, parent, heading=None): 43 | self.formatter = formatter 44 | self.parent = parent 45 | self.heading = heading 46 | self.items = [] 47 | 48 | def format_help(self): 49 | # format the indented section 50 | if self.parent is not None: 51 | self.formatter._indent() 52 | join = self.formatter._join_parts 53 | item_help = join([func(*args) for func, args in self.items]) 54 | if self.parent is not None: 55 | self.formatter._dedent() 56 | 57 | # return nothing if the section was empty 58 | if not item_help: return '' 59 | 60 | # add the heading if the section was non-empty 61 | if self.heading is not argparse.SUPPRESS and self.heading is not None: 62 | current_indent = self.formatter._current_indent 63 | if self.heading == optional_title: 64 | heading = '%*s\n%s:\n' % (current_indent, '', self.heading.title()) 65 | else: 66 | heading = '%*s%s:' % (current_indent, '', self.heading.title()) 67 | else: 68 | heading = '' 69 | 70 | return join(['\n', heading, item_help]) 71 | 72 | 73 | parser = argparse.ArgumentParser(description='ADSLProxy %s - Easily to achieve ADSL Proxy Pool' % version(), 74 | formatter_class=CapitalisedHelpFormatter, add_help=False) 75 | 76 | parser.add_argument('-v', '--version', action='version', version=version(), help='Get version of ADSLProxy') 77 | parser.add_argument('-h', '--help', action='help', help='Show this help message and exit') 78 | 79 | subparsers = parser.add_subparsers(dest='command', title='Available commands', metavar='') 80 | 81 | # serve 82 | parser_serve = subparsers.add_parser('serve', help='Run Server') 83 | 84 | # check 85 | parser_check = subparsers.add_parser('check', help='Run Checker') 86 | parser_check.add_argument('-l', '--loop', default=True, type=str2bool, nargs='?', help='Run checker for infinite') 87 | 88 | # send 89 | parser_send = subparsers.add_parser('send', help='Run Sender') 90 | parser_send.add_argument('-l', '--loop', default=True, type=str2bool, nargs='?', help='Run sender for infinite') 91 | 92 | # show help info when no args 93 | if len(sys.argv[1:]) == 0: 94 | parser.print_help() 95 | parser.exit() 96 | 97 | 98 | def cmd(): 99 | """ 100 | run from cmd 101 | :return: 102 | """ 103 | args = parser.parse_args() 104 | command = args.command 105 | # run server 106 | if command == 'serve': 107 | serve() 108 | # dial and send proxy to redis 109 | elif command == 'send': 110 | send(args.loop) 111 | # check proxies in redis 112 | elif command == 'check': 113 | check(args.loop) 114 | 115 | 116 | if __name__ == '__main__': 117 | cmd() 118 | -------------------------------------------------------------------------------- /adslproxy/db.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import redis 3 | import random 4 | from adslproxy.settings import * 5 | 6 | 7 | class RedisClient(object): 8 | def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, redis_key=REDIS_KEY): 9 | """ 10 | 初始化Redis连接 11 | :param host: Redis 地址 12 | :param port: Redis 端口 13 | :param password: Redis 密码 14 | :param redis_key: Redis 哈希表名 15 | """ 16 | self.db = redis.StrictRedis(host=host, port=port, password=password, decode_responses=True) 17 | self.redis_key = redis_key 18 | 19 | def set(self, name, proxy): 20 | """ 21 | 设置代理 22 | :param name: 主机名称 23 | :param proxy: 代理 24 | :return: 设置结果 25 | """ 26 | return self.db.hset(self.redis_key, name, proxy) 27 | 28 | def get(self, name): 29 | """ 30 | 获取代理 31 | :param name: 主机名称 32 | :return: 代理 33 | """ 34 | return self.db.hget(self.redis_key, name) 35 | 36 | def count(self): 37 | """ 38 | 获取代理总数 39 | :return: 代理总数 40 | """ 41 | return self.db.hlen(self.redis_key) 42 | 43 | def remove(self, name): 44 | """ 45 | 删除代理 46 | :param name: 主机名称 47 | :return: 删除结果 48 | """ 49 | return self.db.hdel(self.redis_key, name) 50 | 51 | def names(self): 52 | """ 53 | 获取主机名称列表 54 | :return: 获取主机名称列表 55 | """ 56 | return self.db.hkeys(self.redis_key) 57 | 58 | def proxies(self): 59 | """ 60 | 获取代理列表 61 | :return: 代理列表 62 | """ 63 | return self.db.hvals(self.redis_key) 64 | 65 | def random(self): 66 | """ 67 | 随机获取代理 68 | :return: 69 | """ 70 | proxies = self.proxies() 71 | return random.choice(proxies) 72 | 73 | def all(self): 74 | """ 75 | 获取字典 76 | :return: 77 | """ 78 | return self.db.hgetall(self.redis_key) 79 | 80 | def close(self): 81 | """ 82 | 关闭 Redis 连接 83 | :return: 84 | """ 85 | del self.db 86 | -------------------------------------------------------------------------------- /adslproxy/sender/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python3WebSpider/AdslProxy/4d1a242d58ee9d8acdbeca9b48e7390ca7243409/adslproxy/sender/__init__.py -------------------------------------------------------------------------------- /adslproxy/sender/sender.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re 3 | import time 4 | import requests 5 | from requests.exceptions import ConnectionError, ReadTimeout 6 | from adslproxy.db import RedisClient 7 | from adslproxy.settings import * 8 | import platform 9 | from loguru import logger 10 | from retrying import retry, RetryError 11 | import redis 12 | 13 | if platform.python_version().startswith('2.'): 14 | import commands as subprocess 15 | elif platform.python_version().startswith('3.'): 16 | import subprocess 17 | else: 18 | raise ValueError('python version must be 2 or 3') 19 | 20 | 21 | class Sender(object): 22 | """ 23 | 拨号并发送到 Redis 24 | """ 25 | 26 | def extract_ip(self): 27 | """ 28 | 获取本机IP 29 | :param ifname: 网卡名称 30 | :return: 31 | """ 32 | (status, output) = subprocess.getstatusoutput('ifconfig') 33 | if not status == 0: return 34 | pattern = re.compile(DIAL_IFNAME + '.*?inet.*?(\d+\.\d+\.\d+\.\d+).*?netmask', re.S) 35 | result = re.search(pattern, output) 36 | if result: 37 | # 返回拨号后的 IP 地址 38 | return result.group(1) 39 | 40 | def test_proxy(self, proxy): 41 | """ 42 | 测试代理,返回测试结果 43 | :param proxy: 代理 44 | :return: 测试结果 45 | """ 46 | try: 47 | response = requests.get(TEST_URL, proxies={ 48 | 'http': 'http://' + proxy, 49 | 'https': 'https://' + proxy 50 | }, timeout=TEST_TIMEOUT) 51 | if response.status_code == 200: 52 | return True 53 | except (ConnectionError, ReadTimeout): 54 | return False 55 | 56 | @retry(retry_on_result=lambda x: x is not True, stop_max_attempt_number=10) 57 | def remove_proxy(self): 58 | """ 59 | 移除代理 60 | :return: None 61 | """ 62 | logger.info(f'Removing {CLIENT_NAME}...') 63 | try: 64 | # 由于拨号就会中断连接,所以每次都要重新建立连接 65 | if hasattr(self, 'redis') and self.redis: 66 | self.redis.close() 67 | self.redis = RedisClient() 68 | self.redis.remove(CLIENT_NAME) 69 | logger.info(f'Removed {CLIENT_NAME} successfully') 70 | return True 71 | except redis.ConnectionError: 72 | logger.info(f'Remove {CLIENT_NAME} failed') 73 | 74 | def set_proxy(self, proxy): 75 | """ 76 | 设置代理 77 | :param proxy: 代理 78 | :return: None 79 | """ 80 | self.redis = RedisClient() 81 | if self.redis.set(CLIENT_NAME, proxy): 82 | logger.info(f'Successfully set proxy {proxy}') 83 | 84 | def loop(self): 85 | """ 86 | 循环拨号 87 | :return: 88 | """ 89 | while True: 90 | logger.info('Starting dial...') 91 | self.run() 92 | time.sleep(DIAL_CYCLE) 93 | 94 | def run(self): 95 | """ 96 | 拨号主进程 97 | :return: None 98 | """ 99 | logger.info('Dial started, remove proxy') 100 | try: 101 | self.remove_proxy() 102 | except RetryError: 103 | logger.error('Retried for max times, continue') 104 | # 拨号 105 | (status, output) = subprocess.getstatusoutput(DIAL_BASH) 106 | if not status == 0: 107 | logger.error('Dial failed') 108 | # 获取拨号 IP 109 | ip = self.extract_ip() 110 | if ip: 111 | logger.info(f'Get new IP {ip}') 112 | if PROXY_USERNAME and PROXY_PASSWORD: 113 | proxy = '{username}:{password}@{ip}:{port}'.format(username=PROXY_USERNAME, 114 | password=PROXY_PASSWORD, 115 | ip=ip, port=PROXY_PORT) 116 | else: 117 | proxy = '{ip}:{port}'.format(ip=ip, port=PROXY_PORT) 118 | time.sleep(10) 119 | if self.test_proxy(proxy): 120 | logger.info(f'Valid proxy {proxy}') 121 | # 将代理放入数据库 122 | self.set_proxy(proxy) 123 | time.sleep(DIAL_CYCLE) 124 | else: 125 | logger.error(f'Proxy invalid {proxy}') 126 | else: 127 | # 获取 IP 失败,重新拨号 128 | logger.error('Get IP failed, re-dialing') 129 | self.run() 130 | 131 | 132 | def send(loop=True): 133 | sender = Sender() 134 | sender.loop() if loop else sender.run() 135 | 136 | 137 | if __name__ == '__main__': 138 | send() 139 | -------------------------------------------------------------------------------- /adslproxy/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python3WebSpider/AdslProxy/4d1a242d58ee9d8acdbeca9b48e7390ca7243409/adslproxy/server/__init__.py -------------------------------------------------------------------------------- /adslproxy/server/server.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import tornado.ioloop 4 | import tornado.web 5 | from tornado.web import RequestHandler, Application 6 | from adslproxy.db import RedisClient 7 | from adslproxy.settings import * 8 | from loguru import logger 9 | 10 | 11 | class Server(RequestHandler): 12 | """ 13 | 服务器,对接 Redis 并提供 API 14 | """ 15 | 16 | def initialize(self, redis): 17 | """ 18 | 初始化 19 | :param redis: 20 | :return: 21 | """ 22 | self.redis = redis 23 | 24 | def get(self, api=''): 25 | """ 26 | API 列表 27 | :param api: 28 | :return: 29 | """ 30 | if not api: 31 | links = ['random', 'proxies', 'names', 'all', 'count'] 32 | self.write('