├── .gitignore ├── README.md ├── dist ├── proxypool_framework-1.8.tar.gz ├── proxypool_framework-1.9.tar.gz ├── proxypool_framework-2.0.tar.gz └── proxypool_framework-2.1.tar.gz ├── proxypool_framework.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt ├── proxypool_framework ├── .editorconfig ├── __init__.py ├── contrib │ ├── __init__.py │ ├── proxy_client.py │ └── user_agents.py ├── functions_of_get_https_proxy_from_websites.py ├── proxy_collector.py ├── proxy_pool_config.py └── tests │ ├── __init__.py │ ├── reduce2.py │ ├── test_rate_of_success.py │ ├── test_rate_of_success2.py │ ├── test_usefull_proxy.py │ └── 随机检测统计结果.png ├── requierments.txt ├── screen_short ├── 代理池维护时候.png ├── 杀死 └── 落地的代理.png └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | env_hotels/ 4 | henv/ 5 | venv/ 6 | *.pyc 7 | app/apis/logs/ 8 | app/logs/ 9 | *.log.* 10 | *.log 11 | *.lock 12 | *.pytest_cache* 13 | nohup.out 14 | apidoc/ 15 | node_modules/ 16 | hotelApi/ 17 | my_patch_frame_config0000.py 18 | my_patch_frame_config_beifen.py 19 | test_frame/my_patch_frame_config.py 20 | function_result_web/ 21 | test_frame/my/ 22 | redis_queue_web/ 23 | nb_log_config.py 24 | db_libs/sqla_lib2.py 25 | tests_ydf/ 26 | reduce2.py 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxypool_framework 2 | 3 | ## 安装 4 | pip install proxypool_framework 5 | 6 | ### 简介 7 | proxypool_framework 是通用ip代理池架构 + 内置的20+ 个免费代理ip网站爬取函数。 8 | 从ip数量、ip质量、代理池实现本身的难度和代码行数、代理池扩展平台需要的代码行数和难度、配置方式、代理检测设计算法, 9 | 是py史上最强的代理池,欢迎对比任意项目代理池项目,如果这不是最强的,可以写出理由反驳,并贴出更好代码的地址。 10 | 11 | 使用方式如下,安装pip包,然后执行python -m proxypool_framework.proxy_collector 接一大串自定义的配置。(也可以拉取git使用) 12 | 13 | 1)安装代理池框架 14 | 15 | pip install proxypool_framework 16 | 17 | 2)python -m 运行起来 18 | 19 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// 20 | MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=10 REQUESTS_TIMEOUT=5 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 21 | 22 | 如果redis和python代码是同一台机器和没有密码,并且其他配置不想重新设置的话可以 python -m proxypool_framework.proxy_collector 就完了。 23 | 24 | 25 | ### 关于免费代理 26 | ``` 27 | 关于免费代理,免费都是垃圾的论断,是由于被xici这类型的网站坑惨了,只弄过xici,然后发现可用数量比例低,响应时间大,接得出结论免费都是垃圾。 28 | 为什么总是要用xici这种实验,是由于基础代码扩展性太差,导致没时间测试验证。代码写好了,验证一个平台就只需要3分钟了。 29 | 目前验证了20个平台,得出结论是xici是中等网站,比xici更垃圾的也有一堆网站。但至少发现了有3个平台,每1个平台的可用数量都是xici的30倍以上。 30 | 31 | 这种方式的代理池数量秒杀任意收费代理,质量超过大部分收费代理(可以通过参数配置调优,来控制数量和质量的平衡)。 32 | ``` 33 | 34 | 35 | ### 设计思路 36 | ``` 37 | 对于主流程相同,但其中某一个环节必须不同的项目代码布局,都用通用的设计思路,完全不需要设计规划打草稿,直接敲键盘就是了。 38 | 对于这种项目,如果学了设计模式,就很容易轻松不懂大脑就能设计。 39 | 40 | 设计这种项目主要有两种大的方向: 41 | 一种是使用模板模式,模板基类实现主流程,空缺一个必须被继承的方法,然后各个扩展平台只需要继承实现那个方法就可以了。这是继承。 42 | 另一种是使用策略模式,Context类实现业务主流程,接受一个策略类(也可以是策略函数),context对象的运行随着策略类(策略函数)的不同而表现不同。 43 | 这两种方式都能很好的轻松节约流程相似的代码,只需要写不相同部分的代码。本项目使用的是第二种是策略模式,扩展品台可以采用喜闻乐见的面向过程函数编程。 44 | 45 | 设计ProxyCollector类和测试需要2小时,然后扩展一个代理平台由于只需要写一个函数3行代码,如果一个函数花费5分钟,这需要100分钟扩展20个平台。 46 | 47 | ``` 48 | 49 | ### 功能介绍。 50 | ``` 51 | 十分方便扩展各种免费和收费的代理池维护,具有高性能和高并发检测。 52 | 53 | 只要写5行代理ip解析函数,传给ProxyCollector类,运行work方法,就可以循环执行拉取新代理ip并检测入库, 54 | 同时按最后一次的检测时间戳,重新检测超过指定时间的存量代理ip 55 | 56 | 代理ip池使用的是 redis的sortedset结构,键是代理ip,评分是检测时候的时间戳。 57 | 58 | 可以一键 将多个网站维护到一个代理池,也可以维护多个不同的redis键代理池。 59 | ``` 60 | 61 | ### 文件作用介绍 62 | ``` 63 | functions_of_get_https_proxy_from_websites.py 64 | 是从各个网站或付费api获取代理ip的爬取函数大全。 65 | 66 | proxy_collector.py 67 | 1)是自动维护代理池,是万能通用的代理池。可以用于任意免费平台或者收费平台进行代理池维护。 68 | 2)启动一个web接口,/get_a_proxy接口返回一个代理ip。/get_a_proxy后面接的数字为最近检测时候的n个代理中随机返回一个,数字越小范围越小质量越好。 69 | 70 | proxy_pool_config.py 71 | 代理池配置,可以写在文件中也可以用python命令参数传参方式。 72 | 73 | tests/test_rate_of_success.py 74 | 是大规模统计代理池中的ip访问互联网的成功率统计。 75 | ``` 76 | 77 | ### 三种启动方式 78 | ``` 79 | 初次运行时候由于redis中没有代理ip做爬取第三方网站的引子,会被免费代理网站反爬,ip在前几分钟内会比较少。之后会增多,耐心等待。 80 | 81 | 启动方式种类: 82 | 1) 83 | export PYTHONPATH=/codes/proxypool_framework (指的是你的代码的位置,codes换成你的位置) # 这个原理就不需解释了,不知道PYTHONPATH是什么就太low了。 84 | 85 | python proxy_collector.py REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 86 | 或者在 proxy_pool_config.py 文件中把配置写好,就不需要命令行来传参了。直接 python proxy_collector.py 87 | 88 | 2)pycharm中打开此项目,可以直接右键点击run proxy_collector.py 89 | 90 | 3)pip install proxypool_framework 91 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 92 | 93 | 也可以分两次启动,指定不同的redis默认键和flask , 94 | 弄一个 MAX_SECONDS_MUST_CHECK_AGAIN REQUESTS_TIMEOUT 时间小的启动配置,生成优质代理池默认维护在proxy1键中,数量少,成功率高。 95 | 再启动一个 MAX_SECONDS_MUST_CHECK_AGAIN REQUESTS_TIMEOUT 时间大的启动配置,生成中等代理池默认维护在proxy2键中,数量多,成功率低。 96 | 97 | 98 | 启动后可以访问127.0.0.1:6795(指定的端口号),有多个api接口 99 | http://127.0.0.1:6795/get_a_proxy/30?u=user2&p=pass2 #指得是从最接近现在的检测时间的30个代理中随机返回一个。 100 | ``` 101 | 102 | ### 配置说明 103 | ``` 104 | # 可以直接修改这里的值为自己的最终值,也可以使用命令行方式覆盖这里的配置。命令行是为了可以快速的不修改代码配置而进行方便质量数量调优,和不改配置,多次启动分别生成优质代理池、普通代理池。 105 | REDIS_URL = 'redis://:@' # redis的url连接方式百度,可以指定db和ip和密码。 106 | MAX_NUM_PROXY_IN_DB = 1000 # redis中存在超过这个代理数量后,将不再拉取新代理,防止检测存量ip消耗资源过多。 107 | 108 | """代理池是sorted set结构,键是ip,值是该ip最后一次的检测时间戳。一轮一轮的扫描,检测到存量代理ip的最后一次检测时间离现在超过这个时间就重新检测,否则此轮不检测此代理, 109 | MAX_SECONDS_MUST_CHECK_AGAIN 的值要适当,过大会导致检测不及时,取出来后使用时成功率变低;过小会导致检测存量代理ip的密度过大,当存量代理太多的时候,会导致cpu消耗高。 110 | 111 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 2 REQUESTS_TIMEOUT = 1, 则会导致数据库检测及时,并且都是优质代理ip,但存量数量会有所减少(但数量还是秒杀任意收费代理),成功率和响应时间很好。 112 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 10 REQUESTS_TIMEOUT = 5, 这个是比较均衡的配置,兼容数量和质量。 113 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 18 REQUESTS_TIMEOUT = 10, 这个可以造成数据库中存量ip多,但有些代理ip响应时间长,随机使用成功率也会有所降低。 114 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 30 REQUESTS_TIMEOUT = 20, 这样数量非常多。 115 | 116 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 1 REQUESTS_TIMEOUT = 40,这种配置就相当不好了,会造成存量大质量差,但又想检测密度高,会造成cpu消耗高。 117 | 建议MAX_SECONDS_MUST_CHECK_AGAIN是REQUESTS_TIMEOUT的 1-2 倍,可以根据自己要数量大自己重试还是实时必须响应速度快进行不同的配置调优。 118 | 119 | """ 120 | MAX_SECONDS_MUST_CHECK_AGAIN = 2 121 | REQUESTS_TIMEOUT = 1 # 请求响应时间超过这个值,视为废物代理。 122 | FLASK_PORT = 6795 # 代理ip获取的接口 123 | PROXY_KEY_IN_REDIS_DEFAULT = 'proxy_free' # 默认的redis sorted set键,指的是如果你不在ProxyCollector实例化时候亲自指定键的名字(主要是为了一次启动实现维护多个redis代理池)。 124 | ``` 125 | 126 | 127 | ### 代理池维护的图片 128 | ``` 129 | 代理池是sorted set结构,元素是代理ip本身,评分是该代理ip的最后一次的检测时间。 130 | 131 | 流程是: 132 | 133 | 1)有专门的n个独立线程去监控每个代理平台的页面,同时支持了分页监控。按照ProxyCollector对象的设置的时间来进行多久重新拉取一次代理ip,解析得到代理ip列表。 134 | 135 | 2)使用了专门的线程池检测解析得到的代理ip列表,有效的跟新时间戳放到数据库,无效的丢弃。 136 | 137 | 3)对于存量ip,检测完一轮后,休息1秒,然后进行下一轮扫描需要被重新检测的代理ip,有专门的线程池检测存量代理ip。 138 | 如果一个存量代理的最后一次检测时间与当前时间差超过了 MAX_SECONDS_MUST_CHECK_AGAIN 则会重新检测, 139 | 如果检测没有失效,则更新检测的时间戳为当前时间;如果检测失效了则删除。请求检测的requests timeout时间是使用 REQUESTS_TIMEOUT。 140 | 一直高速循环检测。 141 | 142 | ``` 143 | ![Image text](https://i.niupic.com/images/2020/06/18/8hbZ.png) 144 | 145 | 146 | ### 随机统计检测代理池的质量 147 | ``` 148 | 这是设置 MAX_SECONDS_MUST_CHECK_AGAIN=2 REQUESTS_TIMEOUT=1 配置的代理池维护的,然后取出来的随机测试结果。 149 | 可以发现平均响应时间是1.5秒,只请求1次不做重试就成功的概率是98.5%。 150 | 如果重试两次,可以保证成功率达到99.9%,这成功率足够可以秒杀任意收费代理。 151 | 152 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=2 REQUESTS_TIMEOUT=1 FLASK_PORT=6795 153 | ``` 154 | ![Image text](https://i.niupic.com/images/2020/06/18/8hbY.png) 155 | 156 | 157 | 158 | ### 落地的代理,使用时候每次随机取出最近30个检测的其中一个,只重试一次,成功率达到99%以上。 159 | 160 | 取出使用方式, 161 | 162 | ``` 163 | proxy_dict = json.loads(random.choice(REDIS_CLIENT.zrevrange(PROXY_KEY_IN_REDIS_DEFAULT, 0, 30))) 164 | proxy_dict['http'] = proxy_dict['https'].replace('https', 'http') 165 | proxy_dict.pop('platform') 166 | ``` 167 | 168 | ![Image text](https://i.niupic.com/images/2020/08/03/8ubG.png) 169 | 170 | -------------------------------------------------------------------------------- /dist/proxypool_framework-1.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/dist/proxypool_framework-1.8.tar.gz -------------------------------------------------------------------------------- /dist/proxypool_framework-1.9.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/dist/proxypool_framework-1.9.tar.gz -------------------------------------------------------------------------------- /dist/proxypool_framework-2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/dist/proxypool_framework-2.0.tar.gz -------------------------------------------------------------------------------- /dist/proxypool_framework-2.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/dist/proxypool_framework-2.1.tar.gz -------------------------------------------------------------------------------- /proxypool_framework.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: proxypool-framework 3 | Version: 2.1 4 | Summary: proxypool_framework 是万能通用代理池架构,实现核心代码很少只有87行,同时想扩展一个代理平台仅需要写三行代码。通用代理池附带20+ 免费网站代理 5 | Home-page: UNKNOWN 6 | Author: bfzs 7 | Author-email: ydf0509@sohu.com 8 | Maintainer: ydf 9 | Maintainer-email: ydf0509@sohu.com 10 | License: BSD License 11 | Description: # proxypool_framework 12 | 13 | ### 介绍 14 | proxypool_framework 是通用ip代理池架构 + 内置的20+ 个免费代理ip网站爬取函数。 15 | 从ip数量、ip质量、代理池实现本身的难度和代码行数、代理池扩展平台需要的代码行数和难度、配置方式、代理检测设计算法, 16 | 是py史上最强的代理池,欢迎对比任意项目代理池项目,如果这不是最强的,可以写出理由反驳,并贴出更好代码的地址。 17 | 18 | 使用方式如下,安装pip包,然后执行python -m proxypool_framework.proxy_collector 接一大串自定义的配置。(也可以拉取git使用) 19 | 20 | 1)安装代理池框架 21 | 22 | pip install proxypool_framework 23 | 24 | 2)python -m 运行起来 25 | 26 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// 27 | MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=10 REQUESTS_TIMEOUT=5 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 28 | 29 | 如果redis和python代码是同一台机器,并且其他配置不想重新设置的话可以 python -m proxypool_framework.proxy_collector 就完了。 30 | 31 | 32 | ### 关于免费代理 33 | ``` 34 | 关于免费代理,免费都是垃圾的论断,是由于被xici这类型的网站坑惨了,只弄过xici,然后发现可用数量比例低,响应时间大,接得出结论免费都是垃圾。 35 | 为什么总是要用xici这种实验,是由于基础代码扩展性太差,导致没时间测试验证。代码写好了,验证一个平台就只需要3分钟了。 36 | 目前验证了20个平台,得出结论是xici是中等网站,比xici更垃圾的也有一堆网站。但至少发现了有3个平台,每1个平台的可用数量都是xici的30倍以上。 37 | 38 | 这种方式的代理池数量秒杀任意收费代理,质量超过大部分收费代理(可以通过参数配置调优,来控制数量和质量的平衡)。 39 | ``` 40 | 41 | 42 | ### 设计思路 43 | ``` 44 | 对于主流程相同,但其中某一个环节必须不同的项目代码布局,都用通用的设计思路,完全不需要设计规划打草稿,直接敲键盘就是了。 45 | 对于这种项目,如果学了设计模式,就很容易轻松不懂大脑就能设计。 46 | 47 | 设计这种项目主要有两种大的方向: 48 | 一种是使用模板模式,模板基类实现主流程,空缺一个必须被继承的方法,然后各个扩展平台只需要继承实现那个方法就可以了。这是继承。 49 | 另一种是使用策略模式,Context类实现业务主流程,接受一个策略类(也可以是策略函数),context对象的运行随着策略类(策略函数)的不同而表现不同。 50 | 这两种方式都能很好的轻松节约流程相似的代码,只需要写不相同部分的代码。本项目使用的是第二种是策略模式,扩展品台可以采用喜闻乐见的面向过程函数编程。 51 | 52 | 设计ProxyCollector类和测试需要2小时,然后扩展一个代理平台由于只需要写一个函数3行代码,如果一个函数花费5分钟,这需要100分钟扩展20个平台。 53 | 54 | ``` 55 | 56 | ### 介绍。 57 | ``` 58 | 十分方便扩展各种免费和收费的代理池维护,具有高性能和高并发检测。 59 | 60 | 只要写5行代理ip解析函数,传给ProxyCollector类,运行work方法,就可以循环执行拉取新代理ip并检测入库, 61 | 同时按最后一次的检测时间戳,重新检测超过指定时间的存量代理ip 62 | 63 | 代理ip池使用的是 redis的sortedset结构,键是代理ip,评分是检测时候的时间戳。 64 | 65 | 可以一键 将多个网站维护到一个代理池,也可以维护多个不同的redis键代理池。 66 | ``` 67 | 68 | ### 文件作用介绍 69 | ``` 70 | functions_of_get_https_proxy_from_websites.py 71 | 是从各个网站或付费api获取代理ip的爬取函数大全。 72 | 73 | proxy_collector.py 74 | 1)是自动维护代理池,是万能通用的代理池。可以用于任意免费平台或者收费平台进行代理池维护。 75 | 2)启动一个web接口,/get_a_proxy接口返回一个代理ip。/get_a_proxy后面接的数字为最近检测时候的n个代理中随机返回一个,数字越小范围越小质量越好。 76 | 77 | proxy_pool_config.py 78 | 代理池配置,可以写在文件中也可以用python命令参数传参方式。 79 | 80 | tests/test_rate_of_success.py 81 | 是大规模统计代理池中的ip访问互联网的成功率统计。 82 | ``` 83 | 84 | ### 三种启动方式 85 | ``` 86 | 初次运行时候由于redis中没有代理ip做爬取第三方网站的引子,会被免费代理网站反爬,ip在前几分钟内会比较少。之后会增多,耐心等待。 87 | 88 | 启动方式种类: 89 | 1) 90 | export PYTHONPATH=/codes/proxypool_framework (指的是你的代码的位置,codes换成你的位置) # 这个原理就不需解释了,不知道PYTHONPATH是什么就太low了。 91 | 92 | python proxy_collector.py REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 93 | 或者在 proxy_pool_config.py 文件中把配置写好,就不需要命令行来传参了。直接 python proxy_collector.py 94 | 95 | 2)pycharm中打开此项目,可以直接右键点击run proxy_collector.py 96 | 97 | 3)pip install proxypool_framework 98 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 99 | 100 | 也可以分两次启动,指定不同的redis默认键和flask , 101 | 弄一个 MAX_SECONDS_MUST_CHECK_AGAIN REQUESTS_TIMEOUT 时间小的启动配置,生成优质代理池默认维护在proxy1键中,数量少,成功率高。 102 | 再启动一个 MAX_SECONDS_MUST_CHECK_AGAIN REQUESTS_TIMEOUT 时间大的启动配置,生成中等代理池默认维护在proxy2键中,数量多,成功率低。 103 | 104 | 105 | 启动后可以访问127.0.0.1:6795(指定的端口号),有多个api接口 106 | http://127.0.0.1:6795/get_a_proxy/30?u=user2&p=pass2 #指得是从最接近现在的检测时间的30个代理中随机返回一个。 107 | ``` 108 | 109 | ### 配置说明 110 | ``` 111 | # 可以直接修改这里的值为自己的最终值,也可以使用命令行方式覆盖这里的配置。命令行是为了可以快速的不修改代码配置而进行方便质量数量调优,和不改配置,多次启动分别生成优质代理池、普通代理池。 112 | REDIS_URL = 'redis://:@' # redis的url连接方式百度,可以指定db和ip和密码。 113 | MAX_NUM_PROXY_IN_DB = 1000 # redis中存在超过这个代理数量后,将不再拉取新代理,防止检测存量ip消耗资源过多。 114 | 115 | """代理池是sorted set结构,键是ip,值是该ip最后一次的检测时间戳。一轮一轮的扫描,检测到存量代理ip的最后一次检测时间离现在超过这个时间就重新检测,否则此轮不检测此代理, 116 | MAX_SECONDS_MUST_CHECK_AGAIN 的值要适当,过大会导致检测不及时,取出来后使用时成功率变低;过小会导致检测存量代理ip的密度过大,当存量代理太多的时候,会导致cpu消耗高。 117 | 118 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 2 REQUESTS_TIMEOUT = 1, 则会导致数据库检测及时,并且都是优质代理ip,但存量数量会有所减少(但数量还是秒杀任意收费代理),成功率和响应时间很好。 119 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 10 REQUESTS_TIMEOUT = 5, 这个是比较均衡的配置,兼容数量和质量。 120 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 18 REQUESTS_TIMEOUT = 10, 这个可以造成数据库中存量ip多,但有些代理ip响应时间长,随机使用成功率也会有所降低。 121 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 30 REQUESTS_TIMEOUT = 20, 这样数量非常多。 122 | 123 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 1 REQUESTS_TIMEOUT = 40,这种配置就相当不好了,会造成存量大质量差,但又想检测密度高,会造成cpu消耗高。 124 | 建议MAX_SECONDS_MUST_CHECK_AGAIN是REQUESTS_TIMEOUT的 1-2 倍,可以根据自己要数量大自己重试还是实时必须响应速度快进行不同的配置调优。 125 | 126 | """ 127 | MAX_SECONDS_MUST_CHECK_AGAIN = 2 128 | REQUESTS_TIMEOUT = 1 # 请求响应时间超过这个值,视为废物代理。 129 | FLASK_PORT = 6795 # 代理ip获取的接口 130 | PROXY_KEY_IN_REDIS_DEFAULT = 'proxy_free' # 默认的redis sorted set键,指的是如果你不在ProxyCollector实例化时候亲自指定键的名字(主要是为了一次启动实现维护多个redis代理池)。 131 | ``` 132 | 133 | 134 | ### 代理池维护的图片 135 | ``` 136 | 代理池是sorted set结构,元素是代理ip本身,评分是该代理ip的最后一次的检测时间。 137 | 138 | 流程是: 139 | 140 | 1)有专门的n个独立线程去监控每个代理平台的页面,同时支持了分页监控。按照ProxyCollector对象的设置的时间来进行多久重新拉取一次代理ip,解析得到代理ip列表。 141 | 142 | 2)使用了专门的线程池检测解析得到的代理ip列表,有效的跟新时间戳放到数据库,无效的丢弃。 143 | 144 | 3)对于存量ip,检测完一轮后,休息1秒,然后进行下一轮扫描需要被重新检测的代理ip,有专门的线程池检测存量代理ip。 145 | 如果一个存量代理的最后一次检测时间与当前时间差超过了 MAX_SECONDS_MUST_CHECK_AGAIN 则会重新检测, 146 | 如果检测没有失效,则更新检测的时间戳为当前时间;如果检测失效了则删除。请求检测的requests timeout时间是使用 REQUESTS_TIMEOUT。 147 | 一直高速循环检测。 148 | 149 | ``` 150 | ![Image text](https://i.niupic.com/images/2020/06/18/8hbZ.png) 151 | 152 | 153 | ### 随机统计检测代理池的质量 154 | ``` 155 | 这是设置 MAX_SECONDS_MUST_CHECK_AGAIN=2 REQUESTS_TIMEOUT=1 配置的代理池维护的,然后取出来的随机测试结果。 156 | 可以发现平均响应时间是1.5秒,只请求1次不做重试就成功的概率是98.5%。 157 | 如果重试两次,可以保证成功率达到99.9%,这成功率足够可以秒杀任意收费代理。 158 | 159 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=2 REQUESTS_TIMEOUT=1 FLASK_PORT=6795 160 | ``` 161 | ![Image text](https://i.niupic.com/images/2020/06/18/8hbY.png) 162 | 163 | 164 | 165 | 166 | Keywords: proxy,proxy_pool 167 | Platform: all 168 | Classifier: Development Status :: 4 - Beta 169 | Classifier: Operating System :: OS Independent 170 | Classifier: Intended Audience :: Developers 171 | Classifier: License :: OSI Approved :: BSD License 172 | Classifier: Programming Language :: Python 173 | Classifier: Programming Language :: Python :: Implementation 174 | Classifier: Programming Language :: Python :: 3.6 175 | Classifier: Topic :: Software Development :: Libraries 176 | Description-Content-Type: text/markdown 177 | -------------------------------------------------------------------------------- /proxypool_framework.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | proxypool_framework/__init__.py 4 | proxypool_framework/functions_of_get_https_proxy_from_websites.py 5 | proxypool_framework/proxy_collector.py 6 | proxypool_framework/proxy_pool_config.py 7 | proxypool_framework.egg-info/PKG-INFO 8 | proxypool_framework.egg-info/SOURCES.txt 9 | proxypool_framework.egg-info/dependency_links.txt 10 | proxypool_framework.egg-info/requires.txt 11 | proxypool_framework.egg-info/top_level.txt 12 | proxypool_framework/contrib/__init__.py 13 | proxypool_framework/contrib/proxy_client.py 14 | proxypool_framework/contrib/user_agents.py 15 | proxypool_framework/tests/__init__.py 16 | proxypool_framework/tests/reduce2.py 17 | proxypool_framework/tests/test_rate_of_success.py 18 | proxypool_framework/tests/test_usefull_proxy.py -------------------------------------------------------------------------------- /proxypool_framework.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /proxypool_framework.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | redis2 2 | nb_log 3 | threadpool_executor_shrink_able 4 | decorator_libs 5 | tornado 6 | db_libs 7 | requests 8 | urllib3 9 | -------------------------------------------------------------------------------- /proxypool_framework.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | proxypool_framework 2 | -------------------------------------------------------------------------------- /proxypool_framework/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | max_line_length = 500 5 | -------------------------------------------------------------------------------- /proxypool_framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/proxypool_framework/__init__.py -------------------------------------------------------------------------------- /proxypool_framework/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/proxypool_framework/contrib/__init__.py -------------------------------------------------------------------------------- /proxypool_framework/contrib/proxy_client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 改版包装requests的Session类,主要使用的是设计模式的代理模式(不是代理ip), 4 | 1、支持每次情趣和重试都自动切换代理ip,可以从http服务或者redis获取代理ip 5 | 2、支持3种类型的cookie添加 6 | 3、支持长会话,保持cookie状态 7 | 4、支持一键设置requests请求重试次数,确保请求成功,默认重试一次。 8 | 5、记录下当天的请求到文件,方便统计,同时开放了日志级别设置参数,用于禁止日志。 9 | 6、从使用requests修改为使用ProxyClient门槛很低,三方包的request方法和此类的方法入参和返回完全100%保持了一致。 10 | 11 | 12 | user agent 大全 github https://github.com/tamimibrahim17/List-of-user-agents/blob/master/user-agent.py 13 | user agent 网址 http://www.useragentstring.com/pages/useragentstring.php?name=Firefox 14 | """ 15 | import json 16 | import random 17 | import copy 18 | import sys 19 | import warnings 20 | from typing import Union 21 | import time 22 | from urllib.parse import quote 23 | 24 | from db_libs.redis_lib import redis2_from_url 25 | import requests 26 | import urllib3 27 | # from fake_useragent import UserAgent 28 | from nb_log import LoggerLevelSetterMixin, LoggerMixinDefaultWithFileHandler 29 | from threadpool_executor_shrink_able import ThreadPoolExecutorShrinkAble 30 | 31 | from proxypool_framework.contrib.user_agents import pc_ua_lists, mobile_ua_lists 32 | 33 | warnings.simplefilter('ignore', category=urllib3.exceptions.InsecureRequestWarning) 34 | 35 | 36 | class HttpStatusError(Exception): 37 | def __init__(self, http_status_code): 38 | super().__init__(f'请求返回的状态码是{http_status_code}') 39 | 40 | 41 | class ProxyClient(LoggerMixinDefaultWithFileHandler, LoggerLevelSetterMixin): 42 | 43 | def __init__(self, flask_addr='127.0.0.1:6795', redis_url='redis://:@', redis_proxy_key='proxy_free', 44 | is_priority_get_proxy_from_redis=True, is_use_proxy=True, ua=None, default_use_pc_ua=True, 45 | is_change_ua_every_request=False, random_ua_list: list = None, 46 | request_retry_times=2, purpose='', 47 | white_list_http_status_code: Union[list, tuple] = (200,)): 48 | """ 49 | :param flask_addr: 50 | :param redis_url: 51 | :param redis_proxy_key: 52 | :param is_priority_get_proxy_from_redis: 是否优先使用redis。因为也可以从api获取ip。 53 | :param is_use_proxy: 是否使用代理ip来请求。 54 | :param ua: 55 | :param default_use_pc_ua: 56 | :param is_change_ua_every_request: 57 | :param random_ua_list: 58 | :param request_retry_times: 59 | :param purpose: 请求用途 60 | :param white_list_http_status_code: 白名单状态码,例如403虽然请求没报错,但状态码不对,可以自动重试。例如假如206是正常现象,可以把206添加到白名单。 61 | """ 62 | self._flask_addr = flask_addr 63 | self._redis_url = redis_url 64 | self._redis_proxy_key = redis_proxy_key 65 | self._is_priority_get_proxy_from_redis = is_priority_get_proxy_from_redis 66 | self._is_use_proxy = is_use_proxy 67 | 68 | default_ua = ( 69 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36' if default_use_pc_ua else 70 | 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Mobile Safari/537.36') 71 | self._ua = ua if ua else default_ua 72 | self._is_change_ua_every_request = is_change_ua_every_request 73 | self._random_ua_list = random_ua_list 74 | if is_change_ua_every_request and not random_ua_list: 75 | self._random_ua_list = pc_ua_lists if default_use_pc_ua else mobile_ua_lists 76 | self.ss = requests.Session() 77 | self._max_request_retry_times = request_retry_times 78 | self._white_list_http_status_code = list(white_list_http_status_code) 79 | self._purpose = purpose 80 | self.prxoy_from_info = '' 81 | if is_use_proxy: 82 | if is_priority_get_proxy_from_redis: 83 | self.prxoy_from_info = f'从redis {redis_url} {redis_proxy_key} 获取的代理' 84 | else: 85 | self.prxoy_from_info = f'从flask {flask_addr} 获取的代理' 86 | else: 87 | self.prxoy_from_info = f'设置为了不使用代理' 88 | 89 | def __add_ua_to_headers(self, headers): 90 | # noinspection PyDictCreation 91 | if not headers: 92 | headers = dict() 93 | headers['user-agent'] = self._ua 94 | else: 95 | if 'user-agent' not in headers and 'User-Agent' not in headers: 96 | headers['user-agent'] = self._ua 97 | if self._is_change_ua_every_request and self._random_ua_list: 98 | headers['user-agent'] = random.choice(self._random_ua_list) 99 | headers.update({'Accept-Language': 'zh-CN,zh;q=0.8'}) 100 | return headers 101 | 102 | def get_cookie_jar(self): 103 | """返回cookiejar""" 104 | return self.ss.cookies 105 | 106 | def get_cookie_dict(self): 107 | """返回cookie字典""" 108 | return self.ss.cookies.get_dict() 109 | 110 | def get_cookie_str(self): 111 | """返回cookie字典""" 112 | cookie_str = '' 113 | for cookie_item in self.get_cookie_dict().items(): 114 | cookie_str += cookie_item[0] + '=' + cookie_item[1] + ';' 115 | return cookie_str[:-1] 116 | 117 | def add_cookies(self, cookies: Union[str, dict]): 118 | """ 119 | :param cookies: 浏览器复制的cookie字符串或字典类型或者CookieJar类型 120 | :return: 121 | """ 122 | cookies_dict = dict() 123 | if not isinstance(cookies, (str, dict,)): 124 | raise TypeError('传入的cookie类型错误') 125 | if isinstance(cookies, str): 126 | cookie_pairs = cookies.split('; ') 127 | for cookie_pair in cookie_pairs: 128 | k, v = cookie_pair.split('=', maxsplit=1) 129 | cookies_dict[k] = v 130 | if isinstance(cookies, (dict,)): 131 | cookies_dict = cookies 132 | self.ss.cookies = requests.sessions.merge_cookies(self.ss.cookies, cookies_dict) 133 | 134 | @staticmethod 135 | def __get_full_url_with_params(url, params): 136 | """ 137 | 主要是用来在发送请求前记录日志,请求了什么url。 138 | :param url: 139 | :param params: 140 | :return: 141 | """ 142 | params = {} if params is None else params 143 | url_params_str = '' 144 | # print(params) 145 | for k, v in params.items(): 146 | # print(k,v) 147 | url_params_str += f'{k}={v}&' 148 | # url = urllib.parse.quote(url) 149 | return f'{url}?{quote(url_params_str)}' 150 | 151 | def __enter__(self): 152 | return self 153 | 154 | def __exit__(self, exc_type, exc_val, exc_tb): 155 | # print('关闭close') 156 | self.ss.close() 157 | return False 158 | 159 | def get_a_proxy(self): 160 | proxy_dict = None 161 | if self._is_use_proxy: 162 | if self._is_priority_get_proxy_from_redis: 163 | proxy_dict = json.loads(random.choice(redis2_from_url(self._redis_url).zrevrange('proxy_free', 0, 30))) 164 | else: 165 | proxy_dict = json.loads(self.ss.request('get', f'http://{self._flask_addr}/get_a_proxy/30?u=user2&p=pass2', ).text) 166 | proxy_dict['http'] = proxy_dict['https'].replace('https', 'http') 167 | # self.logger.debug(proxy_dict) 168 | return proxy_dict 169 | 170 | # noinspection PyProtectedMember 171 | def request(self, method: str, url: str, verify: bool = None, 172 | timeout: Union[int, float, tuple] = (5, 20), params: dict = None, 173 | headers: dict = None, cookies: dict = None, **kwargs): 174 | """ 175 | 使用指定名字的代理请求,从_proxy_name读取,当请求出错时候轮流使用各种代理ip。 176 | :param method: 177 | :param url: 178 | :param verify: 179 | :param timeout: 180 | :param headers: 181 | :param cookies: 182 | :param params: get传参的字典 183 | :param kwargs :可接受一切requests.request方法中的参数 184 | :return: 185 | """ 186 | # self.logger.debug(locals()) 187 | key_word_args = copy.copy(locals()) 188 | key_word_args.pop('self') 189 | key_word_args.pop('kwargs') 190 | key_word_args.pop('headers') 191 | key_word_args.update(kwargs) 192 | resp = None 193 | # self.logger.debug('starting {} this url --> '.format(method) + url) 194 | # print(key_word_args) 195 | exception_request = None 196 | t_start_for_all_retry = time.time() 197 | 198 | # 方向定位到发出request请求的文件行,点击可跳转到该处。 199 | # 获取被调用函数在被调用时所处代码行数 200 | line = sys._getframe().f_back.f_lineno 201 | # 获取被调用函数所在模块文件名 202 | file_name = sys._getframe(1).f_code.co_filename 203 | sys.stdout.write( 204 | f'"{file_name}:{line}" {time.strftime("%H:%M:%S")} \033[0;30;47m 这一行触发 RequestClient 发送 {method} 请求此网址 \033[0m {self.__get_full_url_with_params(url, params)} \n') # 36 93 96 94 101 205 | headers_replace = self.__add_ua_to_headers(headers) 206 | for i in range(self._max_request_retry_times + 1): 207 | t_start = time.time() 208 | try: 209 | resp = self.ss.request(**key_word_args, proxies=self.get_a_proxy(), headers=headers_replace) 210 | time_spend = round(time.time() - t_start, 2) 211 | resp.time_spend = time_spend 212 | resp.ts = time_spend # 简写 213 | log_str = f'{self._purpose} {self.prxoy_from_info} request响应状态: [{i} {method} {resp.status_code} {time_spend:>3.2f} {resp.is_redirect} {resp.text.__len__():>8d}] ----->\033[0m {resp.url}\033[0m' 214 | if resp.status_code in self._white_list_http_status_code: 215 | self.logger.debug(log_str) 216 | else: 217 | self.logger.warning(log_str) 218 | if resp.status_code not in self._white_list_http_status_code and i < self._max_request_retry_times + 1: 219 | raise HttpStatusError(resp.status_code) 220 | if i != 0: 221 | pass 222 | break 223 | except Exception as e: 224 | exception_request = e 225 | if i != self._max_request_retry_times: 226 | msg_level = 30 227 | else: 228 | msg_level = 40 229 | self.logger.log(msg_level, 230 | f'{self._purpose} {self.prxoy_from_info} ProxyClient内部第{i}次请求出错,此次浪费时间[{round(time.time() - t_start, 2)}],' 231 | f'{i + 1}次错误总浪费时间为{round(time.time() - t_start_for_all_retry, 2)},再重试一次,原因是:{type(e)} {e}') 232 | if resp is not None: # 如也是false,但不是none 233 | return resp 234 | else: 235 | raise exception_request 236 | 237 | def pressure_test(self, *args, threads_num=50, is_print_resp=False, **kwargs): 238 | """ 239 | 压力测试 240 | :return: 241 | """ 242 | pool = ThreadPoolExecutorShrinkAble(threads_num) 243 | 244 | def __pressure_test_url(): 245 | resp = self.request(*args, **kwargs) 246 | if is_print_resp: 247 | print(resp.text) 248 | 249 | while 1: 250 | pool.submit(__pressure_test_url) 251 | 252 | def statistic_rate_of_sucess(self): 253 | suceess_count = 0 254 | total_count = 0 255 | total_request_time = 0 256 | 257 | while 1: 258 | t_start = time.time() 259 | for j in range(0, self._max_request_retry_times + 1): 260 | pr = self.get_a_proxy() 261 | try: 262 | resp = requests.get('https://ydgf.sohu.com/schedule/index.json', proxies=pr, timeout=5) 263 | self.logger.info(f'重试 {j} 次请求成功, 消耗时间 {round(time.time() - t_start, 2)}, 代理是 \033[0;41m{pr}\033[0m ,结果长度是 {len(resp.text)}') 264 | suceess_count += 1 265 | total_request_time += time.time() - t_start 266 | break 267 | # print(resp.text[:10]) 268 | except Exception as e: 269 | if j == self._max_request_retry_times: 270 | self.logger.warning(f'重试了 {self._max_request_retry_times}次后仍然失败, 消耗时间{round(time.time() - t_start, 2)}, ' 271 | f' 代理是 \033[0;41m{pr}\033[0m,错误类型是 {type(e)}') 272 | 273 | total_count += 1 274 | if total_count % 10 == 0 and total_count: 275 | self.logger.debug(f'当前请求总次数是 {total_count}, 成功次数是 {suceess_count} ,成功率是 {round((suceess_count / total_count) * 100, 3)}%, ' 276 | f'平均响应时间 {round(total_request_time / suceess_count, 2)}') 277 | 278 | 279 | if __name__ == '__main__': 280 | # with ProxyClient(is_priority_get_proxy_from_redis=False) as pc: 281 | # pc.request('get', 'https://www.baidu.com') 282 | # pc.request('get', 'https://www.baidu.com') 283 | # ProxyClient().pressure_test('get', 'https://www.baidu.com/content-search.xml', threads_num=200, ) 284 | ProxyClient().statistic_rate_of_sucess() 285 | -------------------------------------------------------------------------------- /proxypool_framework/functions_of_get_https_proxy_from_websites.py: -------------------------------------------------------------------------------- 1 | # noinspection PyUnresolvedReferences 2 | import json 3 | # noinspection PyUnresolvedReferences 4 | import random 5 | from functools import wraps 6 | import re 7 | import time 8 | from typing import List 9 | # noinspection PyUnresolvedReferences 10 | import nb_log 11 | from threadpool_executor_shrink_able import BoundedThreadPoolExecutor 12 | import requests 13 | 14 | from proxypool_framework.proxy_pool_config import REDIS_CLIENT 15 | 16 | logger_error_for_pull_ip = nb_log.LogManager('logger_error_for_pull_ip').get_logger_and_add_handlers(log_filename='logger_error_for_pull_ip.log') 17 | logger_normol_for_pull_ip = nb_log.LogManager('logger_normol_for_pull_ip').get_logger_and_add_handlers(log_filename='logger_normol_for_pull_ip.log') 18 | 19 | 20 | def _request_use_proxy(method, url, headers=None): 21 | """ 22 | 有些代理获取网站本身就反扒,这样来请求。 23 | :param method: 24 | :param url: 25 | :param headers: 26 | :return: 27 | """ 28 | headers = headers or {} 29 | if 'User-Agent' not in headers: 30 | headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko)' 31 | exceptx = None 32 | for i in range(10): 33 | try: 34 | proxy_list_in_db = REDIS_CLIENT.zrevrange('proxy_free', 0, 50) 35 | if not proxy_list_in_db: 36 | logger_error_for_pull_ip.warning('proxy_free 键是空的,将不使用代理ip请求三方代理网站') 37 | proxies = None 38 | else: 39 | proxies_str = random.choice(proxy_list_in_db) 40 | proxies = json.loads(proxies_str) 41 | proxies['http'] = proxies['https'].replace('https', 'http') 42 | if i % 2 == 0: 43 | proxies = None 44 | return requests.request(method, url, headers=headers, proxies=proxies) 45 | except Exception as e: 46 | time.sleep(1) 47 | exceptx = e 48 | # print(e) 49 | 50 | raise IOError(f'请求10次了还错误 {exceptx}') 51 | 52 | def _request_use_proxy222(method, url, headers=None): 53 | from mtfy_commons import RequestClient 54 | return RequestClient(RequestClient.ZDAYE).request(method,url,headers==headers) 55 | 56 | 57 | def _check_ip_list(proxy_list: List[str]): 58 | print(proxy_list) 59 | 60 | def __check_a_ip_str(proxy_str): 61 | proxies = {'https': f'https://{proxy_str}', 'http': f'http://{proxy_str}'} 62 | try: 63 | requests.get('https://www.baidu.com/content-search.xml', proxies=proxies, timeout=10, verify=False) 64 | print(f'有效 {proxies}') 65 | except Exception as e: 66 | print(f'无效 {proxies} {type(e)}') 67 | 68 | pool = BoundedThreadPoolExecutor(50) 69 | [pool.submit(__check_a_ip_str, pr) for pr in proxy_list] 70 | 71 | 72 | def _ensure_proxy_list_is_not_empty_deco(fun): 73 | """ 74 | 亲测有时候页面请求没报错,但解析为空,但换代理ip请求可以得到ip列表。 75 | :param fun: 76 | :return: 77 | """ 78 | 79 | @wraps(fun) 80 | def __ensure_proxy_list_is_not_empty_deco(*args, **kwargs): 81 | for i in range(7): 82 | result = fun(*args, **kwargs) 83 | if result: 84 | if i != 0: 85 | pass 86 | logger_error_for_pull_ip.error(f'第 {i} 次 {fun.__name__} 入参 【{args} {kwargs}】 获取代理ip列表才正常') 87 | logger_normol_for_pull_ip.debug(f'第 {i} 次 {fun.__name__} 入参 【{args} {kwargs}】 获取代理ip列表正常,个数是 {len(result)}') 88 | return result 89 | else: 90 | time.sleep(1) 91 | logger_error_for_pull_ip.error(f'重试了 {7} 次 {fun.__name__} 入参 【{args} {kwargs}】 获取代理ip失败,请检查代理网站有无反扒或网站有无改版') 92 | return [] 93 | 94 | return __ensure_proxy_list_is_not_empty_deco 95 | 96 | 97 | @_ensure_proxy_list_is_not_empty_deco 98 | def get_https_proxies_list_from_xici_by_page(p=1): 99 | """ 100 | 十分垃圾,中等偏下。 101 | :param p: 102 | :return: 103 | """ 104 | resp = _request_use_proxy('get', f'https://www.xicidaili.com/wn/{p}') 105 | ip_port_list = re.findall(r'alt="Cn" />\s*?(.*?)\s*?(.*?)', resp.text) 106 | return [f'{ip_port[0]}:{ip_port[1]}' for ip_port in ip_port_list] 107 | 108 | 109 | @_ensure_proxy_list_is_not_empty_deco 110 | def get_https_proxies_list_from_xila_https_by_page(p=1): 111 | """ 112 | 史上最好的免费代理网站 113 | :param p: 114 | :return: 115 | """ 116 | resp = _request_use_proxy('get', f"http://www.xiladaili.com/https/{p}") 117 | return re.findall(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}", resp.text) 118 | 119 | 120 | @_ensure_proxy_list_is_not_empty_deco 121 | def get_https_proxies_list_from_xila_gaoni_by_page(p=1): 122 | # 史上最好的免费代理网站 123 | resp = _request_use_proxy('get', f"http://www.xiladaili.com/gaoni/{p}") 124 | return re.findall(r"(.*?)\s*?.*?HTTPS代理", resp.text) 125 | 126 | 127 | def get_89ip_proxies_list(p=1, ): 128 | """ 129 | 抓的可用性代理不到10% 130 | :param p: 131 | 132 | :return: 133 | """ 134 | resp = _request_use_proxy('get', f'http://www.89ip.cn/index_{p}.html') 135 | ip_port_list = re.findall(r'\s*?\s*?(.*?)\s*?\s*?(.*?)', resp.text) 136 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 137 | 138 | 139 | def get_ip3366_proxies_list(p=1, ): 140 | """ 141 | 抓的https可用性代理可用性为0 142 | :param p: 143 | 144 | :return: 145 | """ 146 | resp = _request_use_proxy('get', f'http://www.ip3366.net/?stype=1&page={p}') 147 | ip_port_list = re.findall( 148 | r'''\s*?(.*?)\s*?(.*?)\s*?.*?\s*?HTTPS\s*?GET, POST''', 149 | resp.content.decode('gbk'), ) 150 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 151 | 152 | 153 | def get_kuaidailifree_proxies_list(p=1, ): 154 | """ 155 | 抓的快带理免费代理可用性为1% 156 | :param p: 157 | 158 | :return: 159 | """ 160 | resp = _request_use_proxy('get', f'https://www.kuaidaili.com/free/inha/{p}/') 161 | ip_port_list = re.findall(r'''\s*?(.*?)\s*?(.*?)''', 162 | resp.text, ) 163 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 164 | 165 | 166 | def get_66ip_proxies_list(area=1, ): 167 | """ 168 | 抓的66免费代理,无效198 ,有效9 169 | :param area:城市,1到30 170 | 171 | :return: 172 | """ 173 | resp = _request_use_proxy('get', f'http://www.66ip.cn/areaindex_{area}/1.html') 174 | ip_port_list = re.findall(r'''(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\d+)''', resp.text, ) 175 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 176 | 177 | 178 | def get_iphai_proxies_list(): 179 | """ 180 | 抓的iphai代理,有效10,无效20 181 | 182 | :return: 183 | """ 184 | resp = _request_use_proxy('get', f'http://www.iphai.com') 185 | ip_port_list = re.findall(r'''\s*?\s*?(.*?)\s*?\s*?(.*?)[\s\S]*?''', resp.text, ) 186 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 187 | 188 | 189 | # noinspection PyUnusedLocal 190 | def get_mimvp_proxies_list(p, ): 191 | """ 192 | 抓的米扑代理,端口是图片。懒的搞。 193 | 194 | :return: 195 | """ 196 | return [] 197 | 198 | 199 | def get_kxdaili_proxies_list(p=1, ): 200 | """ 201 | 开心代理,可用性是0 202 | :param p: 203 | 204 | :return: 205 | """ 206 | resp = _request_use_proxy('get', f'http://www.kxdaili.com/dailiip/1/{p}.html') 207 | ip_port_list = re.findall( 208 | r'''(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*?(\d+)[\s\S]*?HTTPS[\s\S]*?''', 209 | resp.text, ) 210 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 211 | 212 | 213 | def get_7yip_proxies_list(p=1, ): 214 | """ 215 | 齐云代理 有效2 无效58 216 | :param p: 217 | 218 | :return: 219 | """ 220 | resp = _request_use_proxy('get', f'https://www.7yip.cn/free/?action=china&page={p}') 221 | ip_port_list = re.findall( 222 | r'''(.*?)\s*?(.*?)[\s\S]*?HTTPS''', 223 | resp.text, ) 224 | return [f'{"".join(ip.split())}:{"".join(port.split())}' for ip, port in ip_port_list] 225 | 226 | 227 | # http://www.xsdaili.cn/dayProxy/ip/2207.html 228 | def get_xsdaili_proxies_list(): 229 | """ 230 | 小舒代理,可用1,不可用98 231 | 232 | :return: 233 | """ 234 | url = 'http://www.xsdaili.cn/dayProxy/ip/2207.html' # 测试时候要换成当天的页面url 235 | resp = _request_use_proxy('get', url) 236 | return [f'{ip}:{port}' for ip, port in 237 | re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)@HTTP', resp.text)] 238 | 239 | 240 | @_ensure_proxy_list_is_not_empty_deco 241 | def get_nima_proxies_list(p=1, gaoni_or_https='gaoni', ): 242 | """ 243 | 又是一个非常犀利的网站。 244 | gaoni 或https 245 | 有效81,无效93 246 | 247 | :return: 248 | """ 249 | resp = _request_use_proxy('get', f'http://www.nimadaili.com/{gaoni_or_https}/{p}/') 250 | return re.findall(r'(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}:\d{1,5})', resp.text) 251 | 252 | 253 | def get_proxylistplus_proxies_list(p=1, source='SSL-List', ): 254 | """ 255 | 全部无效 256 | SSL-List-1 只有1页。 257 | Fresh-HTTP-Proxy-List-2 有多页。 258 | :param p: 259 | :param source: 260 | 261 | :return: 262 | """ 263 | if source == 'SSL-List' and p > 1: 264 | return [] 265 | resp = _request_use_proxy('get', f'https://list.proxylistplus.com/{source}-{p}') 266 | return [f'{ip}:{port}' for ip, port in 267 | re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\s\S]*?(\d+)', resp.text)] 268 | 269 | 270 | def get_from_seofangfa(): 271 | """ 272 | 有效5 无效45 273 | 274 | :return: 275 | """ 276 | resp = _request_use_proxy('get', 'https://proxy.seofangfa.com/') 277 | return [f'{ip}:{port}' for ip, port in re.findall('(.*?)(.*?)', resp.text)] 278 | 279 | 280 | def get_from_superfastip(p=1, ): 281 | """ 282 | 还可以。 283 | 有效15 无效59 284 | 285 | :return: 286 | """ 287 | resp = _request_use_proxy('get', f'https://api.superfastip.com/ip/freeip?page={p}') 288 | return [f'{ip_port["ip"]}:{ip_port["port"]}' for ip_port in resp.json()['freeips']] 289 | 290 | 291 | @_ensure_proxy_list_is_not_empty_deco 292 | def get_from_jiangxianli(p=1): 293 | """ 294 | 国外代理多。非常犀利的网站。 295 | 有效33,无效27 296 | :param p: 297 | :return: 298 | """ 299 | resp = _request_use_proxy('get', f'https://ip.jiangxianli.com/?page={p}&protocol=http') 300 | return [f'{ip_port[0]}:{ip_port[1]}' for ip_port in re.findall(f'''data-ip="(.*?)" data-port="(\d+?)"''', resp.text)] 301 | 302 | 303 | if __name__ == '__main__': 304 | """ 305 | 一定要https的代理,只能访问http的一概不要。 306 | """ 307 | pass 308 | # _check_ip_list(get_https_proxies_list_from_xici_by_page(1)) 309 | # get_https_proxies_list_from_xila_https_by_page() 310 | # get_https_proxies_list_from_xila_gaoni_by_page() 311 | # print(get_89ip_proxies_list(p=4, )) 312 | for page in range(1, 5): 313 | pass 314 | # print(get_ip3366_proxies_list(p=page, )) 315 | # print(get_kuaidailifree_proxies_list(p=page, )) 316 | # print(get_66ip_proxies_list(area=page, )) 317 | # print(get_kxdaili_proxies_list(page, )) 318 | # print(get_7yip_proxies_list(page, )) 319 | # print(get_nima_proxies_list(page,)) 320 | # print(get_nima_proxies_list(page,'https', )) 321 | 322 | # print(get_proxylistplus_proxies_list(page,)) 323 | # print(get_proxylistplus_proxies_list(page, source='Fresh-HTTP-Proxy-List',)) 324 | 325 | # _check_ip_list(get_from_superfastip(page, )) 326 | 327 | # print(get_iphai_proxies_list()) 328 | # print(get_xsdaili_proxies_list()) 329 | # print(get_from_seofangfa()) 330 | _check_ip_list(get_from_jiangxianli()) 331 | -------------------------------------------------------------------------------- /proxypool_framework/proxy_collector.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from multiprocessing import Process 4 | # noinspection PyUnresolvedReferences 5 | from collections import defaultdict 6 | import urllib3 7 | # noinspection PyUnresolvedReferences 8 | import random 9 | from nb_log import LogManager 10 | # noinspection PyUnresolvedReferences 11 | from threadpool_executor_shrink_able import BoundedThreadPoolExecutor 12 | import decorator_libs 13 | 14 | from flask import Flask, request, make_response 15 | 16 | # noinspection PyUnresolvedReferences 17 | from proxypool_framework.proxy_pool_config import * 18 | from proxypool_framework.functions_of_get_https_proxy_from_websites import * 19 | 20 | warnings.simplefilter('ignore', category=urllib3.exceptions.InsecureRequestWarning) 21 | # CHECK_PROXY_VALIDITY_URL = 'https://www.baidu.com/content-search.xml' 22 | # CHECK_PROXY_VALIDITY_URL = 'https://www.baidu.com/favicon.ico' 23 | CHECK_PROXY_VALIDITY_URL = 'https://www.sohu.com/sohuflash_1.js' 24 | 25 | 26 | def create_app(): 27 | app = Flask(__name__) 28 | 29 | if REDIS_CLIENT.exists('proxy_user_config') is False: 30 | time_begin = int(time.time()) 31 | REDIS_CLIENT.hmset('proxy_user_config', { 32 | "user": json.dumps( 33 | {"password": "mtfy123", "max_count_per_day": 99999999, "max_use_seconds": 3600 * 24 * 3650, 34 | "use_begin_time": time_begin}), 35 | "user2": json.dumps( 36 | {"password": "pass2", "max_count_per_day": 99999999, "max_use_seconds": 3600 * 24 * 3650, 37 | "use_begin_time": time_begin}), 38 | "test": json.dumps( 39 | {"password": "test", "max_count_per_day": 1000, "max_use_seconds": 3600, 40 | "use_begin_time": time_begin}), 41 | }) 42 | 43 | @decorator_libs.FunctionResultCacher.cached_function_result_for_a_time(cache_time=60) 44 | def get_user_config_from_redis(): 45 | config_dict_bytes = REDIS_CLIENT.hgetall('proxy_user_config') 46 | print(config_dict_bytes) 47 | return {k.decode(): json.loads(v) for k, v in config_dict_bytes.items()} 48 | 49 | def auth_deco(v): 50 | @wraps(v) 51 | def _auth_deco(*args, **kwargs): 52 | users_config_dict = get_user_config_from_redis() 53 | if request.authorization: # 请求方式如 requests.get('http://127.0.0.1:6795/get_a_proxy/10',auth=('xiaomin','pass123456') 54 | username = request.authorization.username 55 | password = request.authorization.password 56 | else: # 为了方便浏览器地址栏测试,兼容在?后面传参。 57 | username = request.args.get('u', None) 58 | password = request.args.get('p', None) 59 | if username in users_config_dict and password == users_config_dict[username]['password']: 60 | if users_config_dict[username]['use_begin_time'] + users_config_dict[username]['max_use_seconds'] >= int(time.time()): 61 | return v(*args, **kwargs) 62 | else: 63 | return '免费试用时间已经结束,如需继续使用请联系管理员' 64 | else: 65 | print('账号密码不正确') 66 | return '账号密码不正确' 67 | 68 | return _auth_deco 69 | 70 | @app.route('/get_a_proxy/') 71 | @app.route('/get_a_proxy/') 72 | @auth_deco 73 | def get_a_proxy(random_num=30): 74 | """ 75 | :param random_num: 在最后一次检测可用性时间的最接近现在时间的多少个ip范围内随机返回一个ip.数字范围越小,最后检测时间的随机范围越靠近当前时间。 76 | 此代理池通用架构,可以实现超高的检测频率,基本上任何时刻每秒钟都有几十个比当前时间错小一两秒的。比当前时间戳小10秒的有几百个代理。 77 | 如果是自己从redis去代理ip,一定要使用这种方式,可使代理ip取出来时候的使用成功率提高。 78 | :return: 79 | """ 80 | random_num = 100 if random_num > 100 else random_num # 最大值100 81 | proxy_dict = json.loads(random.choice(REDIS_CLIENT.zrevrange(PROXY_KEY_IN_REDIS_DEFAULT, 0, random_num))) 82 | proxy_dict['http'] = proxy_dict['https'].replace('https', 'http') 83 | proxy_dict.pop('platform') 84 | proxy_str = json.dumps(proxy_dict, ensure_ascii=False) 85 | return proxy_str 86 | 87 | @app.route('/get_m_proxy/') 88 | @app.route('/get_m_proxy/') 89 | @auth_deco 90 | def get_many_proxy(bulk_num=10): 91 | proxy_list = REDIS_CLIENT.zrevrange(PROXY_KEY_IN_REDIS_DEFAULT, 0, bulk_num) 92 | proxy_list_hide_platform = list() 93 | for proxy_item in proxy_list: 94 | proxy_item_hide_platform = json.loads(proxy_item) 95 | proxy_item_hide_platform.pop('platform') 96 | proxy_item_hide_platform['http'] = proxy_item_hide_platform['https'].replace('https', 'http') 97 | proxy_list_hide_platform.append(proxy_item_hide_platform) 98 | return json.dumps(proxy_list_hide_platform) 99 | 100 | @app.route('/txt/') 101 | @auth_deco 102 | def get_proxy_with_newline(): 103 | """ 104 | Content-Type: text/plain; charset=utf-8 105 | :return: 106 | """ 107 | # sep = request.args.get('sep','
') 108 | # return sep.join([json.loads(proxy_str)['https'] for proxy_str in REDIS_CLIENT.zrevrange(PROXY_KEY_IN_REDIS_DEFAULT, 0, request.args.get('num',50))]) 109 | 110 | response = make_response('\r\n'.join([json.loads(proxy_str)['https'].replace('https://', '') for proxy_str in REDIS_CLIENT.zrevrange(PROXY_KEY_IN_REDIS_DEFAULT, 0, request.args.get('num', 50))])) 111 | response.headers['Content-Type'] = 'text/plain; charset=utf-8' 112 | return response 113 | 114 | @app.route('/st') 115 | def statistic_ip_count_by_platform_name(): 116 | platform___ip_count_map = defaultdict(int) 117 | ip__check_time_map = dict() 118 | for proxy_str, timestamp in REDIS_CLIENT.zscan_iter(PROXY_KEY_IN_REDIS_DEFAULT, ): 119 | proxy_dict = json.loads(proxy_str) 120 | ip__check_time_map[proxy_dict['https']] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) 121 | platform___ip_count_map[proxy_dict['platform']] += 1 122 | return json.dumps({'platform___ip_count_map': dict(sorted(platform___ip_count_map.items(), key=lambda x: x[1], reverse=True)), 123 | 'ip__check_time_map': dict(sorted(ip__check_time_map.items(), key=lambda x: x[1], reverse=True))}, ensure_ascii=False, indent=4) 124 | 125 | return app 126 | 127 | 128 | class ProxyCollector: 129 | pool_for_check_new = BoundedThreadPoolExecutor(100) 130 | pool_for_check_exists = BoundedThreadPoolExecutor(200) 131 | redis_key___has_start_check_exists_proxies_in_database_map = dict() 132 | logger_for_check_exists = LogManager('ProxyCollector-check_exists').get_logger_and_add_handlers( 133 | log_filename=f'ProxyCollector-check_exists.log', formatter_template=7) 134 | 135 | @staticmethod 136 | def check_proxy_validity(proxy_dict: dict): 137 | # noinspection PyUnusedLocal 138 | # noinspection PyBroadException 139 | try: 140 | # print(proxy_dict) 141 | requests.get(CHECK_PROXY_VALIDITY_URL, timeout=REQUESTS_TIMEOUT, proxies=proxy_dict, verify=False) 142 | return True 143 | except Exception as e: 144 | # print(e) 145 | return False 146 | 147 | def __init__(self, function_of_get_new_https_proxies_list_from_website, func_args=tuple(), func_kwargs: dict = None, 148 | platform_name='xx平台', redis_key=PROXY_KEY_IN_REDIS_DEFAULT, 149 | time_sleep_for_get_new_proxies=60, 150 | ): 151 | """ 152 | :param function_of_get_new_https_proxies_list_from_website: 獲取代理ip列表的函數,使用策略模式。 153 | :param redis_key 代理ip存放的redis键名,是个zset。 154 | :param time_sleep_for_get_new_proxies:每个单独的网页隔多久拉取一次。 155 | """ 156 | self.function_of_get_new_https_proxies_list_from_website = function_of_get_new_https_proxies_list_from_website 157 | self._func_args = func_args 158 | self._func_kwargs = func_kwargs or {} 159 | self.platform_name = platform_name 160 | self._redis_key = redis_key 161 | self._time_sleep_for_get_new_proxies = time_sleep_for_get_new_proxies 162 | self.logger = LogManager(f'ProxyCollector-{platform_name}').get_logger_and_add_handlers( 163 | log_filename=f'ProxyCollector-{platform_name}.log', formatter_template=7) 164 | 165 | def __check_a_new_proxy_and_add_to_database(self, proxy_dict: dict): 166 | if self.check_proxy_validity(proxy_dict): 167 | # print(type(proxy_dict)) 168 | self.logger.info(f'新增 {self.platform_name} 代理ip到数据库 {json.dumps(proxy_dict, ensure_ascii=False)}') 169 | REDIS_CLIENT.zadd(self._redis_key, json.dumps(proxy_dict, ensure_ascii=False), time.time()) 170 | else: 171 | self.logger.warning(f'新拉取的 {self.platform_name} 平台 代理无效') 172 | 173 | def _check_all_new_proxies(self, ): 174 | """ 175 | 并发检测新代理,有效的入库 176 | :return: 177 | """ 178 | exists_num_in_db = REDIS_CLIENT.zcard(self._redis_key) 179 | if exists_num_in_db < MAX_NUM_PROXY_IN_DB: 180 | self.pool_for_check_new.map(self.__check_a_new_proxy_and_add_to_database, 181 | [{'https': f'https://{ip}', 'platform': self.platform_name} for ip in 182 | self.function_of_get_new_https_proxies_list_from_website( 183 | *self._func_args, **self._func_kwargs)]) 184 | else: 185 | self.logger.critical( 186 | f'{self._redis_key} 键中的代理ip数量为 {exists_num_in_db},超过了制定阈值 {MAX_NUM_PROXY_IN_DB},此次循环暂时不拉取新代理') 187 | 188 | def __check_a_exists_proxy_and_drop_from_database(self, proxy_dict): 189 | if not self.check_proxy_validity(proxy_dict): 190 | self.logger_for_check_exists.warning(f'刪除数据库中失效代理ip {json.dumps(proxy_dict, ensure_ascii=False)}') 191 | REDIS_CLIENT.zrem(self._redis_key, json.dumps(proxy_dict, ensure_ascii=False)) 192 | else: 193 | self.logger_for_check_exists.info(f'数据库中的代理ip {json.dumps(proxy_dict, ensure_ascii=False)} 没有失效') 194 | REDIS_CLIENT.zadd(self._redis_key, json.dumps(proxy_dict, ensure_ascii=False), time.time()) # 更新检测时间。 195 | 196 | def _check_exists_proxies_in_database(self): 197 | """ 198 | 并发删除数据库中的失效代理。上次检测时间离当前超过了指定的秒数,就重新检测。 199 | :return: 200 | """ 201 | redis_proxies_list = REDIS_CLIENT.zrangebyscore(self._redis_key, 0, time.time() - MAX_SECONDS_MUST_CHECK_AGAIN) 202 | self.logger_for_check_exists.debug(f'需要检测的 {self._redis_key} 键中 {MAX_SECONDS_MUST_CHECK_AGAIN} ' 203 | f'秒内没检查过的 存量代理数量是 {len(redis_proxies_list)},总数量是 {REDIS_CLIENT.zcard(self._redis_key)}') 204 | self.pool_for_check_exists.map(self.__check_a_exists_proxy_and_drop_from_database, 205 | [json.loads(redis_proxy) for redis_proxy in redis_proxies_list]) 206 | 207 | @decorator_libs.synchronized 208 | def work(self): 209 | if not self.__class__.redis_key___has_start_check_exists_proxies_in_database_map.get(self._redis_key, False): 210 | self.__class__.redis_key___has_start_check_exists_proxies_in_database_map[self._redis_key] = True 211 | self.logger.warning(f'启动对数据库中 {self._redis_key} zset键 已有代理的检测') 212 | decorator_libs.keep_circulating(1, block=False)( 213 | self._check_exists_proxies_in_database)() 214 | decorator_libs.keep_circulating(self._time_sleep_for_get_new_proxies, block=False)( 215 | self._check_all_new_proxies)() 216 | 217 | 218 | def run_flask_app(): 219 | create_app().run(host='0.0.0.0', port=FLASK_PORT, threaded=True) # 简单的运行起来,一般链接redis使用ip就可以,最好不用接口来获取ip,接口控制10秒才能调用一次要。 220 | 221 | 222 | if __name__ == '__main__': 223 | """初次运行时候由于redis中没有代理ip做爬取第三方网站的引子,会被免费代理网站反爬,ip在前几分钟内会比较少。之后会增多,耐心等待。 224 | 225 | 启动方式种类: 226 | 1) 227 | export PYTHONPATH=/codes/proxypool_framework (指的是你的代码的位置,codes换成你的位置) # 这个原理就不需解释了,不知道PYTHONPATH是什么就太low了。 228 | 229 | python proxy_collector.py REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 230 | 或者在 proxy_pool_config.py 文件中把配置写好,就不需要命令行来传参了。直接 python proxy_collector.py 231 | 232 | 2)pycharm中打开此项目,可以直接右键点击run proxy_collector.py 233 | 234 | 3)pip install proxypool_framework 235 | python -m proxypool_framework.proxy_collector REDIS_URL=redis:// MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=12 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 236 | """ 237 | 238 | """启动代理池自动持续维护""" 239 | ProxyCollector(get_iphai_proxies_list, platform_name='iphai', time_sleep_for_get_new_proxies=70, ).work() 240 | ProxyCollector(get_from_seofangfa, platform_name='seofangfa', time_sleep_for_get_new_proxies=70, ).work() 241 | for p in range(1, 3): 242 | ProxyCollector(get_https_proxies_list_from_xici_by_page, (p,), platform_name='xici', 243 | time_sleep_for_get_new_proxies=70, redis_key='proxy_xici').work() # 这个是演示此框架是如何一次性启动维护多个代理池的,通过设置不同的redis_key来实现。 244 | ProxyCollector(get_89ip_proxies_list, (p,), platform_name='89ip', time_sleep_for_get_new_proxies=70, ).work() 245 | for p in range(1, 6): 246 | ProxyCollector(get_from_superfastip, (p,), platform_name='superfastip', time_sleep_for_get_new_proxies=65).work() 247 | for area in range(1, 30): # 有30个城市区域 248 | ProxyCollector(get_66ip_proxies_list, func_kwargs={'area': area}, platform_name='66ip', time_sleep_for_get_new_proxies=300, ).work() 249 | for p in range(1, 20): 250 | if p < 5: 251 | time_sleep_for_get_new_proxiesx = 30 252 | else: 253 | time_sleep_for_get_new_proxiesx = 300 254 | ProxyCollector(get_https_proxies_list_from_xila_https_by_page, func_args=(p,), platform_name='西拉', time_sleep_for_get_new_proxies=time_sleep_for_get_new_proxiesx, ).work() 255 | ProxyCollector(get_https_proxies_list_from_xila_gaoni_by_page, func_kwargs={'p': p}, platform_name='西拉', time_sleep_for_get_new_proxies=time_sleep_for_get_new_proxiesx, ).work() 256 | ProxyCollector(get_nima_proxies_list, (p, 'gaoni'), platform_name='nima', time_sleep_for_get_new_proxies=time_sleep_for_get_new_proxiesx).work() 257 | ProxyCollector(get_nima_proxies_list, (p, 'https'), platform_name='nima', time_sleep_for_get_new_proxies=time_sleep_for_get_new_proxiesx).work() 258 | ProxyCollector(get_from_jiangxianli, func_kwargs={'p': p}, platform_name='jiangxianli', time_sleep_for_get_new_proxies=time_sleep_for_get_new_proxiesx).work() 259 | 260 | """启动api""" 261 | 262 | # run_flask_app() 263 | Process(target=run_flask_app).start() # 主进程里面线程太多了,直接再当前进程启动falsk,访问flask接口响应时间不稳定。这只是简单部署,10秒种请求1次性能足够了。 264 | -------------------------------------------------------------------------------- /proxypool_framework/proxy_pool_config.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import json 3 | import sys 4 | import nb_log # noqa 5 | import redis2 # pip install redsi2 6 | 7 | # 可以直接修改这里的值为自己的最终值,也可以使用命令行方式覆盖这里的配置。命令行是为了可以快速的不修改代码配置而进行方便质量数量调优,和不改配置,多次启动分别生成优质代理池、普通代理池。 8 | REDIS_URL = 'redis://:@' # redis的url连接方式百度,可以指定db和ip和密码。 9 | # REDIS_URL = 'redis://:yMxsueZD9yx0AkfR@192.168.199.202:6543/7' # redis的url连接方式百度,可以指定db和ip和密码。 10 | MAX_NUM_PROXY_IN_DB = 1000 # redis中存在超过这个代理数量后,将不再拉取新代理,防止检测存量ip消耗资源过多。 11 | 12 | """代理池是sorted set结构,键是ip,值是该ip最后一次的检测时间戳。一轮一轮的扫描,检测到存量代理ip的最后一次检测时间离现在超过这个时间就重新检测,否则此轮不检测此代理, 13 | MAX_SECONDS_MUST_CHECK_AGAIN 的值要适当,过大会导致检测不及时,取出来后使用时成功率变低;过小会导致检测存量代理ip的密度过大,当存量代理太多的时候,会导致cpu消耗高。 14 | 15 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 2 REQUESTS_TIMEOUT = 1, 则数据库检测及时,并且都是响应速度快的优质代理ip,但存量数量会有所减少(但数量还是秒杀任意收费代理),成功率和响应时间很好。 16 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 10 REQUESTS_TIMEOUT = 5, 这个是比较均衡的配置,兼容数量和质量。 17 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 18 REQUESTS_TIMEOUT = 10, 这个可以造成数据库中存量ip多,但有些代理ip响应时间长,随机使用成功率也会有所降低。 18 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 30 REQUESTS_TIMEOUT = 20, 这样数量非常多。 19 | 20 | 如果使 MAX_SECONDS_MUST_CHECK_AGAIN = 1 REQUESTS_TIMEOUT = 40,这种配置就相当不好了,会造成存量大质量差,但又想检测密度高,会造成cpu消耗高。 21 | 建议MAX_SECONDS_MUST_CHECK_AGAIN是REQUESTS_TIMEOUT的 1-2 倍,可以根据自己要数量大自己重试还是实时必须响应速度快进行不同的配置调优。 22 | 23 | """ 24 | MAX_SECONDS_MUST_CHECK_AGAIN = 10 25 | REQUESTS_TIMEOUT = 5 # 请求响应时间超过这个值,视为废物代理。 26 | FLASK_PORT = 6795 # 代理ip获取的接口。 27 | PROXY_KEY_IN_REDIS_DEFAULT = 'proxy_free' # 默认的redis sorted set键,指的是如果你不在ProxyCollector实例化时候亲自指定键的名字(主要是为了一次启动实现维护多个redis代理池)。 28 | 29 | 30 | # python util.py REDIS_URL=redis://:123456@ MAX_NUM_PROXY_IN_DB=500 MAX_SECONDS_MUST_CHECK_AGAIN=8 REQUESTS_TIMEOUT=6 FLASK_PORT=6795 PROXY_KEY_IN_REDIS_DEFAULT=proxy_free 31 | for para in sys.argv[1:]: 32 | print(f'配置项: {para}') 33 | config_name = para.split('=')[0] 34 | if config_name in ['REDIS_URL', 'PROXY_KEY_IN_REDIS_DEFAULT']: 35 | globals()[config_name] = para.split('=')[1] 36 | if config_name in ['MAX_NUM_PROXY_IN_DB', 'MAX_SECONDS_MUST_CHECK_AGAIN', 'REQUESTS_TIMEOUT', 'FLASK_PORT','EXTRA_CHECK_PULL_NEW_IPS_PROCESS_NUM']: 37 | globals()[config_name] = int(para.split('=')[1]) 38 | 39 | globals_copy = copy.copy(globals()) 40 | for g_var in globals_copy: 41 | if g_var.isupper(): 42 | print(f'最终配置是 {g_var} : {globals()[g_var]}') 43 | 44 | REDIS_CLIENT = redis2.from_url(REDIS_URL) 45 | REDIS_CLIENT.ping() # 测试账号密码错误没有。 46 | 47 | # 运行时候的配置写入到redis,免得以后忘了,当初是什么配置参数维护的代理池。 48 | REDIS_CLIENT.hset('proxy_key_run_config', PROXY_KEY_IN_REDIS_DEFAULT, json.dumps({ 49 | 'MAX_NUM_PROXY_IN_DB': MAX_NUM_PROXY_IN_DB, 50 | 'MAX_SECONDS_MUST_CHECK_AGAIN': MAX_SECONDS_MUST_CHECK_AGAIN, 51 | 'REQUESTS_TIMEOUT': REQUESTS_TIMEOUT, # 请求响应时间超过这个值,视为废物代理。 52 | 'FLASK_PORT': FLASK_PORT, # 代理ip获取的接口。 53 | })) 54 | -------------------------------------------------------------------------------- /proxypool_framework/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/proxypool_framework/tests/__init__.py -------------------------------------------------------------------------------- /proxypool_framework/tests/reduce2.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | from proxypool_framework.proxy_pool_config import REDIS_CLIENT 5 | 6 | while 1: 7 | time.sleep(0.1) 8 | pr = random.choice(REDIS_CLIENT.hkeys('useful_proxy')).decode() 9 | 10 | if REDIS_CLIENT.hlen('useful_proxy') > random.randint(18,30): 11 | REDIS_CLIENT.hdel('useful_proxy',pr) 12 | 13 | # python -m proxypool_framework.tests.reduce2 -------------------------------------------------------------------------------- /proxypool_framework/tests/test_rate_of_success.py: -------------------------------------------------------------------------------- 1 | from proxypool_framework.contrib.proxy_client import ProxyClient 2 | from proxypool_framework.proxy_pool_config import REDIS_URL 3 | 4 | 5 | ProxyClient(redis_url=REDIS_URL).statistic_rate_of_sucess() -------------------------------------------------------------------------------- /proxypool_framework/tests/test_rate_of_success2.py: -------------------------------------------------------------------------------- 1 | from proxypool_framework.contrib.proxy_client import ProxyClient 2 | 3 | 4 | REDIS_URL = 'redis://:yMxsueZD9yx0AkfR@192.168.199.202:6543/7' 5 | ProxyClient(redis_url=REDIS_URL,redis_proxy_key='proxy_free_good').statistic_rate_of_sucess() -------------------------------------------------------------------------------- /proxypool_framework/tests/test_usefull_proxy.py: -------------------------------------------------------------------------------- 1 | from proxypool_framework.proxy_pool_config import REDIS_CLIENT 2 | import random 3 | import requests 4 | 5 | sucess_count = 0 6 | fail_count = 0 7 | for _ in range(100000): 8 | pr = random.choice(REDIS_CLIENT.hkeys('useful_proxy')).decode() 9 | # print(pr) 10 | 11 | try: 12 | requests.get('http://www.baidu.com',proxies={'https':f'https://{pr}','http':f'http://{pr}'},timeout=5) 13 | sucess_count += 1 14 | except Exception as e: 15 | print(e) 16 | fail_count += 1 17 | print(f'成功率 {sucess_count / (sucess_count + fail_count)}') 18 | -------------------------------------------------------------------------------- /proxypool_framework/tests/随机检测统计结果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/proxypool_framework/tests/随机检测统计结果.png -------------------------------------------------------------------------------- /requierments.txt: -------------------------------------------------------------------------------- 1 | redis2 2 | nb_log 3 | threadpool_executor_shrink_able 4 | decorator_libs 5 | tornado 6 | db_libs 7 | requests 8 | flask -------------------------------------------------------------------------------- /screen_short/代理池维护时候.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/screen_short/代理池维护时候.png -------------------------------------------------------------------------------- /screen_short/杀死: -------------------------------------------------------------------------------- 1 | ps -aux|grep proxy_collector|grep -v grep|awk '{print $2}' |xargs kill -9 -------------------------------------------------------------------------------- /screen_short/落地的代理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/proxypool_framework/368a2fdb5fc1f0d27c0a49abd108e14088d8e876/screen_short/落地的代理.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from pathlib import Path 3 | from setuptools import setup, find_packages 4 | 5 | # with open("README.md", "r",encoding='utf8') as fh: 6 | # long_description = fh.read() 7 | 8 | # filepath = ((Path(__file__).parent / Path('README.md')).absolute()).as_posix() 9 | filepath = 'README.md' 10 | print(filepath) 11 | 12 | setup( 13 | name='proxypool_framework', # 14 | version="2.1", 15 | description=( 16 | 'proxypool_framework 是万能通用代理池架构,实现核心代码很少只有87行,同时想扩展一个代理平台仅需要写三行代码。通用代理池附带20+ 免费网站代理' 17 | ), 18 | keywords=("proxy", "proxy_pool",), 19 | # long_description=open('README.md', 'r',encoding='utf8').read(), 20 | long_description_content_type="text/markdown", 21 | long_description=open(filepath, 'r', encoding='utf8').read(), 22 | # data_files=[filepath], 23 | author='bfzs', 24 | author_email='ydf0509@sohu.com', 25 | maintainer='ydf', 26 | maintainer_email='ydf0509@sohu.com', 27 | license='BSD License', 28 | packages=find_packages(), 29 | include_package_data=True, 30 | platforms=["all"], 31 | url='', 32 | classifiers=[ 33 | 'Development Status :: 4 - Beta', 34 | 'Operating System :: OS Independent', 35 | 'Intended Audience :: Developers', 36 | 'License :: OSI Approved :: BSD License', 37 | 'Programming Language :: Python', 38 | 'Programming Language :: Python :: Implementation', 39 | 'Programming Language :: Python :: 3.6', 40 | 'Topic :: Software Development :: Libraries' 41 | ], 42 | install_requires=['redis2', 43 | 'nb_log', 44 | 'threadpool_executor_shrink_able', 45 | 'decorator_libs', 46 | 'tornado', 47 | 'db_libs', 48 | 'requests', 49 | 'urllib3', 50 | ] 51 | ) 52 | """ 53 | 打包上传 54 | python setup.py sdist upload -r pypi 55 | 56 | 57 | python setup.py sdist & twine upload dist/proxypool_framework-2.1.tar.gz 58 | twine upload dist/* 59 | 60 | 61 | pip install proxypool_framework --upgrade -i https://pypi.org/simple # 及时的方式,不用等待 阿里云 豆瓣 同步 62 | """ 63 | --------------------------------------------------------------------------------