Mproxy项目实录第6天

关于这个系列

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

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

系列地址:

Mproxy项目实录第1天

Mproxy项目实录第2天

Mproxy项目实录第3天

Mproxy项目实录第4天

Mproxy项目实录第5天

今日计划

到目前为止,第一阶段的任务还剩下代理服务器的重新验证以及其他2个代理服务器爬虫的编写。今天准备完成代理服务器的定时验证以及1个爬虫的编写。

今天对验证器进行了测试,发现之前根本没有把头部信息携带上去。修改以后发现今天快代理的爬虫可用性变得很高,基本都能用。不知道是不是偶然的。持续验证几天应该就会有结果了,我们拭目以待。

代理服务器的定时验证

由于代理服务器可用的时间一般都不长,所以有必要对代理服务器进行定时的验证。实现方法就是调度器中新开一个线程,每隔3小时将超过6小时没有验证的代理服务器信息提交到Kafka的unchecked-server Topic中,则验证器会自动的进行验证。

从数据库中得到需要验证的代理服务器

首先,我们要先把数据库中超过6小时没有验证的代理服务器取出来。

先是从collector里面把dao和service拷过来了(不知道怎么消除重复代码了,后续再重构吧),然后添加了下面的代码。

proxydao(这)

具体代码看下边吧,有全的

proxyservice

def find_proxy_need_to_recheck(self):
    now = datetime.datetime.now();
    timestamp = now - datetime.timedelta(hours=6)
    return self.proxy_dao.find_proxy_need_to_recheck(timestamp)

好的,到现在为止,我们已经可以把需要验证的代理服务器取出来了。

定时提交到Kafka集群

接下来,把快代理爬虫里面的提交工具类拷贝出来(笑)。然后改造Dispatcher。代码如下:

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

import time
from kafka import KafkaProducer

from conf.configloader import ConfigLoader
from service.proxyservice import ProxyService


class ProxyRechecker(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.conf_loader = ConfigLoader()
        self.producer = KafkaProducer(value_serializer=lambda m: json.dumps(m).encode('utf-8'),
                                      bootstrap_servers=self.conf_loader.get_kafka_bootstrap_servers())
        self.proxy_service = ProxyService()

    def run(self):
        while True:
            dao_items = self.proxy_service.find_proxy_need_to_recheck()

            if dao_items is not None:
                print "Proxy Rechecker 提交 %s 个代理服务器进行重新验证" % (len(dao_items))
                for dao_item in dao_items:
                    # construct validate bean
                    validate_item = {'_values':{}}
                    validate_item['_values']['task_id'] = str(uuid.uuid4())
                    validate_item['_values']['ip'] = dao_item.proxy_addr.split(':')[0]
                    validate_item['_values']['port'] = dao_item.proxy_addr.split(':')[1]
                    validate_item['_values']['anonymity'] = dao_item.anonymity
                    validate_item['_values']['type'] = dao_item.type
                    validate_item['_values']['location'] = dao_item.location
                    validate_item['_values']['spider_name'] = "mproxy_dispatcher"
                    self.producer.send('unchecked-servers',validate_item)

            time.sleep(60*60*3)

西刺代理爬虫的开发

这次要开发的代理服务器是西刺代理网站的爬虫。网址是:http://www.xicidaili.com/

搭建环境

使用下面的命令创建一个爬虫项目:

在spiders目录下面创建xicidaili_spider.py。一般来说爬虫的名字以域名或者网站名命名。

先写一个框架代码,像下面这样:

# -*- coding: utf-8 -*-

import scrapy


class XicidailiSpider(scrapy.spiders.Spider):
    name = "xicidaili"
    allowed_domains = ["xicidaili.com"]
    download_delay = 1
    start_urls = []

    def __init__(self):
        for x in range(20):
            self.start_urls.append('http://www.xicidaili.com/nn/%d' % (x + 1))

    def parse(self, response):
        print(response.body)

获取网页内容

然后运行一下,看看我们能不能把内容打印出来。

如果想用PyCharm运行Scrapy,可以参照下图进行设置。首先是点击Run->Edit Configurations。

运行结果如下:

<html>
<head><title>500 Internal Server Error</title></head>
<body bgcolor="white">
<center><h1>500 Internal Server Error</h1></center>
<hr><center>nginx/1.1.19</center>
</body>
</html>

很显然,我们又要用scrapy-splash了。顺便把User-Agent池也用上去,可以参考第二天的相关内容。http://blog.csdn.net/mrbcy/article/details/59110316

把scrapy-splash配上去之后我们就拿到数据了。

提取代理服务器数据

然后用Chrome的xpath把代理服务器数据提取出来。

# -*- coding: utf-8 -*-

import scrapy
from scrapy_splash import SplashRequest


class XicidailiSpider(scrapy.spiders.Spider):
    name = "xicidaili"
    allowed_domains = ["xicidaili.com"]
    download_delay = 1
    start_urls = []

    def __init__(self):
        for x in range(1):
            self.start_urls.append('http://www.xicidaili.com/nn/%d' % (x + 1))

    def start_requests(self):
        for i, url in enumerate(self.start_urls):
            yield SplashRequest(url, self.parse, args={'wait': 1})

    def parse(self, response):
        # print(response.body)

        # //*[@id="ip_list"]/tbody/tr[2]/td[2]
        trs = response.xpath('''//*[@id="ip_list"]/tbody/tr[@class='odd']''')
        # print trs
        for tr_selector in trs:
            ip = tr_selector.xpath('''./td[2]/text()''').extract_first()
            port = tr_selector.xpath('''./td[3]/text()''').extract_first()
            location = tr_selector.xpath('''./td[4]/a/text()''').extract_first()
            anonymity = tr_selector.xpath('''./td[5]/text()''').extract_first()
            type = tr_selector.xpath('''./td[6]/text()''').extract_first()
            print ip,port,location,anonymity,type

输出结果如下:

119.5.0.13 808 四川南充 高匿 HTTP
106.46.136.108 808 None 高匿 HTTP
106.46.136.16 808 None 高匿 HTTP
218.82.112.4 8118 上海 高匿 HTTP
175.155.24.46 808 四川德阳 高匿 HTTP
175.155.24.39 808 四川德阳 高匿 HTTP
106.46.136.177 808 None 高匿 HTTP
112.74.126.182 3128 广东佛山 高匿 HTTP
183.32.88.137 808 广东中山 高匿 HTTP
106.46.136.6 808 None 高匿 HTTP
171.38.181.129 8123 广西钦州 高匿 HTTP
106.46.136.85 808 None 高匿 HTTP
1.199.178.203 8998 河南三门峡 高匿 HTTP
106.46.136.107 808 None 高匿 HTTP
114.239.150.236 808 江苏宿迁市泗阳县 高匿 HTTP
222.22.61.51 8998 河南郑州 高匿 HTTP
106.46.136.103 808 None 高匿 HTTP
106.46.136.114 808 None 高匿 HTTP
119.5.0.61 808 四川南充 高匿 HTTP
119.5.1.88 808 四川南充 高匿 HTTP
106.46.136.84 808 None 高匿 HTTP
106.46.136.73 808 None 高匿 HTTP
106.46.136.161 808 None 高匿 HTTP
106.46.136.122 808 None 高匿 HTTP
125.118.69.130 808 浙江杭州 高匿 HTTP
119.5.1.19 808 四川南充 高匿 HTTP
106.46.136.149 808 None 高匿 HTTP
106.46.136.179 808 None 高匿 HTTP
122.6.151.66 8998 山东临沂 高匿 HTTP
106.46.136.145 808 None 高匿 HTTP
119.5.1.42 808 四川南充 高匿 HTTP
113.69.63.92 808 广东佛山 高匿 HTTP
111.72.126.167 808 江西吉安 高匿 HTTP
106.46.136.154 808 None 高匿 HTTP
106.46.136.178 808 None 高匿 HTTP
121.228.236.56 8998 江苏苏州 高匿 HTTP
175.155.152.172 808 四川德阳 高匿 HTTP
171.39.34.39 8123 广西百色 高匿 HTTP
110.18.181.236 80 内蒙古 高匿 HTTP
175.155.25.107 808 四川德阳 高匿 HTTP
114.239.1.75 808 江苏宿迁 高匿 HTTP
27.129.207.152 8118 河北 高匿 HTTP
183.32.88.182 808 广东中山 高匿 HTTP
106.46.136.139 808 None 高匿 HTTP
106.46.136.192 808 None 高匿 HTTP
114.239.2.191 808 江苏宿迁 高匿 HTTP
183.32.232.242 808 广东中山 高匿 HTTP
113.121.253.85 808 山东德州 高匿 HTTP
106.46.136.138 808 None 高匿 HTTP
111.72.122.171 8998 江西吉安 高匿 HTTP

封装数据到实体类

首先修改一下items.py文件的内容

import scrapy


class XicidailiItem(scrapy.Item):
    task_id = scrapy.Field()
    ip = scrapy.Field()
    port = scrapy.Field()
    anonymity = scrapy.Field()
    type = scrapy.Field()
    location = scrapy.Field()
    spider_name = scrapy.Field()

然后修改xicidaili_spider中的parse方法:

def parse(self, response):
    try:
        # //*[@id="ip_list"]/tbody/tr[2]/td[2]
        trs = response.xpath('''//*[@id="ip_list"]/tbody/tr[@class='odd']''')
        # print trs
        for tr_selector in trs:
            item = XicidailiItem()
            item['ip'] = tr_selector.xpath('''./td[2]/text()''').extract_first()
            item['port'] = tr_selector.xpath('''./td[3]/text()''').extract_first()
            item['location'] = tr_selector.xpath('''./td[4]/a/text()''').extract_first()
            item['anonymity'] = tr_selector.xpath('''./td[5]/text()''').extract_first()
            item['type'] = tr_selector.xpath('''./td[6]/text()''').extract_first()
            item['task_id'] = str(uuid.uuid4())
            item['spider_name'] = self.name
            yield item
    except Exception as e:
        logging.exception("An Error Happens")

整合日志

这一部分参考第2天的相应内容。

将数据提交到Kafka集群中

这一部分也参考第2天的内容。

运行爬虫

完成了上述工作以后就可以开始爬取数据了。这次一共验证了3000+的代理服务器,基本都能用。我看我的博客是快要被封了(哭)

过程中出现了Kafka Customer不能消费消息的问题,参考http://www.lingdonge.com/coding/java/4035.html得到了解决。

现在也有4000+代理服务器了,好开心啊。

添加创建时间字段

突然发现还有一个问题,为了后来研究代理服务器的可用时间,需要在表里面添加一个创建时间字段。使用下面的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 '代理服务器类型',
  `create_time` DATETIME 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

接下来修改dao

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

from dao.proxystatus import ProxyStatus
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.create_time = data[4]
                proxy_dao_item.last_validate_time = data[5]
                proxy_dao_item.retry_count = data[6]
                proxy_dao_item.last_available_time = data[7]
                proxy_dao_item.status = data[8]

            cur.close()
            conn.close()
            return proxy_dao_item
        except Exception as e:
            return None

    def find_proxy_need_to_recheck(self,timestamp):
        try:
            conn = PoolUtil.pool.connection()
            cur = conn.cursor()
            sql = "select * from proxy_list where last_validate_time < %s and status != %s limit 500"
            count = cur.execute(sql,(timestamp,ProxyStatus.PERMANENT_UNAVAILABLE) )
            proxy_dao_items = None
            if count != 0:
                proxy_dao_items = []
                result = cur.fetchall()
                for data in result:
                    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]
                    proxy_dao_items.append(proxy_dao_item)
            cur.close()
            conn.close()
            return proxy_dao_items
        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,create_time,last_validate_time,retry_count,last_available_time,status) " \
                  "values(%s,%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.create_time,
                             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,create_time = %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.create_time,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()

和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 find_proxy_by_addr(self,proxy_addr):
        return self.proxy_dao.find_proxy_by_addr(proxy_addr)

    def find_proxy_need_to_recheck(self):
        now = datetime.datetime.now();
        timestamp = now - datetime.timedelta(hours=6)
        return self.proxy_dao.find_proxy_need_to_recheck(timestamp)

    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.location
            dao_item.type = validation_result_item.type
            dao_item.create_time = datetime.datetime.now()
            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)

重新把数据都清掉,然后运行了一次爬虫。最终拥有了3900+的代理服务器。

到现在为止,第一阶段的开发任务算是完成了。明天起研究部署,补文档。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值