Проверяем список прокси серверов на доступность, с помощью nodejs



Допустим у нас есть список прокси серверов в овер 9999 строк, и как обычно большая часть из них не работает. Нам же нужно выбрать только работоспособные, да ещё которые позволяют достучаться до конкретного сайта.

Для самых нетерпеливых, готовый проект: https://gitlab.com/hololoev/proxy_server_checking

Остальных приглашаю разобрать как оно работает.

Подготавливаем список проксей

Давайте договоримся, что список проксей на проверку будет лежать в csv файле, со следующими заголовками:

addr,port,type

Достать список проксей можно разными способами, самый простой это скопипастить с https://hidemy.name. Но именно там 90% проксей не рабочие.

Пишем скрипт проверки

Делаем заготовку скрипта: проверяем входящие параметры, подготавливаем переменные:

#!/bin/node

'use strict';

const fs = require('fs');
const colors = require('colors');
const request = require('request');

async function main() {  
  if ( process.argv.length < 5 ) {
    console.log('Not enough arguments, usage:');
    console.log('node checker.js input_file.csv output_file.csv http://test_website [ optional pseudo_threads_count ]');
    process.exit(1);
  }
  
  const srcFile = process.argv[ 2 ];
  const dstFile = process.argv[ 3 ];
  const testWebsite = process.argv[ 4 ];
  let pseudoThreads =  parseInt(process.argv[ 5 ]);
  let bruteForce = process.argv[ 6 ]; // yes|no

  if ( !fs.existsSync(srcFile) ) {
    console.log(`${srcFile} not exist`);
    process.exit(1);
  }

}

main();

Затем пишем функцию по разбору csv файла: разбиваем файл на строки, затем строку, разбиваем на колонки использую запятую как разделитель, а из первой строки берём хэдеры колонок.

function parseCsv(srcString) {
  let result = [];
  let headers = [];
  let lines = srcString.split('
');
  
  for (let name of lines[ 0 ].split(',')) {
    headers.push(name.trim());
  }
  
  for (let lineIndex=1; lineIndex<lines.length; lineIndex++) {
    let cols = lines[ lineIndex ].split(',');
    let resLine = {};
    
    for (let colIndex = 0; colIndex<headers.length; colIndex++)
      if( cols[ colIndex ] )
        resLine[ headers[ colIndex ] ] = cols[ colIndex ].trim();
    
    result.push(resLine);
  }

  return result;
}

Следующая важная часть- это модифицированный request. Модифицировать его нужно потому, что его стандартное свойство timeout определяет не таймаут на весь запрос а таймаут между сеансами передачи данных. И может сложиться ситуация, что при выставленном таймауте 1с, запрос будет выполняться десятки секунд.

Нам же нужно ограничить максимальное время выполнения запроса, т.к. нет никакого смысла от прокси, через которую простейшие запросы растягиваются на несколько секунд.

function TRequest(options, timeout) {
  return new Promise( function(resolve, reject) {
    let thisRequest = request(options, function(error, response) {
      if ( error ) {
        return reject(error);
      }
      return resolve(response);
    });
    
    setTimeout(function() {
      thisRequest.abort();
      return reject(new Error('HTTP request timed out: REQUESTTIMEOUT'));
    }, timeout);
  });  
}

В этой функции мы обернули стандартный реквест, в промис, внутри которого выставили обычный жабаскриптовый setTimeout, и если этот setTimeout выполняется быстрее, чем реквест, то абортим реквест и выбрасываем ошибку.

Ещё с реквестом есть бага, а точнее с либой tunnel-agent которая юзается реквестом: https://github.com/request/tunnel-agent/issues/43. По этому нам придётся где-нить в main() воткнуть костыль:

  process.on('uncaughtException', function() {
    // Do nothing
    // see issue: https://github.com/request/tunnel-agent/issues/43
  });

Теперь пишем функцию, по проверке прокси: скармливаем нашей TRequest подготовленные параметры, разбираем ответ, и вводим в консоль результаты. А ещё, научим функцию брутфорсиль логин/пароль, вдруг пригодится.

async function check(proxy, testWebsite, bruteForce) {  
  let options = Object.assign({}, requestOptions);
  
  if( proxy.login && proxy.pass ) {
    options.proxy = `${proxy.type.toLowerCase()}://${proxy.login}:${proxy.pass}@${proxy.addr}:${proxy.port}`;
  } else {
    options.proxy = `${proxy.type.toLowerCase()}://${proxy.addr}:${proxy.port}`;
  }
  
  options.url = testWebsite;
  options.headers.Origin = testWebsite;
  
  let scanStart = new Date();
  let testStart = new Date();
  
  try {
    await TRequest(options, requestTimeout);
    let testTime = new Date() - testStart;
    console.log(colors.green(`${options.proxy} - Result: OK time: ${testTime}`));
    return {
      type: proxy.type,
      addr: proxy.addr,
      port: proxy.port,
      result: 'OK',
      time: testTime,
      login: '',
      password: ''
    };
  } catch (err) {
    // Else, try to authorize with login/password
    if( bruteForce === 'yes' )
      for(let login of logins)
        for(let pass of passwords) {
          testStart = new Date();
          options.proxy = `${proxy.type.toLowerCase()}://${login}:${pass}@${proxy.addr}:${proxy.port}`;
          try {
            await TRequest(options, requestTimeout);
            let testTime = new Date() - testStart;
            console.log(colors.green(`${options.proxy} - Result: OK time: ${testTime}`));
            return {
              type: proxy.type,
              addr: proxy.addr,
              port: proxy.port,
              result: 'OK',
              time: testTime,
              login: login,
              password: pass
            };
          } catch {
            // DO nothing
          }
        }
  }
  
  let scanTime = new Date() - scanStart;
  console.log(colors.red(`${proxy.type}://${proxy.addr}:${proxy.port} - Result: ERROR time: ${scanTime}`));
  
  return {
    type: proxy.type,
    addr: proxy.addr,
    port: proxy.port,
    result: 'ERROR',
    time: scanTime,
    login: '',
    password: ''
  };
}

И последний важный этап, если мы будем проверять строка за строкой, то это может занять очень много времени. Нам нужно распараллелить проверку.

Распараллеливать мы будем условно, т.к. нода асинхронная, и позволяет условно, одновременно, выполнять несколько операций. Дописываем нашу main()

  ...
  let raundsCount = parseInt(srcTable.length / pseudoThreads) + 1;
  
  for (let raund=0; raund<raundsCount; raund++) {
    let promises = [];
    
    for (let i=0; i<pseudoThreads; i++) {
      let index = raund * pseudoThreads + i;
      
      if ( srcTable[ index ] ) {
        promises.push(check(srcTable[ index ], testWebsite, bruteForce));
      }
    }
    
    let results = await Promise.all(promises);
    let outString = '';
    for (let result of results) {
      let cols = [];
      
      for (let header of outHeaders) {
        cols.push(result[ header ]);
      }
      
      outString += cols.join(',') + '';
    }
    
    fs.writeFileSync(dstFile, outString, { encoding: 'utf8', flag: 'a' });
  }

В этом куске кода, мы разбиваем весь список проксей на пачки, затем 1 пачку целиком скармливаем проверке, а результат сохраняем в массив промисов: "promises.push(check(srcTable[ index ], testWebsite));"

После этого через Promise.all дожидаемся всех результатов, разбираем результат и сохраняем его в итоговый файл.

Напоминаю, что готовый скрипт тут: https://gitlab.com/hololoev/proxy_server_checking

Проверяем список проксей на работоспособность

Ну и теперь берём наш скриптик и запускаем его:

./checker.js ./input/proxy_list_example.csv ./result.csv "https://allwebstuff.info" 10

Меньше минуты и 320 проксей проверили, в 10 потоков. А в 100+ потоков проверка занимает секунды.

Update

Апдейтнул и пост и код на гитлабе. Добавилась фича по автобрутфорсу логан/пароля. И ещё немного мелкофиксиков.

Продолжение следует


Сообщество: AWS

Комментариев(2)


ID: #643   Создан:
Автор: Vladimir

Здравствуйте! Работает, но UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'toLowerCase' of undefined at check (checker.js:82:35) at main (checker.js:184:23) at processTicksAndRejections (internal/process/task_queues.js:97:5) (node:8648) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 197)

ID: #644   Создан:
Автор: ololoev
>>643

Привет. 100% неправильную цсвшку скармливаете на проверку. Нужно чтобы все заголовки и колонки были, ну или нужно немного подправить скриптик, чтобы игнорил кривые данные. Будет время, скинте мне цсв, скриншот в телеграм, посмотрю.

Всего: 2 комментариев на 1 страницах

Ваш комментарий будет анонимным. Чтобы оставить не анонимный комментарий, пожалуйста, зарегистрируйтесь



Сообщества