TransWikia.com

¿Hay una manera más fácil de scrapear estos enlaces?

Stack Overflow en español Asked on August 27, 2021

El caso es que tengo el siguiente parseo HTML:

import requests
from bs4 import BeautifulSoup as bs
soup = bs(requests.get("https://keithgalli.github.io/web-scraping/webpage.html").content,features = "lxml")

Estoy intentando scrapear los enlaces de dos tablas distintas, cada una tiene cinco enlaces. Entonces después de revisar el enmaquetado HTML coincido con el intento de esto:

print(soup.find_all(class_ = "block"))

Me devuelve una lista con dos strings navegables:

[<div align="left" class="block">
<ul>
<li><a href="challenge/file_1.html">File 1</a></li>
<li><a href="challenge/file_2.html">File 2</a></li>
<li><a href="challenge/file_3.html">File 3</a></li>
<li><a href="challenge/file_4.html">File 4</a></li>
<li><a href="challenge/file_5.html">File 5</a></li>
</ul>
</div>, <div align="center" class="block">
<ul>
<li><a href="challenge/file_6.html">File 6</a></li>
<li><a href="challenge/file_7.html">File 7</a></li>
<li><a href="challenge/file_8.html">File 8</a></li>
<li><a href="challenge/file_9.html">File 9</a></li>
<li><a href="challenge/file_10.html">File 10</a></li>
</ul>
</div>]

Intente usando tres expresiones generadoras, una (la que itera con z) itera sobre el la lista generada por la suma de las otras dos:

li_s = [z.a["href"] for z in ([x.ul.find_all("li") for x in soup.find_all(class_ = "block")][0] + [y.ul.find_all("li") for y in soup.find_all(class_ = "block")][1])]

A la hora de visualizar el contenido de la siguiente manera obtengo el resultado que espero:

for i in li_s:
    actual = bs(requests.get("https://keithgalli.github.io/web-scraping/{}".format(i)).content,features = "lxml")
    print(actual.get_text())
    print("n")

El problema que tengo es que siento que esa expresión generadora es muy pesada, pienso que hacer otro generador de un elemento específico de una lista no es óptimo. Mi pregunta es: ¿Hay un a manera más óptima y fácil de acceder a esos enlaces?

Justificación de por qué solo encontré solución en hacer esa mega expresión generadora

Con la lista que me devuelve el find_all() de la clase block contiene dos strings navegables, no puedo ni acceder a los <li> con esta sintaxis:

find_all(class_ = "block").li

Tampoco puedo hacer split("n")a los strings navegables, ya que esto implicaría castear los strings navegables a string normales y por lo tanto perderían sus propiedades como objetos. Si decidiera hacer esto tendría que escribir una función que accediera al enlace en cuestión manualmente y eso sería aún menos eficiente.

A final de cuentas si obtengo los resultados que deseo,pero de una manera no tan eficiente. Se me complica mucho ya que estoy interactuando con objetos del modulo, no con strings y listas nativas de Python.

Agradezco mucho cualquier ayuda, aun que sea un comentario. Saludos.

One Answer

Si he entendido correctamente, la siguiente expresión generadora obtiene el mismo resultado que la tuya, y es mucho más simple:

li_s = [ a["href"] for elem in soup.find_all(class_ = "block") for a in elem.find_all("a") ]

En li_s obtienes:

['challenge/file_1.html',
 'challenge/file_2.html',
 'challenge/file_3.html',
 'challenge/file_4.html',
 'challenge/file_5.html',
 'challenge/file_6.html',
 'challenge/file_7.html',
 'challenge/file_8.html',
 'challenge/file_9.html',
 'challenge/file_10.html']

Otra posibilidad...

... es usar CSS selectors en vez de find_all():

li_s = [ a["href"] for a in soup.select(".block > ul > li > a")]

Esto evita el anidamiento de bucles en la expresión generadora. El código queda mucho más limpio en mi opinión.

Correct answer by abulafia on August 27, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP