Прогноз полярных сияний на Python и облачном сервере

Как спрогнозировать полярное сияние с помощью Python и облачного сервера

Александр Шилов Александр Шилов Технический писатель 26 сентября 2024

Разбираемся, как устроены полярные сияния и как код на Python упростит их прогнозирование.

Изображение записи

Доводилось ли вам наблюдать полярное сияние? В окрестностях Санкт-Петербурга это не такое уж и редкое явление, но многие мои друзья и знакомые его ни разу не видели. Как оказалось, они не знают, когда, куда и при каких обстоятельствах отправиться, чтобы его застать. Это побудило меня впервые написать код на Python, чтобы решить проблему. В этой статье расскажу, как я автоматизировал оценку состояния магнитосферы Земли и запустил Telegram-бота на облачном сервере, чтобы получать информацию о предстоящих полярных сияниях.

Чтобы понять, как прогнозировать этот замечательный феномен, нужно немного разобраться в его природе. Впереди несколько абзацев теории и картинок. Тем, кто интересуется вопросом, рекомендую к прочтению. Если вам интереснее результат, переходите по оглавлению к нужному разделу. Я предупредил.

Что такое полярное сияние

Северное полярное сияние (Aurora Borealis, или просто аврора) — это финал длительного, масштабного и достаточно сложного, но все же предсказуемого процесса. Он начинается с магнитных аномалий на Солнце. А заканчивается тем, что свободные электроны из магнитосферы Земли на колоссальной скорости сталкиваются с атомами и молекулами азота и кислорода в верхних слоях атмосферы. Процесс сопровождается испусканием фотонов, что мы и видим как сияние в небе.

Магнитосфера и магнитное поле Земли — одно и то же. Далее я использую оба термина.

Полярное сияние, снятое на смартфон в окрестностях Первого северного форта в Кронштадте.
Полярное сияние, снятое на смартфон в окрестностях Первого северного форта в Кронштадте.

Чаще всего заметны зеленый (иногда с оттенками желтого) и фиолетовый цвета. Реже можно увидеть красный, совсем редко — синий. Зеленый и красный цвет дают молекулы кислорода, фиолетовый и синий — азота. Кстати, возникает и ультрафиолетовое свечение, но невооруженным глазом его не видно.

Зависимость цвета сияния от химических элементов атмосферы, которые взаимодействуют с электронами.
Зависимость цвета сияния от химических элементов атмосферы, которые взаимодействуют с электронами. Источник.

Как зарождается полярное сияние

Корональные выбросы массы

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

Этот процесс мог бы быть самым скучным во Вселенной, если бы не магнитное поле звезды. Иногда оно локально усиливается с обычных 50 до нескольких тысяч гаусс и подавляет конвективные движения. В результате местами количество разогретого вещества из недр звезды на ее поверхности и в фотосфере уменьшается. Участки, где магнитное поле победило конвекцию, недополучают тепла, охлаждаются и визуально кажутся более темными — их называют солнечными пятнами. Кстати, на самом деле они не темные, да и совсем не холодные — их температура составляет около 4 000 градусов по Цельсию.

В больших областях пятен рядом расположены несколько очень сильных магнитных потоков в форме гигантских петель. В некоторые из них без труда поместилась бы наша Земля. Солнечный магнетизм заставляет циркулировать вдоль этих петель колоссальные объемы относительно холодной и более плотной солнечной плазмы. Такие образования называются протуберанцами. Часто, хоть и не всегда, магнитные потоки спутываются и создают напряжение.

Кульминация этого процесса — коллапс протуберанца, который называется корональным выбросом массы, или солнечной вспышкой. При нем плазма Солнца выбрасывается в космос на скорости до 3 000 км/с. На секундочку, это 1% от скорости света. Сгусток вещества по мере движения от звезды увеличивается в объеме и замедляется. В исключительных случаях он может занимать до четверти пространства между Солнцем и Землей.

Корональный выброс массы на снимке спутника SOHO.
Корональный выброс массы на снимке спутника SOHO Европейского космического агентства. Земля добавлена для сравнения. Источник.

Корональные дыры

Есть и второй сценарий — корональные дыры. Это более холодные и менее плотные участки солнечной короны, через которые потоку солнечных частиц легче убежать от звезды. Такие дыры являются источниками быстрого солнечного ветра. В целом, они производят тот же эффект, что и корональный выброс массы, но куда слабее. Кроме того, корональные дыры чаще образуются у полюсов Солнца, хотя могут заходить и в экваториальные широты.

Корональные дыры на снимке NASA.
Корональные дыры на снимке NASA. Источник.

Воздействие на Землю

Когда сгусток солнечного вещества добирается до Земли, начинается самое интересное. Столкнувшись с магнитным полем планеты, выброс деформирует его и придает форму кометного хвоста, вытянутого в направлении от звезды.

В магнитосфере Земли есть свободные электроны, а они обладают массой. Следовательно, им передается огромное количество энергии от столкновения с солнечным веществом. А еще у электронов есть отрицательный заряд, поэтому, получив импульс, они разгоняются до скорости около 20 000 км/с вдоль линий магнитного поля по направлению к магнитным полюсам Земли.

Собственно, далее происходит то, что я кратко описал в самом начале. Эти электроны сталкиваются с атомами азота и кислорода в верхних слоях атмосферы планеты, передают им энергию и приводят в возбужденное состояние, заставляя излучать фотоны. Визуально этот процесс мы и наблюдаем как полярное сияние.

Основная часть электронов, которые сталкиваются в атмосфере с молекулами азота и кислорода, прилетает из хвоста магнитосферы, обращенного от Солнца, а не со стороны звезды, откуда прибыл поток частиц.

Магнитное поле Земли под воздействием солнечного ветра. Здесь показано, что основная часть электронов, вызывающих сияния, прибывает в атмосферу из хвоста магнитосферы, обращенного от солнца, в ночное время.
Магнитное поле Земли под воздействием солнечного ветра. Здесь показано, что основная часть электронов, вызывающих сияния, прибывает в атмосферу из хвоста магнитосферы, обращенного от солнца, в ночное время. Источник.

Какие данные нужны для прогноза

Теперь мы знаем, как устроены полярные сияния. Значит, по поведению Солнца и магнитосферы Земли можем довольно точно предсказать их появление. Посмотрим, какие параметры нам нужны и как их оценивать.

В своей практике наблюдений полярных сияний я опираюсь не на все показатели, которые описываю ниже и которые включу в прогноз. Тем не менее, для полного понимания составления прогнозов я расскажу и о них.

Индекс F10.7 cm

Мы выяснили, что путь полярного сияния начинается с магнитных аномалий на Солнце. А они выражаются в появлении солнечных пятен. Вот с них и начнем. Для отслеживания этого феномена есть два варианта:

  • солнечный телескоп (или обычный с солнечным фильтром) и ежедневные наблюдения за Солнцем;
  • индекс F10.7 cm (он же 10.7 cm Radio Flux).

Думаю, выбор очевиден. Если не вдаваться в детали, F10.7 cm — это поток радиоволн от Солнца на частоте 2 800 МГц. Параметр измеряется в единицах солнечного потока (solar flux units, sfu) и хорошо коррелирует с появлением солнечных пятен. К тому же ученые еще в 1940-х годах научились легко измерять его с поверхности Земли при любой погоде с погрешностью не более 1%.

Индекс крайне редко опускается ниже 64-67 sfu. Этот уровень характерен для так называемого солнечного минимума — периода в 11-летнем цикле активности Солнца, когда звезда наиболее спокойна. Магнитная активность светила, способная вызвать на Земле слабые и умеренные полярные сияния, усиливает радиопоток по меньшей мере до 210-230 sfu.

Среднемесячные значения F10.7 cm с 2000 по 2019 годы.
График показывает среднемесячные значения F10.7 cm с 2000 по 2019 годы (именно среднемесячные, а не среднегодовые; здесь учтены результаты измерений, сделанных в декабре каждого года). Источник.

Но само по себе повышение значения F10.7 cm не стоит рассматривать как однозначный признак скорого сияния. Вот незадача — пятна не приводят к мгновенному корональному выбросу массы. Магнитным потокам у поверхности звезды нужно некоторое время, чтобы накопить напряжение.

При этом Солнце, как и все во Вселенной, еще и вращается. Его экваториальные регионы делают полный оборот за 27 дней, приполярные — чуть медленнее. Разница в скорости вращения объясняется отсутствием у звезды твердой поверхности. Впрочем, аномалии в приполярных регионах Солнца нам не так интересны, поскольку орбита Земли пролегает в плоскости его эклиптики, а вспышка должна быть направлена точно на планету.

Получается, мы можем довольно точно отследить момент образования солнечных пятен. Но затем Солнце повернется и вспышка улетит в пустоту? Да, чаще всего именно так и бывает. Или активность угасает. Но если приборы на Земле фиксируют резкое усиление радиопотока от звезды, это в любом случае хороший знак.

F10.7 cm — надежный индикатор магнитных возмущений на Солнце. Если он превышает 200 sfu, можно говорить о присутствии на поверхности звезды, обращенной к Земле, стабильных магнитных аномалий.

Kp-индекс

Это глобальный планетарный индекс геомагнитной активности. Если не углубляться в детали, то чем сильнее Солнце воздействует на магнитосферу Земли, тем выше Kp.

Kp-индекс измеряется по 10-балльной шкале от 0 до 9 с шагом 0,3(3). В спокойные периоды его величина варьируется от 0 до 4. При таких показателях рассчитывать на сияние не стоит (при Kp 3,67 или 4,00 может возникнуть слабое сияние на северном горизонте, но глазом оно будет неразличимо).

Высокая активность магнитосферы Земли приводит к явлению, которое называют магнитными бурями. Национальное управление океанических и атмосферных исследований США (NOAA) оценивает их силу по шестибалльной шкале от 0 до 5 с индексом G. Для удобства показал в таблице, как соотносятся Kp, G, вероятность наблюдения авроры и последствия для электросетей и связи на Земле.

Как магнитные бури разной силы влияют на электронику.

Широты, приведенные в таблице, — это усредненные значения для западного и восточного полушарий Земли. Дело в том, что полярные сияния, если взглянуть на них из космоса, образуют овалы вокруг полюсов. Только не географических, а магнитных. Эти полюса не совпадают, и фактически при одинаковых условиях в западном полушарии аврору видно южнее, чем в восточном.

Удобство Kp-индекса в том, что его можно прогнозировать. На трехдневные показатели можно смотреть с уверенным оптимизмом, примерно прикидывая, когда отправиться за город для наблюдений. Это нам еще пригодится далее. Реальный Kp-индекс — это то, что уже зафиксировано на Земле. Эти данные обновляются каждые три часа.

Kp-индекс и G — не одно и то же

Повышение Kp-индекса до 5 и выше (магнитные бури G1 и сильнее) дает основания надеяться на наблюдение сияния. Но на широте Петербурга при таких условиях оно может оказаться слабым и едва различимым. При Kp = 9 (G5 — это уже геомагнитный шторм) вы почти наверняка лишитесь интернета, мобильной и спутниковой связи, электричества и всех электроприборов, но полярное сияние будет видно даже в субтропиках.

Хотя Kp-индекс и сила магнитной бури связаны, это не одно и то же. В норме активность Солнца провоцирует возмущения магнитосферы Земли, которые и выражаются Kp-индексом. А эти возмущения, в свою очередь, приводят к магнитным бурям (G). Но иногда что-то в этой логике ломается. Например, 17 сентября 2024 года около 20:00 по московскому времени в NOAA фиксировали повышение Kp-индекса до 5.67, но магнитной бури на планете так и не возникло.

По оценке специалистов NOAA, выбросы солнечной массы, которые способны спровоцировать геомагнитный шторм G5, происходят в среднем четыре раза за 11-летний солнечный цикл. Но обычно — к счастью — они направлены куда угодно, только не на Землю. Вспышки, которых достаточно для появления бурь уровня G1, происходят в среднем 1 700 раз за 11 лет.
Самая мощная магнитная буря в истории наблюдений произошла в 1859 году и получила название «событие Кэррингтона». По словам очевидцев, вызванное ею полярное сияние было настолько ярким, что под ночным небом можно было читать. Тогда самым страшным ее последствием стали неполадки в работе немногочисленного оборудования, например телеграфа. Если бы такая же буря произошла сегодня, человечество столкнулось бы с технологической катастрофой.

Компонент Bt

Дальше к термину «магнитное поле Земли» я добавляю термин «межпланетное магнитное поле». Первое — характеристика нашей планеты. Второе — по сути, часть магнитного поля Солнца, переносимая потоками звездного вещества.

Bt — это общая напряженность межпланетного магнитного поля. В норме рядом с Землей этот параметр составляет около 6 nT (нанотесла). Если он резко растет, скажем до 20 и более nT, значит, магнитосфера планеты испытывает мощное воздействие извне. Его может оказать только массивный поток частиц от Солнца.
Сам по себе Bt лишь указывает на напряженность межпланетного магнитного поля. Но показатель не дает нам никаких важных подробностей. Зато это делает его составляющая — Bz.

Компонент Bz

У межпланетного магнитного поля есть три векторных компонента: Bx, By и Bz. Первые два расположены параллельно плоскости эклиптики и нам не интересны — они никак не влияют на активность полярных сияний. А третий — Bz — перпендикулярен.

Три оси компонентов межпланетного магнитного поля.
Три оси компонентов межпланетного магнитного поля. Источник.

Значение компонента показывает, куда ориентировано межпланетное магнитное поле. Если оно положительное, поле направлено на север, если отрицательное — на юг. Мы знаем, что магнитное поле Земли ориентировано на север, поэтому значение Bz должно быть отрицательным. Когда два поля — земное и межпланетное — направлены к разным полюсам, они не отталкиваются и легко взаимодействуют.

На изменение Bz влияет солнечный ветер: его скорость, плотность, волны и другие характеристики. В нормальных условиях, когда в магнитосфере Земли ничего интересного не происходит, компонент достаточно стабилен и слегка варьируется около 0 nT. Когда до Земли добирается корональный выброс массы, он привносит в магнитное поле хаос, и значение компонента начинает меняться.
Хорошо, если Bz опускается хотя бы до -10 nT. В идеале — ниже -20. Проблема здесь только в том, что этот параметр крайне чувствителен к возмущениям и непредсказуем. Впрочем, достаточно мощный корональный выброс массы обычно делает межпланетное магнитное поле нестабильным, ориентируя его то на юг, то на север. Следовательно, сияние в течение ночи может то разгораться, то угасать.

В идеальных условиях очень низкий Bz должен совпадать по времени с магнитной бурей — это гарантия возникновения полярного сияния. Будучи направленным на север (Bz > 0), межпланетное магнитное поле не даст возникнуть авроре даже при максимально сильном геомагнитном шторме (G5). Зато при слабых бурях (G1) и ориентации межпланетного поля на юг появляется шанс увидеть хотя бы слабую аврору.

Скорость солнечного ветра

Напрямую скорость солнечного ветра не влияет на вероятность возникновения полярного сияния. Однако выброс солнечной массы, способный его вызвать, в любом случае будет двигаться быстро — не менее 450-500 км/с. Я учитываю этот показатель, чтобы рассчитать, когда добравшийся до спутника выброс окажется на Земле (о самом спутнике — чуть ниже).

Погода на Земле

Думаю, тут все просто. Вот что нам понадобится для наблюдений.

  • Темнота. Свечение хорошо видно на темном небе, но не видно на светлом. Имейте в виду, что полная Луна и городская засветка мешают наблюдениям.
  • Ясное небо. Сияния возникают на высотах от 80 до 500 км, что выше любых облаков. Высота объясняется тем, что именно верхние слои атмосферы ионизируются солнечным излучением. В результате этого там появляются необходимые для сияния свободные электроны.

Следить за облачностью не всегда нужно именно там, где вы находитесь. Например, я живу в Санкт-Петербурге. Это почти 60-й градус северной широты. А сияния при слабых магнитных бурях (G ≤ 1, Kp ≤ 5) происходят на широтах к северу от 60 градуса. То есть смотреть нужно в направлении северного горизонта, а не вверх. Даже если прямо над головой облака, но к северу ясно, шанс увидеть сияние сохраняется.

Полярное сияние во время магнитной бури, которое удалось заснять даже при засветке неба.
Полярное сияние во время магнитной бури, которое удалось заснять даже при засветке неба.

Для отслеживания погоды я пользуюсь сайтом Windy. Помимо прочего, он дает очень точный прогноз облачности и тумана, а также предлагает информативную визуализацию. Это крайне полезно при выборе места для наблюдений.

Где брать данные для прогноза

Большинство показателей собираются на Земле. Например, данные о радиопотоке от Солнца фиксирует обсерватория Национального исследовательского совета Канады. А Kp-индекс — это среднее значение К-индексов, зафиксированных в 13 геомагнитных обсерваториях, расположенных между 44 и 60 градусами северной и южной геомагнитных широт.

Тем не менее, что бы ни фиксировалось на Земле, нам нужны данные и из космоса. Допустим, на Солнце только что произошла вспышка, направленная точно на Землю. Через три-четыре дня (в зависимости от скорости потока) солнечные частицы достигнут первой орбитальной точки Лагранжа (L1) примерно в 1,5 млн км от Земли. Это такая точка между нашей планетой и Солнцем, в которой силы гравитации двух космических тел уравновешивают друг друга. Если туда поместить какой-нибудь объект, он будет оставаться почти неподвижным относительно Земли и двигаться вместе с ней вокруг звезды.
Собственно, лучшее, что туда можно поместить, — исследовательский спутник. Нам повезло: в точке Лагранжа находится аппарат DSCOVR от NASA и NOAA. Он передает на Землю важные данные о погоде в космосе.

Анимация движения Земли и спутника DSCOVR.
Анимация движения Земли и спутника DSCOVR. Источник.

Кстати, есть пять точек Лагранжа в системе Земля-Солнце.

Точки Лагранжа.
Точки Лагранжа. В точке L1 находится DSCOVR и другие спутники. В точке L2 — телескоп Джеймс Уэбб. Источник.

NOAA собирает у себя данные с DSCOVR и исследовательского оборудования на Земле. То, что интересно нам, собрано на сайте управления в разделе Aurora Dashboard.

Более того, у NOAA есть свое мобильное приложение Aurora Forecast с красивой визуализацией. Там можно посмотреть как готовый прогноз, так и отдельные параметры, хотя почему-то не все. Так зачем вообще это все, если есть удобное приложение? Считаю вопрос риторическим. Это маленький веселый pet-проект.

Сбор данных для краткосрочного прогноза

Как я сказал в самом начале, это мой первый опыт работы с Python. Используя обрывки знаний с Хабра, Stack Overflow, YouTube и других источников, я написал код, который делает ровно то, что мне нужно. Но настоящий Python-разработчик сказал, что в таком виде показывать код никому нельзя. Проблема тут в том, что объяснить происходящее далее я могу только на том, что написал сам. А облагороженную версию от коллеги оставлю ниже.


    import re
import json
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException

class AuroraParser:
    service = Service(ChromeDriverManager().install())
    URL = "https://www.swpc.noaa.gov/communities/aurora-dashboard-experimental"

    def __init__(self, wind_speed, const_distance):
        self.driver = webdriver.Chrome(service=AuroraParser.service)
        self.wind_speed = wind_speed
        self.const_distance = const_distance

    def get_source_code(self) -> None:
        self.driver.get(AuroraParser.URL)

        while True:
            try:
                e = self.driver.find_element(By.ID, "WindSpeed")
                self.wind_speed = float(e.text)
                e2 = self.driver.find_element(By.ID, "Flux")
                e3 = self.driver.find_element(By.ID, "Bt")
                e4 = self.driver.find_element(By.ID, "Bz")
                e5 = self.driver.find_element(By.XPATH, "/html/body/div[4]/section/div/div/div/div/section[1]/div/div/div[1]/div[4]/div[1]/div/div[2]/div[4]/div[1]")

                p = requests.get("https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json")
                Kp = json.loads(p.text)
                with open("Kp.txt", "w") as l:
                    l.write(str(Kp))
                with open("Kp.txt", "r") as l:
                    text = l.read()
                    pattern = r"\d+\.\d+"
                    numbers = re.findall(pattern, text)
                    if numbers:
                        last_number = numbers[-1]
                        with open("parameters.txt", "w") as a:
                            a.write("Solar Wind Speed: " + str(self.wind_speed) + " km/s" + "\n")
                            a.write("Flux: " + e2.text + " sfu" + "\n")
                            a.write("Bt: " + e3.text + " nT" + "\n")
                            a.write("Bz: " + e4.text + " nT" + "\n")
                            a.write("Storm: " + e5.text + " " "\n")
                            a.write(f"Kp: {last_number}" + "\n")
                            a.write("\n" + "Time to Earth: " + str(round(self.const_distance / self.wind_speed, 1)) + " minutes" + "\n")

                break
            except TimeoutException as _ex:
                print(_ex)
                break

        with open("parameters.txt", "r") as b:
            for line in b:
                location_vars = {"Storm: G ":65, "G1":60, "G2":55, "G3":50, "G4":45, "G5":40}
                with open("parameters.txt", "a", encoding="utf-8") as b:
                	for loc_var in location_vars.keys():
                    	if loc_var in line:
                               b.write(f"Location: north to {location_vars[loc_var]}" + "° when Bz<0" + "\n")

        with open("parameters.txt", "r", encoding="utf-8") as c:
            content = c.read()
            with open("parameters.txt", "a", encoding="utf-8") as c:
                if re.search(r"-\d{1}", content):
                    c.write("Low chance to see aurora 😾")
                elif re.search(r"(-1[0-9]|-19)", content):
                    c.write("Good chance to see aurora 😺")
                elif re.search(r"(-[2-9][0-9]|-100)", content):
                    c.write("WOW! High chance to see aurora! 🙀")
                else:
                    c.write("No chance to see aurora 😿 (Bz ≥ 0)")

Первая мысль — подключиться к API NOAA и написать Telegram-бота, который будет по команде ходить на сайт и забирать данные. В целом, ничего сложного. В Академии Selectel описан подобный пример с ботом, который собирает данные и присылает прогноз погоды для любого города.

Но API NOAA подойдет как раз для прогноза погоды на Земле. Все-таки это управление океанических и атмосферных исследований. Центр прогнозирования космической погоды NOAA свой API не предлагает. Значит, перейдем к другим способам. Похоже, это приключение на 20 минут.

Парсинг с помощью Selenium

Итак, нам нужно собрать данные о погоде в космосе. Сайт NOAA динамический, то есть нужные параметры магнитосферы и космоса там регулярно обновляются. Скорость солнечного ветра, Bz и Bt — каждую минуту, сила магнитных бурь и реальный Kp — каждые три часа, F 10.7 cm и прогноз Kp на три дня — раз в сутки.

Получается, обойтись библиотекой BeautifulSoup не выйдет. Она поможет вытащить со страницы статический HTML-код, но не меняющиеся данные. А нужны нам именно они.

Раздел с нужными данными на сайте NOAA.
Здесь лежат нужные нам данные. Источник.

Очевидное решение — использовать библиотеку Selenium. Устанавливаем ее и webdriver_manager в терминале:

pip install selenium
pip install webdriver_manager

Следом импортируем пакеты и объявляем переменные:


    from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
import re
import json
import requests

service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
URL = "https://www.swpc.noaa.gov/communities/aurora-dashboard-experimental"

Большинство данных будем забирать со страницы центра прогнозирования космической погоды NOAA. Там есть все, кроме реального и прогнозного Kp-индекса, но эту проблему мы решим чуть позже. А пока спарсим скорость солнечного ветра, Bt, Bz, F 10.7 cm и силу магнитной бури.

Первые четыре можно получить одной командой, поскольку все они в HTML-коде страницы находятся внутри id «summary». Удобно, но Selenium в таком случае выдаст результат одной строкой с той же пунктуацией, что на странице.

Результат сбора данных одной командой.
Быстро, но читаемость, на мой взгляд, хромает.

Мне это не нравится. Соберем все по отдельности и запишем каждый показатель с новой строки в текстовый файл:


    def get_source_code(URL: str) -> None:
    driver.get(URL)

    while True:
        try:
            e = driver.find_element(By.ID, "WindSpeed")
            e2 = driver.find_element(By.ID, "Flux")
            e3 = driver.find_element(By.ID, "Bt")
            e4 = driver.find_element(By.ID, "Bz")
            e5 = driver.find_element(By.XPATH, "/html/body/div[4]/section/div/div/div/div/section[1]/div/div/div[1]/div[4]/div[1]/div/div[2]/div[4]/div[1]")
           
            with open("parameters.txt", "w") as a:
                a.write(e.text + "\n")
                a.write(e2.text + "\n")
                a.write(e3.text + "\n")
                a.write(e4.text + "\n")
                a.write(e5.text + "\n")

            break
        except TimeoutException as _ex:
            print(_ex)
            break

def main() -> None:
    get_source_code(URL)

if __name__ =="__main__":
    main()

driver.quit()

Как видите, у каждого элемента есть свой id и только с одним что-то пошло не так. Это сила магнитной бури. В HTML-коде, конечно, есть id нужного элемента — «noaa_scale_info_effect». Но Selenium почему-то не может его достать. Так я пришел к идее спарсить показатель через XPATH. Почему нет? Копируем его из кода страницы — «/html/body/div[4]/section/div/div/div/div/section[1]/div/div/div[1]/div[4]/div[1]/div/div[2]/div[4]/div[1]». Здесь важно, что сила магнитной бури представлена в нескольких местах. Есть максимальная за прошедшие сутки, последняя зафиксированная и три прогнозных на ближайшие три дня. Нас интересует тот показатель, что зафиксирован на планете за последние три часа — Latest Observed.

Достаем со страницы Kp-индекс

Поскольку сила магнитной бури и Kp-индекс хорошо соотносятся друг с другом, на этом сбор данных можно было бы и закончить. Но я решил достать оба показателя. Забегая вперед, скажу, что это опциональное решение. Индекс я буду использовать, просто чтобы точнее отслеживать интенсивность бури. Например, уровню G3 обычно соответствуют индексы 6,67, 7,00 и 7,33. Таким образом, отслеживание Kp позволит судить, усиливается или слабеет буря. Но не забывайте, о чем я писал выше: иногда корреляция двух показателей отключается и при достаточно высоком Kp уровень G остается на нуле.

Итак, на основной странице Kp-индекс есть в интерактивном дашборде, но оттуда данные не достать. Зато они собраны на другой странице этого же сайта, не самой очевидной. Я не придумал ничего лучше, чем загрузить содержимое json-файла в новый текстовый документ, найти в нем все дробные числа и записать последнее в уже имеющийся файл с остальными параметрами. Это и есть самый поздний зафиксированный Kp-индекс. В коде это выглядит так:


    p = requests.get("https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json")
Kp = json.loads(p.text)
with open("Kp.txt", "w") as l:
    l.write(str(Kp))
with open("Kp.txt", "r") as l:
    text = l.read()
    pattern = r"\d+\.\d+"
    numbers = re.findall(pattern, text)
    if numbers:
        last_number = numbers[-1]
        with open("parameters.txt", "w") as a:
            a.write(f"{last_number}" + "\n")

Удлиняет ли это код? Да. Замедляет ли в итоге время ответа бота? Наверное. Так нужно ли это делать? Необязательно, но ради большей информативности можно.

Оценка показателей

Теперь у нас есть текстовый файл, в котором собраны нужные данные. Классно? Классно. Но в текущем виде файл «parameters» не очень информативен. А ведь его содержимое мы и будем потом отправлять в мессенджер.

Мы получили цифры, но пока разобраться в них не так-то просто.

Путем нехитрых манипуляций можно сделать его куда более понятным. Просто чуть пофиксим запись данных от Selenium в текстовый файл:


    with open("parameters.txt", "w") as a:
    a.write("Solar Wind Speed: " + e.text + " km/s" + "\n")
    a.write("Flux: " + e2.text + " sfu" + "\n")
    a.write("Bt: "+ e3.text + " nT" + "\n")
    a.write("Bz: "+ e4.text + " nT" + "\n")
    a.write("Storm: "+ e5.text)
    a.write(f"Kp: {last_number}")

Благодаря этому мы получим уже гораздо более содержательный текст:

Осмысленный, но еще не всем понятный текст.

Правда, первый же человек, которому я показал такое сообщение от бота в Telegram, спросил меня: «Так, ну а сияние-то где будет?». Кажется, приключение на 20 минут выходит из-под контроля.

Расчет времени

Добавим для ясности три строки.

  • В первой строке на основании данных о скорости потока рассчитаем время его прибытия на Землю.
  • Во второй — покажем, к северу от какой широты возможно появление сияния при актуальном состоянии магнитосферы.
  • В третьей — дадим финальную оценку вероятности появления авроры, основываясь на ориентации межпланетного магнитного поля и силе магнитной бури.

Как мы помним со школы, время = расстояние / скорость. Скорость потока солнечных частиц нам известна — ее мы получили от Selenium. Расстояние — тоже. Это около 1,5 млн км от Земли до спутника DSCOVR, который и фиксирует скорость солнечного ветра. Конечно, цифра постоянно варьируется, но в масштабах космоса этим можно пренебречь (как и временем, которое потребуется сигналу, чтобы добраться от спутника до Земли).

Сперва зададим две константы:


    wind_speed = 0
const_distance = 25000 #чтобы получить время сразу в минутах, а не в секундах, заранее разделим 1500000 на 60 и получим 25000.

Затем, добавим формулу с округлением до одного знака после запятой:


    str(round(const_distance/wind_speed, 1))

Добавим новую строку:


    with open("parameters.txt", "a") as a:
    a.write("\n" + "Time to Earth: " + str(round(const_distance/wind_speed, 1)) + " minutes")

Почему я не написал текст по-русски? Из-за падежей. Мне не хочется получать сообщение с текстом «50 минуты» или «51 минут». Наиболее простым решением счел запись на английском. Ну и раз так, дальше тоже буду писать по-английски.

Здесь считаю уместным небольшое пояснение. Все важные для прогноза показатели собираются на Земле. То есть если мы видим, например, Kp = 7,33, то он уже есть. Так зачем рассчитывать время? Оно показывает, как скоро на магнитосферу начнет воздействовать солнечный ветер, только что зафиксированный спутником DSCOVR. Если видим, что его скорость падает, весьма вероятно (но не гарантировано) уменьшение активности магнитосферы. И наоборот. А время показывает, когда произойдет это изменение.

Расчет координат

Следующий шаг — добавить информацию о том, к северу от какой широты вероятно наблюдение сияния при имеющейся магнитной буре. Здесь я воспользовался функцией vars(), чтобы чрезмерно не раздувать код. Я хочу добавить строку «Location: north to X° when Bz<0», в которой Х подбирался бы в зависимости от записанной ранее силы магнитной бури. Уточнение «when Bz<0» просто будет напоминать, что даже при геомагнитном шторме межпланетное магнитное поле должно быть ориентировано на юг. Иначе чуда не случится.


    with open("parameters.txt", "r") as b:
    for line in b:
        location_vars = {"Storm = G ":65, "G1": 60, "G2":55, "G3":50, "G4":45, "G5":40}
        with open("parameters.txt", "a", encoding="utf-8") as b:
            for loc_var in location_vars.keys():
                if loc_var in line:
                    
                b.write(f"Location: north to {location_vars[loc_var]}" + "° when Bz<0" + "\n")

Возникает проблема. При поиске «Storm = G» будут находиться «Storm = G1» и так далее. Проблема изящно решается добавлением пробела в конце строки при записи element5.text и location_vars.

Уточню, что прогнозирование места для наблюдений на основании оценки магнитосферы — это лишь определение широты, к северу от которой может возникнуть сияние. Чтобы найти наиболее удобную конкретную локацию, нужно выбрать пространство с безоблачным темным небом и без высоких объектов на горизонте. Здесь-то и помогут сервисы с прогнозом погоды, например тот же Windy.

Оценка вероятности сияния

Итак, у нас уже есть легкочитаемая информация о том, где в теории можно увидеть сияние. Но пока нет очевидного ответа на вопрос, а возникнет ли оно вообще при имеющихся условиях и какова будет его видимость. Чтобы дать оценку, нужно учесть величину Bz.

Наиболее простым вариантом я счел использование регулярных выражений. Так можно однозначно отличить, скажем, -23 от -2, 2, 3 и 23. При этом найти простое решение с функцией vars(), как это было выше, у меня не получилось. Хотя уверен, что оно есть. Как бы то ни было, вот результат:


    with open("parameters.txt", "r", encoding="utf-8") as c:
    content = c.read()
        with open("parameters.txt", "a", encoding="utf-8") as c:
            if re.search(r"-\d{1}", content):
                c.write("Low chance to see aurora 😾")
            elif re.search(r"(-1[0-9]|-19)", content):
                c.write("Good chance to see aurora 😺")
            elif re.search(r"(-[2-9][0-9]|-100)", content):
                c.write("WOW! High chance to see aurora! 🙀")
            else:
                c.write("No chance to see aurora 😿 (Bz ≥ 0)")

Итак, выше описаны четыре условия, от которых зависит добавление еще одной строки.

  • В файле есть отрицательное однозначное число — добавляем строку «Low chance to see aurora 😾». Отрицательным числом здесь может быть только значение Bz. И если оно не ниже -9, ждать яркого сияния не стоит при любой буре.
  • В файле есть отрицательное число в диапазоне от -10 до -19 — добавляем строку «Good chance to see aurora 😺».
  • В файле есть отрицательное число от -20 до -100 — добавляем строку «WOW! High chance to see aurora! 😺». Как показывает практика, при Bz ниже -20 сияние точно появится и будет хорошо различимо на темном небе.
  • В файле не найдено отрицательных чисел. Что ж, прямо сейчас сияния точно не будет. А при отсутствии магнитных бурь (Storm: G) можно смело ложиться спать.

В целом здесь можно сделать более подробную градацию в зависимости от значения Bz. Но я решил, что и так достаточно. Число -100 не является каким-то сакральным. Чисто теоретически оно может быть и ниже, но шансы на это крайне малы. Даже Bz = -50 — чрезвычайно редкое явление.

После всего описанного выше мы получаем уже такой удобный файл:

Финальный вид сообщения в Telegram.
В таком виде мы и будем получать оценку вероятности сияния в Telegram.

Сбор данных для трехдневного прогноза

Казалось бы, что вот теперь-то точно можно двигаться дальше. Но нет. Сейчас можно получить актуальные данные, которых хватит для прогноза плюс-минус на три часа. Но ночную поездку за город я предпочитаю планировать хотя бы за сутки.

Выше я упомянул, что NOAA дает трехдневный прогноз геомагнитной активности планеты. Он выражается как в Kp-индексе, так и в оценке силы предстоящих магнитных бурь. Можно собирать и то, и другое, но прогноз Kp более информативен. Он формируется на каждые три часа в течение следующих трех суток. Очень удобно.
Данные о прогнозе Kp-индекса на три дня я собрал примерно так же, как уже зафиксированный Kp. Только этот прогноз лежит на другой странице. Загружаем все содержимое в отдельный файл:


    t = request("get", "https://services.swpc.noaa.gov/text/3-day-geomag-forecast.txt").text
with open("days.txt", "w") as x:
    x.write(t)

Эти строки я не добавляю в код сразу. Я использую их чуть позже и добавлю в код бота.

Итак. Мы собрали все данные и дали им необходимую оценку. Пора переходить к написанию Telegram-бота, чтобы получать прогноз в мессенджере по нажатию кнопки.

Написание Telegram-бота

Первым делом открываем Telegram, находим @BotFather и отправляем ему команду /newbot. Далее придумываем будущему боту имя и username. Итогом всех манипуляций будет уникальный токен. Обязательно его сохраните и постарайтесь не потерять. Токен нужен, чтобы управлять ботом.

Процесс создания бота в Telegram.
Процесс создания бота в Telegram.

Теперь переходим к написанию кода. Снова начинаем с установки библиотеки. Я использовал pyTelegramBotAPI:

pip install pyTelegramBotAPI

Импортируем пакеты. На этом шаге вместо «TOKEN» указываем токен, полученный от @BotFather.


    import telebot
from telebot import types
bot = telebot.TeleBot("TOKEN") #Вместо "TOKEN" укажите токен, полученный от @BotFather
import os
from requests import request

Настроим приветствие. По команде /start будут появляться две reply-кнопки. Одна для получения прогноза на основе актуального состояния магнитосферы, другая — для трехдневного прогноза Kp-индекса.


    @bot.message_handler(commands=["start"])
def start(message):
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    keyboard.add(types.KeyboardButton("Актуальные параметры"))
    keyboard.add(types.KeyboardButton("Прогноз на три дня"))
    bot.send_message(message.chat.id, "Какие показатели изволите?", reply_markup=keyboard)

Следующий шаг — настройка действий бота в ответ на нажатие кнопок. Начнем со сценария, в котором мы хотим получить оценку текущих показателей.


    @bot.message_handler(content_types=["text"])
def second(message):
    if message.text == "Актуальные параметры":
        os.system("python aurora.py")
        with open("parameters.txt", "r", encoding="utf-8") as m:
            d = m.read()
            bot.send_message(message.chat.id, text=d)

Здесь все просто: нажатие кнопки «Актуальные параметры» запускает скрипт с Selenium, записанный в файле aurora.py. Тот в свою очередь записывает текстовый файл, а бот отправляет его содержимое в ответ.

Почти то же самое делаем со второй кнопкой. Только при ее нажатии бот собирает данные о прогнозе Kp-индекса на три дня. Вот здесь и пригодился тот небольшой кусочек кода из раздела о сборе трехдневных показателей.


    if message.text == "Прогноз на три дня":
    t = request("get", "https://services.swpc.noaa.gov/text/3-day-geomag-forecast.txt").text
    with open("days.txt", "w") as x:
        x.write(t)
    with open("days.txt", "r", encoding="utf-8") as n:
        lines = n.readlines()
        selected_lines = lines[15:25]
        bot.send_message(message.chat.id, text="\n".join(selected_lines))

Все строки из документа с прогнозом нам не так уж и нужны. На мой взгляд, чтобы отсеять лишнее, но сохранить информативность, хватит строк с 16 по 25. Не забываем, что индексация в Python начинается с 0, поэтому указываем диапазон [15:25]. Бот читает эти строки и отправляет их одним сообщением в ответ на нажатие кнопки.

Как и в предыдущем случае, заботливо собранный код отредактировал мой коллега. Результат ниже.


    import telebot
from telebot import types
bot = telebot.TeleBot("TOKEN") #Замените "TOKEN" на токен от @BotFather
from requests import request
from aurora import AuroraParser

@bot.message_handler(commands=["start"])
def start(message):
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    keyboard.add(types.KeyboardButton("Актуальные параметры"))
    keyboard.add(types.KeyboardButton("Прогноз на три дня"))
    bot.send_message(message.chat.id, "Какие показатели изволите?", reply_markup=keyboard)

@bot.message_handler(content_types=["text"])
def second(message):
    if message.text == "Актуальные параметры":
        parser = AuroraParser(wind_speed=0, const_distance=25000)
        parser.get_source_code()
        parser.driver.quit()
        with open("parameters.txt", "r", encoding="utf-8") as m:
            d = m.read()
            bot.send_message(message.chat.id, text=d)

    if message.text == "Прогноз на три дня":
        t = request("get", "https://services.swpc.noaa.gov/text/3-day-geomag-forecast.txt").text
        with open("days.txt", "w") as x:
            x.write(t)
        with open("days.txt", "r", encoding="utf-8") as n:
            lines = n.readlines()
            selected_lines = lines[15:25]
            bot.send_message(message.chat.id, text="\n".join(selected_lines))

bot.polling(none_stop=True)

Теперь все работает так, как задумано. Остался последний шаг.

Деплой бота в облако

Telegram-бота нужно поселить на облачный сервер. Так он будет работать постоянно, а не только при запуске кода на компьютере. Казалось бы, ну уж этот-то процесс описан миллион раз во всех возможных вариациях. Но нет.

Сервер с Ubuntu

Обычно деплой Telegram-бота на облачный сервер и его запуск выглядят просто. Нужно создать сервер, подключиться через терминал по SSH, установить необходимые пакеты, завести виртуальное окружение, клонировать репозиторий и запустить бота. Но не в этом случае.

После запуска бота на сервере оказалось, что работает только кнопка для вызова прогноза Kp-индекса на три дня. А вот на нажатие кнопки для запроса актуального состояния магнитосферы бот не реагирует. Повторюсь, что локально все работает прекрасно. Ошибок сервер тоже не выдает. 

Смотрим логи:

Логи показывают, что пошло не так.
Упс. Что-то пошло не так.

Как видим, Selenium не хочет работать. Оказывается, такая проблема встречается довольно часто — эта библиотека без нареканий запускается локально, но на сервере начинает капризничать. Вопрос уже поднимался на Stack Overflow. Есть три возможных решения.

Первое решение (правильное) — пофиксить код, опираясь на документацию Selenium, и соответствующим образом донастроить сервер. Плюс такого подхода — возможность нормально запустить бота в облаке. Хотя реализация потребует некоторого количества времени и усилий.

Второе решение — оставить все как есть и запускать код локально. Плюс — все будет работать вообще бесплатно. Можно вечером запустить код на ноутбуке и уехать ловить сияние, отслеживая параметры в Telegram. Минус — остановка кода приведет к остановке бота. А я хочу поделиться ботом с друзьями и знакомыми. Будет странно, если они смогут им пользоваться, только когда мне удобно.

Третье решение — арендовать сервер не на Ubuntu, а на Windows. Плюсы — это самый быстрый и простой способ добиться результата. Не потребуются никакие доработки. С другой стороны, сервер на Windows дороже сервера на Ubuntu, а его ресурсы даже в минимальной комплектации могут быть избыточны для простого Telegram-бота, Google Chrome и IDE (одно ядро, 2 ГБ RAM и 32 ГБ SSD). Хотя если есть другие проекты, для которых нужен Windows Server, решение может оказаться вполне рабочим.

Сервер с Windows

Вероятно, в ближайшее время я все же пойду правильным путем: немного перепишу код и донастрою облачный сервер с Ubuntu. А пока добавим еще немного костылей.
Переходим в панель управления, открываем раздел Облачная платформаСерверы. Нажимаем на кнопку Создать сервер. В поле Источник выбираем Windows и подходящую версию ОС (в моем случае Windows Server 2019 Standard).

Панель управления Selectel.
Панель управления Selectel.

Минимальной конфигурации хватит с запасом. По умолчанию SSH-ключ не требуется, т. к. подключаться мы будем по RDP.

Как только статус сервера сменится на ACTIVE, можно открыть Подключение к удаленному рабочему столу на компьютере. Данные для входа доступны в панели управления. Публичный IP-адрес можно посмотреть на вкладке Порты, а логин и пароль — на вкладке Консоль.

После подключения нужно выполнить несколько простых операций.

  1. 1. Нажмите Пуск и откройте Server Manager.
Меню Пуск на Windows Server.
Меню Пуск на Windows Server.
  1. 2. Перейдите во вкладку Local Server. Найдите справа строку IE Enhanced Security Configuration, переключите настройки на Off и нажмите кнопку ОК.
Вкладка Local Server.
Окно изменения настроек безопасности Internet Explorer.
  1. 3. Скачайте и установите Google Chrome и любой IDE (в моем случае PyCharm).
  2. 4. Скопируйте файлы .py в проект и запустите код.

На этом подключение можно разорвать, бот продолжит работать. При необходимости вы можете доработать бота, добавить асинхронности в функции и обработчики исключений, которые могут возникать, например, из-за проблем с сайтом NOAA.

Бот запущен на сервере и работает.

Немного о тонкостях наблюдений

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

По своему опыту могу сказать, что для наблюдения полярных сияний на широте Санкт-Петербурга Kp-индекс должен достичь хотя бы 5 баллов (отметки слабой магнитной бури G1). В этом случае будет видна та самая едва различимая и слегка мерцающая пелена. Но если вооружиться камерой, пусть даже в смартфоне, на выдержке от 10 секунд вполне получится снять яркое зеленое свечение с выраженными фиолетовыми акцентами. При тех же параметрах, скажем, в Мурманске (68°58’ северной широты) видимость авроры будет намного лучше.

Снято на смартфон в окрестностях Лебяжьего пляжа.
Снято на смартфон в окрестностях Лебяжьего пляжа.

Советую запастись терпением. Капризное межпланетное магнитное поле может внезапно развернуться и несколько часов «смотреть» на север, не давая разгореться авроре. Выезжая за город для наблюдений, стоит рассчитывать, что провести там придется не меньше 2-3 часов. Есть смысл прихватить с собой горячий чай или кофе в термосе и тепло одеться, потому что вы точно не поедете за сиянием жаркой июльской ночью.

Слабая аврора над северным горизонтом на берегу Ладожского озера.
Слабая аврора над северным горизонтом на берегу Ладожского озера.

Также заранее подумайте, куда именно вы поедете для наблюдений. Так вы спланируете время на дорогу, сможете подобрать место без городской засветки и избежите необходимости внезапно штурмовать бездорожье. Например, мои любимые места под Петербургом — Первый северный форт в Кронштадте, окрестности Лебяжьего пляжа на южном берегу Финского залива и Осиновецкий маяк на Ладожском озере. До всех этих локаций можно относительно быстро добраться из любой точки города на любом автомобиле. Если благоприятные условия прогнозируются на выходные, можно отправиться с ночевкой на север Ленобласти, а еще лучше — в Карелию.

Читайте также: