【问题标题】:Scrape Each Table from Drop Down Menu Python从下拉菜单 Python 中抓取每个表
【发布时间】:2020-04-11 19:47:33
【问题描述】:

我希望从以下 NCAA 统计数据页面中获取 Division 3 大学篮球统计数据:

https://stats.ncaa.org/rankings/change_sport_year_div

要访问我所在的页面,点击链接后,选择 Sport = Men's Basketball,Year = 2019-2020,Div = III

点击链接后,左上角表格上方会出现一个下拉菜单。它被标记为“附加统计信息”。对于每个统计数据,都有一个表格,您可以获取其中的 excel 文件,但我想提高效率。我在想可能有一种方法可以使用 BeautifulSoup(甚至可能是 pd.read_html)遍历下拉栏,以获取列出的每个统计信息的数据框。有没有办法做到这一点?手动浏览每个统计信息,下载 excel 文件,然后将 excel 文件读入 pandas 会很痛苦。谢谢。

【问题讨论】:

    标签: python pandas web-scraping beautifulsoup


    【解决方案1】:

    这是我的建议,结合使用 requestsbeautifulsoup 和来自 Scott Rome 的出色 html 表解析器(我对 parse_html_table 函数进行了一些修改以删除 \n 并去除空格)。

    首先,当您检查页面的源代码时,您可以看到它采用以下形式:"https://stats.ncaa.org/rankings/national_ranking?academic_year=2020.0&division=3.0&ranking_period=110.0&sport_code=MBB&stat_seq=145.0" 例如对于 stat 145 即“得分进攻”。

    因此,您可以在每个网址上使用以下代码,将145.0 替换为与不同统计信息对应的值,您可以在检查页面的源代码时看到这些值。

    # <option value="625">3-pt Field Goal Attempts</option>
    # <option value="474">Assist Turnover Ratio</option>
    # <option value="216">Assists Per Game</option>
    # ...
    

    对于特定的统计数据,例如得分进攻,您可以使用以下代码将表格提取为 pandas DataFrame:

    import pandas as pd
    from bs4 import BeautifulSoup
    import requests
    
    
    el = "https://stats.ncaa.org/rankings/national_ranking?academic_year=2020.0&division=3.0&ranking_period=110.0&sport_code=MBB&stat_seq=145.0"
    page = requests.get(el).content.decode('utf-8')
    soup = BeautifulSoup(page, "html.parser")
    ta = soup.find_all('table', {"id": "rankings_table"})
    
    # Scott Rome function tweaked a bit
    def parse_html_table(table):
        n_columns = 0
        n_rows = 0
        column_names = []
    
        # Find number of rows and columns
        # we also find the column titles if we can
        for row in table.find_all('tr'):
    
            # Determine the number of rows in the table
            td_tags = row.find_all('td')
            if len(td_tags) > 0:
                n_rows += 1
                if n_columns == 0:
                    # Set the number of columns for our table
                    n_columns = len(td_tags)
    
            # Handle column names if we find them
            th_tags = row.find_all('th')
            if len(th_tags) > 0 and len(column_names) == 0:
                for th in th_tags:
                    column_names.append(th.get_text())
    
        # Safeguard on Column Titles
        if len(column_names) > 0 and len(column_names) != n_columns:
            raise Exception("Column titles do not match the number of columns")
    
        columns = column_names if len(column_names) > 0 else range(0, n_columns)
        df = pd.DataFrame(columns=columns,
                          index=range(0, n_rows))
        row_marker = 0
        for row in table.find_all('tr'):
            column_marker = 0
            columns = row.find_all('td')
            for column in columns:
                df.iat[row_marker, column_marker] = column.get_text()
                column_marker += 1
            if len(columns) > 0:
                row_marker += 1
    
        # remove \n
        for col in df:
            try:
                df[col] = df[col].str.replace("\n", "")
                df[col] = df[col].str.strip()
            except ValueError:
                pass
        # Convert to float if possible
        for col in df:
            try:
                df[col] = df[col].astype(float)
            except ValueError:
                pass
    
        return df
    
    
    example = parse_html_table(ta[0])
    

    结果是

     Rank                           Team    GM    W-L    PTS    PPG
    0    1             Greenville (SLIAC)  27.0  14-13  3,580  132.6
    1    2  Grinnell (Midwest Conference)  25.0  13-12  2,717  108.7
    2    3             Pacific (OR) (NWC)  25.0   7-18  2,384   95.4
    3    4                  Whitman (NWC)  28.0   20-8  2,646   94.5
    4    5            Valley Forge (ACAA)  22.0  12-11  2,047   93.0
    ...
    

    现在,您要做的就是将其应用于上述所有统计值。

    您可以为上面的代码创建一个函数,并在 for 循环中将其应用于 url "https://stats.ncaa.org/rankings/national_ranking?academic_year=2020.0&amp;division=3.0&amp;ranking_period=110.0&amp;sport_code=MBB&amp;stat_seq={}".format(stat) 其中stat 在所有可能值的列表中。

    希望对你有帮助。

    【讨论】:

      【解决方案2】:

      也许是一种更简洁的方法:

      import requests as rq
      from bs4 import BeautifulSoup as bs
      import pandas as pd
      
      headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0)     Gecko/20100101 Firefox/76.0"}
      params = {"sport_code": "MBB", "stat_seq": "518", "academic_year": "2020.0",  "division":"3.0", "ranking_period":"110.0"}
      url = "https://stats.ncaa.org/rankings/national_ranking"
      
      resp = rq.post(url, headers=headers, params=params)
      soup = bs(resp.content)
      
      colnames = [th.text.strip() for th in soup.find_all("thead")[0].find_all("th")]
      data = [[td.text.strip() for td in tr.find_all('td')] for tr in soup.find_all('tbody')[0].find_all("tr")]
      
      df = pd.DataFrame(data, columns=colnames)
      df.astype({"GM": 'int32'}).dtypes # convert column in type u want
      

      您必须查看 XHR 请求 [on Mozilla: F12 -> Network -> XHR]。

      当您从下拉列表中选择一个项目时,这会通过以下 url 发出一个帖子请求:https://stats.ncaa.org/rankings/national_ranking

      发出此帖子请求需要一些参数,其中之一是“stat_seq”。该值对应于下拉选项的“值”。

      Inspector 给你列表中的“值”-StatName 对应关系:

      <option value="625" selected="selected">3-pt Field Goal Attempts</option>
      <option value="474">Assist Turnover Ratio</option>
      <option value="216">Assists Per Game</option>
      <option value="214">Blocked Shots Per Game</option>
      <option value="859">Defensive Rebounds per Game</option>
      <option value="642">Fewest Fouls</option>
      ...
      ...
      ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-26
        • 2023-03-19
        • 2019-10-31
        • 2021-09-13
        • 1970-01-01
        • 1970-01-01
        • 2020-07-04
        • 1970-01-01
        相关资源
        最近更新 更多