【问题标题】:How to efficiently parse large HTML div-class and span data on Python BeautifulSoup?如何在 Python BeautifulSoup 上有效地解析大型 HTML div 类和跨度数据?
【发布时间】:2019-10-02 03:47:14
【问题描述】:

需要的数据

我想浏览两个网页,一个在这里: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 然后我需要名为 Total Current Liabilities 的行的 5 个值。这些将是: 43,658,000 38,542,000 27,970,000 20,722,000 11,506,000 在第二个链接中,我需要名为 Operating Income 或 Loss 的行的 10 个值。这些将是:52,503,000 48,999,000 55,241,000 33,790,000 18,385,000。

编辑:我也需要 TTM 值,然后是上面提到的五年值。谢谢。 这是我想要的逻辑。我想运行这个模块,运行时,我希望输出是:

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? 

【问题讨论】:

  • 在您链接到的 AAPL 页面中,只有 3 列(2018 年、2017 年和 2016 年的 9 月 30 日)。这些够了吗?
  • “循环遍历所有相关的 div 类”到底是什么意思?您是否只想找到您感兴趣的所有值的 HTML 类?在这种情况下,当您右键单击网页并选择“检查”(或在 Chrome 中按 CTRL + SHIFT + C)时,您可以将鼠标悬停在网页上的项目上,它会显示其背后的 HTML。
  • 所以您不需要 TTM 列来表示营业收入或亏损?
  • 另外,你能用lxml代替beautifulsoup吗?在这种情况下要简单得多。
  • 是的,我也需要 TTM 专栏!对不起,如果我错过了。我现在进行了编辑。是的,如果你有样本,我可以使用 lxml。

标签: python html parsing beautifulsoup


【解决方案1】:

编辑 - 应@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 数据帧中。

【讨论】:

【解决方案2】:

解析html 的一些建议使用对我有帮助的“BeautifulSoup”可能对你有帮助。

  1. 使用“id”来定位元素,而不是使用“class”,因为“class”的变化比 id 更频繁。
  2. 使用结构信息来定位元素而不是使用“类”,结构信息更改的频率较低。
  3. 使用带有用户代理信息的标头来获得响应总是比没有标头要好。在这种情况下,如果不指定 headers 信息,则找不到 id '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'}

【讨论】:

  • 谢谢。在输出中,我看到了总资产 (TA) 和总流动负债 (TCA) 的 3 个值以及 OI&L 的 4 个值。有没有办法传递一个变量来检测该表中的列数以获取和获取统一的值集。那么,如何将这些保存到一个数组中——一个用于 TA,一个用于 TCA,一个用于 OIL?然后检查所有数组是否具有相同的非空数据类型。 (我不明白 i in range(1, len(tgt_div_ele1_row_eles)))... 这就是为什么我不知道如何获得更多值的原因。
  • 您可以使用表头查看列数据编号。虽然我不认为数组对你有用。您应该使用标题作为帮助您处理逻辑的关键。对于范围部分,因为第一个子元素是标签,所以从1到len(chilrend_elements)获取数据信息。我已经更新了我的答案,请再次检查。
  • 在您的输出中,总资产和总当前负债值是相同的。我认为这是不正确的。应该改成'ele2'?
  • 另外,假设其中一个值在网站中不可用。假设总资产在财务网站中设置为“-”。我们如何处理这个值并跳到下一个?
  • @Zac 基于所有答案中的 cmets,我注意到您继续更改与原始问题相关的要求。您似乎需要有人编写一个完整的解决方案来处理雅虎财经上市的所有公司的所有潜在可能性。您可能需要考虑修改您的需求并增加赏金,因为您似乎正在寻找具有身份验证、错误处理和其他尚未定义的内容的完整解决方案。
猜你喜欢
  • 2011-06-15
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2017-01-09
  • 1970-01-01
  • 2020-02-06
  • 2014-03-06
相关资源
最近更新 更多