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个代理服务器。感觉十分开心。

0 0
原创粉丝点击