【问题标题】:BeautifulSoup and List indexingBeautifulSoup 和列表索引
【发布时间】:2021-01-07 09:21:23
【问题描述】:

由于我对网络抓取还很陌生,我目前正在练习一些基础知识,例如这个。我已经从“th”标签和“tr”标签中抓取了类别,并将其附加到几个空列表中。 get_text() 中的类别很好,但是当我尝试打印球员时,它在名字的第一个字母之前有一个数字排名,在姓氏之后有球员的球队缩写字母。 我想做的三件事:

1) 通过从列表中进行一些切片,仅输出每个玩家的名字和姓氏,但我想不出任何更简单的方法来做到这一点。在标签中可能有一种更快的方法,我可以在其中调用类或在 html 中再次使用soup.findAll,或者我不知道的其他东西,但我目前不知道我缺少什么或缺少什么。

2) 取名称前的数字排名并将其附加到一个空列表中。

3)取最后 3 个缩写字母并将其附加到一个空列表中

任何建议将不胜感激!

from bs4 import BeautifulSoup as bs4
import requests
import pandas as pd
from time import sleep

players = []
categories = []

url ='https://www.espn.com/nba/stats/player/_/table/offensive/sort/avgPoints/dir/desc'
source = requests.get(url)
soup = bs4(source.text, 'lxml')

for i in soup.findAll('th'):
    c = i.get_text()
    categories.append(c)

for i in soup.findAll('tr'):
    player = i.get_text()
    players.append(player)

players = players[1:51]

print(categories)
print(players)

【问题讨论】:

    标签: list web-scraping beautifulsoup slice findall


    【解决方案1】:

    在我看来,API 始终是最好的方法。

    不过,这也可以使用 pandas .read_html() 来完成(它在后台使用 beautifulsoup 来解析表格)。

    import pandas as pd
    
    url = 'https://www.espn.com/nba/stats/player/_/table/offensive/sort/avgPoints/dir/desc'
    
    dfs = pd.read_html(url)
    dfs[0][['Name','Team']] = dfs[0]['Name'].str.extract('^(.*?)([A-Z]+)$', expand=True)
    df = dfs[0].join(dfs[1])
    

    输出:

    print (df[['RK','Name','Team','POS']])
    
        RK                     Name  Team POS
    0    1             James Harden   HOU  SG
    1    2            Stephen Curry    GS  PG
    2    3             Bradley Beal   WSH  SG
    3    4               Trae Young   ATL  PG
    4    5             Kevin Durant   BKN  SF
    5    6              CJ McCollum   POR  SG
    6    7             Kyrie Irving   BKN  PG
    7    8             Jaylen Brown   BOS  SG
    8    9    Giannis Antetokounmpo   MIL  PF
    9   10             Jayson Tatum   BOS  PF
    10  11           Damian Lillard   POR  PG
    11  12              Luka Doncic   DAL  PG
    12  13            Collin Sexton   CLE  PG
    13  14              Paul George   LAC  SG
    14  15           Brandon Ingram    NO  SF
    15  16             Nikola Jokic   DEN   C
    16  17             LeBron James   LAL  SF
    17  18              Zach LaVine   CHI  SG
    18  19           Christian Wood   HOU  PF
    19  20            Kawhi Leonard   LAC  SF
    20  21              Joel Embiid   PHI   C
    21  22             Jerami Grant   DET  PF
    22  23            Anthony Davis   LAL  PF
    23  24             Jamal Murray   DEN  PG
    24  25            Julius Randle    NY  PF
    25  26          Malcolm Brogdon   IND  PG
    26  27            Fred VanVleet   TOR  SG
    27  28           Nikola Vucevic   ORL   C
    28  28         Donovan Mitchell  UTAH  SG
    29  30             Terry Rozier   CHA  PG
    30  31             Devin Booker   PHX  SG
    31  32          Khris Middleton   MIL  SF
    32  33            Terrence Ross   ORL  SG
    33  33           Victor Oladipo   IND  SG
    34  35        Russell Westbrook   WSH  PG
    35  36         Domantas Sabonis   IND  PF
    36  36             De'Aaron Fox   SAC  PG
    37  38          Zion Williamson    NO  SF
    38  39            Tobias Harris   PHI  SF
    39  40              Bam Adebayo   MIA   C
    40  41            DeMar DeRozan    SA  SG
    41  41         D'Angelo Russell   MIN  SG
    42  43           Gordon Hayward   CHA  SF
    43  44               Kyle Lowry   TOR  PG
    44  44  Shai Gilgeous-Alexander   OKC  SG
    45  46              Mike Conley  UTAH  PG
    46  47            Malik Beasley   MIN  SG
    47  48               RJ Barrett    NY  SG
    48  49            Thomas Bryant   WSH   C
    49  50            Pascal Siakam   TOR  PF
    

    【讨论】:

    • 这看起来也很干净,目前使用正则表达式对我来说太多了,因为我只查找了 1 个教程。使用 pandas .read_html() 来解析表格,我想我明白了。只是中间行的语法对我来说有点难以想象。
    • 是的,所以表格中的文本将球员姓名与球队一起,即。凯尔·洛瑞因此,所做的就是将字符串尾部的所有大写字母分开,然后将其分开。所以 [Kyle LowryTOR] 变成了 [Kyle Lowry, TOR]。然后我们把它分成两列。是的,我还在努力学习正则表达式。
    • 并且pandas会将所有表返回到一个列表中。第一个表是玩家等级和名称。第二张表是统计数据。所以这就是我加入的。我只打印了你想要的列。如果你执行“打印(df)”,你会看到它也有统计数据
    【解决方案2】:

    这是你想要的吗?

    import requests
    from bs4 import BeautifulSoup
    from tabulate import tabulate
    
    url = "https://www.espn.com/nba/stats/player/_/table/offensive/sort/avgPoints/dir/desc"
    soup = BeautifulSoup(requests.get(url).text, "html.parser")
    
    table_data = [
        [r // 2, i.find("a").getText(), i.find("span").getText()] for r, i in
        enumerate(soup.find_all("td", class_="Table__TD"), start=1)
        if i.find("a") and i.find("span")
    ]
    
    print(tabulate(table_data, headers=["Rank", "Name", "Team"], tablefmt="pretty"))
    
    

    输出:

    | Rank |          Name           | Team |
    +------+-------------------------+------+
    |  1   |      James Harden       | HOU  |
    |  2   |      Stephen Curry      |  GS  |
    |  3   |      Bradley Beal       | WSH  |
    |  4   |       Trae Young        | ATL  |
    |  5   |      Kevin Durant       | BKN  |
    |  6   |       CJ McCollum       | POR  |
    |  7   |      Kyrie Irving       | BKN  |
    |  8   |      Jaylen Brown       | BOS  |
    |  9   |  Giannis Antetokounmpo  | MIL  |
    |  10  |      Jayson Tatum       | BOS  |
    |  11  |     Damian Lillard      | POR  |
    |  12  |       Luka Doncic       | DAL  |
    |  13  |      Collin Sexton      | CLE  |
    |  14  |       Paul George       | LAC  |
    |  15  |     Brandon Ingram      |  NO  |
    |  16  |      Nikola Jokic       | DEN  |
    |  17  |      LeBron James       | LAL  |
    |  18  |       Zach LaVine       | CHI  |
    |  19  |     Christian Wood      | HOU  |
    |  20  |      Kawhi Leonard      | LAC  |
    |  21  |       Joel Embiid       | PHI  |
    |  22  |      Jerami Grant       | DET  |
    |  23  |      Anthony Davis      | LAL  |
    |  24  |      Jamal Murray       | DEN  |
    |  25  |      Julius Randle      |  NY  |
    |  26  |     Malcolm Brogdon     | IND  |
    |  27  |      Fred VanVleet      | TOR  |
    |  28  |     Nikola Vucevic      | ORL  |
    |  29  |    Donovan Mitchell     | UTAH |
    |  30  |      Terry Rozier       | CHA  |
    |  31  |      Devin Booker       | PHX  |
    |  32  |     Khris Middleton     | MIL  |
    |  33  |      Terrence Ross      | ORL  |
    |  34  |     Victor Oladipo      | IND  |
    |  35  |    Russell Westbrook    | WSH  |
    |  36  |    Domantas Sabonis     | IND  |
    |  37  |      De'Aaron Fox       | SAC  |
    |  38  |     Zion Williamson     |  NO  |
    |  39  |      Tobias Harris      | PHI  |
    |  40  |       Bam Adebayo       | MIA  |
    |  41  |      DeMar DeRozan      |  SA  |
    |  42  |    D'Angelo Russell     | MIN  |
    |  43  |     Gordon Hayward      | CHA  |
    |  44  |       Kyle Lowry        | TOR  |
    |  45  | Shai Gilgeous-Alexander | OKC  |
    |  46  |       Mike Conley       | UTAH |
    |  47  |      Malik Beasley      | MIN  |
    |  48  |       RJ Barrett        |  NY  |
    |  49  |      Thomas Bryant      | WSH  |
    |  50  |      Pascal Siakam      | TOR  |
    +------+-------------------------+------+
    

    【讨论】:

    • 这看起来很干净,除了我不熟悉制表模块。我不太了解 table_data 变量。不知道 r//2 做什么?楼层除法是我在实际用例中何时使用时最困惑的一种运算符。但感谢您的回复,我可能不得不查找一些关于制表的教程
    • 您不必使用tabulate 认为baduker 只是用它来以漂亮的方式显示数据。您也可以将 table_data 推送到数据框 -> pd.DataFrame(table_data, columns=["Rank", "Name", "Team"]) - 我喜欢列表理解,因为它看起来比普通的 for 循环视图更紧凑,但对于新的似乎更难阅读。
    【解决方案3】:

    总是问你 - 有没有更简单的方法?

    是的,你应该去 :)

    如果你想抓取,首先看看你是否真的需要从网站上抓取内容,或者是否有 api 提供结构良好的信息。

    请求 api 示例

    import requests
    import pandas as pd
    
    url = "https://site.web.api.espn.com/apis/common/v3/sports/basketball/nba/statistics/byathlete?region=us&lang=en&contentorigin=espn&isqualified=true&page=1&limit=50&sort=offensive.avgPoints%3Adesc&season=2021&seasontype=2"
    headers = {"user-agent": "Mozilla/5.0"}
    
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    
    ranking=[]
    
    for i,player in enumerate(response.json()['athletes'], start=1):
        rank = i
        name = player['athlete']['displayName']
        team = player['athlete']['teamShortName']
        category = player['athlete']['position']['abbreviation']
        ranking.append({'rank':rank, 'name':name, 'team':team, 'category':category})
    
    df = pd.DataFrame(ranking)
    df
    

    输出数据帧

    rank                     name  team category
        1             James Harden   HOU       SG
        2            Stephen Curry    GS       PG
        3             Bradley Beal   WSH       SG
        4               Trae Young   ATL       PG
        5             Kevin Durant   BKN       SF
        6              CJ McCollum   POR       SG
        7             Kyrie Irving   BKN       PG
        8             Jaylen Brown   BOS       SG
        9    Giannis Antetokounmpo   MIL       PF
       10             Jayson Tatum   BOS       PF
    

    但是要回答你的问题

    您也可以使用BeautifulSoup 进行操作,但我认为它更容易出错:

    from bs4 import BeautifulSoup
    import requests
    import pandas as pd
    
    data = []
    
    url ='https://www.espn.com/nba/stats/player/_/table/offensive/sort/avgPoints/dir/desc'
    source = requests.get(url)
    soup = BeautifulSoup(source.text, 'lxml')
    
    for i in soup.select('tr')[1:]:
        if i.select_one('td'):
            rank = i.select_one('td').get_text()
        if i.select_one('div > a'):
            player = i.select_one('div > a').get_text()
        if i.select_one('div > span'):
            team =i.select_one('div > span').get_text()
                      
        data.append({'rank':rank, 'player':player, 'team':team})
    
    pd.DataFrame(data)
    

    如果你不想使用 css 选择器,你也可以这样做

    for i in soup.find_all('tr')[1:]:
        if i.find('td'):
            rank = i.find('td').get_text()
        if i.find('a'):
            player = i.find('a').get_text()
        if i.find('span'):
            team =i.find('span').get_text()
    

    【讨论】:

    • ok 上面这段代码使用 api 对我来说看起来更熟悉,但我最难的是如何使用该 api。我查阅了很多关于 api 的教程,但仍然无法自然理解。 bs 的例子对我来说看起来更难理解。是 > 运算符的意思是 div 大于 a 吗?这种格式看起来很干净也很感谢您的回复。
    • 没问题,我将第二个示例更新为运行版本 - 我使用 css selectorsdiv > a 表示选择 <a> 直接在 <div> 下方,但您可以使用find()find_all() 也是如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-27
    • 2021-02-21
    • 1970-01-01
    • 2014-02-24
    • 2017-08-01
    相关资源
    最近更新 更多