PythonとRedisを使用して無料のプロキシプールを構築する

クローラーを使用してWebサイトを繰り返し要求すると、IPがブロックされた状態に戻る可能性があります。その後、プロキシを使用してIPを偽装し、要求を再度開始できるようにする必要があります。

エージェントプールの全体的な構造

プロキシIPソース:主要なプロキシIP Webサイト

  • プロキシIP取得:pythonクローラー

  • エージェントプールストレージ:redisデータベース

  • プロキシIP検出:リクエストを作成し、利用可能かどうかを確認します

スケジューラー:上記の機能の実行を全体的にスケジュールする責任があります

スケジューラー:スケジューラークラス

スケジューラ自体には特定の機能はありません。既存のクラスとメソッドを呼び出して、エージェントのキャプチャと検出を完了するだけです。

from Myself.config import *
from Myself.getter import ProxyGetter
from Myself.tester import VaildTest
from Myself.db import RedisClient
import time
import multiprocessing
from multiprocessing import Process

'''
调度器
调用tester来检测IP是否可用
调用getter来从各大网站获取代理IP        
'''
class Scheduler():
    #获取代理的方法,传入lock锁住数据库的操作,cycle为周期时间
    def get_proxy(self,lock,cycle=GET_PROXY_CYCLE):
        while True:
            lock.acquire()
            conn = RedisClient()
            #具体的操作由ProxyGetter类发起
            getter =ProxyGetter(conn)
            print('正在获取IP')
            try:
                #执行检测程序
                getter.run()
            except Exception as e:
                print('程序出了点小异常', e.args)
            finally:
                #释放锁并且休眠程序,等待周期结束
                lock.release()
                time.sleep(cycle)

    #检测代理的方法,传入lock锁住数据库的操作,cycle为周期时间
    def test_proxy(self,lock,cycle=TEST_PROXY_CYCLE):
        while True:
            lock.acquire()
            conn = RedisClient()
            # 具体的操作由RedisClient类发起
            tester = VaildTest(conn)
            print('正在检测IP')
            try:
                #执行检测程序
                tester.valid_test()
            except Exception as e:
                print('程序出了点小异常',e.args)
            finally:
                # 释放锁并且休眠程序,等待周期结束
                lock.release()
                time.sleep(cycle)
    def run(self):
        #获取锁的对象
        lock = multiprocessing.Lock()
        if VALID_TEST_PROCESS:
            valid_test_process = Process(target=self.test_proxy,args=(lock,))
            valid_test_process.start()
        if GET_PROXY_PROCESS:
            get_proxy_process = Process(target=self.get_proxy,args=(lock,))
            get_proxy_process.start()

if __name__=='__main__':
    test=Scheduler()
    test.run()

runメソッドでは、2つのプロセスを開いてそれぞれtest_proxyメソッドとget_proxyメソッドを開始し、次にProxyGetterクラスとVaildTestクラスを具体的に呼び出して特定の関数操作を完了しました。

redisデータベースの操作を伴うため、2つのプロセスをロックする必要があります。サイクルパラメータは、実行中の2つのプロセスのサイクルです。

プロキシIP取得:ProxyGetterクラス

import requests
from bs4 import BeautifulSoup
from pyquery import PyQuery as pq
import bs4
from Myself.db import RedisClient

'''
这个类为获取代理IP的类
'''

class ProxyGetter(object):
    # 数据库对象必须要以参数的形式传入,否则多进程的时候会报错(不能序列化_thread.lock对象)。
    # 多进程操作数据库必须要注意进程锁问题
    def __init__(self,conn):
        self.headers={
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'zh-CN,zh;q=0.8'
        }
        self.conn=conn

    '''
    此方法用来获取网页源代码
    '''
    def get_page(self,url,charset='utf-8',options={}):
        headers=dict(self.headers,**options)
        r = requests.get(url, headers=headers,timeout=3)
        r.encoding=charset
        try:
            print('Getting result:',url,r.status_code)
            if r.status_code==200:
                return r.text
        except ConnectionError:
            print('Crawling Failed', url,r.status_code)

    '''
    此代理网站暂时无法使用
    '''
    def crawl_66(self,page_count):
        start_url = 'http://www.66ip.cn/{}.html'
        urls=[start_url.format(page) for page in range(1,page_count) ]
        for url in urls:
            html=self.get_page(url,charset='gb2312')
            soup=BeautifulSoup(html,'lxml')
            table=soup.select('table[bordercolor="#6699ff"]')[0]
            trs=table.find_all('tr')[1:-1]
            for out in trs:
                ip=out.find_all('td')[0].string
                port=out.find_all('td')[1].string
                yield{
                    'scheme':'http',
                    'proxy':':'.join([ip,port])
                }
    '''
    解析网页源代码,并且获取代理IP和端口
    page_count为爬取的页数
    '''
    def crawl_goubanjia(self,page_count):
        start_url = 'http://www.goubanjia.com/free/gngn/index{}.shtml'
        #按照页数来构造多个网页
        urls = [start_url.format(page) for page in range(1, page_count)]
        #遍历每个网页
        for url in urls:
            html = self.get_page(url)
            if html:
                doc = pq(html)
                trs = doc('table.table tr')
                for tr in trs.items():
                    td = tr.find('td.ip')
                    td.find('p').remove()
                    proxy = td.text().replace(' ', '')
                    scheme = tr.find('td:nth-child(3)').text()
                    if ',' in scheme:
                        scheme = scheme.split(',')[0]
                    if scheme and proxy:
                        #构造迭代器并返回
                        yield {
                            'scheme': 'http',
                            'proxy': proxy
                        }
    def crawl_proxy360(self):
        start_url = 'http://www.proxy360.cn/Region/China'
        print('Crawling', start_url)
        html = self.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 {
                    'scheme': 'http',
                    'proxy': ':'.join([ip, port])
                }

    def crwal_xici(self):
        start_url='http://www.xicidaili.com/'
        html=self.get_page(start_url)
        soup=BeautifulSoup(html,'lxml')
        country=soup.find(class_='odd')
        countrys=country.next_siblings
        for out in countrys:
            if(type(out)==bs4.element.Tag):
                infos=out.find_all('td')
                if len(infos)>0:
                    ip=infos[1].string
                    port=infos[2].string
                    yield {
                        'scheme':'http',
                        'proxy':':'.join([ip,port])
                    }
    #运行各个解析方法
    def run(self):
        #获取解析到的数据
        results1=self.crawl_proxy360()
        for result1 in results1:
            print('Getter Proxy:',result1)
            out=result1.get('scheme').lower()
            #将解析过的数据添加到redis数据库中
            self.conn.add(shecme=out,proxy=result1.get('proxy'))
        results2=self.crwal_xici()
        for result2 in results2:
            print('Getter Proxy:',result2)
            out=result2.get('scheme').lower()
            self.conn.add(shecme=out,proxy=result2.get('proxy'))
        results3=self.crawl_goubanjia(10)
        for result3 in results3:
            print('Getter Proxy:',result3)
            out=result3.get('scheme').lower()
            self.conn.add(shecme=out,proxy=result3.get('proxy'))
if __name__=='__main__':
    test=ProxyGetter()
    test.run()

このクラスは主に主要なプロキシWebサイトをクロールし、取得したIPをデータベースに書き込むためのものです。難しいことは何もありません。

プロキシIP検出:VaildTestクラス

このクラスは主に、キャプチャされたプロキシIPを使用してリクエストを作成するためのものです。リクエストが成功すると、プロキシIPはredisデータベースの最後に配置されます。リクエストが失敗すると、プロキシIPが先に進みます。

import redis
import random
from Myself.config import *

class RedisClient(object):

    #初始化驱动
    def __init__(self, host=HOST, port=PORT, domain=DOMAIN):
        if PASSWORD:
            self._db = redis.Redis(host=host, port=port, password=PASSWORD)
        else:
            self._db = redis.Redis(host=host, port=port)
        self.domain = domain

    #获取数据库中所有键值
    def keys(self):
        return self._db.keys(self.key('*'))

    #获取某个键
    def key(self,shecme):
        return '{domain}:{scheme}'.format(domain=self.domain,scheme=shecme)

    #将proxy传入scheme中
    def add(self,shecme,proxy):
        return self._db.zadd(self.key(shecme),proxy,DEFAULT_SCORE)

    #返回固定键值的所有值
    def all(self,scheme):
        return self._db.zrange(self.key(scheme),0,-1)

    #更新scheme键中的proxy参数,将它放在最后一个
    def up(self,scheme,proxy):
        score=self._db.zscore(self.key(scheme),proxy)
        return self._db.zincrby(self.key(scheme),proxy,MAX_SCORE-score)

    #更新scheme键中的proxy参数,将他往前移一个
    def down(self,scheme,proxy):
        self._db.zincrby(self.key(scheme), proxy, -1)
        #如果此代理的分数值小于最小分数值,则将此删除
        if self._db.zscore(self.key(scheme), proxy) <= MIN_SCORE:
            self._db.zrem(self.key(scheme), proxy)

    #设置proxy为使用状态,将数值设为50
    def set_use(self,proxy):
        self._db.zincrby(self.key('http'), proxy, -50)

    #获取一个可用IP
    def get_proxy(self):
        proxy=''
        proxy_db=self._db.zrange(self.key('http'),-2,-1)
        for out in proxy_db:
            proxy=out.decode('utf-8').strip()
            self.set_use(proxy)
        return proxy

    #获取特定数量的可用IP,做测试用
    def use_test(self):
        return self._db.zrange(self.key('http'), -8, -1)

if __name__=='__main__':
    test=RedisClient()
    out=test.use()
    print(type(out))
    print(out)

ここで作成するリクエストはたくさんあるので、grequestsクラスを使用して非同期でリクエストを作成しました。grequestsの具体的な使用法についてはBaiduをご覧ください。

プロキシプールデータベースの保存と操作:RedisClientクラス

プロキシIPは、保存、使用、削除のいずれの場合でも、redisデータベースに配置されるため、最終的な操作はデータベースに実装されるため、データベースを操作するためのRedisClientクラスを定義しました。

import redis
import random
from Myself.config import *

class RedisClient(object):

    #初始化驱动
    def __init__(self, host=HOST, port=PORT, domain=DOMAIN):
        if PASSWORD:
            self._db = redis.Redis(host=host, port=port, password=PASSWORD)
        else:
            self._db = redis.Redis(host=host, port=port)
        self.domain = domain

    #获取数据库中所有键值
    def keys(self):
        return self._db.keys(self.key('*'))

    #获取某个键
    def key(self,shecme):
        return '{domain}:{scheme}'.format(domain=self.domain,scheme=shecme)

    #将proxy传入scheme中
    def add(self,shecme,proxy):
        return self._db.zadd(self.key(shecme),proxy,DEFAULT_SCORE)

    #返回固定键值的所有值
    def all(self,scheme):
        return self._db.zrange(self.key(scheme),0,-1)

    #更新scheme键中的proxy参数,将它放在最后一个
    def up(self,scheme,proxy):
        score=self._db.zscore(self.key(scheme),proxy)
        return self._db.zincrby(self.key(scheme),proxy,MAX_SCORE-score)

    #更新scheme键中的proxy参数,将他往前移一个
    def down(self,scheme,proxy):
        self._db.zincrby(self.key(scheme), proxy, -1)
        #如果此代理的分数值小于最小分数值,则将此删除
        if self._db.zscore(self.key(scheme), proxy) <= MIN_SCORE:
            self._db.zrem(self.key(scheme), proxy)

    #设置proxy为使用状态,将数值设为50
    def set_use(self,proxy):
        self._db.zincrby(self.key('http'), proxy, -50)

    #从数据库中获取一个可用IP
    def get_proxy(self):
        proxy=''
        proxy_db=self._db.zrange(self.key('http'),-2,-1)
        for out in proxy_db:
            proxy=out.decode('utf-8').strip()
            self.set_use(proxy)
        return proxy

    #获取特定数量的可用IP,做测试用
    def use_test(self):
        return self._db.zrange(self.key('http'), -8, -1)

if __name__=='__main__':
    test=RedisClient()
    out=test.use()
    print(type(out))
    print(out)

構成ファイル:

プログラムの実行中は、redisのユーザー名とパスワード、エージェントの取得と検出の期間、テストに使用するURLなど、いくつかの特定のパラメーターがあります。

#域名
DOMAIN='proxy'

HOST='localhost'

PORT=6379

PASSWORD=''

#测试要用的网址
TEST_URL = 'http://www.baidu.com'

#设置获取代理的周期
GET_PROXY_CYCLE=500

#设置检测代理的周期
TEST_PROXY_CYCLE=100

#获取代理的开关
GET_PROXY_PROCESS = True

#检测代理的开关
VALID_TEST_PROCESS = True

#设置代理的最大分数值
MAX_SCORE = 100

#设置代理的最小分数值
MIN_SCORE = 2

#设置代理的默认分数值
DEFAULT_SCORE = 10

#设置使用中的代理的分数值
USE_STATUS_SCORE=50

上記は、エージェントプールの特定のアーキテクチャと実装のアイデアです

おすすめ

転載: blog.csdn.net/mrliqifeng/article/details/78647458