使用 beautifulsoup 进行网页抓取的问题

TLado

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

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"))

它没有给我任何东西,而不是给我一个字符串。

泰迪一世爵士

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

使用 just 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 字符串中,封装在&-characters之间的每个元素实际上是一个单独的参数,随请求一起传递。因此,不是只有一个长而难看的字符串,您可以requests.get使用 -?符号之前的 URL 部分进行调用,并将params所有其他参数设置为字典。
    正如您从我的回答中看到的那样,这使得实际理解请求变得更加容易。

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

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

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

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章