2012-08-02 23 views
6

Estoy haciendo un pequeño proyecto en el que extraigo las apariciones de líderes políticos en los periódicos. Algunas veces se mencionará a un político, y no hay un padre o un niño con un enlace. (Debido a que supongo que es un marcado semánticamente malo).Encontrar el enlace más cercano con BeautifullSoup (python)

Así que quiero crear una función que pueda encontrar el enlace más cercano y luego extraerlo. En el caso debajo de la cadena de búsqueda es Rasmussen y el enlace que quiero es: /307046.

#-*- coding: utf-8 -*- 

from bs4 import BeautifulSoup 
import re 

tekst = ''' 
<li> 
    <div class="views-field-field-webrubrik-value"> 
    <h3> 
     <a href="/307046">Claus Hjort spiller med mrkede kort</a> 
    </h3> 
    </div> 
    <div class="views-field-field-skribent-uid"> 
    <div class="byline">Af: <span class="authors">Dennis Kristensen</span></div> 
    </div> 
    <div class="views-field-field-webteaser-value"> 
    <div class="webteaser">Claus Hjort Frederiksens argumenter for at afvise 
     trepartsforhandlinger har ikke hold i virkeligheden. Hans rinde er nok 
     snarere at forberede det ideologiske grundlag for en Løkke Rasmussens 
     genkomst som statsministe 
    </div> 
    </div> 
    <span class="views-field-view-node"> 
    <span class="actions"> 
     <a href="/307046">Ls mere</a> 
     | 
     <a href="/307046/#comments">Kommentarer (4)</a> 
    </span> 
    </span> 
</li> 
''' 

to_find = "Rasmussen" 
soup = BeautifulSoup(tekst) 
contexts = soup.find_all(text=re.compile(to_find)) 

def find_nearest(element, url, direction="both"): 
    """Find the nearest link, relative to a text string. 
    When complete it will search up and down (parent, child), 
    and only X levels up down. These features are not implemented yet. 
    Will then return the link the fewest steps away from the 
    original element. Assumes we have already found an element""" 

    # Is the nearest link readily available? 
    # If so - this works and extracts the link. 
    if element.find_parents('a'): 
     for artikel_link in element.find_parents('a'): 
      link = artikel_link.get('href') 
      # sometimes the link is a relative link - sometimes it is not 
      if ("http" or "www") not in link: 
       link = url+link 
       return link 
    # But if the link is not readily available, we will go up 
    # This is (I think) where it goes wrong 
    # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 
    if not element.find_parents('a'): 
     element = element.parent 
     # Print for debugging 
     print element #on the 2nd run (i.e <li> this finds <a href=/307056> 
     # So shouldn't it be caught as readily available above? 
     print u"Found: %s" % element.name 
     # the recursive call 
     find_nearest(element,url) 

# run it 
if contexts: 
    for a in contexts: 
     find_nearest(element=a, url="http://information.dk") 

La llamada directa a continuación funciona:

print contexts[0].parent.parent.parent.a['href'].encode('utf-8') 

Como referencia todo el código siento está en bitbucket: https://bitbucket.org/achristoffersen/politikere-i-medierne

(EP Utilizar BeautifullSoup 4)


EDITAR: SimonSapin me pide que defina ne arest: Por "más cercano" me refiero al enlace que tiene el menor número de niveles de anidación fuera del término de búsqueda, en cualquier dirección. En el texto anterior, el a href producido por el sitio de periódico basado en drupal, no es un padre directo o hijo de la etiqueta donde se encuentra la cadena de búsqueda. Así que BeautifullSoup no puede encontrarlo.

Sospecho que un "menor número de charachters" de distancia a menudo también funcionaría. En ese caso, una mezcla podría ser pirateada junto con el hallazgo y la confusión, pero realmente me gustaría hacerlo a través de BS. Como esto funcionaría: contexts[0].parent.parent.parent.a['href'].encode('utf-8'), debe ser posible generalizarlo a un script.

EDIT: Tal vez debería hacer hincapié en que estoy buscando una solución BeautifulSoup. La combinación de BS con una búsqueda de respiración personalizada/simpel según lo sugerido por @ erik85 se convertiría rápidamente en un desastre, creo.

+0

¿Cómo se define "cerca", y cuál es tu pregunta? –

+0

@SimonSapin Pregunta actualizada. Gracias. – Andreas

+0

Y mi pregunta es: ¿cómo extraigo esta lin/cuál es incorrecto con mi código. Gracias – Andreas

Respuesta

2

Aquí hay una solución usando lxml.La idea principal es encontrar todos los elementos anteriores y posteriores y luego hacer una iteración roundrobin través de esos elementos:

def find_nearest(elt): 
    preceding = elt.xpath('preceding::*/@href')[::-1] 
    following = elt.xpath('following::*/@href') 
    parent = elt.xpath('parent::*/@href') 
    for href in roundrobin(parent, preceding, following): 
     return href 

Una solución similar utilizando BeautifulSoups' (o de bs4) next_elements and previous_elements debería también ser posible.


import lxml.html as LH 
import itertools 

def find_nearest(elt): 
    preceding = elt.xpath('preceding::*/@href')[::-1] 
    following = elt.xpath('following::*/@href') 
    parent = elt.xpath('parent::*/@href') 
    for href in roundrobin(parent, preceding, following): 
     return href 

def roundrobin(*iterables): 
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C" 
    # http://docs.python.org/library/itertools.html#recipes 
    # Author: George Sakkis 
    pending = len(iterables) 
    nexts = itertools.cycle(iter(it).next for it in iterables) 
    while pending: 
     try: 
      for n in nexts: 
       yield n() 
     except StopIteration: 
      pending -= 1 
      nexts = itertools.cycle(itertools.islice(nexts, pending)) 

tekst = ''' 
<li> 
    <div class="views-field-field-webrubrik-value"> 
    <h3> 
     <a href="/307046">Claus Hjort spiller med mrkede kort</a> 
    </h3> 
    </div> 
    <div class="views-field-field-skribent-uid"> 
    <div class="byline">Af: <span class="authors">Dennis Kristensen</span></div> 
    </div> 
    <div class="views-field-field-webteaser-value"> 
    <div class="webteaser">Claus Hjort Frederiksens argumenter for at afvise 
     trepartsforhandlinger har ikke hold i virkeligheden. Hans rinde er nok 
     snarere at forberede det ideologiske grundlag for en Løkke Rasmussens 
     genkomst som statsministe 
    </div> 
    </div> 
    <span class="views-field-view-node"> 
    <span class="actions"> 
     <a href="/307046">Ls mere</a> 
     | 
     <a href="/307046/#comments">Kommentarer (4)</a> 
    </span> 
    </span> 
</li> 
''' 

to_find = "Rasmussen" 
doc = LH.fromstring(tekst) 

for x in doc.xpath('//*[contains(text(),{s!r})]'.format(s = to_find)): 
    print(find_nearest(x)) 

produce

/307046 
+0

Gracias, por lo que este encuentra el elemento más cercano. ¿Qué pasa si hay una coincidencia en ambas direcciones 3 iteraciones abajo? ¿Se devolverán ambos enlaces? – Andreas

+0

Devolverá solo el primer elemento más cercano. El iterador 'roundrobin' produce un elemento de' parent', luego 'previous', then' following'. Entonces, si hay más de un elemento más cercano, el que está en 'parent' tiene la precedencia más alta, luego el que está en' previous' y luego 'following'. – unutbu

12

Alguien probablemente propondrá una solución que funcione con la copia & y crees que esto resuelve tu problema. ¡Tu problema no es el código, sin embargo! Es tu estrategia. Hay un principio de diseño de software llamado "divide y vencerás" que debes aplicar en un rediseño a tu código: separa el código que interpreta tus cadenas HTML como árboles/gráficos de la búsqueda en el nodo más cercano (probablemente breadth-first-search). No solo aprenderá a diseñar un mejor software, , su problema probablemente dejará de existir.

creo que usted es lo suficientemente inteligente como para resolver esto por sí mismo, pero también quiero proveer un esqueleto:

def parse_html(txt): 
    """ reads a string of html and returns a dict/list/tuple presentation""" 
    pass 

def breadth_first_search(graph, start, end): 
    """ finds the shortest way from start to end 
    You can probably customize start and end to work well with the input you want 
    to provide. For implementation details see the link in the text above. 
    """ 
    pass 

def find_nearest_link(html,name): 
    """putting it all together""" 
    return breadth_first_search(parse_html(html),name,"link") 

PS: Hacer esto también se aplica otro principio, pero a partir de las matemáticas: Suponiendo que hay un problema que no conoce una solución para (encontrar enlaces cerca de una subcadena elegida) y hay un grupo de problemas para los que conoce una solución (recorrido de gráfico), luego intente transformar su problema para que coincida con el grupo de problemas que puede resolver, por lo que puede usar patrones de solución básicos (que probablemente ya estén implementados en un lenguaje/marco de su elección) y listo.

+2

+1 muy buena respuesta conceptual para alguien que probablemente necesite el principio más que el código. – msw

+0

Gracias. Aunque aprecio mucho tu respuesta, también creo que se pierde un poco la marca: tienes razón en que debería (y lo haré) ver el principio de 'divide y vencerás' (por ejemplo, recuerdo recordar a Python el duro manera en que más de dos niveles de anidación no son buenos). Pero me gustaría utilizar BS aquí, y no creo que su respuesta me ayude a comprender por qué no puedo generalizar 'contexts [0] .parent.parent.parent.a ['href']'. Sin embargo, todos los buenos consejos merecen un voto por lo menos :-) - p.s. Todavía no he leído el wiki artikel ... – Andreas

Cuestiones relacionadas