mesa de análise python BeautifulSoup


89

Estou aprendendo python requestse BeautifulSoup. Para um exercício, escolhi escrever um analisador rápido de bilhetes de estacionamento em Nova York. Consigo obter uma resposta html que é bastante feia. Preciso pegar lineItemsTablee analisar todos os tíquetes.

Você pode reproduzir a página indo aqui: https://paydirect.link2gov.com/NYCParking-Plate/ItemSearche inserindo uma NYplacaT630134C

soup = BeautifulSoup(plateRequest.text)
#print(soup.prettify())
#print soup.find_all('tr')

table = soup.find("table", { "class" : "lineItemsTable" })
for row in table.findAll("tr"):
    cells = row.findAll("td")
    print cells

Alguém pode me ajudar? Simplesmente procurar por todos trnão me leva a lugar nenhum.


Em uma leitura mais atenta, não tenho certeza de qual é a sua pergunta. Você pode esclarecer exatamente em que parte você precisa de ajuda?
TML

question links-broken: Abaixo um exemplo de trabalho para uma <table> genérica.
eusoubrasileiro

Respostas:


173

Aqui está:

data = []
table = soup.find('table', attrs={'class':'lineItemsTable'})
table_body = table.find('tbody')

rows = table_body.find_all('tr')
for row in rows:
    cols = row.find_all('td')
    cols = [ele.text.strip() for ele in cols]
    data.append([ele for ele in cols if ele]) # Get rid of empty values

Isso dá a você:

[ [u'1359711259', u'SRF', u'08/05/2013', u'5310 4 AVE', u'K', u'19', u'125.00', u'$'], 
  [u'7086775850', u'PAS', u'12/14/2013', u'3908 6th Ave', u'K', u'40', u'125.00', u'$'], 
  [u'7355010165', u'OMT', u'12/14/2013', u'3908 6th Ave', u'K', u'40', u'145.00', u'$'], 
  [u'4002488755', u'OMT', u'02/12/2014', u'NB 1ST AVE @ E 23RD ST', u'5', u'115.00', u'$'], 
  [u'7913806837', u'OMT', u'03/03/2014', u'5015 4th Ave', u'K', u'46', u'115.00', u'$'], 
  [u'5080015366', u'OMT', u'03/10/2014', u'EB 65TH ST @ 16TH AV E', u'7', u'50.00', u'$'], 
  [u'7208770670', u'OMT', u'04/08/2014', u'333 15th St', u'K', u'70', u'65.00', u'$'], 
  [u'$0.00\n\n\nPayment Amount:']
]

Algumas coisas a serem observadas:

  • A última linha na saída acima, o valor do pagamento não faz parte da tabela, mas é assim que a tabela é apresentada. Você pode filtrar verificando se o comprimento da lista é inferior a 7.
  • A última coluna de cada linha terá que ser tratada separadamente, pois é uma caixa de texto de entrada.

6
Eu me pergunto por que funciona para você ... Eu entendorows = table_body.find_all('tr') AttributeError: 'NoneType' object has no attribute 'find_all'
Cmag

@Cmag Você está usando o Beautiful Soup 4?
shaktimaan

1
Substituir find_allporfindAll
user2314737

4
@ user2314737 BS suporta a notação camel case e sublinhado. Eu uso o sublinhado que está em sintonia com as diretrizes de codificação do Python.
shaktimaan

3
Ok, resolvi meu erro: No modo de inspeção de html ele mostra tbody, no entanto, quando imprimi o valor table = soup.find('table', attrs={'class':'analysis'})dele não mostrava tbody ali, então simplesmente encontrar td e tr resolveu o problema. Então na minha opinião a causa do erro AttributeError: 'NoneType' object has no attribute 'find_all'é quando passamos uma tag ou campo que não está no html da página.
Umesh Kaushik

23

Resolvido, é assim que você analisa os resultados html:

table = soup.find("table", { "class" : "lineItemsTable" })
for row in table.findAll("tr"):
    cells = row.findAll("td")
    if len(cells) == 9:
        summons = cells[1].find(text=True)
        plateType = cells[2].find(text=True)
        vDate = cells[3].find(text=True)
        location = cells[4].find(text=True)
        borough = cells[5].find(text=True)
        vCode = cells[6].find(text=True)
        amount = cells[7].find(text=True)
        print amount

18

Resposta Atualizada

Se um programador estiver interessado apenas em analisar a tabela de uma página da web, ele pode utilizar o método pandas pandas.read_html.

Digamos que desejemos extrair a tabela de dados do PIB do site: https://worldpopulationreview.com/countries/countries-by-gdp/#worldCountries

Em seguida, os códigos a seguir funcionam perfeitamente (não há necessidade de beautifulsoup e html sofisticado):

import pandas as pd
import requests

url = "https://worldpopulationreview.com/countries/countries-by-gdp/#worldCountries"

r = requests.get(url)
df_list = pd.read_html(r.text) # this parses all the tables in webpages to a list
df = df_list[0]
df.head()

Resultado

As primeiras cinco linhas da tabela do site


Concordo - esta é claramente a melhor abordagem em 2020!
kfmfe04

2
Somente se você já usa pandas em algum lugar do seu projeto. Muitas dependências para uma tabela
Сергей Яхницкий

haha você copiou meu exemplo abaixo e melhorou a resposta. Bom, pelo menos gostei de saber que os pandas têm esse método. Agradável!
eusoubrasileiro

Sim, eu costumava dados url do PIB do seu exemplo. Sim, se você gosta de métodos rápidos, podemos simplesmente usar em pd.read_htmlvez de toda dança de pedidos e uma bela sopa.
Bhishan Poudel

4

Aqui está um exemplo de trabalho para um genérico <table>. ( links da pergunta quebrados )

Extraindo a tabela daqui países por PIB (Produto Interno Bruto).

htmltable = soup.find('table', { 'class' : 'table table-striped' })
# where the dictionary specify unique attributes for the 'table' tag

A tableDataTextfunção analisa um segmento html iniciado com tag <table> seguida por tags múltiplas <tr>(linhas da tabela) e <td>tags internas (dados da tabela). Ele retorna uma lista de linhas com colunas internas. Aceita apenas um <th>(cabeçalho / dados da tabela) na primeira linha.

def tableDataText(table):       
    rows = []
    trs = table.find_all('tr')
    headerow = [td.get_text(strip=True) for td in trs[0].find_all('th')] # header row
    if headerow: # if there is a header row include first
        rows.append(headerow)
        trs = trs[1:]
    for tr in trs: # for every table row
        rows.append([td.get_text(strip=True) for td in tr.find_all('td')]) # data row
    return rows

Usando-o, obtemos (duas primeiras linhas).

list_table = tableDataText(htmltable)
list_table[:2]

[['Rank',
  'Name',
  "GDP (IMF '19)",
  "GDP (UN '16)",
  'GDP Per Capita',
  '2019 Population'],
 ['1',
  'United States',
  '21.41 trillion',
  '18.62 trillion',
  '$65,064',
  '329,064,917']]

Isso pode ser facilmente transformado em pandas.DataFrameferramentas mais avançadas.

import pandas as pd
dftable = pd.DataFrame(list_table[1:], columns=list_table[0])
dftable.head(4)

saída da tabela html do pandas DataFrame


0
from behave import *
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
import pandas as pd
import requests
from bs4 import BeautifulSoup
from tabulate import tabulate

class readTableDataFromDB: 
    def LookupValueFromColumnSingleKey(context, tablexpath, rowName, columnName):
        print("element present readData From Table")
        element = context.driver.find_elements_by_xpath(tablexpath+"/descendant::th")
        indexrow = 1
        indexcolumn = 1
        for values in element:
            valuepresent = values.text
            print("text present here::"+valuepresent+"rowName::"+rowName)
            if valuepresent.find(columnName) != -1:
                 print("current row"+str(indexrow) +"value"+valuepresent)
                 break
            else:
                 indexrow = indexrow+1    

        indexvalue = context.driver.find_elements_by_xpath(
            tablexpath+"/descendant::tr/td[1]")
        for valuescolumn in indexvalue:
            valuepresentcolumn = valuescolumn.text
            print("Team text present here::" +
                  valuepresentcolumn+"columnName::"+rowName)
            print(indexcolumn) 
            if valuepresentcolumn.find(rowName) != -1:
                print("current column"+str(indexcolumn) +
                      "value"+valuepresentcolumn)
                break
            else:
                indexcolumn = indexcolumn+1

        print("index column"+str(indexcolumn))
        print(tablexpath +"//descendant::tr["+str(indexcolumn)+"]/td["+str(indexrow)+"]")
        #lookupelement = context.driver.find_element_by_xpath(tablexpath +"//descendant::tr["+str(indexcolumn)+"]/td["+str(indexrow)+"]")
        #print(lookupelement.text)
        return context.driver.find_elements_by_xpath(tablexpath+"//descendant::tr["+str(indexcolumn)+"]/td["+str(indexrow)+"]")

    def LookupValueFromColumnTwoKeyssss(context, tablexpath, rowName, columnName, columnName1):
        print("element present readData From Table")
        element = context.driver.find_elements_by_xpath(
            tablexpath+"/descendant::th")
        indexrow = 1
        indexcolumn = 1
        indexcolumn1 = 1
        for values in element:
            valuepresent = values.text
            print("text present here::"+valuepresent)
            indexrow = indexrow+1
            if valuepresent == columnName:
                print("current row value"+str(indexrow)+"value"+valuepresent)
                break

        for values in element:
            valuepresent = values.text
            print("text present here::"+valuepresent)
            indexrow = indexrow+1
            if valuepresent.find(columnName1) != -1:
                print("current row value"+str(indexrow)+"value"+valuepresent)
                break

        indexvalue = context.driver.find_elements_by_xpath(
            tablexpath+"/descendant::tr/td[1]")
        for valuescolumn in indexvalue:
            valuepresentcolumn = valuescolumn.text
            print("Team text present here::"+valuepresentcolumn)
            print(indexcolumn)
            indexcolumn = indexcolumn+1
            if valuepresent.find(rowName) != -1:
                print("current column"+str(indexcolumn) +
                      "value"+valuepresentcolumn)
                break
        print("indexrow"+str(indexrow))
        print("index column"+str(indexcolumn))
        lookupelement = context.driver.find_element_by_xpath(
            tablexpath+"//descendant::tr["+str(indexcolumn)+"]/td["+str(indexrow)+"]")
        print(tablexpath +
              "//descendant::tr["+str(indexcolumn)+"]/td["+str(indexrow)+"]")
        print(lookupelement.text)
        return context.driver.find_element_by_xpath(tablexpath+"//descendant::tr["+str(indexrow)+"]/td["+str(indexcolumn)+"]")
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.