我正在尝试通过网络抓取 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"))
它没有给我一个字符串,它没有给我任何东西。
答案 0 :(得分:2)
您没有得到网站上显示的结果的原因是这些结果实际上是由 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 字典。
我决定分解这个网址,以实际向您展示发生了什么。这是我的完整代码:
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']
除了单纯的 URL 之外,requests.get
还接受(除其他外)另一个名为 params
的参数。
在 URL 字符串中,封装在 &
字符之间的每个元素实际上是一个与请求一起传递的单独参数。因此,您可以使用 requests.get
符号之前的 URL 部分调用 ?
,并将 params
设置为包含所有其他需要的参数的字典,而不是只有一个长而难看的字符串也包括在内。
从我的回答中可以看出,这使得实际理解请求变得更加容易。
如果您查看我的代码,您会意识到我实际上并没有这样做。 为什么?
当通过 params
指定参数时,requests
不会将它们传递给原始请求,而是对它们进行编码,即 +
变为 %2B
和等等。
在这种情况下,问题是如果请求编码 404 not found
-signs,服务器将返回 +
,所以我需要另一种方法来编码 payload,而不会丢失 {{1 }}-标志。
解决方案是使用 +
,它接受要从编码中排除的字符串,在这种情况下,我使用了以下字符串:urllib.parse
.
所以我的解决方案是对有效负载进行预编码,然后将字符串传递给 '+[]{}'
而不是字典。
由于服务器太挑剔,我还不得不用小写版本替换 python requests
、True
和 False
,否则参数将无法识别。
当你发出请求时,你会得到一个 JSON-dict 而不是网站的 html-source,所以你不需要 None
,你只需解析json 并留下一个简单的 Python BeautifulSoup
。
这本词典可能包含对您有用的其他信息。如果您想仔细查看它,只需在浏览器中打开上面的长 URL。大多数浏览器会自动为您“美化” JSON。