Массовая проверка proxy на работоспособность

Многие из тех, кто писал парсеры, сталкивались с очень неприятной ситуацией, когда сайт блокирует частые запросы с одного IP. Если же выставить задержку с помощью sleep(), процесс идёт очень медленно, что нас не очень устраивает.
Проведя в поиске решения какое-то время, все понимают, что для того, чтобы сервер, на который посылаются запросы, не блокировал их,- нужно использовать прокси-серверы.
Ну что-ж, отлично, решение найдено! Программист, довольный собой, идёт искать списки прокси-серверов. Если задача, которую ему нужно выполнить, небольшая, то он успешно находит несколько IP прокси, вручную их записывает в файл и успешно использует.
Но если запросов нужно делать очень много, десятка прокси нам не хватит. В ужасе мы открываем для себя новость, что нет больших списков прокси, 100% которых бы работали и предоставлялись бесплатно.
Бесплатные прокси часто перестают работать после одного дня работы. И даже если мы сегодня скопируем к себе пару сотен прокси, назавтра половина из них будет недоступно.

Я в один прекрасный день столкнулся именно с такой ситуацией. Не знал, что делать. Конечно можно было бы купить платные списки прокси, но не вариант, что и там все прокси будут 100% рабочими.
Поэтому я пришёл к выводу, что нужно как-то проверять прокси серверы на работоспособность, перед тем, как их использовать. Поэтому решил написать скрипт проверки, который и представляю Вашему вниманию:
<?php
/**
    * Для загрузки proxy
    */
    class Proxy {        
        /**
        * 
        * @var Адрес, с которого берём proxy 
        * Не менять без понимая работы этого класса!
        * 
        */
        private static $url = 'http://www.freeproxylists.com/';
        
        /**
        * 
        * @var Расширение файлов кэша с proxy
        * 
        */
        private static $ext = '.proxy';
                
        /**
        * 
        * @var Массив со списком proxy
        * 
        */
        private static $proxy = array();
                
        /**
        * Для загрузки страницы, на которой список proxy
        * 
        * @return
        */
        private static function loadProxy(){  
            # Создаём экземпляр класса работы с CURL
            $curl = new RollingCurl;
            
            # Добавляем URL на вполнение
            $curl->get( self::$url . 'elite.html' );
            
            # Выполняем запрос
            $html = $curl->execute();
            
            # Ищем на странице ссылку на самую новую версию списка
            preg_match( '~elite/(\d+\.html)~', $html, $matches );
            
            # Формируем ссылку на загрузку proxy
            $url = self::$url . 'load_elite_' . $matches[1];
            
            # Добавляем URL на вполнение
            $curl->get( $url );
            
            # Выполняем запрос
            $data = $curl->execute();
            
            # Находим в полученных данных proxy
            preg_match_all('~(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[^\d]+(\d{1,4})~', $data, $matches);
            
            # Вычисляем количество найденных IP
            $size = count( $matches[1] );
            
            # Обходим список IP
            for( $i=0; $i<$size; $i++ )
                # Пишем в итоговый массив найденные proxy
                self::$proxy[] = array(
                    'ip'   => $matches[1][$i],
                    'port' => $matches[2][$i]
                );
        }

        /**
        * Для получения временной метки текущей даты
        * 
        * @return
        */
        private static function getNameCache(){
            # Возвращаем текущую дата
            return 
                date('dmy') . self::$ext;
        }

        /**
        * Для загрузки proxy из tmp-папки
        * 
        * @return
        */
        private static function loadFromCache(){
            # Получаем данные из TMP
            if( !$data = Tmp::read( self::getNameCache() ) )
                # Прерываем работу
                return false;
            
            # Перекодируем и заносим данные в итоговый массив
            return
                (bool) self::$proxy = json_decode( $data, true );
        }
        
        /**
        * Для кэширования proxy
        * 
        * @return
        */
        private static function rememberProxy(){
            # Записываем закодированный массив в кэш
            Tmp::write( 
                self::getNameCache(), 
                json_encode( self::$proxy )
            );
        }
        
        /**
        * Для обратного вызова
        * 
        * @return
        */
        public static function callbackCheck( $output, $info ){
            # Если в полученных от данных нет слова "success"
            if( !substr_count( $output, 'success' ) )
                # В цикле обходим массив proxy
                foreach( self::$proxy as $key=>$proxy )
                    # Если IP равен текущему
                    if( $proxy['ip'] == $info['primary_ip'] ){
                        # Удаляем элемент
                        unset( self::$proxy[$key] );
                    }
        }
        
        /**
        * Для проверки работоспособности proxy
        * 
        * @return
        */
        private static function checkProxy(){
            # Создаём экземпляр класса работы с CURL
            $curl = new RollingCurl( 'Proxy::callbackCheck' );
            
            # Обходим массив proxy
            foreach( self::$proxy as $proxy ){
                # Дополняем запросы
                $curl->get( 
                    'http://' . DOMAIN . '/responseproxy.php',
                    null,
                    array(
                        CURLOPT_PROXY     => $proxy['ip'], 
                        CURLOPT_PROXYPORT => $proxy['port'],
                        CURLOPT_CONNECTTIMEOUT => 20,
                        CURLOPT_TIMEOUT        => 20   
                    )
                );
            }
           
            # Выполняем запросы
            $curl->execute( 50 );
        }
            
        /**
        * Для получения массива proxy серверов
        * 
        * @return
        */
        public static function get() {
            # Если в tmp-папке нет записанных proxy
            if( !self::loadFromCache() )
                # Загружаем список proxy
                self::loadProxy();
                
            # Если не удалось получить proxy
            if( !sizeof( self::$proxy ) )
                # Бросаем исключение
                throw new Exception('Не удалось загрузить список proxy');
            
            # Проверяем proxy на работоспособность
            self::checkProxy();
            
            # Пишем прокси в кэш
            self::rememberProxy();
            
            # Возвращаем данные
            return self::$proxy;
        }
    }

Как видите, в данном классе прокси-серверы уже грузятся с сайта freeproxylists.com и Вам нет необходимости искать где-то прокси, а потом передавать на проверку. Всё уже сделано мной :)
Теперь давайте разберём некоторые методы этого класса. Во-первых, метод проверки (checkProxy()):
Для выполнения запросов используется CURL, а вернее библиотека RollingCurl (скачать Вы её можете с GitHub), которая является надстройкой над стандартным CURL. Работать с данной библиотекой очень удобно, так как можно использовать так называемую многопоточность, т.е. несколько запросов может выполняться независимо друг от друга. Таким образом мы за определённый промежуток времени сможем проверить большее количество прокси-серверов.

Перед использованием данного класса Вам необходимо установить константу DOMAIN, в которой укажите свой домен, например так:
define(DOMAIN, getenv('SERVER_NAME'));

Отлично, константу установили. Теперь нам нужно делать куда-то запросы через определённый прокси, откуда будем получать ответ. Давайте создадим для этого в корне сайта файл "responseproxy.php", в который поместим просто слово "success". Более в нём ничего не требуется.
Нужно это для того, чтобы класс делал запросы к этому файлу и получал его содержимое.

В методе checkProxy качестве callback-функции мы указываем метод Proxy::callbackCheck, в котором проверяем, присутствует ли в ответе слово "success". Если присутствует, запоминаем этот proxy, как рабочий.
В итоге будет выполнен запрос к файлу "responseproxy.php" через определённый прокси, и если прокси рабочий, мы получим содержимое файла "responseproxy.php".

В данном классе также реализована функция кэширования проверенных прокси на сутки. В методе loadFromCache производится идёт проверка, есть ли сохранённые прокси за сегоднящний день в папке tmp.
Сохранение и чтение кэша производится с помощью класса Tmp, который представляю ниже:
<?php
    /**
    * Для работы с папкой tmp
    */
    class Tmp{
        /**
        * 
        * @var Временная метка текущей даты
        * 
        */
        private static $validity;
        
        /**
        * 
        * @var Для установки текущей временной метки
        * 
        */
        private static function setValidity(){
            # Устанавливаем метку
            self::$validity = strtotime("-1 day");
        }

        /**
        * Для считывания информации из файла
        * @param string $name - имя файла
        * 
        * @return
        */
        public static function read( $name ){
            # Если нет файла
            if( !file_exists( TMP . $name ) )
                # Прерываем работу
                return false;
            
            # Считываем и возвращаем данные из кэша
            return 
                file_get_contents( TMP . $name );
        }
        
        /**
        * Для записи нового файла в папку
        * @param string $name - имя нового файла
        * @param string $contents
        * 
        * @return
        */
        public static function write( $name, $contents ){
            # Пишем новый файл
            file_put_contents( TMP . $name, $contents );
        }
        
        /**
        * Для очистки папки TMP
        * 
        * @return
        */
        public static function clean(){
            # Устанавливаем текущую временную метку
            self::setValidity();
            
             # Создаём объект работы с директорией
            $iterator = new DirectoryIterator( TMP );
            
            # Обходим полученные данные
            foreach( $iterator as $object ){
                # Если объект - файл
                if( $object->isFile() && $object->getMTime() < self::$validity )
                    # Удаляем файл
                    @unlink( $object->getPathname() );
            }
        }
    }

На самом деле Вы можете не использовать этот класс, а немного изменить класс Proxy под использование Redis или Memcashed.
Для использования этого класса Вам нужно указать установить константу TMP, указав в неё путь от корня к папке, в которой Вы будете хранить файлы с сохранёнными proxy.
Автор: