Tutti sanno chi sia Buffy, ma pochi sanno dove sia, a che ora si mostri e con quali mostri.
T: ottimo inizio, complimenti.Lo scopo di questo scriptino è proprio quello di
sciogliere questi dubbi.
Lo script in questione non fa altro che simulare l'interazione con il web, come potrebbe avvenire con un browser, leggere le informazioni dal sito in cui si trova il palinsesto di Italia1 ed estrarre l'unica informazione di interesse (per noi).
T: come sarebbe a dire, "per noi"? Da quando, esattamente, ci interessa il parere degli altri?Sì, questo programmolo farebbe proprio di mestiere il client web. Non è un browser, ma è comunque un client.
T: è un duro lavoro, ma qualcuno deve pur farlo...Il tutto è scritto in Perl, e come chi conosce il Perl sa bene, TMTOWTDT (There's More Than One Way To Do This), cioé C'è Più Di Un Modo Di Farlo. L'implementazione che presentiamo è solo uno dei tanti modi di realizzare un programma del genere. È il modo più flessibile, ma meno leggero, perché usa una moltitudine di moduli esterni che devono essere caricati alla partenza dello script.
T: e sopratutto, fa più ridere, non dimenticarlo...Ci sono anche svariate cosette da notare all'interno di questo breve script, per cui provo ad analizzarlo riga per riga.
T: CHI E' CHE DORME LAGGIU'? Gurdate che dopo interrogo, eh!
1 #!/usr/bin/perl
2
3 # Project Nando Santagata
4 # Implementation Maurizio Lemmo
5 # 0.4
6
7 use HTML::Parser;
8 use HTTP::Request;
9 use HTTP::Response;
10 use LWP::UserAgent;
11 use URI::URL;
12 use Date::Manip;
13 use strict;
14 use vars qw($prev);
15
16 sub text
17 {
18 my $text = shift;
19
20 # Skip blank lines (no further analysis)
21 if(! defined $text || $text =~ /^[\s\n]+$/){
22 return
23 }
24
25 if($text =~ /buffy/i){
26 print $prev, " ", $text, "<br>\n";
27 }else{ # Copy previous line (to get the time)
28 $prev = $text;
29 }
30 }
31
32 # Get the date of the next Sunday
33 my $date = ParseDate("today");
34 my $day;
35 if(UnixDate("today", "%a") eq "Sun"){
36 $day = substr($date, 0, 8);
37 }else{
38 $day = substr(Date_GetNext($date, "Sun"), 0, 8);
39 }
40
41 my $url = "http://www.mediasetonline.com/italia1/palinsestoi1.jsp";
42 my %form = (day => $day, periododay => 'seleziona');
43 my $curl = url("http:");
44 $curl->query_form(%form);
45
46 my $req = HTTP::Request->new('POST', $url);
47 $req->push_header(Referer => $url);
48 $req->content_type('application/x-www-form-urlencoded');
49 $req->content($curl->equery);
50
51 my $ua = LWP::UserAgent->new;
52 my $resp = $ua->request($req);
53
54 my $pars = HTML::Parser->new(api_version => 3,
55 handlers => [ text => [\&text, "dtext"] ] );
56 print q{Content-type: text/html
57
58 <title>"Where is Buffy?"</title>
59 <center><font size = "+2">
60 "Where is Buffy" - Television Retriever
61 </font>
62 <hr><br>
63 <font size = "+1">
64 };
65 if(UnixDate("today", "%a") ne "Sun") {
66 print "Episodi di domenica prossima di Buffy The Vampire Slayer:<br><br>\n";
67 }else{
68 print "Episodi di questa domenica di Buffy The Vampire Slayer:<br><br>\n";
69 }
70 print "</font>";
71 $pars->parse($resp->content);
72 print q{
73 <br>nota: a volte negli episodi pomeridiani non inseriscono il secondo titolo.
74 <br><hr>
75 "Where is Buffy" - Television Retriever brought to you by
76 <a href = "mailto:mizio@stige-net.com">Tannoiser</a>
77 };
Le righe 6-14 includono i moduli esterni necessari. In realtà sarebbe stato tutto molto più semplice e sarebbero stati necessari molti moduli in meno se il programma che risponde alla ricerca sul palinsesto sul sito di Italia1 non insistesse a dare informazioni solo a chi lo interroga provenendo da una pagina dello stesso sito.
T: pretenzioso, sono d'accordo.Per far finta di essere un browser posizionato sulla pagina da cui parte la ricerca, abbiamo dovuto giocare un po' con gli header della richiesta HTTP e questo ci ha costretto ad includere tutti i moduli necessari ad una interazione approfondita con il protocollo.
Per il momento saltiamo le righe 16-31, su cui ritorneremo
più tardi.
Le righe 33-39 usano delle funzioni membro del modulo Date::Manip
per calcolare la data della prossima domenica.
Le righe 41-43 definiscono un po' di parametri per la nostra interazione con il server Web: dobbiamo simulare l'interazione con una form, quindi riempiamo dei campi, come se selezionassimo dei valori da dalle liste usando il nostro browser preferito.
T: no, non è exploder...Per fare questo riempiamo semplicemente una variabile, un hash,
in cui associamo i nomi dei campi da valorizzare ai valori che
vogliamo impostare: il giorno e il periodo da esaminare. Da notare
che in questa particolare form, se vogliamo selezionare l'intera
giornata dobbiamo usare il valore 'seleziona' , che è
il valore di default della lista dei periodi del giorno
selezionabili.
A questo punto (linea 43) creiamo una entità URL, usando il
modulo URI::URL, e usiamo questa entità per sistemarci
dentro i nostri parametri della query, nel formato accettato dal
protocollo HTTP.
Le righe 51-52 creano una richiesta da dare in pasto al protocollo HTTP (riga 46) e aggiungono sia l'informazione della query così come l'avevamo composta nelle righe 41-44, sia l'informazione (falsa) che siamo posizionati sulla pagina che contiene la form, usando la riga di header Referer .
A questo punto abbiamo composto il nostro pacchettino e siamo
pronti a spedirlo al server per ottenere l'agognata pagina di
risposta.
Quello che ci resta da fare è di creare uno User Agent, un
piccolo browser, se vogliamo, che sia in grado di creare una
connessione, trasmettere una richiesta e leggere una risposta.
Per questo usiamo un oggetto del modulo LWP::UserAgent (LWP sta
per Lib WWW for Perl) e facciamo partire una connessione usando
come argomento la richiesta così come l'avevamo sintetizzata
artificialmente nelle righe 46-49.
La risposta del server ci verrà depositata nella variabile
$resp .
A questo punto abbiamo un sacco di informazioni di cui non ci
importa assolutamente niente, perché nulla cambiano
nell'economia globale dell'universo, mentre l'unica risposta che ci
interessi realmente giace negletta chissà dove in questa
pagina.
Il modo scelto a questo punto per estrarre l'informazione è
quello di usare un parser, HTML::Parser, piuttosto che
navigare alla cieca all'interno della struttura HTML della pagina
testé scaricata (Ho mai detto che l'HTML è malvagio,
irregolare, mal formato e fa perdere i capelli? Bhe, date
un'occhiata alle foto degli iscritti ad ErLUG!).
HTML::Parser offre la possibilità al programmatore di definire delle funzioni callback per ogni entità HTML, che vengono attivate quando il parser rileva una di queste. In questo caso ci interessa del semplice testo, così dobbiamo definire una funzione di callback che venga richiamata quando il parser rileva del testo.
Le righe 54-55 creano un'oggetto della classe HTML::Parser, specificando che vogliamo usare la sintassi della versione 3 delle API (già, questo modulo è stato riscritto svariate volte e ha cambiato notevolmente la sua interfaccia verso il programmatore) e che vogliamo definire un handler per le entità di testo, che deve puntare ad una funzione che abbiamo definito (ricordate le righe 16-30 che avevo saltato a piè pari?) e che riceverà il testo rilevato con le entità HTML già decodificate.
T: sentite un senso di panico che serpeggia nelle vostre viscere? Non ci fate caso, è normale..Dopo aver stampato un po' di chiacchiere per intrattenere i lettori, righe 52-61, facciamo partire il parser (riga 71), passandogli il testo ricevuto dal server.
T: il mio contributo più ampio ed importante, liquidato come, "un po' di chiacchere". Ah, non c' è più religione... ah, la tauromachia..Una piccola nota: il nostro User Agent ci ha fatto grazia non solo del testo ricevuto dal server, ma anche di tutti gli header, di solito non mostrati dai browser normali. Il contenuto della variabile $resp è quindi molto vario e se vogliamo solo il testo dobbiamo selezionarlo appositamente con un bel $resp->content.
Analizziamo ora le righe 16-30: la riga 18 riceve il testo
individuato dal parser, per non perdere tempo saltiamo tutto il
testo inutile (righe 21-23), come le righe vuote che i
programmatori insistono ad inserire per rendere il codice
più leggibile, sforzo alquanto vano, se devo proprio
esprimere un parere, basti leggere questo nostro programmino...
A questo punto entriamo nel vivo del problema. Per risolverlo in
modo efficace abbiamo imbrogliato un po', usando qualche dato che
avevamo ricavato leggendo il codice HTML. Avevamo notato che il
titolo della trasmissione era sempre preceduto di stretta misura
dell'orario della messa in onda. Abbiamo così pensato di
usare una variabile per tenere sempre traccia del testo letto
precedentemente.
Il programma quindi fa un'analisi del testo: se non contiene la
stringa "buffy", riga 28, lo salva nella variabile $prev, se
invece abbiamo trovato l'oggetto del nostro desiderio (oops, l'ho
detto, lo sapevo che nella foga non sarei riuscito a
controllarmi!), stampiamo la riga precedente, che supponiamo sia
l'orario e il titolo della puntata del giorno.
Un po' contorto, ma efficace :-)
T: decisamente...