¿Por qué BeautifulSoup no encuentra una clase de tabla específica?

Estoy usando Beautiful Soup para tratar de raspar la tabla de Commodities de Oil-Price.net. Puedo encontrar el primer div, tabla, cuerpo de tabla y las filas del cuerpo de tabla. Pero hay una columna en una de las filas que no puedo encontrar usando Beautiful soup. Cuando le digo a Python que imprima todas las tablas en esa fila en particular, no muestra la que quiero. Este es mi código:

from urllib2 import urlopen from bs4 import BeautifulSoup html = urlopen('http://oil-price.net').read() soup = BeautifulSoup(html) div = soup.find("div",{"id":"cntPos"}) table1 = div.find("table",{"class":"cntTb"}) tb1_body = table1.find("tbody") tb1_rows = tb1_body.find_all("tr") tb1_row = tb1_rows[1] td = tb1_row.find("td",{"class":"cntBoxGreyLnk"}) print td 

Todo lo que se imprime es Ninguno. Incluso trato de imprimir cada una de las filas para ver si puedo encontrar la columna manualmente y nada. “ Se mostrará a los demás. Pero no el que yo quiero.

La página utiliza HTML roto y diferentes analizadores intentarán repararlo de manera diferente. Instala el analizador lxml , analiza mejor esa página:

 >>> BeautifulSoup(html, 'html.parser').find("div",{"id":"cntPos"}).find("table",{"class":"cntTb"}).tbody.find_all("tr")[1].find("td",{"class":"cntBoxGreyLnk"}) is None True >>> BeautifulSoup(html, 'lxml').find("div",{"id":"cntPos"}).find("table",{"class":"cntTb"}).tbody.find_all("tr")[1].find("td",{"class":"cntBoxGreyLnk"}) is None False 

Esto no significa que lxml manejará todo el HTML roto mejor que las otras opciones del analizador. También vea html5lib , una implementación de Python pura de la especificación HTML WHATWG y, por lo tanto, sigue más de cerca cómo las implementaciones actuales del navegador manejan el HTML dañado.

Mirando la fuente de la página:

    

los datos que desea se cargan dinámicamente en la página; no puedes obtenerlo con BeautifulSoup porque no existe en el HTML.

Si miras la url del script vinculado en http://www.oil-price.net/COMMODITIES/gen.php?lang=en , verás un montón de javascript como

 document.writeln(''); document.writeln(''); /* ... */ document.writeln(''); document.writeln('
'); document.writeln('Heating Oil<\/a>'); document.writeln('<\/td>'); document.writeln(''); document.writeln('3.05'); document.writeln('<\/td>'); document.writeln(''); document.writeln('+1.81%'); document.writeln('<\/td><\/tr>');

Cuando se carga la página, este javascript se ejecuta y escribe dinámicamente en los valores que está buscando. (Dejando de lado: esta es una forma completamente arcaica, denigrada y generalmente horrible de hacer las cosas; solo puedo suponer que alguien lo considera como una capa adicional de seguridad. ¡Merecen ser castigados por su temeridad! ).

Ahora, este código es bastante sencillo; probablemente podrías tomar los datos html con una expresión regular. Pero (a) hay algunos códigos de escape que podrían causar problemas, (b) no hay garantía de que no puedan ofuscar su código en el futuro, y (c) ¿dónde está la diversión?

El módulo PyV8 proporciona un método sencillo para ejecutar código javascript desde Python, ¡e incluso nos permite escribir código Python que se puede llamar javascript! Aprovecharemos eso para obtener los datos de una manera no obcusable:

 import PyV8 import requests from bs4 import BeautifulSoup SCRIPT = "http://www.oil-price.net/COMMODITIES/gen.php?lang=en" class Document: def __init__(self): self.lines = [] def writeln(self, s): self.lines.append(s) @property def content(self): return '\n'.join(self.lines) class DOM(PyV8.JSClass): def __init__(self): self.document = Document() def main(): # Create a javascript context which contains # a document object having a writeln method. # This allows us to capture the calls to document.writeln() dom = DOM() ctxt = PyV8.JSContext(dom) ctxt.enter() # Grab the javascript and execute it js = requests.get(SCRIPT).content ctxt.eval(js) # The result is the HTML code you are looking for html = dom.document.content # html is now " ... 
" containing the data you are after; # you can go ahead and finish parsing it with BeautifulSoup tbl = BeautifulSoup(html) for row in tbl.findAll('tr'): print(' / '.join(td.text.strip() for td in row.findAll('td'))) if __name__ == "__main__": main()

Esto resulta en:

 Crude Oil / 99.88 / +2.04% Natural Gas / 4.78 / -3.27% Gasoline / 2.75 / +2.40% Heating Oil / 3.05 / +1.81% Gold / 1263.30 / +0.45% Silver / 19.92 / +0.06% Copper / 3.27 / +0.37% 

cual es la información que querías

Edit: Ya no puedo dejar de decirlo; Es el código mínimo muerto que hace el trabajo. Pero tal vez pueda explicar mejor cómo funciona (¡en realidad no es tan aterrador como parece!):

El módulo PyV8 envuelve el intérprete de javascript V8 de Google de tal manera que Python pueda interactuar con él. Deberá ir a https://code.google.com/p/pyv8/downloads/list para descargar e instalar la versión adecuada antes de poder ejecutar mi código.

El lenguaje javascript, por sí mismo, no sabe nada sobre cómo interactuar con el mundo exterior; no tiene métodos de entrada o salida incorporados. Esto no es terriblemente útil. Para resolver esto, podemos pasar un ‘objeto de contexto’ que contiene información sobre el mundo exterior y cómo interactuar con él. Cuando se ejecuta javascript en un navegador web, obtiene un objeto de contexto que proporciona todo tipo de información sobre el navegador y la página web actual y cómo interactuar con ellos.

El código javascript de http://www.oil-price.net/COMMODITIES/gen.php?lang=en supone que se ejecutará en un navegador, donde el contexto tiene un objeto “documento” que representa la página web, que tiene un método “writeln” que agrega texto al final actual de la página web. A medida que se carga la página, el script se carga y se ejecuta; escribe texto (que resulta ser un HTML válido) en la página; esto se representa como parte de la página y termina como la tabla de productos básicos que deseaba. No puede obtener la tabla con BeautifulSoup porque la tabla no existe hasta que se ejecuta javascript, y BeautifulSoup no carga ni ejecuta javascript.

Queremos ejecutar el javascript; para hacerlo, necesitamos un contexto de navegador falso que tenga un objeto “documento” con un método “writeln”. Luego necesitamos almacenar la información que se pasa a “writeln”, y necesitamos una forma de recuperarla cuando el script haya finalizado. Mi clase DOM es el contexto falso del navegador; cuando se crea una instancia (es decir, cuando hacemos uno de ellos), se da a sí mismo un objeto de documento llamado documento, que tiene un método de escritura. Cuando se llama a document.writeln, se agrega la línea de texto a document.lines, y en cualquier momento podemos llamar a document.content para recuperar todo el texto escrito hasta el momento.

Ahora: la acción! En la función principal, creamos un contexto de navegador falso, lo configuramos como el contexto actual del intérprete y lo iniciamos. Tomamos el código javascript y le decimos al intérprete que lo evalúe (es decir, lo ejecute). (La ofuscación de código fuente, que puede arruinar el análisis estático, no nos afectará porque el código tiene que producir una buena salida cuando se ejecuta, ¡y en realidad lo estamos ejecutando!) Una vez que se termina el código, obtenemos la salida final del documento .contexto; esta es la tabla html que no pudo obtener. Lo devolvemos a BeautifulSoup para extraer los datos y luego imprimirlos.

¡Espero que ayude!