Articoli Manifesto Tools Links Canali Libri Contatti ?
SIForge / GaraGuru / .1

GaraGuru .1: Sfida 1, Web Clipping

Abstract
Scrivete, in un linguaggio a piacere, un componente (ovvero una classe o una funzione) che dato un url, sia in grado di estrarne un frammento. Per esempio, dato http://www.slashdot.org/, il webclipper deve essere in grado di estrarne un particolare articolo.
Data di stesura: 15/06/2004
Data di pubblicazione: 22/11/2004
Ultima modifica: 04/04/2006
di Riccardo Galli Discuti sul forum   Stampa

La mia soluzione, sviluppata in python, richiede python >=2.3.
Si basa su componenti standard della libreria di python, l'implementazione è molto semplice, compatta, ben commentata e probabilmente comprensibile anche per chi è a digiuno di python.

Qualora sorgessero dubbi o particolari quesiti, riproponeteli liberamente sul forum dell'articolo, sicuramente Riccardo sarà ben contento di rispondervi.

  1. #!/usr/bin/env python 
  2.  
  3. __author__="Riccardo Attilio Galli (riccardo<at>sideralis<dot>net)" 
  4.  
  5. import HTMLParser,urllib,re 
  6.  
  7. class MatchNode(object): 
  8.     """ 
  9.     Nodo che contiene tutti gli elementi interni ad una coppia di tag 
  10.     di cui e' risultato un match. Se contiene dei tag uguali a starttag 
  11.     e endtag, il loro contenuto si trova in un altro nodo, che figura nella 
  12.     lista children 
  13.     """ 
  14.     __slots__=['starttag','endtag','data','children','parent'] 
  15.     def __init__(self): 
  16.         self.starttag='' 
  17.         self.endtag='' 
  18.         self.data=[] 
  19.         self.children=[] 
  20.         self.parent=None 
  21.  
  22.     def add_child(self,child): 
  23.         self.children.append(child) 
  24.         self.data.append(child) 
  25.  
  26. class WebClipper(HTMLParser.HTMLParser): 
  27.     """ 
  28.     La classe WebClipper si occupa del parsing di un documento html ben 
  29.     formattato. 
  30.     Dati in fase di costruzione dell'istanza un tag d'inizio ed uno di 
  31.     fine, ogni qual volta il primo viene trovato viene aggiunto un nodo 
  32.     di tipo MatchNode, che contiene tutti i dati trovati tra i due tag. 
  33.     La radice dell'albero creato e' self.root 
  34.     """ 
  35.     def __init__(self,starttag,endtag): 
  36.         HTMLParser.HTMLParser.__init__(self) 
  37.         self.starttag=starttag[1:-1].lower() 
  38.         self.endtag=endtag[1:-1].lower() 
  39.         self.fetching=0 
  40.         self.root=MatchNode() 
  41.         self.currentNode=self.root 
  42.  
  43.     def handle_data(self,data): 
  44.         if self.fetching: 
  45.             self.currentNode.data.append(data) 
  46.  
  47.     def handle_starttag(self,tag,attrs): 
  48.         orig_tag=self.get_starttag_text() 
  49.         if self.starttag==tag: 
  50.             self.fetching+=1 
  51.             node=MatchNode() 
  52.             node.parent=self.currentNode 
  53.             node.starttag=orig_tag 
  54.             self.currentNode.add_child(node) 
  55.             self.currentNode=node 
  56.         elif self.fetching: 
  57.             self.currentNode.data.append(orig_tag) 
  58.  
  59.     def parse_endtag(self,i): 
  60.         #override di un metodo ereditato 
  61.         #utilizzato per avere l'end-tag originale, per esempio </tAbLE> 
  62.         #se non si fosse interessati a mantenere il case, sarebbe sufficiente 
  63.         #usare handle_endtag 
  64.         j=HTMLParser.HTMLParser.parse_endtag(self,i) #dato l'indice d'inizio 

            di un tag, trova la fine 
  65.         if j==-1: return -1  #il tag non si chiude correttamente 
  66.         if self.fetching: 
  67.             orig_tag=self.rawdata[i:j] #recupero il tag originale, comprensivo 

                di < > 
  68.             tag=orig_tag[1:-1].lower() 
  69.             if self.endtag==tag: 
  70.                 self.currentNode.endtag=orig_tag 
  71.                 if self.currentNode!=self.root: 
  72.                     self.currentNode=self.currentNode.parent 
  73.                 self.fetching-=1 
  74.             else: self.currentNode.data.append(orig_tag) 
  75.         return j 
  76.  
  77.     # i metodi seguenti gestiscono dati particolari 
  78.     #   che vengono parsati separatemente 
  79.     def handle_comment(self,data): 
  80.         if self.fetching: 
  81.             self.currentNode.data.append('<!--%s-->' % data) 
  82.  
  83.     def handle_decl(self,data): 
  84.         if self.fetching: 
  85.             self.currentNode.data.append('<!%s>' % data) 
  86.  
  87.     def handle_pi(self,data): 
  88.         if self.fetching: 
  89.             self.currentNode.data.append('<?%s>' % data) 
  90.  
  91.     def hanle_charref(self,ref): 
  92.         if self.fetching: 
  93.             self.currentNode.data.append('&#%s;' % ref) 
  94.  
  95.     def handle_entityref(self,ref): 
  96.         if self.fetching: 
  97.             self.currentNode.data.append('&%s;' % ref) 
  98.  
  99.  
  100. def traverse(child,leaf_only=False,is_first=True): 
  101.     """ 
  102.     Ricostruisce il contenuto di un node MatchNode. 
  103.     Se leaf_only e' True, considera solo le foglie dell'albero. 
  104.     """ 
  105.     if leaf_only and child.children: 
  106.         for node in child.children: 
  107.             return traverse(node,leaf_only) 
  108.     data=[] 
  109.     if not is_first: 
  110.         data.append(child.starttag) 
  111.     for i in child.data: 
  112.         if isinstance(i,str): 
  113.             data.append(i) 
  114.         else: data.append(traverse(i,is_first=False)) 
  115.  
  116.     if not is_first: 
  117.         data.append(child.endtag) 
  118.     return ''.join(data) 
  119.  
  120. def parseURL(url,starttag,endtag,leaf_only=False,bufsize=8192): 
  121.     """ 
  122.     Parsa una url o un file, restituendo una lista di nodi MatchNode 
  123.     per ogni coppia starttag/endtag trovata nell'url. 
  124.     Se leaf_only e' True considera le sole coppie di tag che non contengono 
  125.     se' stesse al loro interno. 
  126.     """ 
  127.     fd=urllib.urlopen(url) 
  128.     clipper=WebClipper(starttag,endtag) 
  129.  
  130.     while True: 
  131.         data=fd.read(bufsize) 
  132.         if not data: break 
  133.         clipper.feed(data) 
  134.     fd.close() 
  135.  
  136.     result=[] 
  137.     for node in clipper.root.children: 
  138.         result.append(traverse(node,leaf_only=leaf_only)) 
  139.     return result 
  140.  
  141.  
  142. if __name__=='__main__': 
  143.     from optparse import OptionParser 
  144.  
  145.     usage='%prog [options] url starttag endtag regexp' 
  146.     parser=OptionParser(usage=usage) 
  147.     parser.set_conflict_handler('resolve') 
  148.     parser.add_option('-d','--deepest',action='store_true',dest='leaf_only',de

        fault=False, 
  149.                       help="controlla solo i tag che non contengono se' 

                          stessi") 
  150.     parser.add_option('-r','--reg-ignorecase',action='store_true',dest='reg_ig

        norecase',default=False, 
  151.                       help='la regexp ignora maiuscole/minuscole') 
  152.     parser.add_option('-h','--help',action='store_true',dest='help',default=Fa

        lse, 
  153.                       help='mostra questo help ed esci') 
  154.  
  155.     opt,args=parser.parse_args() 
  156.     if len(args)!=4 or opt.help: 
  157.         import sys 
  158.         parser.print_help() 
  159.         sys.exit(-1) 
  160.  
  161.     url,starttag,endtag,regexp=args 
  162.     try: 
  163.         result=parseURL(url,starttag,endtag,leaf_only=opt.leaf_only) 
  164.  
  165.         if not opt.reg_ignorecase: 
  166.             PATTERN=re.compile(regexp,re.DOTALL) 
  167.         else: PATTERN=re.compile(regexp,re.DOTALL|re.IGNORECASE) 
  168.  
  169.         for text in result: 
  170.             for match in PATTERN.findall(text): 
  171.                 if match: 
  172.                     print match 
  173.     except IOError,e: 
  174.         print e 

Informazioni sull'autore

Riccardo Galli, studente iscritto alla facoltà di informatica presso l'università Statale di Milano, si impegna nello sviluppo di soluzioni software, principalmente in Python o C. Collabora attivamente alla crescita di Sideralis Programs, una società orientata al problem solving di cui è cofondatore.

È possibile consultare l'elenco degli articoli scritti da Riccardo Galli.

Altri articoli sul tema SIForge / GaraGuru / .1.

Risorse

  1. I sorgenti del progetto.
    http://www.siforge.org/articles/2004/11/web_clipping/webclipper.py (6Kb)
  2. Bando della prima edizione di GaraGuru.
    http://www.siforge.org/articles/2004/05/10-garaguru.html
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.