python爬虫--代理的使用

代理的使用

  • 代理池的维护
    • 代理池
    • 付费代理
    • ADSL拨号代理

代理池的维护

基本模块:

  • 存储模块: 使用Redis的有序集合负责存储抓取下来的代理,保证代理不重复
  • 获取模块:需要定时在各大代理网站抓取代理,代理的形式都是IP加端口,尽量从不同来源获取,尽量抓取高匿代理。
  • 检测模块:需要定时检测数据库中的代理,设置一个检测链接,最好是爬取哪个网站就检测哪个网站
  • 接口模块:需要用API来提供对外服务的接口,由于可用代理可能有多个,那么我们可以设置一个随机返回某个可用代理的接口,这样就能保证每个可用代理都可以取到实现负载均衡

代理池

存储模块:使用redis的有序集合,有序集合的每个元素都有一个分数字段,分数是可以重复的。根据分数对集合进行排序,可以作为判断一个代理是否可用的标志。

操作数据库的有序集合

MAX_SCORE=100
MIN_SCORE=0
INITIAL_SCORE=10
REDIS_HOST='localhost'
REDIS_PORT=6379
REDIS_PADDWORD=None
REDIS_KEY='proxies'
import redis
from random import choice

class RedisClient(object):
    def __init__(self,host=REDIS_HOST,port=REDIS_PORT,password=REDIS_PADDWORD):
        """
        初始化
        :param host:Redis 地址
        :param port: Redis 端口
        :param password: Redis 密码
        """

        self.db=redis.StrictRedis(host=host,port=port,password=password,decode_responses=True)
    def add(self,proxy,score=INITIAL_SCORE):
        """
        添加代理,设置分数为最高
        :param proxy: 代理
        :param score: 分数
        :return: 添加结果
        """
        if not self.db.zscore(REDIS_KEY,proxy):
            return self.db.zadd(REDIS_KEY,score,proxy)
    #随机获取代理
    def random(self):
        """
        随机获取有效代理,首先尝试获取最高分数代理,如果最高分数不存在,则按照排名获取,否则异常
        :return: 随即代理
        """
        result=self.db.zrangebyscore(REDIS_KEY,MAX_SCORE,MAX_SCORE)
        if len(result):
            return choice(result)
        else:
            result=self.db.zrevrange(REDIS_KEY,0,100)
            if len(result):
                return choice(result)
            else:
                raise PoolEmptyError
    #检测代理无效时分数减1
    def decrease(self,proxy):
        """
        代理值减一分,分数小于最小值,则代理删除
        :param proxy: 代理
        :return: 修改后的代理分数
        """
        score=self.db.zscore(REDIS_KEY,proxy)
        if score and score > MIN_SCORE:
            print('代理',proxy,'当前分数',score,'减1')
            return self.db.zincrby(REDIS_KEY,proxy,-1)
        else:
            print('代理',proxy,'当前分数',score,'移除')
            return self.db.zrem(REDIS_KEY,proxy)
    #判断代理是否在集合
    def exists(self,proxy):
        """
        判断是否存在
        :param proxy:代理
        :return: 是否存在
        """
        return not self.db.zscore(REDIS_KEY,proxy)==None
    #设置代理分数
    def max(self,proxy):
        """
        将代理设置为MAX_SCORE
        :param proxy: 代理
        :return: 设置结果
        """
        print('代理',proxy,'可用,设置为',MAX_SCORE)
        return self.db.zadd(REDIS_KEY,MAX_SCORE,proxy)
    #返回当前集合的元素个数
    def count(self):
        """
        获取数量
        :return:数量
        """
        return self.db.zcard(REDIS_KEY)
    #返回所有的代理列表
    def all(self):
        """
        获取全部代理
        :return: 全部代理列表
        """
        return self.db.zrangebyscore(REDIS_KEY,MIN_SCORE,MAX_SCORE)

获取模块

import json
from .utils import get_page
from pyquery import PyQuery as pq

class ProxyMetaclass(type):
    def __new__(cls, name,bases,attrs):
        count=0
        attrs['__CrawlFunc__']=[]
        # 判断方法开头是否是Crawl,如果是则将其加入到__CrawlFunc__属性中,就成功将所有以crawl开头的方法定义成了一个属性,动态获取到所有以crawl开头的方法列表
        for k,v in attrs.items():
            if 'crawl_'in K:
                attrs['__CrawlFunc__'].append(k)
                count+=1
        attrs['__CrawlFuncCount__']=count
        return type.__new__(cls,name,bases,attrs)

class Crawler(object,metaclass=ProxyMetaclass):
    def get_proxies(self,callback):
        proxies=[]
        for proxy in eval("self.{}()".format(callback)):
            print('成功获取到代理',proxy)
            proxies.append(proxy)
        return proxies
    def crawl_daili66(self,page_count=4):
        """
        获取代理66
        :param page_count:页码
        :return: 代理
        """
        start_url='http://www.66ip.cn/{}/html'
        urls=[start_url.format(page)for page in range(1,page_count+1)]
        for url in urls:
            print('Crawling',url)
            html=get_page(url)
            if html:
                doc=pq(html)
                trs=doc('.containerbox table tr:gt(0)').items()
                for tr in trs:
                    ip=tr.find('td:nth-child(1)').text()
                    port=tr.find('td:nth-child(2)').text()
                    yield ':'.join([ip,port])
    def crawl_proxy360(self):
        """
        获取Proxy360
        :return: 代理
        """
        start_url='http://www.proxy360.cn/Regio.China'
        print('Crawling',start_url)
        html=get_page(start_url)
        if html:
            doc=pq(html)
            lines=doc('div[name="list_proxy_ip"]').items()
            for line in lines:
                ip=line.find('.tbBottomLine:nth-child(1)').text()
                port=line.find('.tbBottomLine:nth-child(2)').text()
                yield ':'.join([ip,port])

    def crawl_goubanjia(self):
        """
        获取Goubanjia
        :return: 代理
        """
        start_url='http://www.goubanjia.com/free/gngn/index.shtml'
        html=get_page(start_url)
        if html:
            doc=pq(html)
            tds=doc('td.ip').items()
            for td in tds:
                td.find('p').remove()
                yield td.text().replace(' ','')
#定义一个Getter类,用来动态调用所有以crawl开头的方法
from db import RedisClient
from crawler import Crawler
POOL_UPPER_THRESHOLD=10000

class Getter():
    def __init__(self):
        self.redis=RedisClient()
        self.crawler=Crawler()
    def is_over_threshold(self):
        """
        判断你是否达到了代理池的限制
        :return:
        """
        if self.redis.count()>=POOL_UPPER_THRESHOLD:
            return True
        else:
            return False
    def run(self):
        print('获取器开始执行')
        if not self.is_over_threshold():
            for callback_label in range(self.crawler.__CrawFuncCount__):
                callback=self.crawler.CrawlFunc__[callback_label]
                proxies=self.crawler.get_proxies(callback)
                for proxy in proxies:
                    self.redis.add(proxy)

检测模块

VALID_STATUS_CODES=[200]
TEST_URL='http://www.baidu.com'
BATCH_TEST_SIZE=100
class Tester(object):
    def __init__(self):
        self.redis=RedisClient()
    async def test_single_proxy(self,proxy):
        """
        测试单个代理
        :param proxy: 单个代理
        :return: None
        """
        conn=aiohttp.TCPConnector(verify_sal=False)
        async with aiohttp.ClientSession(connector=conn)as session:
            try:
                if isinstance(proxy,bytes):
                    proxy=proxy.decode('utf-8')
                real_proxy='http://'+proxy
                print('正在测试',proxy)
                async with session.get(TEST_URL,proxy=real_proxy,timeout=15) as response:
                    if response.status in VALID_STATUS_CODES:
                        self.redis.max(proxy)
                        print('代理可用',proxy)
                    else:
                        self.redis.decrease(proxy)
                        print('请求响应码不合法',proxy)
            except(ClientError,clientConnectorError,TimeoutError,AttributeError):
                self.redis.decrease(proxy)
                print('代理请求失败',proxy)
    #获取所有的代理列表
    def run(self):
        """
        测试主函数
        :return: None
        """
        print('测试器开始运行')
        try:
            proxies=self.redis.all()
            loop=asyncio.get_event_loop()
            #批量测试
            for i in range(0,len(proxies),BATCH_TEST_SIZE):
                test_proxies=proxies[i:i+BATCH_TEST_SIZE]
                tasks=[self.test_single_proxy(proxy) for proxy in test_proxies]
                loop.run_until_complete(asyncio.wait(tasks))
                time.sleep(5)
        except Exception as e:
            print('测试器发送错误',e.args)

接口模块

from flask import Flask,g
from db import RedisClient
__all__=['app']
app=Flask(__name__)

def get_conn():
    if not hasattr(g,'redis'):
        g.redis=RedisClient()
    return g.redis
@app.route('/')
def index():
    return '

Welcome to Proxy Pool System

'
@app.route('/random') def get_proxy(): """ 获取随机可用代理 :return: 随机代理 """ conn=get_conn() return conn.random() @app.route('/count') def get_counts(): """ 获取代理池总量 :return: 代理池总量 """ conn=get_conn() return str(conn.count())

调度模块

TEST_CYCLE=20
GETTER_CYCLE=20
TESTER_ENABLD=True
GETTER_ENABLD=True
API_ENABLD=True
from multiprocessing import Process
from api import app
from getter import Getter
from tester import Tester

class Scheduler():
    def schedule_tester(self,cycle=TEST_CYCLE):
        """
        定时测试代理
        :param cycle:
        :return:
        """
        tester=Tester()
        while True:
            print('测试器开始运行')
            tester.run()
            time.sleep(cycle)
    def schedule_api(self):
        """
        开启API
        :return:
        """
        app.run(API_HOST,API_PORT)
    def run(self):
        print('代理池开始运行')
        if TESTER_ENABLD:
            tester_process=Process(target=self.schedule_tester)
            tester_process.start()
        if GETTER_ENABLD:
            getter_process=Process(target=self.schedule_getter)
            getter_process=start()
        if API_ENABLD:
            api_process=Process(target=self.schedule_api())
            api_process.start()
if __name__ == '__main__':
    app.run()

付费代理

  • 讯代理:提供接口获取海量代理,按天或者按量收费。
  • 阿布云代理:搭建了代理隧道,直接设置固定域名代理。

ADSL拨号代理

ADSL通过拨号的方式上网,需要输入ADSL账号和密码,每次拨号就更换一个IP。

你可能感兴趣的:(python爬虫,python,proxy)