Mproxy项目实录第5天

关于这个系列

这个项目实录系列是记录Mproxy项目的整个开发流程。项目最终的目标是开发一套代理服务器的API。这个系列中会记录项目的需求、设计、验证、实现、升级等等,包括设计决策的依据,开发过程中的各种坑。希望和大家共同交流,一起进步。

项目的源码我会同步更新到GitHub,项目地址:https://github.com/mrbcy/Mproxy

系列地址:

Mproxy项目实录第1天

Mproxy项目实录第2天

Mproxy项目实录第3天

Mproxy项目实录第4天

今日计划

到目前为止,我们已经拥有了一个代理服务器的爬虫,完善的验证器。还实现了调度器的部分功能以保证只有在所有的验证器都在线时才进行验证工作。今天我们将完成收集器部分的开发工作,将通过所有验证器验证的代理服务器保存到MySQL数据库中。并定时清理12小时后仍未收到所有验证器验证结果的任务。

从Kafka集群中读取验证结果

#-*- coding: utf-8 -*-
import json

from kafka import KafkaConsumer


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
        print v


if __name__ == '__main__':
    func()

获取验证器列表

验证器的列表信息写入配置文件中。配置文件的内容如下:

[validator]
validator_list = validator_huabei_test_1,validator_huabei_test_2

然后写一个读取配置文件的工具类。代码如下:

#-*- coding: utf-8 -*-
import ConfigParser


class 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 ConfigLoader


def start_working():
    config_loader = ConfigLoader()
    validator_list = config_loader.get_validator_list()
    print validator_list


if __name__ == '__main__':
    start_working()

输出结果如下:

['validator_huabei_test_1', 'validator_huabei_test_2']

匹配验证结果

接下来我们就可以筛选哪些是通过了所有验证器的代理服务器。以task_id作为key,然后value可以是一个list。每当有新的value加入的时候就判断一下是否已经通过了所有验证器。如果通过了,就删除这个key,然后交给对应的函数进行处理。只要有一个代理服务器未通过,就表示这个代理服务器未通过,也交给对应的函数进行处理。

#-*- coding: utf-8 -*-
import json
import traceback

from kafka import KafkaConsumer

from conf.configloader import ConfigLoader
from validationresultitem import ValidationResultItem

validate_result = {}
validator_list = []

def print_validator_names():
    global validator_list
    config_loader = ConfigLoader()
    validator_list = config_loader.get_validator_list()
    print validator_list

def 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()
        pass


def 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 datetime

from dao.proxydao import ProxyDao
from dao.proxystatus import ProxyStatus
from domain.proxydaoitem import ProxyDaoItem


class 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 traceback

from dbpool.poolutil import PoolUtil
from domain.proxydaoitem import ProxyDaoItem


class 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 datetime

from dao.proxydao import ProxyDao
from dao.proxystatus import ProxyStatus
from domain.proxydaoitem import ProxyDaoItem

proxy_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_item

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值