Raspando una web para obtener una tabla, usando Beautiful soup y Pandas. Una de las columnas tiene algunas URL. Cuando paso html a pandas, href se pierden.

¿Hay alguna forma de preservar el enlace de URL solo para esa columna?

Datos de ejemplo (editados para un mejor caso de rally de trajes):

  <html>
        <body>
          <table>
              <tr>
               <td>customer</td>
               <td>country</td>
               <td>area</td>
               <td>website link</td>
             </tr>
             <tr>
               <td>IBM</td>
               <td>USA</td>
               <td>EMEA</td>
               <td><a href="http://www.ibm.com">IBM site</a></td>
            </tr>
          <tr>
            <td>CISCO</td>
            <td>USA</td>
            <td>EMEA</td>
            <td><a href="http://www.cisco.com">cisco site</a></td>
         </tr>
           <tr>
            <td>unknown company</td>
            <td>USA</td>
            <td>EMEA</td>
            <td></td>
         </tr>
       </table>
     </body>
  </html>

Mi código de python:

    file = open(url,"r")

    soup = BeautifulSoup(file, 'lxml')

    parsed_table = soup.find_all('table')[1] 

    df = pd.read_html(str(parsed_table),encoding='utf-8')[0]

 df

Salida (exportada a CSV):

customer;country;area;website
IBM;USA;EMEA;IBM site
CISCO;USA;EMEA;cisco site
unknown company;USA;EMEA;

La salida de df está bien pero se pierde el enlace Necesito preservar el enlace. La URL al menos.

Alguna pista?

8
Forge 17 feb. 2017 a las 00:55

3 respuestas

La mejor respuesta

Simplemente verifique si la etiqueta existe de esta manera:

 import numpy as np

 with open(url,"r") as f:
     sp = bs.BeautifulSoup(f, 'lxml')
     tb = sp.find_all('table')[56] 
     df = pd.read_html(str(tb),encoding='utf-8', header=0)[0]
     df['href'] = [np.where(tag.has_attr('href'),tag.get('href'),"no link") for tag in tb.find_all('a')]
5
Community 15 nov. 2017 a las 16:13

Aquí hay otra forma de hacerlo si tiene más de un enlace para tomar de la tabla html. En lugar de hacer la comprensión de la lista, preferiría usar bucles separados para que el código sea más legible para aquellos que son nuevos en python y es más fácil ajustar el código o manejar errores si surgen. Espero que ayude a alguien.

soup = BeautifulSoup(html, "lxml")
table = table.find('table')
thead = table.find('thead')
column_names = [th.text.strip() for th in thead.find_all('th')]

data = []
for row in table.find_all('tr'):
    row_data = []
    for td in row.find_all('td'):
        td_check = td.find('a')
        if td_check is not None:
            link = td.a['href']
            row_data.append(link)
        else:
            not_link = ''.join(td.stripped_strings)
            if not_link == '':
                 not_link = None
            row_data.append(not_link)
    data.append(row_data)
df = pd.DataFrame(data[1:], columns=column_names)
df_dict = df.to_dict('records')

for row in df_dict:
    print(row)
2
PythonMan 14 dic. 2018 a las 07:36

pd.read_html asume que los datos que le interesan están en el texto, no en los atributos de la etiqueta. Sin embargo, no es difícil raspar la mesa usted mismo:

import bs4 as bs
import pandas as pd

with open(url,"r") as f:
    soup = bs.BeautifulSoup(f, 'lxml')
    parsed_table = soup.find_all('table')[1] 
    data = [[td.a['href'] if td.find('a') else 
             ''.join(td.stripped_strings)
             for td in row.find_all('td')]
            for row in parsed_table.find_all('tr')]
    df = pd.DataFrame(data[1:], columns=data[0])
    print(df)  

Rendimientos

          customer country  area          website link
0              IBM     USA  EMEA    http://www.ibm.com
1            CISCO     USA  EMEA  http://www.cisco.com
2  unknown company     USA  EMEA                      
12
unutbu 17 feb. 2017 a las 10:55