15 September 2011

Многопоточность в Perl, или Как я посмотрел ролик о съёмках Warehouse 13

Perl
image
Всё началось с того, что я наткнулся на видео, которое рассказывало о съёмках одного из моих любимых сериалов, Warehouse 13:
www.aoltv.com/2009/07/10/behind-the-scenes-of-warehouse-13

Клёвые штуковины в стиле «стимпанк», рассказ о взаимовыручке и дружбе, и лёгкая волшебная атмосфера, уводящая от серых будней — вот, чем мне нравится этот сериал. И вообще мне не менее интересно, чем сам фильм, бывает смотреть, как снимают этот фильм.

Но, как принято на сайте AOL TV, видео оказалось доступно только жителям US. Зачем накладывать такие ограничения — я не могу взять в толк. Где и какая жаба их душит, непонятно.
Но такая мелочь меня не могла остановить.

Очевидно, необходимо было найти прокси-сервер, за которым я бы выглядел для сайта aoltv как настоящий америкос. А для этого нужно было сначала найти список проксей, потом найти рабочие, и потом — американские.

Кстати, список подходящих проксей я нашёл здесь:
www.proxz.com/proxy_list_anonymous_us_0.html

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

Понятно, что проверка прокси практически не загружает компьютер и сеть, но при этом может проходить довольно долго, учитывая что прокся может не работать или находиться слишком далеко. Поэтому логичным было подключить многопоточность. Заодно была протестирована многопоточность в perl под Windows, с ActiveState perl 5.10.1

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

Потоки в perl подключаются прагмой

use threads;

Подробности: perldoc.perl.org/threads.html

Если при её использовании вы получите такой отказ:

This Perl hasn't been configured and built properly for the threads module to work. (The 'useithreads' configuration option hasn't been used.)

значит, пришло время собирать perl’ы.

После этого мы можем создать новый поток командой

$thread=threads->create(\&job_todo, $param);

job_todo — это имя функции, в которой и должна быть описана работа потока.
После ссылки на функцию можно передавать другие параметры, например, порядковый номер потока. Правда, у потока есть метод tid(), который и возвращает порядковый номер, но для примера оставим этот параметр.
На выходе получаем объект $thread.

Однако, если так создать все потоки, и далее в программе ничего не делать — основной скрипт завершится, и все потоки умрут на вдохе, не доделав работу.

Чтобы основная программа дождалась потоков, необходимо выполнить команду

$thread->join();

Ещё один вопрос с потоками — а как же им обмениваться информацией друг с другом, например, узнать, какие прокси из списка уже обработаны, а какие — нет? Дело в том, что по умолчанию каждый поток получает независимую копию объявленных до создания потоков переменных, и они остаются отрезаны друг от друга, как Золушка от принца.

Для этого существует прагма

use threads::shared;

которая позволяет объявлять и использовать общие, т.е. shared, переменные. Если переменную объявить так:

my $useme:shared=0;

то у всех потоков переменная $useme станет общей — когда один поток меняет её значение, другие потоки видят уже измененное значение.

Таким образом, мы можем сделать массив проксей, и к нему — shared указатель на позицию массива, по которой лежит следующая необработанная прокся.

Итак, каждый поток в начале работы берёт следующую проксю из списка, и передвигает указатель далее по массиву.
Проверяется прокся на дееспособность просто. Как известно, существуют три типа проксей — анонимные, открытые и нерабочие.
А ещё, анонимные бывают сильно анонимные (от которых никак не узнать, кто за ней стоит) и слабо анонимные (которые в одной из переменных всё-таки передают ip-адрес скрытного хитреца).
Нам нужно просто понять, работает ли прокся вообще, и какой ip у нас получается при использовании этой прокси. Для этого любой подойдёт сайт, который сообщает каждому его внешний ip-адрес.

Я выбрал для эксперимента сайт www.myip.ru. Однако, если заниматься проверкой проксей на постоянной основе, лучше сделать свой простой скрипт и положить его на какой-нибудь сервер — и быстрее, и чужой сервер не грузим.
Тем более, что скрипт этот может быть настолько простым:

#!/usr/bin/perl
 
print "Content-type: text/html\n\n$ENV{REMOTE_ADDR}";


Итак, поток, взяв проксю, пытается через неё запросить сайт и посмотреть, какой ip возвращает сайт.
Если запрос не проходит — прокси не работает, если сайт показывает наш собственный ip — значит, прокся открытая. А вот если сайт показывает другой ip, значит мы достигли цели, прокся и рабочая и анонимная.

Список проксей скрипт получает из файла, формат которого неважен, главное что там встречаются ip-адреса проксей с портами, в виде 12.34.56.78:8080
Конечно, можно пойти чуть дальше и автоматически парсить какой-нибудь из сайтов, публикующих список проксей.

Также мне не потребовалось проверять прокси на принадлежность к стране, так как первая же прокси оказалась из US. Это тоже будет нетрудно сделать вам в качестве домашнего задания ( use Geo::IP; ).

Далее приведён текст скрипта с комментариями. Используйте, как хотите. В случае получения лютой прибыли вышлю номер кошелька для пожертвований.
Главное, не причиняйте никому вреда, и да пребудет с вами perl.

PS: ролик поглядел,- коротко, но интересно.

#!/usr/bin/perl -w
 
# Подключаем библиотеки
use strict;
use LWP::UserAgent;
use threads;
use threads::shared;
 
# Установка переменных
my @threads;
my @proxy;
my $threads=100;
my $last_p:shared=0;
my $ip_checker='http://www.myip.ru/get_ip.php?loc=';
 
# Список рабочих анонимных проксей
open LOG, ">>proxy.log";
 
# Узнаем свой текущий внешний ip
my $ua = LWP::UserAgent->new;
$ua->agent("Mozilla/5.0");
my $res=$ua->get($ip_checker);
exit if (!$res->is_success);
my $s=$res->decoded_content;
$s=~m/(\d+\.\d+\.\d+\.\d+)/s;
my $myip=$1;
 
# Читаем список проксей
open FIL, 'proxy.lst';
while ($s=<FIL>)
{
  while ($s=~s/(\d+\.\d+\.\d+\.\d+:\d+)//)
  {
    push(@proxy, $1);
  }
}
print "Proxies found: ".@proxy."\n";
 
# Создаём нужное количество потоков
for my $t (1..$threads) {
  push @threads, threads->create(\&check_proxy, $t);
}
# Дожидаемся окончания работы всех потоков
foreach my $t (@threads) {
  $t->join();
}
 
sub check_proxy
# Проверка проксей
{
  # Номер текущего потока
  my $num=shift;
  print "+ Thread $num started.\n";
 
  # Бесконечный цикл
  while (1)
  {
    # Берём следующий номер в списке
    my $seq=$last_p++;
    # Если список кончился, заканчиваем
    if ($seq>=@proxy)
    {
      print "- Thread $num done.\n";
      return;
    }
    # Получаем следующую проксю из списка
    my $proxy=$proxy[$seq];
 
    # Стартует качалка
    my $ua = LWP::UserAgent->new;
    $ua->agent("Mozilla/5.0");
    $ua->proxy(['http'], "http://$proxy/");
    my $res=$ua->get($ip_checker);
 
    # Отчёт
    printf("Thread %02d; Seq. %03d; Proxy %20s; Status: ", $num, $seq, $proxy);
    # Не работает
    if (!$res->is_success) {
      print "Unable to connect.\n";
      next;
    }
 
    # Отданная страничка
    my $s = $res->decoded_content;
 
    # На странице наш ip, значит прокся открытая
    if ($s =~ m/$myip/s)
    { 
      print "Open.\n";
    }
    # Прокся не обязательно оставляет свой ip.
    # Если на странице есть какой-то ip, но не наш - значит, прокся анонимная
    elsif ($s =~ m/(\d+\.\d+\.\d+\.\d+)/s)
    { 
      print "Anonymous!\n";
      print LOG "$proxy\n";
    } 
    # Что-то нехорошее произошло
    else
    { 
      print "Not working...\n";
    }
  }
}
 
Tags: многопоточность threads warehouse 13
Hubs: Perl
+23
13.3k 45
Comments 30
Ads