Los datos necesarios :

Quiero pasar por dos páginas web, una aquí: https: // finance .yahoo.com / quote / AAPL / balance-sheet? p = AAPL y el otro: https://finance.yahoo.com/quote/AAPL/financials?p=AAPL. Desde la primera página, necesito valores de la fila llamada Activos totales . Esto sería 5 valores en esa fila nombrados: 365,725,000 375,319,000 321,686,000 290,479,000 231,839,000 Luego necesito 5 valores de la fila llamada Pasivos corrientes totales . Estos serían: 43,658,000 38,542,000 27,970,000 20,722,000 11,506,000 Desde el segundo enlace, necesito 10 valores de la fila denominada Ingresos o pérdidas operativas . Estos serían: 52,503,000 48,999,000 55,241,000 33,790,000 18,385,000.

EDITAR : también necesito el valor TTM, y luego los valores de los cinco años mencionados anteriormente. Gracias. Aquí está la lógica de lo que quiero. Quiero ejecutar este módulo, y cuando lo ejecute, quiero que la salida sea:

TTM array: 365725000, 116866000, 64423000
year1 array: 375319000, 100814000, 70898000
year2 array: 321686000, 79006000, 80610000

Mi código :

Esto es lo que he escrito hasta ahora. Puedo extraer el valor dentro de la clase div si solo lo pongo en una variable como se muestra a continuación. Sin embargo, ¿cómo hago un bucle eficiente a través de las clases 'div' ya que hay miles de ellas en la página? En otras palabras, ¿cómo encuentro solo los valores que estoy buscando?

# Import libraries
import requests
import urllib.request
import time
from bs4 import BeautifulSoup

# Set the URL you want to webscrape from
url = 'https://finance.yahoo.com/quote/AAPL/balance-sheet?p=AAPL'

# Connect to the URL
response = requests.get(url)

# Parse HTML and save to BeautifulSoup object¶
soup = BeautifulSoup(response.text, "html.parser")
soup1 = BeautifulSoup("""<div class="D(tbc) Ta(end) Pstart(6px) Pend(4px) Bxz(bb) Py(8px) BdB Bdc($seperatorColor) Miw(90px) Miw(110px)--pnclg" data-test="fin-col"><span>321,686,000</span></div>""", "html.parser")
spup2 = BeautifulSoup("""<span data-reactid="1377">""", "html.parser");

#This works
print(soup1.find("div", class_="D(tbc) Ta(end) Pstart(6px) Pend(4px) Bxz(bb) Py(8px) BdB Bdc($seperatorColor) Miw(90px) Miw(110px)--pnclg").text)

#How to loop through all the relevant div classes? 
1
Zac 2 oct. 2019 a las 06:47

3 respuestas

La mejor respuesta

Algunas sugerencias para analizar html usan 'BeautifulSoup', lo cual es útil para mí y quizás para usted.

  1. use 'id' para ubicar el elemento, en lugar de usar 'class' porque la 'clase' cambia con más frecuencia que id.
  2. usa información de estructura para ubicar el elemento en lugar de usar 'clase', la información de estructura cambia con menos frecuencia.
  3. usar encabezados con información de agente de usuario para obtener respuesta siempre es mejor que ningún encabezado. En este caso, si no especifica la información de encabezados, no puede encontrar la identificación 'Col1-1-Financials-Proxy', pero puede encontrar 'Col1-3-Financials-Proxy', que no es lo mismo con el resultado en el inspector de Chrome.

Aquí hay códigos ejecutables para su requisito de usar información de estructura para elementos de ubicación. Definitivamente puedes usar la información de 'clase' para hacerlo. Solo recuerde que cuando su código no funciona bien, verifique el código fuente del sitio web.

# import libraries
import requests
from bs4 import BeautifulSoup

# set the URL you want to webscrape from
first_page_url = 'https://finance.yahoo.com/quote/AAPL/balance-sheet?p=AAPL'
second_page_url = 'https://finance.yahoo.com/quote/AAPL/financials?p=AAPL'
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}

#################
# first page
#################

print('*' * 10, ' FIRST PAGE RESULT ', '*' * 10)

total_assets = {}
total_current_liabilities = {}
operating_income_or_loss = {}
page1_table_keys = []
page2_table_keys = []

# connect to the first page URL
response = requests.get(first_page_url, headers=headers)

# parse HTML and save to BeautifulSoup object¶
soup = BeautifulSoup(response.text, "html.parser")
# the nearest id to get the result
sheet = soup.find(id='Col1-1-Financials-Proxy')
sheet_section_divs = sheet.section.find_all('div', recursive=False)
# last child
sheet_data_div = sheet_section_divs[-1]
div_ele_table = sheet_data_div.find('div').find('div').find_all('div', recursive=False)
# table header
div_ele_header = div_ele_table[0].find('div').find_all('div', recursive=False)
# first element is label, the remaining element containing data, so use range(1, len())
for i in range(1, len(div_ele_header)):
    page1_table_keys.append(div_ele_header[i].find('span').text)
# table body
div_ele = div_ele_table[-1]
div_eles = div_ele.find_all('div', recursive=False)
tgt_div_ele1 = div_eles[0].find_all('div', recursive=False)[-1]
tgt_div_ele1_row = tgt_div_ele1.find_all('div', recursive=False)[-1]
tgt_div_ele1_row_eles = tgt_div_ele1_row.find('div').find_all('div', recursive=False)
# first element is label, the remaining element containing data, so use range(1, len())
for i in range(1, len(tgt_div_ele1_row_eles)):
    total_assets[page1_table_keys[i - 1]] = tgt_div_ele1_row_eles[i].find('span').text
tgt_div_ele2 = div_eles[1].find_all('div', recursive=False)[-1]
tgt_div_ele2 = tgt_div_ele2.find('div').find_all('div', recursive=False)[-1]
tgt_div_ele2 = tgt_div_ele2.find('div').find_all('div', recursive=False)[-1]
tgt_div_ele2_row = tgt_div_ele2.find_all('div', recursive=False)[-1]
tgt_div_ele2_row_eles = tgt_div_ele2_row.find('div').find_all('div', recursive=False)
# first element is label, the remaining element containing data, so use range(1, len())
for i in range(1, len(tgt_div_ele2_row_eles)):
    total_current_liabilities[page1_table_keys[i - 1]] = tgt_div_ele2_row_eles[i].find('span').text

print('Total Assets', total_assets)
print('Total Current Liabilities', total_current_liabilities)

#################
# second page, same logic as the first page
#################

print('*' * 10, ' SECOND PAGE RESULT ', '*' * 10)

# Connect to the second page URL
response = requests.get(second_page_url, headers=headers)

# Parse HTML and save to BeautifulSoup object¶
soup = BeautifulSoup(response.text, "html.parser")
# the nearest id to get the result
sheet = soup.find(id='Col1-1-Financials-Proxy')
sheet_section_divs = sheet.section.find_all('div', recursive=False)
# last child
sheet_data_div = sheet_section_divs[-1]
div_ele_table = sheet_data_div.find('div').find('div').find_all('div', recursive=False)
# table header
div_ele_header = div_ele_table[0].find('div').find_all('div', recursive=False)
# first element is label, the remaining element containing data, so use range(1, len())
for i in range(1, len(div_ele_header)):
    page2_table_keys.append(div_ele_header[i].find('span').text)
# table body
div_ele = div_ele_table[-1]
div_eles = div_ele.find_all('div', recursive=False)
tgt_div_ele_row = div_eles[4]
tgt_div_ele_row_eles = tgt_div_ele_row.find('div').find_all('div', recursive=False)
for i in range(1, len(tgt_div_ele_row_eles)):
    operating_income_or_loss[page2_table_keys[i - 1]] = tgt_div_ele_row_eles[i].find('span').text

print('Operating Income or Loss', operating_income_or_loss)

Salida con información de encabezado:

**********  FIRST PAGE RESULT  **********
Total Assets {'9/29/2018': '365,725,000', '9/29/2017': '375,319,000', '9/29/2016': '321,686,000'}
Total Current Liabilities {'9/29/2018': '116,866,000', '9/29/2017': '100,814,000', '9/29/2016': '79,006,000'}
**********  SECOND PAGE RESULT  **********
Operating Income or Loss {'ttm': '64,423,000', '9/29/2018': '70,898,000', '9/29/2017': '61,344,000', '9/29/2016': '60,024,000'}
1
Fogmoon 5 oct. 2019 a las 08:25

ACTUALIZACIÓN

La respuesta de Jack Fleeting es probablemente el mejor enfoque para resolver la pregunta del OP.

Mi respuesta funciona, pero palidece en comparación con el enfoque de Jack. Dejo mi respuesta aquí porque quiero que sirva como marcador de posición para ayudar a otros que necesiten abordar un problema similar en el futuro.


RESPUESTA ORIGINAL

Aquí hay otra respuesta, que probablemente pueda ser consumada por alguien con mejores habilidades para hacer sopa que yo.

Puse los datos raspados en 2 diccionarios, que se denominan balance_sheet_dict y financials_dict . También eliminé las fechas asociadas con las columnas, porque las usaría en alguna otra función. También volví a formatear estas fechas de% m /% d /% Y a% m% d% Y.

También usé sopas find_all_next (tag_name, limit = int) para recopilar solo las subetiquetas requeridas. Puede ajustar este límite para recopilar los elementos que necesita de las tablas.

En general, esta fue una pregunta interesante, que requirió un pensamiento adicional. Gracias por publicar la pregunta.

import requests
from datetime import datetime
from bs4 import BeautifulSoup
import re as regex

operating_income_or_loss_keys = []
operating_income_or_loss_values = []

def get_operating_income_or_loss(soup):
  for rows in soup.find_all('div', {'class': 'D(tbr)'}):
    for date_row in rows.find_all('div', {'class': 'D(ib)'}):
      chart_dates = date_row.find_all_next('span', limit=8)
      for dates in chart_dates[1:]:
       if dates.text == 'ttm':
         operating_income_or_loss_keys.append(dates.text)
       else:
         date_format = regex.match(r'(\d{1,2}/\d{2}/\d{4})', dates.text)
         if date_format:
          reformatted_date = datetime.strptime(dates.text, '%m/%d/%Y').strftime('%m%d%Y')                            
          operating_income_or_loss_keys.append(reformatted_date)

    for sub_row in rows.find_all('div', {'class': 'D(tbc)'}):
      for row_item in sub_row.find_all('span', {'class': 'Va(m)'}):
        if row_item.text == 'Operating Income or Loss':
         operating_income_or_loss = row_item.find_all_next('span', limit=len(operating_income_or_loss_keys))
         for item in operating_income_or_loss[1:]:
           if len(item) == 0 or item.text == '-':
             operating_income_or_loss_values.append('no value provided')
           else:
             operating_income_or_loss_values.append(item.text)
 return


total_assets_values = []
total_current_liabilities = []
balance_sheet_keys = []

def get_total_assets(soup):
  for rows in soup.find_all('div', {'class': 'D(tbr)'}):
    for date_row in rows.find_all('div', {'class': 'D(ib)'}):
        if date_row.text == 'Breakdown':
            chart_dates = date_row.find_all_next('span', limit=8)
            for dates in chart_dates[1:]:
                date_format = regex.match(r'(\d{1,2}/\d{2}/\d{4})', dates.text)
                if date_format:
                    reformatted_date = datetime.strptime(dates.text, '%m/%d/%Y').strftime('%m%d%Y')
                    balance_sheet_keys.append(reformatted_date)

  for rows in soup.find_all('div', {'class': 'D(tbr)'}):
    for sub_row in rows.find_all('div', {'class': 'D(tbc)'}):
        for row_item in sub_row.find_all('span', {'class': 'Va(m)'}):
            if row_item.text == 'Total Assets':
                    total_assets = row_item.find_all_next('span', limit=len(balance_sheet_keys))
                    for item in total_assets:
                        if len(item) == 0 or item.text == '-':
                            total_assets_values.append('no value provided')
                        else:
                            total_assets_values.append(item.text)
  return

def get_total_current_liabilities(soup):
  for rows in soup.find_all('div', {'class': 'D(tbr)'}):
    for sub_row in rows.find_all('div', {'class': 'D(tbc)'}):
        for row_item in sub_row.find_all('span', {'class': 'Va(m)'}):
            if row_item.text == 'Total Current Liabilities':
                current_liabilities = row_item.find_all_next('span', limit=len(balance_sheet_keys))
                for item in current_liabilities:
                    if len(item) == 0 or item.text == '-':
                        total_current_liabilities.append('no value provided')
                    else:
                        total_current_liabilities.append(item.text)
  return


urls = ['https://finance.yahoo.com/quote/AAPL/balance-sheet?p=AAPL',
        'https://finance.yahoo.com/quote/AAPL/financials?p=AAPL']

for url in urls:

  stock_symbol = url.rpartition('?p=')[-1]

  if 'balance-sheet' in url:
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "html.parser")
    get_total_assets(soup)
    get_total_current_liabilities(soup)
    balance_sheet_dict = {k: v for k, v in zip(balance_sheet_keys, zip(total_assets_values,total_current_liabilities))}
    print('*' * 10, f'Balance sheet results for {stock_symbol}', '*' * 10)
    for key, values in balance_sheet_dict.items():
        total_asset = values[0]
        current_liabilities = values[1]
        print (f'Year: {key}, Total Asset: {total_asset}')
        print (f'Year: {key}, Current liabilities: {current_liabilities}')
        # output
        ********** Balance sheet results for AAPL **********
        Year: 09292018, Total Asset: 365,725,000
        Year: 09292018, Current liabilities: 116,866,000
        Year: 09292017, Total Asset: 375,319,000
        Year: 09292017, Current liabilities: 100,814,000
        Year: 09292016, Total Asset: 321,686,000
        Year: 09292016, Current liabilities: 79,006,000

elif 'financials' in url:
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "html.parser")
    get_operating_income_or_loss(soup)
    financials_dict = {k: v for k, v in zip(operating_income_or_loss_keys, operating_income_or_loss_values)}
    print('*' * 10, f'Financials results for {stock_symbol}', '*' * 10)
    for key, value in financials_dict.items():
      print (f'Year: {key}, Operating income or loss: {value}')
      # output 
      ********** Financials results for AAPL **********
      Year: ttm, Operating income or loss: 64,423,000
      Year: 09292018, Operating income or loss: 70,898,000
      Year: 09292017, Operating income or loss: 61,344,000
      Year: 09292016, Operating income or loss: 60,024,000

Consulte su comentario al consultar el diccionario llamado balance_sheet_dict para el año 2016:

for key, values in balance_sheet_dict.items():
  if key.endswith('2016'):
   total_asset = values[0]
   current_liabilities = values[1]
   print (f'Year: {key}, Total assets: {total_asset}, Total current liabilities: {current_liabilities}')
   # output
   Year: 09292016, Total assets: 321,686,000, Total current liabilities: 79,006,000
0
Life is complex 8 oct. 2019 a las 13:50

EDITAR: a solicitud de @Life es complejo, editado para agregar encabezados de fecha.

Prueba esto usando lxml:

import requests
from lxml import html

url = 'https://finance.yahoo.com/quote/AAPL/balance-sheet?p=AAPL'
url2 = 'https://finance.yahoo.com/quote/AAPL/financials?p=AAPL'
page = requests.get(url)
page2 = requests.get(url2)


tree = html.fromstring(page.content)
tree2 = html.fromstring(page2.content)

total_assets = []
Total_Current_Liabilities = []
Operating_Income_or_Loss = []
heads = []


path = '//div[@class="rw-expnded"][@data-test="fin-row"][@data-reactid]'
data_path = '../../div/span/text()'
heads_path = '//div[contains(@class,"D(ib) Fw(b) Ta(end)")]/span/text()'

dats = [tree.xpath(path),tree2.xpath(path)]

for entry in dats:
    heads.append(entry[0].xpath(heads_path))
    for d in entry[0]:
        for s in d.xpath('//div[@title]'):
            if s.attrib['title'] == 'Total Assets':
                total_assets.append(s.xpath(data_path))
            if s.attrib['title'] == 'Total Current Liabilities':
                Total_Current_Liabilities.append(s.xpath(data_path))
            if s.attrib['title'] == 'Operating Income or Loss':
                Operating_Income_or_Loss.append(s.xpath(data_path))

del total_assets[0]
del Total_Current_Liabilities[0]
del Operating_Income_or_Loss[0]

print('Date   Total Assets Total_Current_Liabilities:')
for date,asset,current in zip(heads[0],total_assets[0],Total_Current_Liabilities[0]):    
         print(date, asset, current)
print('Operating Income or Loss:')
for head,income in zip(heads[1],Operating_Income_or_Loss[0]):
         print(head,income)

Salida:

Date      Total Assets Total_Current_Liabilities:
9/29/2018 365,725,000 116,866,000
9/29/2017 375,319,000 100,814,000
9/29/2016 321,686,000 79,006,000
Operating Income or Loss:
ttm       64,423,000
9/29/2018 70,898,000
9/29/2017 61,344,000
9/29/2016 60,024,000

Por supuesto, si así lo desea, esto se puede incorporar fácilmente en un marco de datos de pandas.

2
Jack Fleeting 7 oct. 2019 a las 15:58
58194952