Как эффективно анализировать большие HTML-классы и данные span на Python BeautifulSoup? - PullRequest
1 голос
/ 02 октября 2019

Необходимые данные :

Я хочу пролистать две веб-страницы, одну здесь: https://finance.yahoo.com/quote/AAPL/balance-sheet?p=AAPL, а другую: https://finance.yahoo.com/quote/AAPL/financials?p=AAPL. С первой страницыМне нужны значения строки с именем Total Assets . Это будет 5 значений в этой строке с именем: 365 725 000 375 319 000 321 686 000 290 479 000 231 839 000 Тогда мне нужно 5 значений строки с именем Всего текущих обязательств . Это будет: 43 658 000 38 542 000 27 970 000 20 722 000 11 506 000 Из второй ссылки мне нужно 10 значений строки с именем Операционный доход или убыток . Это будут: 52 503 000 48 999 000 55 241 000 33 790 000 18 385 000.

РЕДАКТИРОВАТЬ : мне тоже нужно значение ТТМ, а затем значения пяти лет, упомянутые выше. Благодарю. Вот логика того, чего я хочу. Я хочу запустить этот модуль, и при запуске я хочу, чтобы вывод был:

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

Мой код :

Это то, что я написал до сих пор,Я могу извлечь значение в классе div, если я просто помещу его в переменную, как показано ниже. Тем не менее, как эффективно выполнить цикл по классам 'div', поскольку на странице их тысячи. Другими словами, как мне найти только те значения, которые я ищу?

# 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? 

Ответы [ 3 ]

2 голосов
/ 04 октября 2019

РЕДАКТИРОВАТЬ - По запросу @Life является сложным, редактируется для добавления заголовков даты.

Попробуйте это с помощью 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)

Вывод:

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

Конечно, если это желательно, это может быть легко включено в кадр данных pandas.

1 голос
/ 04 октября 2019

Некоторые предложения для разбора html используйте 'BeautifulSoup', что полезно для меня, может быть полезно для вас.

  1. используйте 'id' для определения местоположения элемента вместо использования 'class«потому что« класс »меняется чаще, чем идентификатор.
  2. использует информацию о структуре для определения местоположения элемента вместо« класса », информация о структуре меняется реже.
  3. использует заголовки с пользователемИнформация агента для получения ответа всегда лучше, чем отсутствие заголовков. В этом случае, если не указать информацию заголовков, вы не сможете найти идентификатор «Col1-1-Financials-Proxy», но вы можете найти «Col1-3-Financials-Proxy», что не совпадает с результатом в Инспекторе Chrome.

Здесь выполняемые коды для вашего требования используют информацию о структуре для элементов местоположения. Вы определенно можете использовать информацию «класса», чтобы сделать это. Просто помните, что когда ваш код не работает должным образом, проверьте исходный код веб-сайта.

# 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)

Вывод с информацией заголовка:

**********  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'}
0 голосов
/ 04 октября 2019

ОБНОВЛЕНИЕ

Ответ Джека Флитинга, вероятно, является лучшим подходом для решения вопроса ОП.

Мой ответ работает, но он бледнеет по сравнению с подходом Джека. Я оставляю здесь свой ответ, потому что я хочу, чтобы он служил заполнителем, чтобы помочь другим, которым необходимо решить аналогичную проблему в будущем.


ОРИГИНАЛЬНЫЙ ОТВЕТ

Вот еще один ответ, который, вероятно, может быть получен кем-то с лучшими навыками приготовления супа, чем я.

Я поместил очищенные данные в 2 словаря с именами balance_sheet_dict и financials_dict . Я также очистил даты, связанные со столбцами, потому что я использовал бы их в некоторой другой функции. Я также переформатировал эти даты с% m /% d /% Y до% m% d% Y.

Я также использовал супы find_all_next (tag_name, limit = int), чтобы собирать только требуемые вложенные теги. Вы можете отрегулировать этот лимит, чтобы собирать нужные вам предметы из таблиц.

В целом это был интересный вопрос, который потребовал некоторых дополнительных размышлений. Спасибо за публикацию вопроса.

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

Ссылка на ваш комментарий по запросу словаря с именем balance_sheet_dict за 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...