Figura 1: La pagina principale di freshmeat.net
Figura 2
Socket
o meglio ancora di IO::Socket
, quest'ultimo
infatti fornisce un'interfaccia ad oggetti per l'uso delle socket.
L'esempio successivo mostra un uso di IO::Socket per ottenere la pagina
all'indirizzo http://localhost/.
#!/usr/bin/perl -w
use IO::Socket;
$remote = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "http(80)",
) or die "cannot connect to http port at localhost";
print $remote <<'EOT';
GET / HTTP/1.0
Host: localhost
EOT
while (<$remote>) {
print;
}
Socket
è peggiore, poiché questo modulo non fa
altro che esporre in Perl le primitive C di socket.h
, tanto vale
scrivere il tutto direttamente in C!
Ovviamente lo scopo primario per uno sviluppatore dovrebbe essere semplificarsi
la vita per concentrarsi sul problema e non sui problemi necessari per ottenere
un contesto risolutivo del problema ... anche se non sempre è così.
LWP::UserAgent
permette di simulare un
client web.
require LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->env_proxy;
my $res = $ua->get('http://localhost/');
die $res->status_line unless $res->is_success;
print $res->content;
LWP::Simple
che espone un'interfaccia
procedurale equivalente a LWP::UserAgent
.
Ecco dunque cosa accade quando si usa questo modulo per tentare di risolvere il
nostro problema:
#!/usr/bin/perl -w
use strict;
use LWP::UserAgent;
use HTTP::Cookies;
use IO::Handle;
autoflush STDERR 1;
#
# LWP::UserAgent setup
#
my $cookies = HTTP::Cookies->new;
my $ua = LWP::UserAgent->new(
keep_alive => 1,
agent => 'SIBot/1.0',
requests_redirectable => ['HEAD', 'GET', 'POST'],
cookie_jar => $cookies,
);
$ua->env_proxy;
#
# quick'n'dirty user/pass request
#
print STDERR "Login: ";
my $user = <STDIN>;
chomp $user;
print STDERR "Password: ";
my $pass = <STDIN>;
chomp $pass;
#
# login
#
my $login = $ua->post('http://freshmeat.net/login', {
url => '/',
username => $user,
password => $pass,
persistent => 1,
submit => 'Login',
});
die "login failed" unless
$login->is_success or $login->content =~ /logged in as user /;
#
# retrieve data
#
my $articles = $ua->get('http://freshmeat.net/lounge/articles/');
die "failed retrieving articles" unless $articles->is_success;
#
# content parsing
#
my @list = grep(m#<a href="[^"]+"><b>[^<]+</b></a>#,
split(/[\r\n]/, $articles->content));
foreach (@list[0 .. 9]) {
m#<a href="([^"]+)"><b>([^<]+)</b></a>#;
print "$2\nhttp://freshmeat.net$1\n";
}
LWP::UserAgent
con quest'ultimo
esempio, è possibile notare come la complessità del sorgente sia ovviamente
aumentata, in particolare si rende necessario tenere traccia di una serie
di riferimenti ad oggetti creati lungo il flusso d'esecuzione.
LWP::UserAgent
sono superabili grazie
all'adozione di un altro modulo.
WWW::Mechanize
[5] è un'estensione di
LWP::UserAgent
e rende decisamente più agile la realizzazione di
scripts di automazione in virtù di una serie di metodi che permettono di
navigare con continuità attraverso i links ed i documenti html scaricati.
Una delle prime cose da notare è che WWW::Mechanize
tenta di
emulare un web client "classico", l'interfaccia esposta è sicuramente più ad
alto livello di LWP::UserAgent
, poiché ad esempio tiene traccia
della sequenza di link visitati in maniera analoga alla history di un browser,
gestisce i cookies in maniera automatica (il default di
LWP::UserAgent
è di non gestirli, da qui la necessità
nell'esempio precedente di passargli un cookie_jar
).
Altro punto a favore di WWW::Mechanize
è la possibilità di
compilare i forms e farne il submit più comodamente di quanto non si riesca
con LWP::UserAgent
.
#!/usr/bin/perl -w
use strict;
use WWW::Mechanize;
use IO::Handle;
autoflush STDERR 1;
#
# WWW::Mechanize setup
#
my $mech = WWW::Mechanize->new(
keep_alive => 1,
agent => 'SIBot/1.1',
autocheck => 1,
);
$mech->env_proxy;
#
# quick'n'dirty user/pass request
#
print STDERR "Login: ";
my $user = <STDIN>;
chomp $user;
print STDERR "Password: ";
my $pass = <STDIN>;
chomp $pass;
#
# login
#
$mech->get('http://freshmeat.net/');
$mech->submit_form(
form_number => 4,
fields => {
username => $user,
password => $pass,
}
);
die "login failed" unless $mech->content =~ /logged in as user /;
#
# retrieve data
#
$mech->follow_link(text_regex => qr/\[My\]/) or die;
$mech->follow_link(text_regex => qr/\d+ new articles/) or die;
#
# content parsing
#
my @list = grep(m#<a href="[^"]+"><b>[^<]+</b></a>#,
split(/[\r\n]/, $mech->content));
foreach (@list[0 .. 9]) {
m#<a href="([^"]+)"><b>([^<]+)</b></a>#;
print "$2\nhttp://freshmeat.net$1\n";
}
WWW::Mechanize
mantiene tutto al suo interno ed è possibile
interrogarlo solo quando servono realmente i dati.
Altra cosa importante è la gestione automatica degli errori di connessione
tramite l'impostazione ad 1 del valore di autocheck
.
die "login failed" unless
$login->is_success or $login->content =~ /logged in as user /;
die "login failed" unless $mech->content =~ /logged in as user /;
WWW:Mechanize
, è possibile seguire i
links senza dover inserire le url di cui fare GET o POST.
$mech->follow_link(text_regex => qr/\[My\]/) or die;
$mech->follow_link(text_regex => qr/\d+ new articles/) or die;
$ perl -MCPAN -e shell Terminal does not support AddHistory. cpan shell -- CPAN exploration and modules installation (v1.7601) ReadLine support available (try 'install Bundle::CPAN') cpan>
WWW::Mechanize
ed un paio di moduli di supporto.