2015-12-15 5 views
5

BeautifulSoup restituisce l'elenco vuoto durante la ricerca per nomi di classi composti mediante espressioni regolari.BeautifulSoup restituisce l'elenco vuoto durante la ricerca per nomi di classi composti

Esempio:

import re 
from bs4 import BeautifulSoup 

bs = 
    """ 
    <a class="name-single name692" href="www.example.com"">Example Text</a> 
    """ 

bsObj = BeautifulSoup(bs) 

# this returns the class 
found_elements = bsObj.find_all("a", class_= re.compile("^(name-single.*)$")) 

# this returns an empty list 
found_elements = bsObj.find_all("a", class_= re.compile("^(name-single name\d*)$")) 

ho bisogno la selezione di classe di essere molto precisi. Qualche idea?

+4

Sì, perché la classe attributo valori vengono trattati come liste/array di valori separati con lo spazio. –

+3

Beh, ho cercato una soluzione una volta, ma non ho potuto. Suggerisco solo di farlo in 2 passaggi: 1) ottenere tutti gli ancore con la classe "name-single" e 2) ottenere quelli che corrispondono anche alla regex '^ name \ d * $'. –

+1

FYI, ha creato un follow-up e una domanda più generica sulla gestione dell'attributo 'class': [Disabilita la gestione dell'attributo" class "speciale] (http://stackoverflow.com/questions/34295928/disable-special-class-attribute -la gestione). – alecxe

risposta

4

Sfortunatamente, quando si tenta di associare un'espressione regolare a un valore di attributo di classe che contiene più classi, BeautifulSoup applicherà l'espressione regolare a ogni singola classe separatamente. Qui ci sono i temi rilevanti sul problema:

Questo è tutto, perché class is a very special multi-valued attribute e ogni volta che analizzare HTML, uno dei costruttori di albero s' il BeautifulSoup (a seconda della scelta del parser) divide internamente un valore di stringa di classe in un elenco di classi (citazione dalla docstring di HTMLTreeBuilder):

# The HTML standard defines these attributes as containing a 
# space-separated list of values, not a single value. That is, 
# class="foo bar" means that the 'class' attribute has two values, 
# 'foo' and 'bar', not the single value 'foo bar'. When we 
# encounter one of these attributes, we will parse its value into 
# a list of values if possible. Upon output, the list will be 
# converted back into a string. 

ci sono più soluzioni alternative, ma qui è un hack-ish uno - ci accingiamo a chiedere BeautifulSoup di non gestire class come un attributo con più valori, rendendo il nostro semplice costruttore di albero personalizzato:

import re 

from bs4 import BeautifulSoup 
from bs4.builder._htmlparser import HTMLParserTreeBuilder 


class MyBuilder(HTMLParserTreeBuilder): 
    def __init__(self): 
     super(MyBuilder, self).__init__() 

     # BeautifulSoup, please don't treat "class" specially 
     self.cdata_list_attributes["*"].remove("class") 


bs = """<a class="name-single name692" href="www.example.com"">Example Text</a>""" 
bsObj = BeautifulSoup(bs, "html.parser", builder=MyBuilder()) 
found_elements = bsObj.find_all("a", class_=re.compile(r"^name\-single name\d+$")) 

print(found_elements) 

In questo caso l'espressione regolare verrebbe applicata a un valore di attributo class nel suo complesso.


In alternativa, si può solo analizzare il codice HTML con xml funzioni abilitate (laddove prevista):

soup = BeautifulSoup(data, "xml") 

È anche possibile utilizzare CSS selectors e abbinare tutti gli elementi con name-single classe e una classe staring con "nome":

soup.select("a.name-single,a[class^=name]") 

Yo u può quindi applicare l'espressione regolare manualmente, se necessario:

pattern = re.compile(r"^name-single name\d+$") 
for elm in bsObj.select("a.name-single,a[class^=name]"): 
    match = pattern.match(" ".join(elm["class"])) 
    if match: 
     print(elm) 
+0

Ottima risposta. L'opzione MyBuilder ha effetti collaterali, qualcosa che non funziona/funziona in modo diverso dopo questo hack? –

+1

@ivan_bilan per ora, funziona come previsto, grazie. – alecxe

1

Per questo caso d'uso vorrei semplicemente utilizzare un custom filter, in questo modo:

import re 

from bs4 import BeautifulSoup 
from bs4.builder._htmlparser import HTMLParserTreeBuilder 

def myclassfilter(tag): 
    return re.compile(r"^name\-single name\d+$").search(' '.join(tag['class'])) 

bs = """<a class="name-single name692" href="www.example.com"">Example Text</a>""" 
bsObj = BeautifulSoup(bs, "html.parser") 
found_elements = bsObj.find_all(myclassfilter) 

print(found_elements) 
Problemi correlati