Mproxy项目实录第5天
来源:互联网 发布:sal绘画软件 sai 编辑:程序博客网 时间:2024/05/22 17:40
关于这个系列
这个项目实录系列是记录Mproxy项目的整个开发流程。项目最终的目标是开发一套代理服务器的API。这个系列中会记录项目的需求、设计、验证、实现、升级等等,包括设计决策的依据,开发过程中的各种坑。希望和大家共同交流,一起进步。
项目的源码我会同步更新到GitHub,项目地址:https://github.com/mrbcy/Mproxy。
系列地址:
Mproxy项目实录第1天
Mproxy项目实录第2天
Mproxy项目实录第3天
Mproxy项目实录第4天
今日计划
到目前为止,我们已经拥有了一个代理服务器的爬虫,完善的验证器。还实现了调度器的部分功能以保证只有在所有的验证器都在线时才进行验证工作。今天我们将完成收集器部分的开发工作,将通过所有验证器验证的代理服务器保存到MySQL数据库中。并定时清理12小时后仍未收到所有验证器验证结果的任务。
从Kafka集群中读取验证结果
#-*- coding: utf-8 -*-import jsonfrom kafka import KafkaConsumerdef func(): consumer = KafkaConsumer('checked-servers', group_id='mproxy_collector', bootstrap_servers=['amaster:9092','anode1:9092','anode2:9092'], auto_offset_reset='earliest', enable_auto_commit=False, value_deserializer=lambda m: json.loads(m.decode('utf-8'))) for message in consumer: v = message.value print vif __name__ == '__main__': func()
获取验证器列表
验证器的列表信息写入配置文件中。配置文件的内容如下:
[validator]validator_list = validator_huabei_test_1,validator_huabei_test_2
然后写一个读取配置文件的工具类。代码如下:
#-*- coding: utf-8 -*-import ConfigParserclass ConfigLoader: def __init__(self): self.cp = ConfigParser.SafeConfigParser() self.cp.read('collector.cfg') def get_validator_list(self): text = self.cp.get('validator','validator_list') return text.split(',')
即可使用下面的代码获得验证器的列表:
from conf.configloader import ConfigLoaderdef start_working(): config_loader = ConfigLoader() validator_list = config_loader.get_validator_list() print validator_listif __name__ == '__main__': start_working()
输出结果如下:
['validator_huabei_test_1', 'validator_huabei_test_2']
匹配验证结果
接下来我们就可以筛选哪些是通过了所有验证器的代理服务器。以task_id作为key,然后value可以是一个list。每当有新的value加入的时候就判断一下是否已经通过了所有验证器。如果通过了,就删除这个key,然后交给对应的函数进行处理。只要有一个代理服务器未通过,就表示这个代理服务器未通过,也交给对应的函数进行处理。
#-*- coding: utf-8 -*-import jsonimport tracebackfrom kafka import KafkaConsumerfrom conf.configloader import ConfigLoaderfrom validationresultitem import ValidationResultItemvalidate_result = {}validator_list = []def print_validator_names(): global validator_list config_loader = ConfigLoader() validator_list = config_loader.get_validator_list() print validator_listdef do_validation_match(proxy_task): global validate_result try: if validate_result.has_key(proxy_task['task_id']) == False: validate_result[proxy_task['task_id']] = [ValidationResultItem(proxy_task)] else: validate_result[proxy_task['task_id']].append(ValidationResultItem(proxy_task)) check_validation_result(proxy_task['task_id']) except Exception as e: # traceback.print_exc() passdef deal_unavailable(result_list): if len(result_list) > 0: print "代理服务器 %s:%s 不能用" %(result_list[0].ip,result_list[0].port)def deal_available(result_list): if len(result_list) > 0: print "代理服务器 %s:%s 可用" % (result_list[0].ip, result_list[0].port)def check_validation_result(task_id): global validate_result global validator_list # print validate_result[task_id] result_list = validate_result[task_id] # iteratively check whether proxy passes all the validators all_pass_flag = True for validator in validator_list: pass_flag = False for result_item in result_list: if validator == result_item.validator_name: pass_flag = True if result_item.validate_result == False: # print "根据 %s 判定,代理服务器不能用" % result_item.validator_name deal_unavailable(result_list) return break if pass_flag == False: all_pass_flag = False if all_pass_flag == True: deal_available(result_list)def func(): consumer = KafkaConsumer('checked-servers', group_id='mproxy_collector', bootstrap_servers=['amaster:9092','anode1:9092','anode2:9092'], auto_offset_reset='earliest', enable_auto_commit=False, value_deserializer=lambda m: json.loads(m.decode('utf-8'))) for message in consumer: v = message.value do_validation_match(v)if __name__ == '__main__': print_validator_names() func()
根据实验结果来看,快代理网站的代理服务器可用率非常低,大概3%上下。这个数据是我把响应时间放宽到20秒后得到的,因此可用率实际上更低。迫切的需要从其他的数据源获得代理服务器信息。
统一的数据结构
接下来我们来设计一下代理服务器信息的存储结构。在Kafka集群中,代理服务器的信息实际上包含在任务信息中。有下列字段组成。
- task_id
- ip
- port
- validator_name
- spider_name
- location
- validate_result
- anonymity
- type
而在数据库中,则包括下列字段,以支持后续的持续验证过程。
- proxy_addr 包括 ip:port
- location
- anonymity
- type
- last_validate_time
- retry_count
- last_available_time
- status
将可用的代理服务器保存到MySQL中
使用下面的SQL语句创建表:
CREATE TABLE `proxy_list` ( `proxy_addr` VARCHAR(255) NOT NULL COMMENT '代理服务器地址', `location` VARCHAR(255) DEFAULT NULL COMMENT '位置信息', `anonymity` VARCHAR(255) DEFAULT NULL COMMENT '匿名度', `type` VARCHAR(255) DEFAULT NULL COMMENT '代理服务器类型', `last_validate_time` DATETIME DEFAULT NULL COMMENT '最后一次验证时间', `retry_count` INT(11) DEFAULT NULL COMMENT '重试次数', `last_available_time` DATETIME DEFAULT NULL COMMENT '最后一次验证可用时间', `status` VARCHAR(255) DEFAULT NULL COMMENT '代理服务器状态', PRIMARY KEY (`proxy_addr`)) ENGINE=INNODB DEFAULT CHARSET=utf8
接下来就是传统的三层架构用起来。
service
#-*- coding: utf-8 -*-import datetimefrom dao.proxydao import ProxyDaofrom dao.proxystatus import ProxyStatusfrom domain.proxydaoitem import ProxyDaoItemclass ProxyService: def __init__(self): self.proxy_dao = ProxyDao() def save_proxy(self,validation_result_item): # Firstly, we look up the proxy_info in db dao_item = self.proxy_dao.find_proxy_by_addr(validation_result_item.ip + ':'+validation_result_item.port) insert_flag = False if dao_item is None: dao_item = ProxyDaoItem() dao_item.proxy_addr = validation_result_item.ip + ':'+validation_result_item.port dao_item.anonymity = validation_result_item.anonymity dao_item.location = validation_result_item.anonymity dao_item.type = validation_result_item.type insert_flag = True if validation_result_item.validate_result == True: dao_item.last_validate_time = datetime.datetime.now() dao_item.last_available_time = datetime.datetime.now() dao_item.retry_count = 0 dao_item.status = ProxyStatus.AVAILABLE else: dao_item.last_validate_time = datetime.datetime.now() dao_item.retry_count += 1 if dao_item.retry_count >= 3: dao_item.status = ProxyStatus.PERMANENT_UNAVAILABLE else: dao_item.status = ProxyStatus.TEMP_UNAVAILABLE if insert_flag == True: self.proxy_dao.insert_proxy(dao_item) else: self.proxy_dao.update_proxy(dao_item)
dao
#-*- coding: utf-8 -*-import tracebackfrom dbpool.poolutil import PoolUtilfrom domain.proxydaoitem import ProxyDaoItemclass ProxyDao: def find_proxy_by_addr(self, proxy_addr): ":param proxy_addr format like ip:port" try: conn = PoolUtil.pool.connection() cur = conn.cursor() sql = "select * from proxy_list where proxy_addr=%s" count = cur.execute(sql,(proxy_addr) ) proxy_dao_item = None if count != 0: data = cur.fetchone() proxy_dao_item = ProxyDaoItem() proxy_dao_item.proxy_addr = data[0] proxy_dao_item.location = data[1] proxy_dao_item.anonymity = data[2] proxy_dao_item.type = data[3] proxy_dao_item.last_validate_time = data[4] proxy_dao_item.retry_count = data[5] proxy_dao_item.last_available_time = data[6] proxy_dao_item.status = data[7] cur.close() conn.close() return proxy_dao_item except Exception as e: return None def insert_proxy(self, dao_item): try: conn = PoolUtil.pool.connection() cur = conn.cursor() sql = "insert into proxy_list(proxy_addr,location,anonymity,type,last_validate_time,retry_count,last_available_time,status) " \ "values(%s,%s,%s,%s,%s,%s,%s,%s)" cur.execute(sql,(dao_item.proxy_addr,dao_item.location,dao_item.anonymity,dao_item.type,dao_item.last_validate_time,dao_item.retry_count, dao_item.last_available_time,dao_item.status)) cur.close() conn.commit() conn.close() except Exception as e: traceback.print_exc() traceback.print_exc() def update_proxy(self, dao_item): try: conn = PoolUtil.pool.connection() cur = conn.cursor() sql = "update proxy_list set location = %s,anonymity = %s,type = %s,last_validate_time = %s,retry_count = %s,last_available_time = %s,status = %s where proxy_addr=%s" cur.execute(sql,(dao_item.location,dao_item.anonymity,dao_item.type,dao_item.last_validate_time,dao_item.retry_count, dao_item.last_available_time,dao_item.status,dao_item.proxy_addr)) cur.close() conn.commit() conn.close() except Exception as e: traceback.print_exc()
单元测试
#-*- coding: utf-8 -*-import datetimefrom dao.proxydao import ProxyDaofrom dao.proxystatus import ProxyStatusfrom domain.proxydaoitem import ProxyDaoItemproxy_dao = ProxyDao()def test_insert_proxy(): global proxy_dao dao_item = ProxyDaoItem() dao_item.proxy_addr = "127.0.0.1:5002" dao_item.location = "北京 海淀 移动" dao_item.status = ProxyStatus.PERMANENT_UNAVAILABLE dao_item.anonymity = "高匿名" dao_item.last_available_time = datetime.datetime.now() dao_item.last_validate_time = datetime.datetime.now() dao_item.retry_count = 0 dao_item.type = "HTTP" proxy_dao.insert_proxy(dao_item)def test_find_one(): global proxy_dao dao_item = proxy_dao.find_proxy_by_addr('127.0.0.1:5002') print dao_itemdef test_update(): global proxy_dao dao_item = proxy_dao.find_proxy_by_addr('127.0.0.1:5002') dao_item.location = "测试位置" dao_item.type = "HTTP,HTTPS" dao_item.anonymity = '透明' dao_item.status = ProxyStatus.AVAILABLE dao_item.retry_count = 3 dao_item.last_available_time = datetime.datetime.now() dao_item.last_validate_time = datetime.datetime.now() proxy_dao.update_proxy(dao_item)if __name__ == '__main__': test_update()
测试的结果还挺完美的,都顺利的实现了目标。
最后就是改造collector了,任务结果检查完毕以后就将结果更新到MySQL数据库里面去。
def deal_unavailable(result_list): global proxy_service if len(result_list) > 0: print "代理服务器 %s:%s 不能用" %(result_list[0].ip,result_list[0].port) dao_item = proxy_service.find_proxy_by_addr(result_list[0].ip + ':'+result_list[0].port) if dao_item is not None: proxy_service.save_proxy(result_list[0])def deal_available(result_list): global proxy_service if len(result_list) > 0: print "代理服务器 %s:%s 可用" % (result_list[0].ip, result_list[0].port) proxy_service.save_proxy(result_list[0])
这边我做了一个检查,如果代理服务器不可用而且是第一次验证的话说明开始这个代理服务器就不能用,就不必保存到数据库了,抛弃即可,其他情况下则更新到数据库中。
把Kafka集群里面的数据清理干净,然后重新运行了一次,得到了6个代理服务器。感觉十分开心。
- Mproxy项目实录第5天
- Mproxy项目实录第1天
- Mproxy项目实录第2天
- Mproxy项目实录第3天
- Mproxy项目实录第4天
- Mproxy项目实录第6天
- Mproxy项目实录第7天
- java项目开发全程实录第5章运行错误。
- 项目实录
- 项目开发实录
- C# MFC项目实录
- C#项目开发案例全程实录(第2版)(奋斗的小鸟)_PDF电子书
- 【原创】svn项目转移实录
- VC++项目开发全程实录
- Java项目开发过程实录
- IBM cognos项目进程实录
- 第一篇:践履实录2006-2013
- Visual Basic项目开发全程实录
- 华为oj初级 寻找等差数列
- 【Linux】Centos之安装Nginx及注意事项
- 我眼中的敏捷团队
- mahout bayesian
- 统计学习方法--学习笔记----6.1.1 Logistic 回归 (拟牛顿法- DFP算法)
- Mproxy项目实录第5天
- 1049. 数列的片段和
- leetcode 516. Longest Palindromic Subsequence
- 负载均衡分类
- 【OpenCV学习笔记】八、鼠标与滑动条操作
- Eclipse4.4以上版本不能使用easyExplorer,采用OpenExplorer
- 1. Two Sum
- Callable与Future的介绍
- Android Studio的下载安装及配置