【问题标题】:Problem with web-scraping with beautifulsoup使用 beautifulsoup 进行网页抓取的问题
【发布时间】:2021-04-10 11:18:47
【问题描述】:

我正在尝试对 who 网站 (https://www.who.int/emergencies/diseases/novel-coronavirus-2019) 进行网络抓取,以了解当前 covid 的死亡人数。我遇到了一个问题,它在哪里找到了元素,但它给了我不同的内容。我的代码:

import requests 
from bs4 import BeautifulSoup

WORLDWIDE_URL = "https://www.who.int/emergencies/diseases/novel-coronavirus-2019"

page = requests.get(WORLDWIDE_URL)

soup = BeautifulSoup(page.content, "lxml")

print(soup.find(id="confirmedDeaths"))

它没有给我一个字符串,它什么也没给我。

【问题讨论】:

    标签: python html web-scraping beautifulsoup python-requests


    【解决方案1】:

    您没有得到网站上显示的结果的原因是这些结果实际上是由 JavaScript 函数填充的,而不是硬编码到网站中的。
    如果您只是检查源代码(Firefox 中的Ctrl+U),您会得到相同的结果。

    仅使用requests.get,您只需检索不存在信息的源代码。

    大多数时候你现在会被卡住,不得不求助于解决方案,比如在访问信息之前使用selenium渲染 Javascript,但我找到了一个不同的解决方案,你甚至不需要BeautifulSoup

    在查看页面的源代码时,我发现负责设置这些值的 JavaScript 在内部只是调用了一个又长又丑的 URL 来检索这些信息:

    https://services.arcgis.com/5T5nSi527N4F7luB/arcgis/rest/services/COVID_19_Historic_cases_by_country_pt_v7_view/FeatureServer/0/query?where=CumCase+%3E+0&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&resultType=none&distance=0.0&units=esriSRUnit_Meter&returnGeodetic=false&outFields=OBJECTID%2CISO_2_CODE%2CISO_3_CODE%2CADM0_NAME%2Cdate_epicrv%2CNewCase%2CCumCase%2CNewDeath%2CCumDeath&returnGeometry=true&featureEncoding=esriDefault&multipatchOption=xyFootprint&maxAllowableOffset=&geometryPrecision=&outSR=4326&datumTransformation=&applyVCSProjection=false&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnExtentOnly=false&returnQueryGeometry=false&returnDistinctValues=false&cacheHint=false&orderByFields=date_epicrv+desc&groupByFieldsForStatistics=date_epicrv&outStatistics=[{%27statisticType%27%3A+%27sum%27%2C+%27onStatisticField%27%3A+%27NewCase%27}%2C+{%27statisticType%27%3A+%27sum%27%2C+%27onStatisticField%27%3A+%27NewDeath%27}%2C+{%27statisticType%27%3A+%27sum%27%2C+%27onStatisticField%27%3A+%27CumCase%27}%2C+{%27statisticType%27%3A+%27sum%27%2C+%27onStatisticField%27%3A+%27CumDeath%27}%2C+{%27statisticType%27%3A+%27Count%27%2C+%27onStatisticField%27%3A+%27ADM0_NAME%27}]&having=&resultOffset=&resultRecordCount=&returnZ=false&returnM=false&returnExceededLimitFeatures=true&quantizationParameters=&sqlFormat=none&f=pjson&token=
    

    调用此 URL,您可以检索一个 JSON 字典,其中包含您需要的所有信息。

    我决定分解此 URL 以实际向您展示发生了什么。这是我的完整代码:

    import requests
    import json
    import urllib.parse
    
    payload = {
        'where': 'CumCase+>+0',
        'objectIds': '',
        'time': '',
        'geometry': '',
        'geometryType': 'esriGeometryEnvelope',
        'inSR': '',
        'spatialRel': 'esriSpatialRelIntersects',
        'resultType': None,
        'distance': '0.0',
        'units': 'esriSRUnit_Meter',
        'returnGeodetic': False,
        'outFields': ','.join([
            'OBJECTID',
            'ISO_2_CODE',
            'ISO_3_CODE',
            'ADM0_NAME',
            'date_epicrv',
            'NewCase',
            'CumCase',
            'NewDeath',
            'CumDeath'
        ]),
        'returnGeometry': True,
        'featureEncoding': 'esriDefault',
        'multipatchOption': 'xyFootprint',
        'maxAllowableOffset': '',
        'geometryPrecision': '',
        'outSR': 4326,
        'datumTransformation': '',
        'applyVCSProjection': False,
        'returnIdsOnly': False,
        'returnUniqueIdsOnly': False,
        'returnCountOnly': False,
        'returnExtentOnly': False,
        'returnQueryGeometry': False,
        'returnDistinctValues': False,
        'cacheHint': False,
        'orderByFields': 'date_epicrv+desc',
        'groupByFieldsForStatistics': 'date_epicrv',
        'outStatistics': [
            {"statisticType": "sum", "onStatisticField": "NewCase"},
            {"statisticType": "sum", "onStatisticField": "NewDeath"},
            {"statisticType": "sum", "onStatisticField": "CumCase"},
            {"statisticType": "sum", "onStatisticField": "CumDeath"},
            {"statisticType": "Count", "onStatisticField": "ADM0_NAME"}
        ],
        'having': '',
        'resultOffset': '',
        'resultRecordCount': '',
        'returnZ': False,
        'returnM': False,
        'returnExceededLimitFeatures': True,
        'quantizationParameters': '',
        'sqlFormat': None,
        'f': 'pjson',
        'token': ''
    
    }
    
    payload_str = urllib.parse.urlencode(payload, safe='+[]{}')
    
    # Replace True, False, None
    payload_str = payload_str.replace('False', 'false')
    payload_str = payload_str.replace('True', 'true')
    payload_str = payload_str.replace('None', 'none')
    
    r = requests.get(
        'https://services.arcgis.com/5T5nSi527N4F7luB/arcgis/rest/services/COVID_19_Historic_cases_by_country_pt_v7_view/FeatureServer/0/query',
        params=payload_str
    )
    json_dict = json.loads(r.text)
    
    total_deaths = json_dict['features'][0]['attributes']['SUM_CumDeath']
    

    说明

    1. 除了一个简单的 URL,requests.get 还接受(除其他外)另一个名为 params 的参数。
      在 URL 字符串中,封装在 & 字符之间的每个元素实际上是与请求一起传递的单独参数。因此,您可以使用?-符号之前的URL部分调用requests.get,而不是只有一个长而丑陋的字符串,并将params设置为包含所有其他参数的字典。 br> 正如您从我的回答中看到的那样,这使得实际理解请求变得更加容易。

      如果您查看我的代码,您会发现我实际上并没有这样做。 为什么?
      当通过params 指定参数时,requests 不会将它们传递给原始请求,而是将它们编码,即+ 变为%2B 等等。
      在这种情况下,问题是如果请求编码+-signs,服务器将返回404 not found,所以我需要另一种方法来编码payload,而不会丢失+-signs。 解决方案是使用urllib.parse,它接受要从编码中排除的字符串,在这种情况下,我使用了以下字符串:'+[]{}'
      所以我的解决方案是对有效载荷进行预编码,然后将字符串传递给requests 而不是字典。

    2. 由于服务器非常挑剔,我还不得不将 pythons TrueFalseNone 替换为小写版本,否则无法识别参数。

    3. 当你发出请求时,你会得到一个JSON-dict而不是网站的html-source,所以你不需要BeautifulSoup,你只需要解析json就可以了使用简单的 Python dict

      这本词典可能包含对您有用的其他信息。如果您想仔细查看它,只需在浏览器中打开上面的长网址即可。大多数浏览器会自动为您“美化” JSON。

    【讨论】:

    • 感谢您非常详细的解释!你肯定帮了我很多。
    猜你喜欢
    • 1970-01-01
    • 2018-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-04
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多