© 1999 Michael J. Hammel
indent

Catalogazione e schedatura - il recupero dei dati on-line (continuo ...)

Il file modello

Dopo aver installato NewsClipper (ci sono molte prescrizioni da rispettare ma se si seguono le istruzioni riportate nel file README dall'inizio alla fine si riesce ad installare il tutto in un battibaleno), si è pronti a testarlo.  All'interno della versione Open Source del pacchetto c'è un file modello che contiene alcuni esempi di acquisizioni NewsClipper, filtri generali e di output.  Allo stato attuale si può provare il software senza doverlo modificare.  Se si dovessero riscontrare problemi si possono sempre commentare tutti i comandi NewsClipper relativi ai vari filtri eccetto uno modificando le righe del testo come appresso specificato: in no time), you're ready to give it a whirl.  Included in the Open

<!-- newsclipper
to
<!-- Xnewsclipper
Il formato di un file modello è abbastanza semplice:
... Codice HTML ....
<!-- newsclipper
<input filter=name params=xxx>
<filter name=yyy>
<output name=zzz>
-->
... ulteriore codice HTML ...
La prima riga è proprio il tag che dice a NewsClipper che i filtri dichiarati di seguito vanno elaborati.  Se si modifica il nome del comando da newsclipper in Xnewsclipper allora NewsClipper lo ignorerà e copierà l'intero commento sul file HTML in output.  Essendo il testo inserito in un commento le tre righe di filtro saranno ignorate dal browser. HTML output file.  Since it's wrapped in a comment, the three filter

La riga contenente il termine input dice a NewsClipper quale filtro di acquisizione eseguire e quali parametri passargli.  I filtri di acquisizione possono avere quanti parametri si vuole anche se, in pratica, quelli che accompagnano il pacchetto in distribuzione ne hanno pochi, se ne hanno.

La riga con l'indicazione filtro consente di scrivere un processore per l'output a partire dal filtro di acquisizione.  I filtri di acquisizione restituiscono al programma dei dati sotto forma di stringhe, di array o di tabelle hash (casuali).  C'è una grande varietà di filtri per la conversione di tabelle hash in stringhe o vettori e nel file modello sono riportati esempi del loro utilizzo.  Sebbene in un primo momento abbia ritenuto che questo potesse essere il punto nel quale dare spazio a modificazioni o alla scrittura di nuovi filtri di fatto sono riuscito esclusivamente ad utilizzare i manipolatori preconfezionati forniti col pacchetto.  La maggior parte delle variazioni che ho messo a punto sono state effettuate sui manipolatori di acquisizione.

Manipolatori di acquisizione

Quasi tutti i manipolatori di acquisizione forniti con il pacchetto in distribuzione fanno uso di alcune subroutine fornite nei moduli perl di NewsClipper:  GetURL, GetLinks, GetText, GetHtml e così via. Queste subroutine rendono molto agevole acquisire un intero sito o soltanto alcune sue porzioni.  GetURL acquisisce l'intero sito e lo restituisce come una stringa.  GetText e GetHTML fanno cose molto simili ma filtrano parti della pagina. GetLinks è utilizzato per acquisire esclusivamente i link HREF presenti nella pagina.

Una volta che la pagina o una sua porzione siano state scaricate i filtri di acquisizione possono effettuare un ulteriore lavoro di elaborazione dei dati.  Alcuni dei manipolatori scompongono i dati in tabelle hash, elenchi perl di coppie nomi/valori.  Quando tutto ciò viene restituito a NewsClipper può essere passato al filtro generale map, che subito dopo lo passa al filtro hash2string per realizzare la formattazione HTML.  In un esempio che riporto di seguito faccio vedere come questo possa realizzarsi.

Manipolatori Generali

Una volta che i manipolatori di acquisizione hanno finito restituiscono i dati a NewsClipper che li passa a dei filtri generali.  In pratica ho scoperto che l'utilizzo migliore dei filtri generali si esplica nella formattazione HTML dell'output mediante l'utilizzo delle tabelle hash ottenute dai filtri di acquisizione.  Di fatto possono essere utilizzati per qualunque scopo venga in mente in quanto si tratta di elaborare stringhe, vettori o tabelle hash che vengono fornite dai filtri di acquisizione.

Manipolatori di Output

Questi manipolatori, come quelli generali, sembrano più adatti alla formattazione del testo.  Uno degli utilizzi più comuni che di essi si fa è la trasposizione di vettori in elenchi a più colonne.  In pratica li ho utilizzati soltanto sull'HTML formattato proveniente da un manipolatore generale sotto forma di un'unica stringa.  In questo modo mi sono assicurato che non venisse effettuata alcuna ulteriore elaborazione sul mio HTML già formattato da un altro manipolatore di uscita.

L'aggiunta di una base dati - mSQL

Tutti i manipolatori sono memorizzati all'interno di differenti aree (possono essere infatti più di una) tutte elencate nel file di configurazione NewsClipper.cfg.  Normalmente, e fatte salve indicazioni diverse, tutto ciò viene a trovarsi all'interno di $HOME/.NewsClipper/NewsClipper.cfg.  Quando NewsClipper acquisisce automaticamente un filtro aggiornato lo dispone nella prima area specificata dalla variabile handlerlocations (locazioni dei manipolatori).  Quando ho iniziato a modificare i filtri di acquisizione esistenti li ho innanzitutto ricopiati su dei file che avessero dei nomi molto simili - come ad es. cola.pm in colagm.pm - e all'interno della stessa area in cui si trovavano i file originali.

Una volta realizzata una copia del file originale, per aggiungere l'accesso mSQL, ho inserito la seguente riga all'interno del file: Once I had a copy of the original, I added the following line to add mSQL access from within the file:

use Msql;
La precedente riga è stata inserita esattamente dopo la riga "use strict;".  Grazie all'utilizzo dell'interfaccia del modulo perl Msql.pm ho potuto avere accesso alla base dati mediante lo script di acquisizione.  Nota:  è molto importante che la persona o lo script che mandano in esecuzione lo script NewsClipper.pl, che poi manda in esecuzione i filtri di acquisizione ed accede alla base dati mSQL, abbia i giusti diritti di accesso (lettura e probabilmente scrittura) sulle basi dati mSQL. Using the Msql.pm perl module's interface, I could then access the database from the acquisition script.  Note:  it's important that the person or script that runs the NewsClipper.pl script, which will runs the acquisition filter and accesses the mSQL database, is someone that has read and probably write access to  the mSQL databases.

Il passo successivo è consistito nel modificare il manipolatore di acquisizione al fine di analizzare i dati (se già non ve ne fosse uno) ed aggiungerli alla base dati.  La maniera più semplice di spiegare quanto appena detto è forse l'analisi di un esempio.

Esempio - acquisire e collazionare messaggi per il newsgroup grabbing and logging submissions to comp.os.linux.announce

Innanzitutto si dia un'occhiata al file modello per acquisire i più recenti messaggi inviati a comp.os.linux.announce:

<!--
   #####################################
   comp.os.linux.announce
   #####################################
-->
<p>
<table width=100% border=0 cellpadding=2 cellspacing=0 NOSAVE>
<tr>
<td colspan=2 ALIGN=LEFT bgcolor="#00f000"><font size=4><i>c.o.l.a</i></font></td>
<td ALIGN=LEFT bgcolor="#00f000">
   <font size=2>Select All:
      &nbsp;&nbsp;Yes<input type=radio value=yes name=gmcolaall>
      &nbsp;&nbsp;No<input type=radio value=no name=gmcolaall CHECKED>
   </font>
   </td>
</tr>
<tr>
<td bgcolor="#00f000" ALIGN=CENTER VALIGN=TOP>Keep</td>
<td bgcolor="#00f000" ALIGN=CENTER VALIGN=TOP>Drop</td>
<td bgcolor="#00f000" ALIGN=LEFT VALIGN=TOP>Title</td>
</tr>
<!-- newsclipper
  <input name=colagm department=sorted>
   <filter name=map filter=hash2string format='
      <tr>
      <td ALIGN=CENTER VALIGN=MIDDLE>
            <input type=radio value=keep name=gmcola%{index}></td>
      <td ALIGN=CENTER VALIGN=MIDDLE>
            <input type=radio value=drop name=gmcola%{index} CHECKED></td>
      <td ALIGN=LEFT VALIGN=TOP><font size=1>%{url}</font></td>
      </tr>
      '>
   <output name=array numcols=1 prefix='' suffix=''>
-->
</table>
</p>
Nell'esempio riportato i comandi di NewsClipper sono immersi all'interno di una tabella.  Per ogni tabella hash restituita dal filtro di acquisizione colagm viene chiamato il manipolatore generale hash2string. Questo formatta allora il testo in HTML e inserisce al suo interno alcune variabili riconoscibili dal testo %{var}, con il valore della tabella hash che riporta quel nome.  Ne consegue che la variabile %{url} verrà rimpiazzata con il valore proveniente dalla tabella hash che porta il nome url.

Il manipolatore di acquisizione colagm.pm si presenta più o meno come segue (le modifiche che ho apportato io sono evidenziate in rosso:
 

# -*- mode: Perl; -*-

# AUTHOR: John Goerzen
# EMAIL: jgoerzen@complete.org
# ONE LINE DESCRIPTION: Latest messages from comp.os.linux.announce
# URL: http://www.cs.helsinki.fi/~mjrauhal/linux/cola.archive/*.html
# TAG SYNTAX:
# <input name=colagm department=X>
#   Returns an array of links
# X: One of#     last50   - search cola-last-50.html for entries.
#     sorted   - search cola-sorted.html for entries.
#     www      - search cola-www.html for entries.
# LICENSE: GPL
# NOTES:

package NewsClipper::Handler::Acquisition::colagm;

use strict;
use Msql;

use NewsClipper::Handler;
use NewsClipper::Types;
use vars qw( @ISA $VERSION );
@ISA = qw(NewsClipper::Handler);

# DEBUG for this package is the same as the main.
use constant DEBUG => main::DEBUG;

use NewsClipper::AcquisitionFunctions qw( &GetLinks );

$VERSION = 0.3;

# ------------------------------------------------------------------------------
sub gettime
{
   my $sec;
   my $min;
   my $hour;
   my $mday;
   my $mon;
   my $year;
   my $wday;
   my $yday;
   my $isdist;
   my $datestring;

   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdist) = localtime(time);

   $datestring = $year + 1900;
   $datestring *= 10000;
   $datestring += $mon*100 + $mday;

   return $datestring;
}

# This function is used to get the raw data from the URL.
sub Get
{
   my $self = shift;
   my $attributes = shift;
   my $url;
   my $data;
   my $colafile;
   my $start_delimiter;
   my $end_delimiter;
   my $urllink;
   my @results;
   my $tempRef;
   my $query_line;
   my @query_lines;
   my $sth;
   my $newcount;
   my $dbdate;
   my $title;

   $attributes->{department} = "last50"
      unless defined $attributes->{department};

   #
   # Determine which file to search.
   #
   if ( "$attributes->{department}" eq "last50" )
   {
      $colafile = "cola-last-50.html";
      $start_delimiter = "newest ones first";
      $end_delimiter = "Last modified";
   }
   elsif ( "$attributes->{department}" eq "sorted" )
   {
      $colafile = "cola-sorted.html";
      $start_delimiter = "order by the subject";
      $end_delimiter = "Last modified";
   }
   else
   {
      $colafile = "cola-www.html";
      $start_delimiter = "the last one first";
      $end_delimiter = "Last modified";
   }

   #
   # Build the URL which is to be queried.
   #
   $url = join("",
      "http://www.cs.helsinki.fi/~mjrauhal/linux/cola.archive/",
      $colafile);

   #
   # Now run off and get those links!
   #
   $data = &GetLinks($url, $start_delimiter, $end_delimiter);

   return undef unless defined $data;

   #
   # Weed out any User Group messages
   #
   @$data = grep {!/(LOCAL:)/} @$data;

   # Open the Msql connections and select the databases of interest.
   my $dbh1 = Msql->connect();
   $dbh1->selectdb('gm-news');

   # Clear the "new article" table - if we haven't processed those
   # entries yet, then we'll see them again anyway.
   $query_line = join("", "DELETE FROM new_cola");
   $sth = $dbh1->query($query_line);
   $newcount = 1;

   #
   # Now run through the list to find only the news ones.  Then add these

   # to the proper database.
   #
   while (@{$data})
   {
      $_= shift @{$data};

      # Escape single quotes.  We take them out later when we display them, if
      # necessary.
      $title = $_;
      $title =~ s/'/\\'/g;

      # Query the Accepted table for this article name.
      $query_line =
         join("", "SELECT title FROM accepted WHERE title = '", $title, "'");

      $sth = $dbh1->query($query_line);
      if ( $sth->numrows > 0 )
      {
         next;
      }

      # Query the Rejected table for this article name.
      $query_line =
         join("", "SELECT title FROM rejected WHERE title = '", $title, "'");

      $sth = $dbh1->query($query_line);
      if ( $sth->numrows > 0 )
      {
         next;
      }

     # Article has not been seen previously.  Add it to the new database.
      $dbdate = gettime();
      $query_lines[0] = "INSERT INTO new_cola VALUES (";
      $query_lines[1] = $newcount;
      $query_lines[2] = ", ";
      $query_lines[3] = $dbdate;
      $query_lines[4] = ", '";
      $query_lines[5] = $title;
      $query_lines[6] = "', '";
      $query_lines[7] = "cola";
      $query_lines[8] = "', '";
      $query_lines[9] = " ";
      $query_lines[10] = "', '";
      $query_lines[11] = " ";
      $query_lines[12] = "', '";
      $query_lines[13] = " ";
      $query_lines[14] = "')";

      $query_line = join('', @query_lines);
      $sth = $dbh1->query($query_line);

      # Abort on errors encountered while inserting into the new article table.
      if ( length(Msql->errmsg) > 0 )
      {
         print "<!--News Clipper message:\n",
            "Failed new article db update for colaGM handler\n",
            "Error message: ", Msql->errmsg, "\n",
            "-->\n" and return undef;
      }

      # Everything went ok, and it's a new article.  Save it for return to the
      # caller.
      push @results,
      {
         index    => $newcount,
         url      => $_
      };

      $newcount++;
   }

   $tempRef = \@results;
   MakeSubtype('ArrayOfCOLAHash','ArrayOfHash');

   bless $tempRef,'ArrayOfCOLAHash';
   return $tempRef;
}

# ------------------------------------------------------------------------------

sub GetDefaultHandlers
{
  my $self = shift;
  my $inputAttributes = shift;
  my @returnVal;

  my @returnVal = (
     {'name' => 'limit','number' => '10'},
     {'name' => 'array'},
  );

  return @returnVal;
}

1;

i Una cosa sulla quale non mi sono soffermato molto è l'aggiunta che ho operato in termini di alcuni nuovi parametri al manipolatore in maniera che si potessero acquisire diversi archivi c.o.l.a.  Chi lo desidera può anche prelevare una copia dei sorgenti se vuole provarli da sé, occorre però anche creare un'appropriata base dati.

Il sottoprogramma gettime è qualcosa che ho aggiunto soltanto per dare un formato di data compatibile con quello gestito dalla base dati.  Sicuramente ci saranno maniere più semplici per ottenere il medesimo risultato - ma non sono certamente il miglior programmatore perl al mondo.

I rimanenti cambiamenti, diciamo dalla metà del testo alla fine del sottoprogramma Get, sono utilizzati per analizzare i collegamenti restituiti e vedere se essi già esistono in una delle due tabelle.  Se così non è il nuovo collegamento viene aggiunto ad una terza tabella e alla tabella hash in output.  Tutto ciò può sembrare complesso se non si ha familiarità con la sintassi SQL ma non è proprio una cosa impossibile.  The rest of the changes, near the center to end of the Get subroutine, are used to parse the returned links and check if they already exist in one of two tables.  If not, the new link is added to a third table and added to the outgoing hash.  It may look a little complex if you're not familiar with SQL syntax, but really there isn't much too this.  Nel nostro caso i valori restituiti da GetLinkx() rappresentano soltanto un elenco di collegamenti, che rendono l'elaborazione dei dati provenienti da un sito abbastanza facile.  In passato, ed in altre circostanze, ho dovuto scomporre separatamente ogni riga di codice HTML, cercare le parole chiave, ed eliminare ogni tag aggiuntivo ed il codice HTML per poter raggiungere il testo e/o i collegamenti che mi interessavano.  Il fatto è che poiché tutto viene realizzato in Perl ed il Perl è unico nell'analisi del testo allora tutto risulta più agevole. In this case, the returned values from GetLinkx() is just a list of links, which makes processing the site's data pretty easy.  In other cases I had to break apart HTML line by line, searching for key words, then stripping out extra tags and HTML to get at the text and/or links of interest.  The thing is, since this is all done in Perl, and Perl is great for parsing text, this all really wasn't too difficult.

Tutto ciò che mi serviva era una tabella di nuovi dati provenienti dagli archivi c.o.l.a., dai quali poter selezionare gli articoli più interessanti.  Il file modello produce una pagina HTML con un form che viene poi sottoposto ad uno script CGI al fine di spostare nuovi articoli o nella base dati di quelli accettati o in quella dei rifiutati.  In un secondo momento potrò scrivere script per produrre pagine web con i dati relativi agli articoli accettati o rifiutati.  Ogni ulteriore esecuzione di NewsClipper su questo file modello produrrà nuovi dati provenienti da c.o.l.a.!  Carino, no? What all this gets me is a table of new entries from the c.ol.a. archives in a table, from which I select the articles of interest.  The template file produces an HTML page with a form which gets submitted to a CGI script to move new articles into either the accepted or rejected database.  Later, I can write scripts for producing web pages with the accepted or rejected entries.  And any future runs of NewsClipper on this template file will only produce new entries from c.o.l.a!  Pretty nifty.

Come scrivere un manipolatore di acquisizione - MakeHandler.pl

All'interno della distribuzione di NewsClipper c'è uno script perl denominato MakeHandler.pl che aiuta chi volesse scriversi i suoi manipolatori di acquisizione ex novo.  Sebbene abbia sentito dire che sia molto utile e facile da utilizzare non l'ho mai utilizzato personalmente.  Tutti i siti ai quali sono interessato (almeno cos&ì mi è parso) hanno dei manipolatori già scritti allo scopo per cui non ho dovuto far altro che modificarli per farli lavorare con la mia base dati mSQL. There is a perl script included with the NewsClipper distribution called MakeHandler.pl that assists you in writing your own acquisition handlers from scratch.  Although I've heard it's quite useful and very easy to use, I've never used it myself.  All of the sites I was interested in (at least so far) have handlers written for them already, so I just had to modify them for working with my mSQL database.

Siti Linux supportati

I manipolatori standard contenuti nel pacchetto comprendono il supporto per poter scaricare informazioni da The default handlers include Slashdot, Freshmeat, LinuxToday, Linux Daily News, e c.o.l.a, insieme, ovviamente, a tantissimi altri.  Cosa abbastanza interessante è che il manipolatore di Slashdot acquisisce la pagina principale in luogo di quelle terminali, apparentemente perché l'autore era preoccupato dal fatto che le pagine terminali non erano mantenute aggiornate. along with many, many others.  Interestingly enough, the Slashdot handler grabs the main page instead of the backend pages, apparently because the author was worried that the backend was not kept up to date.

Sebbene non abbia avuto l'opportunità di vederlo sospetto che l'utilizzo di NewsClipper per scaricare il file dei dati di Freshmeat sia possibile.  Trattandosi di un semplice file di testo con un formato abbastanza usuale per ogni dato dovrebbe essere abbastanza facile da analizzare. Although I didn't see one, I suspect using NewsClipper to download the Freshmeat database file would also be possible.  Since it's a simple text file with a common format for each entry it should be pretty easy to parse.

Attenzione

Una delle limitazioni di NewsClipper sembra risiedere nel fatto che non digerisce siti che non hanno i tag chiusi nei luoghi appropriati.  A titolo di esempio: sapevate che i tag di paragrafo <p>, hanno i corrispondenti tag di chiusura, </p>?  Senza di essi è possibile che i filtri di acquisizione di NewsClipper vengano fuorviati.  Si potrà comunque eleborare questi siti utilizzando GetURL() ed analizzando manualmente le pagine, anche se sono convinto che si sarebbe molto più felici e contenti se si avesse soltanto a che fare con siti che funzionano sempre bene.  Se può essere interessante ho scoperto che le mie pagine, si proprio queste della Musa, non sono corrette - il Composer di Netscape, infatti, non aggiunge il tag di chiusura dei paragrafi </p>. Without them, it's possible NewsClipper's acquisition filters can get confused.  You might be able to process these sites using GetURL() and parsing the pages manually, but you'll be happier if you can just find sites that do the right thing.  Interestingly enough, I've discovered that my pages here in the Muse are not right - Netscape's Composer doesn't add that closing </p> tag.

Ma chi aveva detto che scrivere questi articoli non sarebbe stato utile aanche sul piano didattico? And who said writing this column wouldn't be educational?
 
indent
© 1999 by Michael J. Hammel