Python В BeautifulSoup отсутствует тег, который, как мне кажется, явно присутствует - PullRequest
0 голосов
/ 06 августа 2020

Я искал несколько разных вопросов и не нашел для них точного соответствия. Мои критерии поиска довольно просты, поэтому я думаю, что проблема в том, что я чего-то не понимаю в HTML или в том, как работает BeautifulSoup.

Я ищу фактическое количество осадков Сводная таблица с этого сайта. В ссылке ниже его значение 0,01. В конечном итоге я буду перебирать этот сайт, вставляя другой день в URL-адрес для каждого дня года, давая мне ежедневное количество осадков в Хьюстоне для каждого дня 2019 года.

https://www.wunderground.com/history/daily/KHOU/date/2019-01-01

это изображение веб-сайта и html, чтобы было более понятно, что я хочу найти

код ниже:

result = requests.get('https://www.wunderground.com/history/daily/KHOU/date/2019-01-01')
content = result.content
soup= BeautifulSoup(content, 'html.parser')  #BS4 Documentation states this is 


test1 = soup.find_all('div', {'class':'summary-table'})
len(test1)  #1
print(test1[0].prettify())
#<div _ngcontent-sc235="" class="summary-table">
# No data recorded
# <!-- -->
# <!-- -->
#</div>

#I also tried just finding the tbody tags directly
test2 = soup.find_all('tbody')
len(test2)  #1
print(test2[0].prettify())
#<tbody _ngcontent-sc225="">
# <!-- -->
#</tbody>

Ожидаемый результат:

Test1 : я ожидал получить список тегов div с class = 'summary-table' (Я почти уверен, что есть только один из них). Я ожидал, что это также будет содержать все теги div и теги tbody / thead, которые были в нем.

Test2 : Я ожидал, что это будет список всех тегов tbody , чтобы я мог перебирать их, чтобы найти то, что мне нужно.

Поскольку я очень четко вижу теги, которые хочу захватить, я чувствую, что есть что-то очевидное, что мне здесь не хватает. Любая помощь будет очень признательна!

1 Ответ

0 голосов
/ 06 августа 2020

Вы не можете рассчитывать использовать BeautifulSoup для очистки абсолютно всего - не все веб-страницы одинаковы. В случае страницы, которую вы пытаетесь очистить, данные в таблице генерируются асинхронно через JavaScript. Вот примерная последовательность событий, которые происходят, когда вы посещаете свой URL-адрес в браузере:

  1. Ваш браузер делает начальный HTTP-запрос GET на URL-адрес веб-страницы.
  2. Сервер отвечает и обслуживает вас, что HTML документ
  3. Ваш браузер анализирует документ и делает гораздо больше асинхронных запросов к серверу (и, возможно, другим серверам) к ресурсам, которые ему необходимы для полной визуализации страницы, как это предназначено для быть увиденным человеческими глазами (шрифты, изображения и т. д. c.). На этом этапе браузер также делает запросы к API, который обслуживает JSON, чтобы он мог заполнить таблицу данными.

Ваш код в основном выполняет только первый шаг. Он делает запрос к документу HTML, который еще не был заполнен. Вот почему BeautifulSoup не может видеть данные, которые вы ищете. В общем, вы действительно можете использовать BeautifulSoup для очистки данных с веб-страниц, только если данная веб-страница содержит все данные, запеченные в документе HTML. Раньше это было чаще go, но я бы сказал, что в настоящее время большинство (современных) страниц заполняют DOM асинхронно, используя JavaScript.

Обычно именно здесь люди рекомендуют использовать Selenium или что-то еще. другой вид безголового браузера для полной имитации сеанса просмотра, но в вашем случае это излишне. Чтобы получить нужные данные, все, что вам нужно сделать, это сделать запросы к тому же API (тот, который я упоминал ранее на шаге 3), к которому ваш браузер обращается. Для этого вам даже не понадобится BeautifulSoup.

Если вы зарегистрируете свой сетевой трафик c, вы увидите, что ваш браузер делает несколько запросов к API, обслуживающему JSON. Вот пара ссылок, которые появляются. Go вперед и щелкните по ним, чтобы просмотреть структуру ответа 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: США / наблюдения / история. json? ApiKey = 6532d6454b8aa370768e63d6ba5a832e & units = e & startDate = 20190101 & endDate = 20190101

* еще 1026 * Есть Но Вы получаете идею. Запрос довольно прост: вы передаете ключ API и дату (или иногда дату начала и окончания) в качестве параметров строки запроса. Не уверен, что означает units=e.

Кроме того, этот API, похоже, не заботится о заголовках запросов, что приятно. Это не всегда так - некоторые API-интерфейсы отчаянно заботятся о всевозможных заголовках, таких как user-agent, et c. Подделать тоже не составит большого труда, но я ценю простые 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, которую на самом деле не использовал, я просто реализовал ее для удовольствия. Он извлекает данные об осадках с графика.

...