فهرست منبع

Merge pull request #2 from jhao104/master

New update
bobobo80 8 سال پیش
والد
کامیت
e0eca49c7f
24فایلهای تغییر یافته به همراه693 افزوده شده و 263 حذف شده
  1. 11 6
      Api/ProxyApi.py
  2. 6 1
      Config.ini
  3. 25 20
      DB/DbClient.py
  4. 54 43
      DB/MongodbClient.py
  5. 8 2
      DB/RedisClient.py
  6. 48 40
      DB/SsdbClient.py
  7. 39 0
      Dockerfile
  8. 21 0
      LICENSE
  9. 23 9
      Manager/ProxyManager.py
  10. 123 91
      README.md
  11. 3 1
      Run/main.py
  12. 63 0
      Schedule/ProxyCheck.py
  13. 18 11
      Schedule/ProxyRefreshSchedule.py
  14. 14 27
      Schedule/ProxyValidSchedule.py
  15. 17 0
      Util/EnvUtil.py
  16. 9 0
      Util/GetConfig.py
  17. 7 4
      Util/LogHandler.py
  18. 21 6
      Util/utilFunction.py
  19. 1 0
      _config.yml
  20. 173 0
      doc/introduce.md
  21. 9 0
      doc/release_notes.md
  22. 0 0
      log/__init__.py
  23. 0 1
      log/test.log
  24. 0 1
      requirements.txt

+ 11 - 6
Api/ProxyApi.py

@@ -15,9 +15,12 @@ __author__ = 'JHao'
 
 import sys
 
+sys.path.append('../')
+
 from flask import Flask, jsonify, request
+from Util.GetConfig import GetConfig
+
 
-sys.path.append('../')
 
 from Manager.ProxyManager import ProxyManager
 
@@ -26,9 +29,10 @@ app = Flask(__name__)
 
 api_list = {
     'get': u'get an usable proxy',
-    'refresh': u'refresh proxy pool',
+    # 'refresh': u'refresh proxy pool',
     'get_all': u'get all proxy from proxy pool',
     'delete?proxy=127.0.0.1:8080': u'delete an unable proxy',
+    'get_status': u'proxy statistics'
 }
 
 
@@ -54,7 +58,7 @@ def refresh():
 @app.route('/get_all/')
 def getAll():
     proxies = ProxyManager().getAll()
-    return jsonify(list(proxies))
+    return jsonify(proxies)
 
 
 @app.route('/delete/', methods=['GET'])
@@ -65,13 +69,14 @@ def delete():
 
 
 @app.route('/get_status/')
-def get_status():
-    status = ProxyManager().get_status()
+def getStatus():
+    status = ProxyManager().getNumber()
     return jsonify(status)
 
 
 def run():
-    app.run(host='0.0.0.0', port=5000)
+    config = GetConfig()
+    app.run(host=config.host_ip, port=config.host_port)
 
 if __name__ == '__main__':
     run()

+ 6 - 1
Config.ini

@@ -1,6 +1,6 @@
 [DB]
 ;Configure the database information
-;type: SSDB/REDIS
+;type: SSDB/REDIS/MONGODB if use redis, only modify the host port,the type should be SSDB
 type = SSDB
 host = localhost
 port = 8888
@@ -13,3 +13,8 @@ freeProxySecond = 1
 freeProxyThird  = 1
 freeProxyFourth = 1
 freeProxyFifth  = 1
+
+[HOST]
+; API接口配置 http://127.0.0.1:5051
+ip = 0.0.0.0
+port = 5010

+ 25 - 20
DB/DbClient.py

@@ -2,7 +2,7 @@
 # !/usr/bin/env python
 """
 -------------------------------------------------
-   File Name:     DbClient.py
+   File Name:    DbClient.py
    Description :  DB工厂类
    Author :       JHao
    date:          2016/12/2
@@ -31,13 +31,16 @@ class DbClient(object):
         useful_proxy_queue: 存放检验后的代理;
 
     抽象方法定义:
-        get: 随机返回一个代理;
-        put: 放回一个代理;
-        getvalue: 返回代理属性(一个计数器);
-        inckey: 修改代理属性计数器的值;
-        delete: 删除指定代理;
-        getAll: 返回所有代理;
-        changeTable: 切换 table or collection or hash;
+        get(proxy): 返回proxy的信息;
+        put(proxy): 存入一个代理;
+        pop(): 弹出一个代理
+        exists(proxy): 判断代理是否存在
+        getNumber(raw_proxy): 返回代理总数(一个计数器);
+        update(proxy, num): 修改代理属性计数器的值;
+        delete(proxy): 删除指定代理;
+        getAll(): 返回所有代理;
+        changeTable(name): 切换 table or collection or hash;
+
 
         所有方法需要相应类去具体实现:
             SSDB:SsdbClient.py
@@ -65,6 +68,8 @@ class DbClient(object):
             __type = "SsdbClient"
         elif "REDIS" == self.config.db_type:
             __type = "RedisClient"
+        elif "MONGODB" == self.config.db_type:
+            __type = "MongodbClient"
         else:
             pass
         assert __type, 'type error, Not support DB type: {}'.format(self.config.db_type)
@@ -72,32 +77,32 @@ class DbClient(object):
                                                           host=self.config.db_host,
                                                           port=self.config.db_port)
 
-    def get(self, **kwargs):
-        return self.client.get(**kwargs)
+    def get(self, key, **kwargs):
+        return self.client.get(key, **kwargs)
 
     def put(self, key, **kwargs):
         return self.client.put(key, **kwargs)
 
-    def getvalue(self, key, **kwargs):
-        return self.client.getvalue(key, **kwargs)
-
-    def pop(self, **kwargs):
-        return self.client.pop(**kwargs)
-
-    def inckey(self, key, value, **kwargs):
-        return self.client.inckey(key, value, **kwargs)
+    def update(self, key, value, **kwargs):
+        return self.client.update(key, value, **kwargs)
 
     def delete(self, key, **kwargs):
         return self.client.delete(key, **kwargs)
 
+    def exists(self, key, **kwargs):
+        return self.client.exists(key, **kwargs)
+
+    def pop(self, **kwargs):
+        return self.client.pop(**kwargs)
+
     def getAll(self):
         return self.client.getAll()
 
     def changeTable(self, name):
         self.client.changeTable(name)
 
-    def get_status(self):
-        return self.client.get_status()
+    def getNumber(self):
+        return self.client.getNumber()
 
 
 if __name__ == "__main__":

+ 54 - 43
DB/MongodbClient.py

@@ -1,63 +1,74 @@
 # coding: utf-8
-
-__author__ = 'Maps'
+"""
+-------------------------------------------------
+   File Name:    MongodbClient.py
+   Description :  封装mongodb操作
+   Author :       JHao netAir
+   date:          2017/3/3
+-------------------------------------------------
+   Change Activity:
+                   2017/3/3:
+                   2017/9/26:完成对mongodb的支持
+-------------------------------------------------
+"""
+__author__ = 'Maps netAir'
 
 from pymongo import MongoClient
-import random
-import json
 
 
 class MongodbClient(object):
+    def __init__(self, name, host, port):
+        self.name = name
+        self.client = MongoClient(host, port)
+        self.db = self.client.proxy
 
-	def __init__(self, name, host, port):
-		self.name = name
-		self.client = MongoClient(host, port)
-		self.db = self.client.proxy
-
-
-	def changeTable(self, name):
-		self.name = name
-
+    def changeTable(self, name):
+        self.name = name
 
-	def get(self):
-		proxy = self.getAll()
-		return random.choice(proxy) if proxy else None
+    def get(self, proxy):
+        data = self.db[self.name].find_one({'proxy': proxy})
+        return data['num'] if data != None else None
 
+    def put(self, proxy, num=1):
+        if self.db[self.name].find_one({'proxy': proxy}):
+            return None
+        else:
+            self.db[self.name].insert({'proxy': proxy, 'num': num})
 
-	def put(self, value):
-		if self.db[self.name].find_one({'proxy': value}):
-			return None
-		else:
-			self.db[self.name].insert({'proxy': value})
+    def pop(self):
+        data = list(self.db[self.name].aggregate([{'$sample': {'size': 1}}]))
+        if data:
+            data = data[0]
+            value = data['proxy']
+            self.delete(value)
+            return {'proxy': value, 'value': data['num']}
+        return None
 
+    def delete(self, value):
+        self.db[self.name].remove({'proxy': value})
 
-	def pop(self):
-		value = self.get()
-		if value:
-			self.delete(value)
-		return value
+    def getAll(self):
+        return {p['proxy']: p['num'] for p in self.db[self.name].find()}
 
+    def clean(self):
+        self.client.drop_database('proxy')
 
-	def delete(self, value):
-		self.db[self.name].remove({'proxy': value})
+    def delete_all(self):
+        self.db[self.name].remove()
 
+    def update(self, key, value):
+        self.db[self.name].update({'proxy': key}, {'$inc': {'num': value}})
 
-	def getAll(self):
-		return [p['proxy'] for p in self.db[self.name].find()]
+    def exists(self, key):
+        return True if self.db[self.name].find_one({'proxy': key}) != None else False
 
-
-	def clean(self):
-		self.client.drop_database('proxy')
-
-
-	def delete_all(self):
-		self.db[self.name].remove()
+    def getNumber(self):
+        return self.db[self.name].count()
 
 
 if __name__ == "__main__":
-	db = MongodbClient('first', 'localhost', 27017)
-	db.put('127.0.0.1:1')
-	db2 = MongodbClient('second', 'localhost', 27017)
-	db2.put('127.0.0.1:2')
-	db.clean()
-
+    db = MongodbClient('first', 'localhost', 27017)
+    # db.put('127.0.0.1:1')
+    # db2 = MongodbClient('second', 'localhost', 27017)
+    # db2.put('127.0.0.1:2')
+    print(db.pop())

+ 8 - 2
DB/RedisClient.py

@@ -6,11 +6,17 @@ self.name为Redis中的一个key
 2017/4/17 修改pop
 '''
 
+# ############################
+# 已弃用,
+# SsdbClient.py 支持redis
+##############################
+
 import json
 import random
 import redis
 import sys
 
+
 class RedisClient(object):
     """
     Reids client
@@ -41,7 +47,7 @@ class RedisClient(object):
             return rkey.decode('utf-8')
         else:
             return rkey
-        # return self.__conn.srandmember(name=self.name)
+            # return self.__conn.srandmember(name=self.name)
 
     def put(self, key):
         """
@@ -87,7 +93,7 @@ class RedisClient(object):
             return [key.decode('utf-8') for key in self.__conn.hgetall(self.name).keys()]
         else:
             return self.__conn.hgetall(self.name).keys()
-        # return self.__conn.smembers(self.name)
+            # return self.__conn.smembers(self.name)
 
     def get_status(self):
         return self.__conn.hlen(self.name)

+ 48 - 40
DB/SsdbClient.py

@@ -9,15 +9,17 @@
 -------------------------------------------------
    Change Activity:
                    2016/12/2:
-                   2017/04/26: 添加get_status方法获取hash长度
+                   2017/09/22: PY3中 redis-py返回的数据是bytes型
+                   2017/09/27: 修改pop()方法 返回{proxy:value}字典
 -------------------------------------------------
 """
 __author__ = 'JHao'
 
-from ssdb.connection import BlockingConnectionPool
-from ssdb import SSDB
+from Util import EnvUtil
+
+from redis.connection import BlockingConnectionPool
+from redis import Redis
 import random
-import json
 
 
 class SsdbClient(object):
@@ -25,8 +27,8 @@ class SsdbClient(object):
     SSDB client
 
     SSDB中代理存放的容器为hash:
-        原始代理存放在name为raw_proxy的hash中,key为代理的ip:port,value为None,以后扩展可能会加入代理属性;
-        验证后供flask使用的代理存放在name为useful_proxy的hash中,key为代理的ip:port,value为None,以后扩展可能会加入代理属性
+        原始代理存放在name为raw_proxy的hash中,key为代理的ip:port,value为None,以后扩展可能会加入代理属性;
+        验证后的代理存放在name为useful_proxy的hash中,key为代理的ip:port,value为一个计数,初始为1,每校验失败一次减1
 
     """
 
@@ -39,45 +41,30 @@ class SsdbClient(object):
         :return:
         """
         self.name = name
-        self.__conn = SSDB(connection_pool=BlockingConnectionPool(host=host, port=port))
+        self.__conn = Redis(connection_pool=BlockingConnectionPool(host=host, port=port))
 
-    def get(self):
+    def get(self, proxy):
         """
         get an item
-
-        从useful_proxy_queue随机获取一个可用代理, 使用前需要调用changeTable("useful_proxy_queue")
+        从hash中获取对应的proxy, 使用前需要调用changeTable()
+        :param proxy:
         :return:
         """
-        values = self.__conn.hgetall(name=self.name)
-        return random.choice(values.keys()) if values else None
+        data = self.__conn.hget(name=self.name, key=proxy)
+        if data:
+            return data.decode('utf-8') if EnvUtil.PY3 else data
+        else:
+            return None
 
-    def put(self, key):
+    def put(self, proxy, num=1):
         """
-        put an  item
-
         将代理放入hash, 使用changeTable指定hash name
-        :param key:
+        :param proxy:
+        :param num:
         :return:
         """
-        key = json.dump(key, ensure_ascii=False).encode('utf-8') if isinstance(key, (dict, list)) else key
-        return self.__conn.hincr(self.name, key, 1)
-        # return self.__conn.hset(self.name, value, None)
-
-    def getvalue(self, key):
-        value = self.__conn.hget(self.name, key)
-        return value if value else None
-
-    def pop(self):
-        """
-        pop an item
-
-        弹出一个代理, 使用changeTable指定hash name
-        :return:
-        """
-        key = self.get()
-        if key:
-            self.__conn.hdel(self.name, key)
-        return key
+        data = self.__conn.hincrby(self.name, proxy, num)
+        return data
 
     def delete(self, key):
         """
@@ -87,18 +74,39 @@ class SsdbClient(object):
         """
         self.__conn.hdel(self.name, key)
 
-    def inckey(self, key, value):
-        self.__conn.hincr(self.name, key, value)
+    def update(self, key, value):
+        self.__conn.hincrby(self.name, key, value)
+
+    def pop(self):
+        """
+        弹出一个代理
+        :return: dict {proxy: value}
+        """
+        proxies = self.__conn.hkeys(self.name)
+        if proxies:
+            proxy = random.choice(proxies)
+            value = self.__conn.hget(self.name, proxy)
+            self.delete(proxy)
+            return {'proxy': proxy.decode('utf-8') if EnvUtil.PY3 else proxy,
+                    'value': value.decode('utf-8') if EnvUtil.PY3 and value else value}
+        return None
+
+    def exists(self, key):
+        return self.__conn.hexists(self.name, key)
 
     def getAll(self):
-        return self.__conn.hgetall(self.name).keys()
+        item_dict = self.__conn.hgetall(self.name)
+        if EnvUtil.PY3:
+            return {key.decode('utf8'): value.decode('utf8') for key, value in item_dict.items()}
+        else:
+            return item_dict
 
-    def get_status(self):
+    def getNumber(self):
         """
         Return the number of elements in hash ``name``
         :return:
         """
-        return self.__conn.hsize(self.name)
+        return self.__conn.hlen(self.name)
 
     def changeTable(self, name):
         self.name = name

+ 39 - 0
Dockerfile

@@ -0,0 +1,39 @@
+FROM python:3.6
+
+WORKDIR /usr/src/app
+
+COPY . .
+
+ENV DEBIAN_FRONTEND noninteractive
+ENV TZ Asia/Shanghai
+
+RUN pip install --no-cache-dir -r requirements.txt && \
+	apt-get update && \
+	apt-get install -y --force-yes git make gcc g++ autoconf && apt-get clean && \
+	git clone --depth 1 https://github.com/ideawu/ssdb.git ssdb && \
+	cd ssdb && make && make install && cp ssdb-server /usr/bin && \
+	apt-get remove -y --force-yes git make gcc g++ autoconf && \
+	apt-get autoremove -y && \
+	rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
+	cp ssdb.conf /etc && cd .. && yes | rm -r ssdb && \
+
+	mkdir -p /var/lib/ssdb && \
+	sed \
+		-e 's@home.*@home /var/lib@' \
+		-e 's/loglevel.*/loglevel info/' \
+		-e 's@work_dir = .*@work_dir = /var/lib/ssdb@' \
+		-e 's@pidfile = .*@pidfile = /run/ssdb.pid@' \
+		-e 's@level:.*@level: info@' \
+		-e 's@ip:.*@ip: 0.0.0.0@' \
+		-i /etc/ssdb.conf && \
+
+	echo "# ! /bin/sh " > /usr/src/app/run.sh && \
+	echo "cd Run" >> /usr/src/app/run.sh && \
+	echo "/usr/bin/ssdb-server /etc/ssdb.conf &" >> /usr/src/app/run.sh && \
+	echo "python main.py" >> /usr/src/app/run.sh && \
+
+	chmod 777 run.sh
+
+EXPOSE 5010
+
+CMD [ "sh", "run.sh" ]

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 J_hao104
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 23 - 9
Manager/ProxyManager.py

@@ -13,6 +13,9 @@
 """
 __author__ = 'JHao'
 
+import random
+
+from Util import EnvUtil
 from DB.DbClient import DbClient
 from Util.GetConfig import GetConfig
 from Util.LogHandler import LogHandler
@@ -40,13 +43,16 @@ class ProxyManager(object):
             proxy_set = set()
             # fetch raw proxy
             for proxy in getattr(GetFreeProxy, proxyGetter.strip())():
-                if proxy.strip():
+                if proxy:
                     self.log.info('{func}: fetch proxy {proxy}'.format(func=proxyGetter, proxy=proxy))
                     proxy_set.add(proxy.strip())
 
             # store raw proxy
-            self.db.changeTable(self.raw_proxy_queue)
             for proxy in proxy_set:
+                self.db.changeTable(self.useful_proxy_queue)
+                if self.db.exists(proxy):
+                    continue
+                self.db.changeTable(self.raw_proxy_queue)
                 self.db.put(proxy)
 
     def get(self):
@@ -55,7 +61,13 @@ class ProxyManager(object):
         :return:
         """
         self.db.changeTable(self.useful_proxy_queue)
-        return self.db.get()
+        item_dict = self.db.getAll()
+        if item_dict:
+            if EnvUtil.PY3:
+                return random.choice(list(item_dict.keys()))
+            else:
+                return random.choice(item_dict.keys())
+        return None
         # return self.db.pop()
 
     def delete(self, proxy):
@@ -69,20 +81,22 @@ class ProxyManager(object):
 
     def getAll(self):
         """
-        get all proxy from pool
+        get all proxy from pool as list
         :return:
         """
         self.db.changeTable(self.useful_proxy_queue)
-        return self.db.getAll()
+        item_dict = self.db.getAll()
+        if EnvUtil.PY3:
+            return list(item_dict.keys()) if item_dict else list()
+        return item_dict.keys() if item_dict else list()
 
-    def get_status(self):
+    def getNumber(self):
         self.db.changeTable(self.raw_proxy_queue)
-        total_raw_proxy = self.db.get_status()
+        total_raw_proxy = self.db.getNumber()
         self.db.changeTable(self.useful_proxy_queue)
-        total_useful_queue = self.db.get_status()
+        total_useful_queue = self.db.getNumber()
         return {'raw_proxy': total_raw_proxy, 'useful_proxy': total_useful_queue}
 
 if __name__ == '__main__':
     pp = ProxyManager()
     pp.refresh()
-    print(pp.get_status())

+ 123 - 91
README.md

@@ -1,153 +1,185 @@
 
-爬虫代理IP
+爬虫IP代理池
 =======
-[![Build Status](https://travis-ci.org/jhao104/proxy_pool.svg?branch=master)](https://travis-ci.org/jhao104/proxy_pool)[![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](http://www.spiderpy.cn/) [![Requirements Status](https://requires.io/github/jhao104/proxy_pool/requirements.svg?branch=master)](https://requires.io/github/jhao104/proxy_pool/requirements/?branch=master)
+[![Build Status](https://travis-ci.org/jhao104/proxy_pool.svg?branch=master)](https://travis-ci.org/jhao104/proxy_pool)
+[![](https://img.shields.io/badge/Powered%20by-@j_hao104-green.svg)](http://www.spiderpy.cn/blog/)
+[![Requirements Status](https://requires.io/github/jhao104/proxy_pool/requirements.svg?branch=master)](https://requires.io/github/jhao104/proxy_pool/requirements/?branch=master)
+[![Packagist](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://github.com/jhao104/proxy_pool/blob/master/LICENSE)
+[![GitHub contributors](https://img.shields.io/github/contributors/jhao104/proxy_pool.svg)](https://github.com/jhao104/proxy_pool/graphs/contributors)
+[![](https://img.shields.io/badge/language-Python-green.svg)](https://github.com/jhao104/proxy_pool)
 
+    ______                        ______             _
+    | ___ \_                      | ___ \           | |
+    | |_/ / \__ __   __  _ __   _ | |_/ /___   ___  | |
+    |  __/|  _// _ \ \ \/ /| | | ||  __// _ \ / _ \ | |
+    | |   | | | (_) | >  < \ |_| || |  | (_) | (_) || |___
+    \_|   |_|  \___/ /_/\_\ \__  |\_|   \___/ \___/ \_____\
+                           __ / /
+                          /___ /
 
-> 在公司做分布式深网爬虫,搭建了一套稳定的代理池服务,为上千个爬虫提供有效的代理,保证各个爬虫拿到的都是对应网站有效的代理IP,从而保证爬虫快速稳定的运行,当然在公司做的东西不能开源出来。不过呢,闲暇时间手痒,所以就想利用一些免费的资源搞一个简单的代理池服务。
- 
+##### [介绍文档](https://github.com/jhao104/proxy_pool/blob/master/doc/introduce.md)
 
-### 1、问题
+* 支持版本: ![](https://img.shields.io/badge/Python-2.x-green.svg) ![](https://img.shields.io/badge/Python-3.x-blue.svg)
 
-* 代理IP从何而来?
+* 测试地址: http://123.207.35.36:5010 (单机勿压。感谢)
 
-  刚自学爬虫的时候没有代理IP就去西刺、快代理之类有免费代理的网站去爬,还是有个别代理能用。当然,如果你有更好的代理接口也可以自己接入。
-  
-  免费代理的采集也很简单,无非就是:访问页面页面 —> 正则/xpath提取 —> 保存
+### 下载安装
 
-* 如何保证代理质量?
+* 下载源码:
 
-  可以肯定免费的代理IP大部分都是不能用的,不然别人为什么还提供付费的(不过事实是很多代理商的付费IP也不稳定,也有很多是不能用)。所以采集回来的代理IP不能直接使用,可以写检测程序不断的去用这些代理访问一个稳定的网站,看是否可以正常使用。这个过程可以使用多线程或异步的方式,因为检测代理是个很慢的过程。
-
-* 采集回来的代理如何存储?
-
-  这里不得不推荐一个高性能支持多种数据结构的NoSQL数据库[SSDB](http://ssdb.io/docs/zh_cn/),用于替代Redis。支持队列、hash、set、k-v对,支持T级别数据。是做分布式爬虫很好中间存储工具。
+```shell
+git clone git@github.com:jhao104/proxy_pool.git
 
-* 如何让爬虫更简单的使用这些代理?
+或者直接到https://github.com/jhao104/proxy_pool 下载zip文件
+```
 
-  答案肯定是做成服务咯,python有这么多的web框架,随便拿一个来写个api供爬虫调用。这样有很多好处,比如:当爬虫发现代理不能使用可以主动通过api去delete代理IP,当爬虫发现代理池IP不够用时可以主动去refresh代理池。这样比检测程序更加靠谱。
+* 安装依赖:
 
-### 2、代理池设计
+```shell
+pip install -r requirements.txt
+```
 
-  代理池由四部分组成:
+* 配置Config.ini:
 
-* ProxyGetter:
+```shell
+# Config.ini 为项目配置文件
+# 配置DB
+type = SSDB       # 如果使用SSDB或redis数据库,均配置为SSDB
+host = localhost  # db host
+port = 8888       # db port
+name = proxy      # 默认配置
 
-  代理获取接口,目前有5个免费代理源,每调用一次就会抓取这个5个网站的最新代理放入DB,可自行添加额外的代理获取接口;
+# 配置 ProxyGetter
+freeProxyFirst  = 1  # 这里是启动的抓取函数,可在ProxyGetter/getFreeProxy.py 扩展
+freeProxySecond = 1
+....
 
-* DB:
+# 配置 HOST (api服务)
+ip = 127.0.0.1       # 监听ip,0.0.0.0开启外网访问
+port = 5010          # 监听端口
+# 上面配置启动后,代理api地址为 http://127.0.0.1:5010
 
-  用于存放代理IP,现在暂时只支持SSDB。至于为什么选择SSDB,大家可以参考这篇[文章](https://www.sdk.cn/news/2684),个人觉得SSDB是个不错的Redis替代方案,如果你没有用过SSDB,安装起来也很简单,可以参考[这里](https://github.com/jhao104/memory-notes/blob/master/SSDB/SSDB%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E8%AE%B0%E5%BD%95.md);
+```
 
-* Schedule:
+* 启动:
 
-  计划任务用户定时去检测DB中的代理可用性,删除不可用的代理。同时也会主动通过ProxyGetter去获取最新代理放入DB;
+```shell
+# 如果你的依赖已经安全完成并且具备运行条件,可以直接在Run下运行main.py
+# 到Run目录下:
+>>>python main.py
 
-* ProxyApi:
+# 如果运行成功你应该看到有4个main.py进程
 
-  代理池的外部接口,由于现在这么代理池功能比较简单,花两个小时看了下[Flask](http://flask.pocoo.org/),愉快的决定用Flask搞定。功能是给爬虫提供get/delete/refresh等接口,方便爬虫直接使用。
-<!--#### 功能图纸-->
-![设计](https://pic2.zhimg.com/v2-f2756da2986aa8a8cab1f9562a115b55_b.png)
+# 你也可以分别运行他们,
+# 依次到Api下启动ProxyApi.py,Schedule下启动ProxyRefreshSchedule.py和ProxyValidSchedule.py即可.
+```
 
-### 3、代码模块
+### 使用
 
-  Python中高层次的数据结构,动态类型和动态绑定,使得它非常适合于快速应用开发,也适合于作为胶水语言连接已有的软件部件。用Python来搞这个代理IP池也很简单,代码分为6个模块:
+  启动过几分钟后就能看到抓取到的代理IP,你可以直接到数据库中查看,推荐一个[SSDB可视化工具](https://github.com/jhao104/SSDBAdmin)。
 
-* Api:
+  也可以通过api访问http://127.0.0.1:5010 查看。
 
-  api接口相关代码,目前api是由Flask实现,代码也非常简单。客户端请求传给Flask,Flask调用ProxyManager中的实现,包括`get/delete/refresh/get_all`;
+* Api
 
-* DB:
+| api | method | Description | arg|
+| ----| ---- | ---- | ----|
+| / | GET | api介绍 | None |
+| /get | GET | 随机获取一个代理 | None|
+| /get_all | GET | 获取所有代理 |None|
+| /get_status | GET | 查看代理数量 |None|
+| /delete | GET | 删除代理  |proxy=host:ip|
 
-  数据库相关代码,目前数据库是采用SSDB。代码用工厂模式实现,方便日后扩展其他类型数据库;
+* 爬虫使用
 
-* Manager:
+  如果要在爬虫代码中使用的话, 可以将此api封装成函数直接使用,例如:
 
-  `get/delete/refresh/get_all`等接口的具体实现类,目前代理池只负责管理proxy,日后可能会有更多功能,比如代理和爬虫的绑定,代理和账号的绑定等等;
+```python
+import requests
 
-* ProxyGetter:
+def get_proxy():
+    return requests.get("http://127.0.0.1:5010/get/").content
 
-  代理获取的相关代码,目前抓取了[快代理](http://www.kuaidaili.com)、[代理66](http://www.66ip.cn/)、[有代理](http://www.youdaili.net/Daili/http/)、[西刺代理](http://api.xicidaili.com/free2016.txt)、[guobanjia](http://www.goubanjia.com/free/gngn/index.shtml)这个五个网站的免费代理,经测试这个5个网站每天更新的可用代理只有六七十个,当然也支持自己扩展代理接口;
+def delete_proxy(proxy):
+    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
 
-* Schedule:
+# your spider code
 
-  定时任务相关代码,现在只是实现定时去刷新代码,并验证可用代理,采用多进程方式;
+def getHtml():
+    # ....
+    retry_count = 5
+    proxy = get_proxy()
+    while retry_count > 0:
+        try:
+            html = requests.get('https://www.example.com', proxies={"http": "http://{}".format(proxy)})
+            # 使用代理访问
+            return html
+        except Exception:
+            retry_count -= 1
+    # 出错5次, 删除代理池中代理
+    delete_proxy(proxy)
+    return None
+```
 
-* Util:
+### 扩展代理
 
-  存放一些公共的模块方法或函数,包含`GetConfig`:读取配置文件config.ini的类,`ConfigParse`: 集成重写ConfigParser的类,使其对大小写敏感, `Singleton`:实现单例,`LazyProperty`:实现类属性惰性计算。等等;
+  项目默认包含几个免费的代理获取方法,但是免费的毕竟质量不好,所以如果直接运行可能拿到的代理质量不理想。所以,提供了代理获取的扩展方法。
 
-* 其他文件:
+  添加一个新的代理获取方法如下:
 
-  配置文件:Config.ini,数据库配置和代理获取接口配置,可以在GetFreeProxy中添加新的代理获取方法,并在Config.ini中注册即可使用;
+* 1、首先在[GetFreeProxy](https://github.com/jhao104/proxy_pool/blob/b9ccdfaada51b57cfb1bbd0c01d4258971bc8352/ProxyGetter/getFreeProxy.py#L32)类中添加你的获取代理的静态方法,
+该方法需要以生成器(yield)形式返回`host:ip`格式的代理,例如:
 
-### 4、安装
+```python
 
-下载代码:
-```
-git clone git@github.com:jhao104/proxy_pool.git
+class GetFreeProxy(object):
+    # ....
 
-或者直接到https://github.com/jhao104/proxy_pool 下载zip文件
-```
+    # 你自己的方法
+    @staticmethod
+    def freeProxyCustom():  # 命名不和已有重复即可
 
-安装依赖:
-```
-pip install -r requirements.txt
+        # 通过某网站或者某接口或某数据库获取代理 任意你喜欢的姿势都行
+        # 假设你拿到了一个代理列表
+        proxies = ["139.129.166.68:3128", "139.129.166.61:3128", ...]
+        for proxy in proxies:
+            yield proxy
+        # 确保每个proxy都是 host:ip正确的格式就行
 ```
 
-启动:
+* 2、添加好方法后,修改Config.ini文件中的`[ProxyGetter]`项:
 
-```
-如果你的依赖已经安全完成并且具备运行条件,可以直接在Run下运行main.py
-到Run目录下:
->>>python main.py
+  在`Config.ini`的`[ProxyGetter]`下添加自定义的方法的名字:
 
-如果运行成功你应该可以看到有4个main.py进程在
+```shell
 
+[ProxyGetter]
+;register the proxy getter function
+freeProxyFirst  = 0  # 如果要取消某个方法,将其删除或赋为0即可
+....
+freeProxyCustom  = 1  # 确保名字和你添加方法名字一致
 
-你也可以分别运行他们,依次到Api下启动ProxyApi.py,Schedule下启动ProxyRefreshSchedule.py和ProxyValidSchedule.py即可
 ```
 
-### 5、使用
-  定时任务启动后,会通过代理获取方法fetch所有代理放入数据库并验证。此后默认每20分钟会重复执行一次。定时任务启动大概一两分钟后,便可在[SSDB](https://github.com/jhao104/SSDBAdmin)中看到刷新出来的可用的代理:
-    
-![useful_proxy](https://pic2.zhimg.com/v2-12f9b7eb72f60663212f317535a113d1_b.png)
-    
-  启动ProxyApi.py后即可在浏览器中使用接口获取代理,一下是浏览器中的截图:
 
-  index页面:
+  `ProxyRefreshSchedule`会每隔一段时间抓取一次代理,下次抓取时会自动识别调用你定义的方法。
 
-![index](https://pic3.zhimg.com/v2-a867aa3db1d413fea8aeeb4c693f004a_b.png)
-    
-  get:
+### 问题反馈
 
-![get](https://pic1.zhimg.com/v2-f54b876b428893235533de20f2edbfe0_b.png)
+  任何问题欢迎在[Issues](https://github.com/jhao104/proxy_pool/issues) 中反馈,如果没有账号可以去 我的[博客](http://www.spiderpy.cn/blog/message)中留言。
 
-  get_all:
+  你的反馈会让此项目变得更加完美。
 
-![get_all](https://pic3.zhimg.com/v2-5c79f8c07e04f9ef655b9bea406d0306_b.png)
-    
+### 贡献代码
 
-  爬虫中使用,如果要在爬虫代码中使用的话, 可以将此api封装成函数直接使用,例如:
-```
-import requests
+  本项目仅作为基本的通用的代理池架构,不接收特有功能(当然,不限于特别好的idea)。
 
-def get_proxy():
-    return requests.get("http://127.0.0.1:5000/get/").content
+  本项目依然不够完善,如果发现bug或有新的功能添加,请在[Issues](https://github.com/jhao104/proxy_pool/issues)中提交bug(或新功能)描述,在确认后提交你的代码。
 
-def delete_proxy(proxy):
-    requests.get("http://127.0.0.1:5000/delete/?proxy={}".format(proxy))
+  这里感谢以下contributor的无私奉献:
 
-# your spider code
+  [@kangnwh](https://github.com/kangnwh)| [@bobobo80](https://github.com/bobobo80)| [@halleywj](https://github.com/halleywj)| [@newlyedward](https://github.com/newlyedward)| [@wang-ye](https://github.com/wang-ye)| [@gladmo](https://github.com/gladmo)| [@bernieyangmh](https://github.com/bernieyangmh)| [@PythonYXY](https://github.com/PythonYXY)| [@zuijiawoniu](https://github.com/zuijiawoniu)| [@netAir](https://github.com/netAir)| [@gladmo](https://github.com/gladmo)
 
-def spider():
-    # ....
-    requests.get('https://www.example.com', proxies={"http": "http://{}".format(get_proxy())})
-    # ....
 
-```
 
-  测试地址:http://123.207.35.36:5000 单机勿压测。谢谢
 
-### 6、最后
-  时间仓促,功能和代码都比较简陋,以后有时间再改进。喜欢的在github上给个star。感谢!

+ 3 - 1
Run/main.py

@@ -15,6 +15,7 @@ __author__ = 'JHao'
 import sys
 from multiprocessing import Process
 
+
 sys.path.append('../')
 
 from Api.ProxyApi import run as ProxyApiRun
@@ -30,10 +31,11 @@ def run():
     p_list.append(p2)
     p3 = Process(target=RefreshRun, name='RefreshRun')
     p_list.append(p3)
+
     for p in p_list:
         p.start()
     for p in p_list:
         p.join()
 
 if __name__ == '__main__':
-    run()
+   run()

+ 63 - 0
Schedule/ProxyCheck.py

@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""
+-------------------------------------------------
+   File Name:     ProxyCheck
+   Description :   多线程验证useful_proxy
+   Author :        J_hao
+   date:          2017/9/26
+-------------------------------------------------
+   Change Activity:
+                   2017/9/26: 多线程验证useful_proxy
+-------------------------------------------------
+"""
+__author__ = 'J_hao'
+
+import sys
+from time import sleep
+from threading import Thread
+
+sys.path.append('../')
+
+from Util.utilFunction import validUsefulProxy
+from Manager.ProxyManager import ProxyManager
+from Util.LogHandler import LogHandler
+
+FAIL_COUNT = 1  # 校验失败次数, 超过次数删除代理
+
+
+class ProxyCheck(ProxyManager, Thread):
+    def __init__(self):
+        ProxyManager.__init__(self)
+        Thread.__init__(self)
+        self.log = LogHandler('proxy_check')
+
+    def run(self):
+        self.db.changeTable(self.useful_proxy_queue)
+        while True:
+            proxy_item = self.db.pop()
+            while proxy_item:
+                proxy = proxy_item.get('proxy')
+                counter = proxy_item.get('value')
+                if validUsefulProxy(proxy):
+                    # 验证通过计数器加1
+                    if counter and int(counter) < 1:
+                        self.db.put(proxy, num=int(counter) + 1)
+                    else:
+                        self.db.put(proxy)
+                    self.log.info('ProxyCheck: {} validation pass'.format(proxy))
+                else:
+                    self.log.info('ProxyCheck: {} validation fail'.format(proxy))
+                    # 验证失败,计数器减1
+                    if counter and int(counter) <= -FAIL_COUNT:
+                        self.log.info('ProxyCheck: {} fail too many, delete!'.format(proxy))
+                        self.db.delete(proxy)
+                    else:
+                        self.db.put(proxy, num=int(counter) - 1)
+
+                proxy_item = self.db.pop()
+            sleep(60 * 5)
+
+
+if __name__ == '__main__':
+    p = ProxyCheck()
+    p.run()

+ 18 - 11
Schedule/ProxyRefreshSchedule.py

@@ -46,19 +46,26 @@ class ProxyRefreshSchedule(ProxyManager):
         :return:
         """
         self.db.changeTable(self.raw_proxy_queue)
-        raw_proxy = self.db.pop()
-        self.log.info('%s start validProxy_a' % time.ctime())
-        exist_proxy = self.db.getAll()
-        while raw_proxy:
-            if validUsefulProxy(raw_proxy) and (raw_proxy not in exist_proxy):
+        raw_proxy_item = self.db.pop()
+        self.log.info('ProxyRefreshSchedule: %s start validProxy' % time.ctime())
+        # 计算剩余代理,用来减少重复计算
+        remaining_proxies = self.getAll()
+        while raw_proxy_item:
+            raw_proxy = raw_proxy_item.get('proxy')
+            if isinstance(raw_proxy, bytes):
+                # 兼容Py3
+                raw_proxy = raw_proxy.decode('utf8')
+
+            if (raw_proxy not in remaining_proxies) and validUsefulProxy(raw_proxy):
                 self.db.changeTable(self.useful_proxy_queue)
                 self.db.put(raw_proxy)
-                self.log.info('validProxy_a: %s validation pass' % raw_proxy)
+                self.log.info('ProxyRefreshSchedule: %s validation pass' % raw_proxy)
             else:
-                self.log.debug('validProxy_a: %s validation fail' % raw_proxy)
+                self.log.info('ProxyRefreshSchedule: %s validation fail' % raw_proxy)
             self.db.changeTable(self.raw_proxy_queue)
-            raw_proxy = self.db.pop()
-        self.log.info('%s validProxy_a complete' % time.ctime())
+            raw_proxy_item = self.db.pop()
+            remaining_proxies = self.getAll()
+        self.log.info('ProxyRefreshSchedule: %s validProxy complete' % time.ctime())
 
 
 def refreshPool():
@@ -86,9 +93,9 @@ def main(process_num=30):
 
 
 def run():
-    # main()
+    main()
     sched = BlockingScheduler()
-    sched.add_job(main, 'interval', minutes=5)
+    sched.add_job(main, 'interval', minutes=10)  # 每10分钟抓取一次
     sched.start()
 
 

+ 14 - 27
Schedule/ProxyValidSchedule.py

@@ -16,41 +16,28 @@ import sys
 
 sys.path.append('../')
 
-from Util.utilFunction import validUsefulProxy
-from Manager.ProxyManager import ProxyManager
-from Util.LogHandler import LogHandler
+from Schedule.ProxyCheck import ProxyCheck
 
 
-class ProxyValidSchedule(ProxyManager):
+class ProxyValidSchedule(object):
     def __init__(self):
-        ProxyManager.__init__(self)
-        self.log = LogHandler('valid_schedule')
+        pass
 
-    def __validProxy(self):
+    def __validProxy(self, threads=5):
         """
-        验证代理
+        验证useful_proxy代理
+        :param threads: 线程数
         :return:
         """
-        while True:
-            self.db.changeTable(self.useful_proxy_queue)
-            for each_proxy in self.db.getAll():
-                if isinstance(each_proxy, bytes):
-                    each_proxy = each_proxy.decode('utf-8')
+        thread_list = list()
+        for index in range(threads):
+            thread_list.append(ProxyCheck())
 
-                if validUsefulProxy(each_proxy):
-                    # 成功计数器加1
-                    self.db.inckey(each_proxy, 1)
-                    self.log.debug('validProxy_b: {} validation pass'.format(each_proxy))
-                else:
-                    # 失败计数器减一
-                    self.db.inckey(each_proxy, -1)
-                    # self.db.delete(each_proxy)
-                    self.log.info('validProxy_b: {} validation fail'.format(each_proxy))
-                value = self.db.getvalue(each_proxy)
-                if value and int(value) < -5:
-                    # 计数器小于-5删除该代理
-                    self.db.delete(each_proxy)
-        self.log.info('validProxy_a running normal')
+        for thread in thread_list:
+            thread.start()
+
+        for thread in thread_list:
+            thread.join()
 
     def main(self):
         self.__validProxy()

+ 17 - 0
Util/EnvUtil.py

@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+"""
+-------------------------------------------------
+   File Name:     EnvUtil
+   Description :   环境相关
+   Author :        J_hao
+   date:          2017/9/18
+-------------------------------------------------
+   Change Activity:
+                   2017/9/18: 区分Python版本
+-------------------------------------------------
+"""
+__author__ = 'J_hao'
+
+import sys
+
+PY3 = sys.version_info >= (3,)

+ 9 - 0
Util/GetConfig.py

@@ -49,6 +49,13 @@ class GetConfig(object):
     def proxy_getter_functions(self):
         return self.config_file.options('ProxyGetter')
 
+    @LazyProperty
+    def host_ip(self):
+        return self.config_file.get('HOST','ip')
+
+    @LazyProperty
+    def host_port(self):
+        return self.config_file.get('HOST', 'port')
 
 if __name__ == '__main__':
     gg = GetConfig()
@@ -57,3 +64,5 @@ if __name__ == '__main__':
     print(gg.db_host)
     print(gg.db_port)
     print(gg.proxy_getter_functions)
+    print(gg.host_ip)
+    print(gg.host_port)

+ 7 - 4
Util/LogHandler.py

@@ -2,12 +2,13 @@
 """
 -------------------------------------------------
    File Name:     LogHandler.py
-   Description :
+   Description :  日志操作模块
    Author :       JHao
    date:          2017/3/6
 -------------------------------------------------
    Change Activity:
                    2017/3/6: log handler
+                   2017/9/21: 屏幕输出/文件输出 可选(默认屏幕和文件均输出)
 -------------------------------------------------
 """
 __author__ = 'JHao'
@@ -38,12 +39,14 @@ class LogHandler(logging.Logger):
     LogHandler
     """
 
-    def __init__(self, name, level=DEBUG):
+    def __init__(self, name, level=DEBUG, stream=True, file=True):
         self.name = name
         self.level = level
         logging.Logger.__init__(self, self.name, level=level)
-        self.__setFileHandler__()
-        self.__setStreamHandler__()
+        if stream:
+            self.__setStreamHandler__()
+        if file:
+            self.__setFileHandler__()
 
     def __setFileHandler__(self, level=None):
         """

+ 21 - 6
Util/utilFunction.py

@@ -17,7 +17,7 @@ from lxml import etree
 from Util.LogHandler import LogHandler
 from Util.WebRequest import WebRequest
 
-logger = LogHandler(__name__)
+logger = LogHandler(__name__, stream=False)
 
 
 # noinspection PyPep8Naming
@@ -67,6 +67,19 @@ def getHtmlTree(url, **kwargs):
     return etree.HTML(html)
 
 
+def tcpConnect(proxy):
+    """
+    TCP 三次握手
+    :param proxy:
+    :return:
+    """
+    from socket import socket, AF_INET, SOCK_STREAM
+    s = socket(AF_INET, SOCK_STREAM)
+    ip, port = proxy.split(':')
+    result = s.connect_ex((ip, int(port)))
+    return True if result == 0 else False
+
+
 # noinspection PyPep8Naming
 def validUsefulProxy(proxy):
     """
@@ -74,13 +87,15 @@ def validUsefulProxy(proxy):
     :param proxy:
     :return:
     """
-    proxies = {"https": "https://{proxy}".format(proxy=proxy)}
+    if isinstance(proxy, bytes):
+        proxy = proxy.decode('utf8')
+    proxies = {"http": "http://{proxy}".format(proxy=proxy)}
     try:
-        # 超过40秒的代理就不要了
-        r = requests.get('https://www.baidu.com', proxies=proxies, timeout=40, verify=False)
+        # 超过20秒的代理就不要了
+        r = requests.get('http://httpbin.org/ip', proxies=proxies, timeout=20, verify=False)
         if r.status_code == 200:
-            logger.debug('%s is ok' % proxy)
+            logger.info('%s is ok' % proxy)
             return True
     except Exception as e:
-        logger.info(e)
+        logger.debug(e)
         return False

+ 1 - 0
_config.yml

@@ -0,0 +1 @@
+theme: jekyll-theme-time-machine

+ 173 - 0
doc/introduce.md

@@ -0,0 +1,173 @@
+
+## 代理池介绍
+
+本项目通过爬虫方式持续抓取代理网站公布的免费代理IP,实时校验,维护部分可以使用的代理,并通过api的形式提供外部使用。
+
+### 1、问题
+
+构建一个代理IP池,可能有下面这些问题:
+
+* 代理IP从何而来?
+
+  许多刚接触爬虫的,都试过去西刺、快代理之类有免费代理的网站去抓些免费代理,还是有一些代理能用。
+当然,如果你有更好的代理接口也可以自己接入。
+
+  免费代理的采集也很简单,无非就是:`访问页面`` —> `正则/xpath提取` —> `保存`
+
+* 如何保证代理质量?
+
+  可以肯定免费的代理IP大部分都是不能用的,不然别人还提供付费接口干嘛(不过事实上很多代理商的付费IP也不稳定,也有很多是不能用)。
+所以采集回来的代理IP不能直接使用,检测的办法也很简单:可以写个程序不断的用代理访问一个稳定的网站,看是否可以正常访问即可。
+这个过程可以使用多线/进程或异步的方式,因为检测代理是个很慢的过程。
+
+* 采集回来的代理如何存储?
+
+  这里不得不推荐一个国人开发的高性能支持多种数据结构的NoSQL数据库[SSDB](http://ssdb.io/docs/zh_cn/),用于替代Redis。支持队列、hash、set、k-v对,支持T级别数据。是做分布式爬虫很好中间存储工具。
+
+* 如何让爬虫更方便的用到这些代理?
+
+  答案肯定是做成服务咯,Python有这么多的web框架,随便拿一个来写个api供爬虫调用。这样代理和爬虫架构分离有很多好处,
+比如:当爬虫完全不用考虑如何校验代理,如何保证拿到的代理可用,这些都由代理池来完成。这样只需要安静的码爬虫代码就行啦。
+
+### 2、代理池设计
+
+  代理池由四部分组成:
+
+* ProxyGetter:
+
+  代理获取接口,目前有5个免费代理源,每调用一次就会抓取这个5个网站的最新代理放入DB,支持自定义扩展额外的代理获取接口;
+
+* DB:
+
+  用于存放代理IP,目前支持SSDB和Redis(推荐SSDB)。至于为什么选择SSDB,大家可以参考这篇[文章](https://www.sdk.cn/news/2684),个人觉得SSDB是个不错的Redis替代方案,如果你没有用过SSDB,安装起来也很简单,可以参考[这里](https://github.com/jhao104/memory-notes/blob/master/SSDB/SSDB%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E8%AE%B0%E5%BD%95.md);
+
+* Schedule:
+
+  计划任务,定时去检测DB中的代理可用性,删除不可用的代理。同时也会主动通过ProxyGetter去获取最新代理放入DB;
+
+* ProxyApi:
+
+  代理池的外部接口,由[Flask](http://flask.pocoo.org/)实现,功能是给爬虫提供与代理池交互的接口。
+
+<!--#### 功能图纸-->
+![设计](https://pic2.zhimg.com/v2-f2756da2986aa8a8cab1f9562a115b55_b.png)
+
+### 3、代码模块
+
+  Python中高层次的数据结构,动态类型和动态绑定,使得它非常适合于快速应用开发,也适合于作为胶水语言连接已有的软件部件。用Python来搞这个代理IP池也很简单,代码分为6个模块:
+
+* Api:
+
+  api接口相关代码,目前api是由Flask实现,代码也非常简单。客户端请求传给Flask,Flask调用`ProxyManager`中的实现,包括`get/delete/refresh/get_all`;
+
+* DB:
+
+  数据库相关代码,目前数据库是支持SSDB/Redis。代码用工厂模式实现,方便日后扩展其他类型数据库;
+
+* Manager:
+
+  `get/delete/refresh/get_all`等接口的具体实现类,目前代理池只负责管理proxy,日后可能会有更多功能,比如代理和爬虫的绑定,代理和账号的绑定等等;
+
+* ProxyGetter:
+
+  代理获取的相关代码,目前抓取了[快代理](http://www.kuaidaili.com)、[代理66](http://www.66ip.cn/)、[有代理](http://www.youdaili.net/Daili/http/)、[西刺代理](http://api.xicidaili.com/free2016.txt)、[guobanjia](http://www.goubanjia.com/free/gngn/index.shtml)这个五个网站的免费代理,经测试这个5个网站每天更新的可用代理只有六七十个,当然也支持自己扩展代理接口;
+
+* Schedule:
+
+  定时任务相关代码,现在只是实现定时去刷新代理,并验证可用代理,采用多进程方式;
+
+* Util:
+
+  存放一些公共的模块方法或函数,包含`GetConfig`:读取配置文件config.ini的类,`ConfigParse`: 扩展ConfigParser的类,使其对大小写敏感, `Singleton`:实现单例,`LazyProperty`:实现类属性惰性计算。等等;
+
+* 其他文件:
+
+  配置文件:`Config.ini``,数据库配置和代理获取接口配置,可以在GetFreeProxy中添加新的代理获取方法,并在Config.ini中注册即可使用;
+
+### 4、安装
+
+下载代码:
+```
+git clone git@github.com:jhao104/proxy_pool.git
+
+或者直接到https://github.com/jhao104/proxy_pool 下载zip文件
+```
+
+安装依赖:
+```
+pip install -r requirements.txt
+```
+
+启动:
+
+```
+如果你的依赖已经安全完成并且具备运行条件,可以直接在Run下运行main.py
+到Run目录下:
+>>>python main.py
+
+如果运行成功你应该可以看到有4个main.py进程在
+
+
+你也可以分别运行他们,依次到Api下启动ProxyApi.py,Schedule下启动ProxyRefreshSchedule.py和ProxyValidSchedule.py即可
+```
+
+docker:
+```
+git clone git@github.com:jhao104/proxy_pool.git
+
+cd proxy_pool
+
+docker build -t proxy:latest -f Dockerfile .
+
+docker run -p 5010:5010 -d proxy:latest
+
+# Wait a few minutes
+curl localhost:5010/get/
+# result: xxx.xxx.xxx.xxx:xxxx
+
+curl localhost:5010/get_all/
+```
+
+### 5、使用
+  定时任务启动后,会通过GetFreeProxy中的方法抓取代理存入数据库并验证。此后默认每10分钟会重复执行一次。定时任务启动大概一两分钟后,便可在[SSDB](https://github.com/jhao104/SSDBAdmin)中看到刷新出来的可用的代理:
+
+![useful_proxy](https://pic2.zhimg.com/v2-12f9b7eb72f60663212f317535a113d1_b.png)
+
+  启动ProxyApi.py后即可在浏览器中使用接口获取代理,一下是浏览器中的截图:
+
+  index页面:
+
+![index](https://pic3.zhimg.com/v2-a867aa3db1d413fea8aeeb4c693f004a_b.png)
+
+  get:
+
+![get](https://pic1.zhimg.com/v2-f54b876b428893235533de20f2edbfe0_b.png)
+
+  get_all:
+
+![get_all](https://pic3.zhimg.com/v2-5c79f8c07e04f9ef655b9bea406d0306_b.png)
+
+
+  爬虫中使用,如果要在爬虫代码中使用的话, 可以将此api封装成函数直接使用,例如:
+```
+import requests
+
+def get_proxy():
+    return requests.get("http://127.0.0.1:5010/get/").content
+
+def delete_proxy(proxy):
+    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
+
+# your spider code
+
+def spider():
+    # ....
+    requests.get('https://www.example.com', proxies={"http": "http://{}".format(get_proxy())})
+    # ....
+
+```
+
+  测试地址:http://123.207.35.36:5010 单机勿压测。谢谢
+
+### 6、最后
+  时间仓促,功能和代码都比较简陋,以后有时间再改进。喜欢的在github上给个star。感谢!

+ 9 - 0
doc/release_notes.md

@@ -0,0 +1,9 @@
+## Release Notes
+
+* newest
+  1.使用多线程验证useful_pool
+
+* 1.10
+  1. 第一版;
+  2. 支持PY2/PY3;
+  3. 代理池基本功能;

+ 0 - 0
log/__init__.py


+ 0 - 1
log/test.log

@@ -1 +0,0 @@
-2017-04-24 15:34:08,434 LogHandler.py[line:85] ERROR this is a test msg

+ 0 - 1
requirements.txt

@@ -3,7 +3,6 @@ Flask==0.11.1
 requests==2.11.0
 lxml==3.7.1
 
-ssdb==0.0.3
 pymongo==3.2.2
 redis==2.10.5