Зручне скачування з сайту Books.ru або пристроюємо до справи WWW::Mechanize

Картинка для привернення уваги
Якщо книжка є — це добре,
А коли навпаки — погано
Замість епіграфа
Як всі знають, нещодавно була акція з можливістю придбання великої кількості електронних книг на сайті books.ru за справедливою ціною. Користувач icoz зробив скрипт для пакетного завантаження, але скрипт не дуже зручний, так як книги зберігаються під незручними іменами і їх треба скачувати руками.
Загалом, сказав я собі, що все має бути зручним і автоматичним, як відомо, «сказано-зроблено», що особливо актуально в світлі майбутньої завтра розпродажі.

Крок 1. Підключаємо необхідні модулі.
Нам знадобиться
use WWW::Mechanize;
use HTTP::Request::Common;
use LWP;
use LWP::UserAgent;

Сам модуль і кілька службових модулів, від яких він залежить. Якщо ви, як і я використовуєте Ubuntu то скачувати WWW::Mechanize з CPAN протипоказано, а замість цього краще сказати
sudo apt-get install libwww-mechanize-perl

Крок 2. Створюємо об'єкт механізації і забираємо параметри скрипта з командного рядка: логін і пароль.
my $mech = WWW::Mechanize->new();
$booklog = $ARGV[0];
$bookpsw = $ARGV[1];

Крок 3. Логинимся на сайті
my $resp = $mech->get('http://www.books.ru/member/login.php');
$mech->cookie_jar->set_cookie(0, 'cookie_first_timestamp',DateTime->now->epoch, '/', 'www.books.ru');
$mech->cookie_jar->set_cookie(0, 'cookie_pages', '1', '/', 'www.books.ru');
$resp = $mech->post('http://www.books.ru/member/login.php',[
'login' => $mail,
'password' => $password,
'go' => 'login',
'x' => rand_from_to(40, 50), 'y' => rand_from_to(1, 20), 
'token' => "
]);

Звертаю увагу рядка 2 і 3. В оригінальному коді, ці куки формуються за допомогою JavaScript, але тільки заради обчислення двох параметрів підключати JavaScript не раціонально і простіше переписати його на перлі.
Крок 4. Отримуємо загальний список наших замовлень і створюємо за нього ітератор:
$resp = $mech->get('http://www.books.ru/member/orders/');
my @order_list = mkGunz($resp->content) =~ /\<a\shref=\"http:\/\/www\.books\ru\/order.php\?order\=(\d+)\"\>/gi;
foreach my $order_id (@order_list) {...}

Звертаю увагу на функцію mkGunz, яка автоматично розтискає дані якщо сервер запакував їх за допомогою gzip.
Крок 5. Тепер нам треба з сторінки витягти авторів книги і її назва. Так як ми використовуємо модуль HTML::TokeParser для розбору сторінки, то простіше всього в потоковому режимі вихоплювати потрібні нам дані орієнтуючись по URL.
my $fname = ";
my $authors = ";
while (my $token = $stream->get_token) 
{
if ($token->[0] eq 'S' && $token->[1] eq 'a') 
{
my $href = $token->[2]{'href'};
$authors .= $stream->get_trimmed_text('/a').',' if ($href =~ /\/author\//);
if ($href =~ /show\=1/)
{
$fname = $stream->get_trimmed_text('/a');
$fname =~ s/\файл\sPDF\)//gi;
}
if ($href =~ /download\/\?file_type\=pdf/)
{
chop($authors);
$fname = trim($authors.','.$fname);
$fname =~ tr/\//_/;
$fname .= 'Pdf';
....
}
}

Крок 6. Отримати і зберегти PDF. Тут відразу кілька цікавих моментів: якщо не зробити clone, то завантажується тільки одна книжка, мабуть, баг на сайті books.ru. Не можна для збереження файлів з російськими літерами використовувати модуль IO::File, баг в модулі для версії перла v5.14.2. ну і виклик binmode, щоб не поламати PDF файли.
my $gbm = $mech->clone();
$resp = $gbm->get($href);
$resp = $gbm->submit_form(with_fields => {'agreed' => 'Y', 'go' => 1});
my $pdfFile = $resp->content;
$pdfFile = mkGunz($resp->content) unless ($resp->content =~ /^\%PDF/);
print "Saving ".$fname." as ".length($pdfFile)." bytes.\n" ;
open(my $fh, ">", $fname);
if (defined $fh) 
{
binmode($fh);
print $fh $pdfFile; 
close($fh);
}


Ну і, нарешті, все в зборі.
#!/usr/bin/perl
use WWW::Mechanize;
use HTTP::Request::Common;
use LWP;
use LWP::UserAgent;
use URI::Escape;
use HTML::TokeParser;
use DateTime;
use Compress::Raw::Zlib;
use Encode qw(decode encode);
use warnings;

sub trim($);
my $mech = WWW::Mechanize->new();
$booklog = $ARGV[0];
$bookpsw = $ARGV[1];
#die "Usage: books.su.pl <login> <password> \n" if (scalar @ARGV < 2);


$mail = $booklog;
$password = $bookpsw;

$mech->agent_alias("Linux Mozilla");
#$mech->proxy('https', 'http://127.0.0.1:8888/');
#$mech->proxy('http', 'http://127.0.0.1:8888/');
my $resp = $mech->get('http://www.books.ru/member/login.php');
$mech->cookie_jar->set_cookie(0, 'cookie_first_timestamp',DateTime->now->epoch, '/', 'www.books.ru');
$mech->cookie_jar->set_cookie(0, 'cookie_pages', '1', '/', 'www.books.ru');
#print mkGunz($resp->content)."\n";
$resp = $mech->post('http://www.books.ru/member/login.php',[
'login' => $mail,
'password' => $password,
'go' => 'login',
'x' => rand_from_to(40, 50), 'y' => rand_from_to(1, 20), 
'token' => "
]);
#print mkGunz($resp->content)."\n";
$resp = $mech->get('http://www.books.ru/member/orders/');
my @order_list = mkGunz($resp->content) =~ /\<a\shref=\"http:\/\/www\.books\ru\/order.php\?order\=(\d+)\"\>/gi;
foreach my $order_id (@order_list)
{
$resp = $mech->get('http://www.books.ru/order.php?order='.$order_id);
my $hcont = mkGunz($resp->content);
my $stream = HTML::TokeParser->new(\$hcont);
$stream->empty_element_tags(1);
my $fname = ";
my $authors = ";
while (my $token = $stream->get_token) 
{
if ($authors eq " && $fname ne "" && $token->[0] eq 'S' && $token->[1] eq 'br') 
{
$authors .= cnv($stream->get_trimmed_text('/p')).','; 
}
if ($token->[0] eq 'S' && $token->[1] eq 'a') 
{
my $href = $token->[2]{'href'};

if ($href =~ /show\=1/)
{
$fname = cnv($stream->get_trimmed_text('/a'));
$fname =~ s/\файл\sPDF\)//gi;
}
if ($href =~ /download\/\?file_type\=pdf/)
{
chop($authors);
$fname = trim($authors.','.$fname);
$fname =~ tr/\//_/;
$fname .= 'Pdf';
my $gbm = $mech->clone();
$resp = $gbm->get($href);
$resp = $gbm->submit_form(with_fields => {'agreed' => 'Y', 'go' => 1});
my $pdfFile = $resp->content;
$pdfFile = mkGunz($resp->content) unless ($resp->content =~ /^\%PDF/);
print "Saving ".$fname." as ".length($pdfFile)." bytes.\n" ;
open(my $fh, ">", $fname);
if (defined $fh) 
{
binmode($fh);
print $fh $pdfFile; 
close($fh);
}
else
{ 
die "Unable to open:".$fname."\n";
}
$authors = ";
$fname = ";
}
}
}
}

sub cnv {return shift;}#encode('cp866', decode('UTF-8', shift));}

sub rand_from_to
{
my($from, $to) = @_;
return int(rand($to - $from)) + $from;
}

sub mkGunz
{
my ($ind) = @_;
return $ind if($ind =~ /html/);
my $gun = new Compress::Raw::Zlib::Inflate(WindowBits => WANT_GZIP);
{
my $out;
my $status = $gun->inflate($ind, $out);
if ($status == Z_OK || $status == Z_STREAM_END)
{
return $out;
}
else
{
die $status.":".$ind;
}
};
}

sub trim($)
{
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}




Примітка для любителів Windows:
Швидше за все, треба змінити рядок $fname =~ tr/\//_/; $fname =~ tr/\/\:\*\?\\/_/; так як в NTFS більше заборонених символів ніж в ext4 і повозитися з кодуванням, для чого передбачена функція cnv.

Обов'язкові побажання читачеві: Бажаю не пропустити розпродаж, купити багато книжок, завантажити їх собі на планшет і спокійно на вихідних читати на дачі без інтернету.

Legal disclaimer: Так як за ліцензійною угодою після скачування перейменовувати файли забороняється, скачувати їх треба відразу під правильним і зручним ім'ям, що даний скрипт і робить!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.