Вы не можете ожидать, что будете использовать BeautifulSoup для парсинга абсолютно всего — не все веб-страницы одинаковы. В случае со страницей, которую вы пытаетесь очистить, данные в таблице генерируются асинхронно с помощью JavaScript. Вот грубая последовательность событий, которые происходят, когда вы посещаете свой URL-адрес в браузере:
- Ваш браузер отправляет начальный HTTP-запрос GET к URL-адресу веб-страницы.
- Сервер отвечает и предоставляет вам этот HTML-документ
- Ваш браузер анализирует документ и делает гораздо больше асинхронных запросов к серверу (и, возможно, к другим серверам) к ресурсам, которые ему необходимы для полного отображения страницы в том виде, в котором она предназначена для человеческого глаза (шрифты, изображения и т. д.). На этом этапе браузер также отправляет запросы к API, который обслуживает JSON, чтобы он мог заполнить таблицу данными.
Ваш код в основном делает только первый шаг. Он делает запрос к пустому HTML-документу, который еще не заполнен. Вот почему BeautifulSoup не может видеть данные, которые вы ищете. В общем, вы действительно можете использовать BeautifulSoup для извлечения данных с веб-страниц только в том случае, если на данной веб-странице все данные запечены в HTML-документе. Раньше это было более распространено несколько лет назад, но я бы сказал, что в настоящее время большинство (современных) страниц заполняют DOM асинхронно с использованием JavaScript.
Обычно люди рекомендуют вам использовать Selenium или какой-либо другой безголовый браузер для полной имитации сеанса просмотра, но в вашем случае это излишне. Чтобы получить нужные данные, все, что вам нужно сделать, это отправить запросы к тому же API (тому, о котором я упоминал ранее на третьем шаге), к которому обращается ваш браузер. Для этого вам даже не нужен BeautifulSoup.
Если вы зарегистрируете свой сетевой трафик, вы увидите, что ваш браузер делает несколько запросов к API, обслуживающему JSON. Вот пара ссылок, которые появляются. Нажмите на них, чтобы просмотреть структуру ответа JSON:
https://api.weather.com/v1/location/KHOU:9:US/almanac/daily.json?apiKey=6532d6454b8aa370768e63d6ba5a832e&units=e&start=0101
https: //api.weather.com/v1/location/KHOU:9:US/observations/historical.json?apiKey=6532d6454b8aa370768e63d6ba5a832e&units=e&startDate=20190101&endDate=20190101
Есть еще, но вы поняли идею. Запрос довольно прост, вы передаете ключ API и дату (или иногда дату начала и окончания) в качестве параметров строки запроса. Не знаю, что означает units=e
.
Кроме того, этот API, похоже, не заботится о заголовках запросов, что приятно. Это не всегда так — некоторые API-интерфейсы отчаянно заботятся о всевозможных заголовках, таких как user-agent
и т. д. Подделать их тоже было бы несложно, но я ценю простые API-интерфейсы.
Вот какой код я придумал:
def get_api_key():
import requests
import re
url = "https://www.wunderground.com/history/daily/KHOU/date/2019-01-01"
response = requests.get(url)
response.raise_for_status()
pattern = "SUN_API_KEY&q;:&q;(?P<api_key>[^&]+)"
return re.search(pattern, response.text).group("api_key")
def get_average_precip(api_key, date):
import requests
url = "https://api.weather.com/v1/location/KHOU:9:US/almanac/daily.json"
params = {
"apiKey": api_key,
"units": "e",
"start": date.strftime("%m%d")
}
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()["almanac_summaries"][0]["avg_precip"]
def get_total_precip(api_key, start_date, end_date):
import requests
url = "https://api.weather.com/v1/location/KHOU:9:US/observations/historical.json"
params = {
"apiKey": api_key,
"units": "e",
"startDate": start_date.strftime("%Y%m%d"),
"endDate": end_date.strftime("%Y%m%d")
}
response = requests.get(url, params=params)
response.raise_for_status()
return next(obv["precip_total"] for obv in response.json()["observations"] if obv["precip_total"] is not None)
def get_hourly_precip(api_key, start_date, end_date):
import requests
url = "https://api.weather.com/v1/location/KHOU:9:US/observations/historical.json"
params = {
"apiKey": api_key,
"units": "e",
"startDate": start_date.strftime("%Y%m%d"),
"endDate": end_date.strftime("%Y%m%d")
}
response = requests.get(url, params=params)
response.raise_for_status()
for observation in response.json()["observations"]:
yield observation["precip_hrly"]
def main():
import datetime
api_key = get_api_key()
# January 3rd, 2019
date = datetime.date(2019, 1, 3)
avg_precip = get_average_precip(api_key, date)
start_date = date
end_date = date
total_precip = get_total_precip(api_key, start_date, end_date)
print(f"The average precip. is {avg_precip}")
print(f"The total precip between {start_date.isoformat()} and {end_date.isoformat()} was {total_precip:.2f} inches")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Выход:
The average precip. is 0.12
The total precip between 2019-01-03 and 2019-01-03 was 1.46 inches
>>>
Я также определил функцию get_hourly_precip
, которую я на самом деле не использовал, я просто реализовал ее для удовольствия. Он извлекает данные об осадках из графика.
06.08.2020
get_api_key
, которая использует регулярное выражение для извлечения ключа API из HTML-документа. Любая другая функция теперь принимает параметрapi_key
. 06.08.2020