【问题标题】:Python - BeautifulSoup - Selecting a 'div' with 'class'-attribute shows every div in the htmlPython - BeautifulSoup - 选择具有“类”属性的“div”显示 html 中的每个 div
【发布时间】:2022-01-07 01:36:58
【问题描述】:

我正在尝试使用 BeautifulSoup 抓取 coinmarketcap.com(我知道有一个 API,出于培训目的,我想使用 BeautifulSoup)。到目前为止爬取的每条信息都很容易选择,但现在我喜欢让“持有者统计信息”看起来像这样:

holder stats

用于选择包含所需信息的特定 div 的测试代码如下所示:

import requests
from bs4 import BeautifulSoup

url = 'https://coinmarketcap.com/currencies/bitcoin/holders/'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
holders = soup.select('div', class_='n0m7sa-0 kkBhMM')
print(holders)

print(holders) 的输出不是 div 的预期内容,而是网站的整个 html 内容。因为输出代码太长,所以我附上了一张图片。

Output Code

有谁知道,为什么会这样?

【问题讨论】:

    标签: python beautifulsoup web-crawler


    【解决方案1】:

    当您想用作 css 选择器时,您应该使用 .select()。在这种情况下,holders = soup.select('div', class_='n0m7sa-0 kkBhMM') 类部分基本上被忽略了......它会找到所有具有任何类的<div>。要指定该特定类,请使用.find_all(),或更改您的.select()

    holders = soup.select('div.n0m7sa-0.kkBhMM')

    或

    holders = soup.find_all('div', class_='n0m7sa-0 kkBhMM')

    现在在这两种情况下,它都会返回None 或一个空列表。那是因为该类属性不在源 html 中。该站点是动态的,因此这些类是在初始请求之后生成的。所以要么需要先用Selenium渲染页面,然后拉取html,要么看看有没有api直接获取数据源。

    有一个api可以获取数据:

    import requests
    import pandas as pd
    
    alpha = ['count', 'ratio']
    payload = {
    'id': '1',
    'range': '7d'}
    
    
    for each in alpha:
            url = f'https://api.coinmarketcap.com/data-api/v3/cryptocurrency/detail/holders/{each}'
            jsonData = requests.get(url, params=payload).json()['data']['points']
            
            if each == 'count':
                count_df = pd.DataFrame.from_dict(jsonData,orient='index')
                count_df = count_df.rename(columns={0:'Total Addresses'})
                
            else:
                ratio_df = pd.DataFrame.from_dict(jsonData,orient='index')
                df = count_df.merge(ratio_df, how='left', left_index=True, right_index=True)
                
    df = df.sort_index()          
            
    

    输出:

    print(df.to_string())
                          Total Addresses  topTenHolderRatio  topTwentyHolderRatio  topFiftyHolderRatio  topHundredHolderRatio
    2021-11-24T00:00:00Z         39279627               5.25                  7.19                10.51                  13.26
    2021-11-25T00:00:00Z         39255811               5.25                  7.19                10.49                  13.22
    2021-11-26T00:00:00Z         39339840               5.25                  7.19                10.51                  13.24
    2021-11-27T00:00:00Z         39391849               5.23                  7.11                10.45                  13.18
    2021-11-28T00:00:00Z         39505340               5.24                  7.11                10.45                  13.18
    2021-11-29T00:00:00Z         39502099               5.24                  7.11                10.43                  13.16
    2021-11-30T00:00:00Z         39523000               5.24                  7.11                10.38                  13.12
    

    您的其他选项是数据位于 json 格式的 <script> 标记内。 S0,您也可以通过这种方式从初始请求站点中将其拉出:

    from bs4 import BeautifulSoup
    import requests
    import json
    import re
    
    url = 'https://coinmarketcap.com/currencies/bitcoin/holders/'
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    jsonStr = str(soup.find('script', {'id':'__NEXT_DATA__'}))
    jsonStr = re.search(r"({.*})", jsonStr).groups()[0]
    jsonData = json.loads(jsonStr)['props']['initialProps']['pageProps']['info']['holders']
    
    df = pd.DataFrame(jsonData).drop('holderList', axis=1).drop_duplicates()
    

    输出:

    print(df.to_string())
       holderCount  dailyActive  topTenHolderRatio  topTwentyHolderRatio  topFiftyHolderRatio  topHundredHolderRatio
    0     39523000       963625               5.24                  7.11                10.38                  13.12
    

    对于 Project Info 中的社会统计,这是在特定的 api 中:

    import requests
    import pandas as pd
    
    url = 'https://api.coinmarketcap.com/data-api/v3/project-info/detail?slug=bitcoin'
    jsonData = requests.get(url).json()
    socialStats = jsonData['data']['socialStats']
    
    row = {}
    for k, v in socialStats.items():
        if type(v) == dict:
            row.update(v)
        else:
            row.update({k:v})
            
    df = pd.DataFrame([row])
    

    输出:

    print(df.to_string())
       cryptoId commits contributors  stars  forks watchers              lastCommitAt  members               updatedTime
    0         1   31588          836  59687  30692     3881  2021-11-30T00:09:02.000Z  3617460  2021-11-30T16:00:02.365Z
    

    【讨论】:

    • 你能详细解释一下为什么 find_all 的返回会是 None 吗?我最初的猜测也是动态渲染。但是当我打开特定链接“coinmarketcap.com/currencies/bitcoin/holders”时,内容已经在 dom-tree 中。请不要评判我,这整件事对我来说都是新的:)
    • 完全没有判断力!您正在查看的 dom-tree 在发出初始请求后呈现(使用 javascript)。 request 包不执行 java 脚本,所以你只得到静态的 html 响应。我添加到上面的解决方案以及有一个 api 来获取数据
    • @MxclFhn 我还在初始化请求的<script> 标记中找到了数据。所以你也可以走那条路
    • 同意@chitown88关于从<script>标签中提取数据。正如另一个答案中提到的那样,selenium 没有真正的需要,因为它会稍微减慢提取过程(或很多)。
    • 谢谢大家,我很欣赏建议的解决方案,我会试试看:)
    【解决方案2】:

    由于 chitown88 已经提到内容是动态提供的,因此替代解决方案可能是硒。

    如何选择?

    如果你得到html 和selenium,你应该避免按类选择元素,因为它们也是动态生成的。

    选择包含持有人统计数据的<h3> 及其所有<span> 没有子<span>:

    soup.select('h3:-soup-contains("Holders Statistics") ~ div :not(span)')
    

    示例基于Selenium 4

    from bs4 import BeautifulSoup
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service as ChromeService
    options = webdriver.ChromeOptions()
    service = ChromeService(executable_path='YOUR PATH TO CHROM DRIVER')
    driver = webdriver.Chrome(service=service, options=options)
    driver.get('https://coinmarketcap.com/currencies/bitcoin/holders/')
    
    soup = BeautifulSoup(driver.page_source,'html.parser')
    
    data = {x.get_text('|').split('|')[0]:x.get_text('|').split('|')[1] for x in soup.select('h3:-soup-contains("Holders Statistics") ~ div :not(span)')}
    print(data)
    
    driver.close()
    

    输出

    {'Total Addresses': '39,523,000',
     'Active Addresses': '24h',
     'Top 10 Holders': '5.24%',
     'Top 20 Holders': '7.11%',
     'Top 50 Holders': '10.38%',
     'Top 100 Holders': '13.12%'}
    

    【讨论】:

      猜你喜欢
      • 2021-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-03
      • 1970-01-01
      • 2016-03-26
      相关资源
      最近更新 更多