【问题标题】:Beautiful Soup Parsing from a HTML Website从 HTML 网站解析美丽的汤
【发布时间】:2022-10-20 18:16:12
【问题描述】:

我对网络报废非常陌生,也许 python3 也是如此,希望得到帮助以解决我的问题。

我正在从以下网页抓取:http://ets.aeso.ca/ets_web/ip/Market/Reports/CSDReportServlet

我收到的代码来自:New to Beautiful Soup. Need to scrape tables from an online report

这是我所拥有的代码示例。 (见下文)

这会产生:

                                      GAS            GAS.1            GAS.2  \
0                            Simple Cycle     Simple Cycle     Simple Cycle   
1                                   ASSET               MC              TNG   
2                     AB Newsprint (ANC1)               63               65   
3                           Bantry (ALP1)                7                6   
4                        Bellshill (BHL1)                5                5   
5                     Carson Creek (GEN5)               15               12   
6                     Cloverbar #1 (ENC1)               48               35   
7                     Cloverbar #2 (ENC2)              101               93   
8                     Cloverbar #3 (ENC3)              101               91   
9      Crossfield Energy Centre #1 (CRS1)               48                0   
10     Crossfield Energy Centre #2 (CRS2)               48               41   
11    Crossfield Energy Centre #3 (CRS3)^               48               41   

问题:不知道为什么代码将它分成不同的列。 GAS.2 \ 这会将 GAS.3 的数据拆分到完全不同的行上。

我正在寻找的是如何将这些信息写入excel? IE。需要删除前 3 行,GAS & Simple Cycle & ASSET 不是我需要显示的信息。

                                      GAS            GAS.1            GAS.2  \
0                            Simple Cycle     Simple Cycle     Simple Cycle   
1                                   ASSET               MC              TNG   
2                     AB Newsprint (ANC1)               63               65   
3                           Bantry (ALP1)                7                6   

我需要具有两个值的字典中名称的前两项。

IE afc = {'AB Newsprint (ANC1)': {'MC':63,'TNG': 65}, 'Bantry (ALP1)': {'MC': 7,'TNG': 6}}

对于从 GAS 表中抓取的整个 td 列表,放入上面的字典中。

然后我需要以 DataPipe (excel) 方式显示它。

IE

A1 = AB Newsprint (ANC1)
B1 = 63
C1 = 65

A2 = Bantry (ALP1)
B2 = 7
C2 = 6

Please click to see image

我应该如何编码或继续以完成此操作?

import requests, sys, re
import pandas as pd
from bs4 import BeautifulSoup
import numpy as np
#np.set_printoptions(threshold=sys.maxsize)
#np.set_printoptions(threshold='nan')
pd.set_option('display.max_rows', 100000)
pd.set_option('display.max_columns', 100000)

def get_summary(soup):
    summary = soup.select_one(
        "table:has(b:-soup-contains(SUMMARY)):not(:has(table))"
    )
    summary.tr.extract()
    return pd.read_html(str(summary))[0]

def get_generation(soup):
    generation = soup.select_one(
        "table:has(b:-soup-contains(GENERATION)):not(:has(table))"
    )
    generation.tr.extract()
    for td in generation.tr.select("td"):
        td.name = "th"
    return pd.read_html(str(generation))[0]

def get_interchange(soup):
    interchange = soup.select_one(
        "table:has(b:-soup-contains(INTERCHANGE)):not(:has(table))"
    )
    interchange.tr.extract()
    for td in interchange.tr.select("td"):
        td.name = "th"
    return pd.read_html(str(interchange))[0]

def get_gas(soup):
    gas = soup.select_one(
        "table:has(b:-soup-contains(GAS)):not(:has(table))"
    )
    #for td in gas.tr.select("td"):
        #td.name = "th"
    return pd.read_html(str(gas))[0]
'''    
def print_full(x):
    dim = x.shape
    pd.set_option('display.max_rows', dim[0])#dim[0] = len(x)
    pd.set_option('display.max_columns', dim[1])
    #print(x)
    pd.reset_option('display.max_rows')
    pd.reset_option('display.max_columns')
'''
url = "http://ets.aeso.ca/ets_web/ip/Market/Reports/CSDReportServlet"
#html5lib: A pure-python library for parsing HTML. It is designed to conform to the WHATWG HTML specification
soup = BeautifulSoup(requests.get(url).content, "html5lib")

#print(get_summary(soup))
#print(get_generation(soup))
#print(get_interchange(soup))
print (get_gas(soup))
#print(get_hydro(soup))
#print(get_biomass(soup))
#print(get_energystorage(soup))
#print(get_solar(soup))
#print(get_wind(soup))
#print(get_coal(soup))

【问题讨论】:

    标签: python pandas beautifulsoup


    【解决方案1】:

    已经有一段时间了,所以也许您已经解决了部分或全部问题,但如果您还没有,我可以提出一些解决方案。

    (首先,我将 DataFrame 保存为ggDfggDf = get_gas(soup),因此更容易参考,我不必一次又一次地调用get_gas。)


    问题:不知道为什么代码将它分成不同的列。 GAS.2 这将 GAS.3 的数据拆分到完全不同的行上。

    这是 pandas 所做的事情 - 默认情况下,如果它比 80chars 更宽,它将split the table;你可以调整这个最大值

    pd.set_option('display.width', 1000000)
    
    print(ggDf)
    

    现在你应该能够看到桌子而不会分裂。 (顺便说一句,这张桌子可能只需要 100 个就足够了。)


    需要删除前 3 行,GAS & Simple Cycle & ASSET 不是我需要显示的信息。

    即 afc = {'AB Newsprint (ANC1)': {'MC':63,'TNG': 65}, 'Bantry (ALP1)': {'MC': 7,'TNG': 6}}

    对于从 GAS 表中抓取的整个 td 列表,放入上面的字典中。

    在形成字典之前,你需要清理一下表格

    # get rid of GAS-GAS.1-GAS.2-GAS.3 headers row
    ggDf2 = ggDf.rename(columns=dict(
        zip(list(ggDf.columns), list(ggDf.loc[1]))))
    
    # get rid of 0-1-2-3....n index column
    ggDf2 = ggDf2.set_index('ASSET') 
    
    # filter: only keep cells with numbers  
    ggDf2 = ggDf2[ggDf2['MC'].str.isnumeric()]  
    # [so Simple Cycle & ASSET lines will be gone] 
    

    此时ggDf2 看起来像:

    ASSET MC TNG DCR
    AB Newsprint (ANC1) 63 0 0
    Bantry (ALP1) 7 0 0
    Bellshill (BHL1) 5 0 0
    Carson Creek (GEN5) 15 12 0
    Cloverbar #1 (ENC1) 48 0 0
    Cloverbar #2 (ENC2) 101 0 0

    我假设您不在乎保留 GAS.3/DCR 列,因为您只提到想要 MC 和 TNG;您可以删除该列

    ggDf2 = ggDf2.drop('DCR', axis=1)
    

    由于数据框已经清理完毕,可以直接用to_dict函数形成字典:

    ggDict = ggDf2.to_dict('index')
    print(ggDict)
    

    输出:

    {'AB Newsprint (ANC1)': {'MC': '63', 'TNG': '0'}, 'Bantry (ALP1)': {'MC': '7', 'TNG': '0'}, 'Bellshill (BHL1)': {'MC': '5', 'TNG': '0'}, 'Carson Creek (GEN5)': {'MC': '15', 'TNG': '12'}, 'Cloverbar #1 (ENC1)': {'MC': '48', 'TNG': '0'}, 'Cloverbar #2 (ENC2)': {'MC': '101', 'TNG': '0'}, 'Cloverbar #3 (ENC3)': {'MC': '101', 'TNG': '94'}, 'Crossfield Energy Centre #1 (CRS1)': {'MC': '48', 'TNG': '0'}, 'Crossfield Energy Centre #2 (CRS2)': {'MC': '48', 'TNG': '0'}, 'Crossfield Energy Centre #3 (CRS3)^': {'MC': '48', 'TNG': '0'}, 'Drywood (DRW1)': {'MC': '6', 'TNG': '0'}, 'H.R. Milner (HRM)': {'MC': '300', 'TNG': '0'}, 'Judy Creek (GEN6)': {'MC': '15', 'TNG': '13'}, 'Lethbridge Burdett (ME03)': {'MC': '7', 'TNG': '0'}, 'Lethbridge Coaldale (ME04)': {'MC': '6', 'TNG': '0'}, 'Lethbridge Taber (ME02)': {'MC': '8', 'TNG': '0'}, 'NPC2 JL Landry (NPC2)': {'MC': '9', 'TNG': '0'}, 'NPC3 Elmworth (NPC3)': {'MC': '9', 'TNG': '0'}, 'Northern Prairie Power Project (NPP1)': {'MC': '105', 'TNG': '70'}, 'Parkland (ALP2)': {'MC': '10', 'TNG': '0'}, 'Poplar Hill #1 (PH1)': {'MC': '48', 'TNG': '32'}, 'Rainbow #5 (RB5)': {'MC': '50', 'TNG': '0'}, 'Ralston (NAT1)': {'MC': '20', 'TNG': '0'}, 'South Edmonton Terminal (SET1)': {'MC': '20', 'TNG': '16'}, 'Valley View 1 (VVW1)': {'MC': '50', 'TNG': '0'}, 'Valley View 2 (VVW2)': {'MC': '50', 'TNG': '0'}, 'West Cadotte (WCD1)': {'MC': '20', 'TNG': '19'}, 'West Pembina (PMB1)*': {'MC': '13', 'TNG': '0'}, 'Air Liquide Scotford #1 (ALS1)': {'MC': '106', 'TNG': '56'}, 'AltaGas Harmattan (HMT1)': {'MC': '45', 'TNG': '24'}, 'Base Plant (SCR1)': {'MC': '50', 'TNG': '18'}, 'Bear Creek 1 (BCRK)': {'MC': '64', 'TNG': '57'}, 'Bear Creek 2 (BCR2)': {'MC': '36', 'TNG': '32'}, 'Blackfalds (BFD1)': {'MC': '6', 'TNG': '1'}, 'CNRL Horizon (CNR5)*': {'MC': '203', 'TNG': '181'}, 'COD1 Coaldale (COD1)': {'MC': '5', 'TNG': '5'}, 'Camrose (CRG1)*': {'MC': '10', 'TNG': '5'}, 'Caroline (SHCG)*': {'MC': '19', 'TNG': '0'}, 'Carseland Cogen (TC01)': {'MC': '95', 'TNG': '77'}, 'Christina Lake (CL01)': {'MC': '100', 'TNG': '53'}, 'Dow Hydrocarbon (DOWG)': {'MC': '326', 'TNG': '183'}, 'Edson (TLM2)': {'MC': '13', 'TNG': '9'}, 'Empress (EPS1)*': {'MC': '46', 'TNG': '0'}, 'Firebag (SCR6)': {'MC': '497', 'TNG': '403'}, 'Fort Hills (FH1)': {'MC': '199', 'TNG': '169'}, 'Foster Creek (EC04)': {'MC': '98', 'TNG': '68'}, 'Heartland Petrochemical (HRT1)': {'MC': '108', 'TNG': '96'}, 'Joffre #1 (JOF1)': {'MC': '474', 'TNG': '137'}, 'Kearl (IOR3)': {'MC': '84', 'TNG': '66'}, 'Lindbergh (PEC1)*': {'MC': '16', 'TNG': '0'}, 'MEG1 Christina Lake (MEG1)': {'MC': '202', 'TNG': '150'}, 'MacKay River (MKRC)': {'MC': '207', 'TNG': '176'}, 'Mahkeses (IOR1)': {'MC': '180', 'TNG': '152'}, 'Mulligan (MUL1)*': {'MC': '5', 'TNG': '0'}, 'Muskeg River (MKR1)': {'MC': '202', 'TNG': '165'}, 'Nabiye (IOR2)*': {'MC': '195', 'TNG': '156'}, 'Nexen Inc #2 (NX02)': {'MC': '220', 'TNG': '167'}, 'Poplar Creek (SCR5)': {'MC': '376', 'TNG': '261'}, 'Primrose #1 (PR1)': {'MC': '100', 'TNG': '74'}, 'Rainbow Lake #1 (RL1)': {'MC': '47', 'TNG': '37'}, 'Redwater Cogen (TC02)': {'MC': '92', 'TNG': '76'}, 'Saddle Hills (SDH1)': {'MC': '10', 'TNG': '6'}, 'Scotford Upgrader (APS1)': {'MC': '195', 'TNG': '148'}, 'Strathcona (IOR4)*': {'MC': '43', 'TNG': '43'}, 'Syncrude #1 (SCL1)*': {'MC': '510', 'TNG': '333'}, 'U of C Generator (UOC1)*': {'MC': '12', 'TNG': '13'}, 'University of Alberta (UOA1)*': {'MC': '39', 'TNG': '32'}, 'Cavalier (EC01)': {'MC': '120', 'TNG': '84'}, 'ENMAX Calgary Energy Centre (CAL1)': {'MC': '330', 'TNG': '201'}, 'Fort Nelson (FNG1)': {'MC': '73', 'TNG': '0'}, 'Medicine Hat #1 (CMH1)': {'MC': '299', 'TNG': '129'}, 'Nexen Inc #1 (NX01)': {'MC': '120', 'TNG': '102'}, 'Shepard (EGC1)': {'MC': '868', 'TNG': '648'}, 'Battle River #4 (BR4)': {'MC': '155', 'TNG': '29'}, 'Battle River #5 (BR5)': {'MC': '395', 'TNG': '90'}, 'Keephills #2 (KH2)': {'MC': '395', 'TNG': '67'}, 'Keephills #3 (KH3)': {'MC': '463', 'TNG': '465'}, 'Sheerness #1 (SH1)': {'MC': '400', 'TNG': '123'}, 'Sheerness #2 (SH2)': {'MC': '400', 'TNG': '135'}, 'Sundance #6 (SD6)': {'MC': '401', 'TNG': '137'}}
    

    我正在寻找的是如何将这些信息写入excel?

    这也可以直接使用to_excel 函数完成

    ggDf2.to_excel('ggDf.xlsx', header=False)
    

    如果没有之前的清理,语句会更长:

    ggDf[ggDf['GAS.1'].str.isnumeric()].drop(
        'GAS.3', axis=1).to_excel('ggDf.xlsx', header=False, index=False)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-05
      • 2018-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-11
      • 2013-04-29
      相关资源
      最近更新 更多