Это старая версия документа.


HOW-TO: Программа на Python, Часть 11

Автор — Грэг Валтерс (Greg Walters)

В нашем прошлом уроке мы рассмотрели API сервиса wunderground и написали немного кода для получения текущих погодных условий. Сейчас мы займёмся той частью API, которая отвечает за прогноз. Если вы пропустили два последних выпуска, рассказывающих об XML, в особенности последний, то, возможно, вам потребуется с ними ознакомиться, прежде чем продолжить.

Как и для текущих погодных условий, для прогноза существует свой веб-адрес. Ссылка на XML-страницу прогноза: http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=80013

Как и раньше, вы можете сменить «80013» на код вашего города или почтовый индекс. В ответ от сервера придёт около 600 строк XML кода. Вы получите корневой элемент «forecast» и затем четыре вложенных элемента: «termsofservice», «txt_forecast», «simpleforecast» и «moon_phase». Для работы нам понадобятся в основном элементы «txt_forecast» и «simpleforecast».

Так как мы уже ознакомились с секциями usage, main и «if name», займитесь ими самостоятельно. Я же сконцентрируюсь на вещах, необходимых в данный момент. Так как я показал вам фрагмент txt_forecast, давайте начнём разбирать его.

Ниже показана небольшая часть txt_forecast для моего региона.

Элемент txt_forecast включает в себя элементы: date, number, forecastday. Контейнер forecastday, в свою очередь, содержит элементы: period, icon, icons, title и что-то под названием fcttext… Далее структура повторяется. Первое, что стоит заметить, — это то, что в txt_forecast элемент date содержит не дату, а время. Оно показывает, когда прогноз был создан. Тег <number> показывает, сколько имеется прогнозов на ближайшие 24 часа. Я не припоминаю, чтобы это значение было меньше 2. Для каждого суточного прогноза указывается номер периода, несколько наборов иконок, заголовок («Днём», «Вечером», «Завтра») и краткое описание. Обычно такой текст содержит обзор прогноза на ближайшие 12 часов.

Прежде чем мы начнём работать над нашим кодом, посмотрим на элемент <simpleforecast> нашего XML-файла. Он показан справа.

Каждый день прогноза (обычно их 6, включая сегодняшний день) представлен тегом <forecastday>. Он содержит информацию о дате в различных форматах (мне лично нравится тег <pretty>), значения температур по Фаренгейту и по Цельсию, общий прогноз, различные иконки, значок неба (показывающий облачность в районе метеорологической станции) и тег <pop>, что расшифровывается как «вероятность осадков» (Probability Of Precipitation). Тег <moon_phase> предоставляет информацию о закате и восходе солнца и фазах луны.

Теперь мы начнём писать код. Вот необходимые директивы импорта:

from xml.etree import ElementTree as ET

import urllib

import sys

import getopt

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

Если вам не требуется представлять данные и по Цельсию, и по Фаренгейту, можете исключить соответствующий набор переменных. Моё решение — использовать оба.

Далее мы реализуем основную функцию для извлечения данных о прогнозе. Код показан внизу справа на следующей странице.

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

Заметьте, что в этот раз мы используем tree.find, а для обхода данных применяются циклы for. В отличие от других языков, в Python отсутствует конструкция SELECT/CASE. Вместо неё используются выражения IF/ELIF, хоть они и чуть более громоздкие. Начнём разбирать код. Мы присваиваем переменной fcst содержимое тега <txt_forecast>, тем самым* получив все данные этой группы. Затем обращаемся к тегам <date> и <number>, поскольку это метки «первого уровня», и загружаем их в наши переменные. Теперь всё становится немного сложнее. Посмотрите ещё раз на пример XML-ответа. Там два вхождения тега <forecastday>, у каждого есть дочерние элементы: <period>, <icon>, <icons>, <title> и <fcttext>. С помощью цикла мы обходим их и используем оператор IF, чтобы сохранить их в наши переменные.

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

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

И здесь тоже, если вы не хотите получать информацию и в градусах Цельсия, и в градусах Фаренгейта, измените код для показа того, что вам нужно. В итоге получаем следующий вид метода «DoIt»:

def DoIt(self,Location,US,IncludeToday,Output):

self.GetForecastData(Location)

self.output(US,IncludeToday,Output)

Теперь мы можем вызвать наш метод следующим образом:

forecast = ForecastInfo()

forecast.DoIt('80013',1,0,0) # Укажите ваш собственный индекс

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

Здесь можно просмотреть полностью рабочий код: http://pastebin.com/wsSXMXQx

Удачи и до скорого!

Коде

<txt_forecast>
              <date>3:31 PM MDT</date>
              <number>2</number>
              −<forecastday>
                            <period>1</period>
                            <icon>nt_cloudy</icon>
                            +<icons></icons>
                            <title>Tonight</title>
                            −<fcttext>
                            Mostly cloudy with a 20 percent chance of thunderstorms in the evening...then partly cloudy after midnight. Lows in the mid 40s. Southeast winds 10 to 15 mph shifting to the south after midnight.
                    </fcttext>
              </forecastday>
              +<forecastday></forecastday>
</txt_forecast>

<simpleforecast>
    −<forecastday>
        <period>1</period>
        −<date>
            <epoch>1275706825</epoch>
            <pretty_short>9:00 PM MDT</pretty_short>
            <pretty>9:00 PM MDT on June 04, 2010</pretty>
            <day>4</day>
            <month>6</month>
            <year>2010</year>
            <yday>154</yday>
            <hour>21</hour>
            <min>00</min>
            <sec>25</sec>
            <isdst>1</isdst>
            <monthname>June</monthname>
            <weekday_short/>
            <weekday>Friday</weekday>
            <ampm>PM</ampm>
            <tz_short>MDT</tz_short>
            <tz_long>America/Denver</tz_long>
        </date>
        −<high>
            <fahrenheit>92</fahrenheit>
            <celsius>33</celsius>
        </high>
        −<low>
            <fahrenheit>58</fahrenheit>
            <celsius>14</celsius>
        </low>
        <conditions>Partly Cloudy</conditions>
        <icon>partlycloudy</icon>
        +<icons>
        <skyicon>partlycloudy</skyicon>
        <pop>10</pop>
    </forecastday>
              ...
</simpleforecast>

#=================================   
# Get the forecast for today and (if available) tonight
#=================================
 fcst = tree.find('.//txt_forecast')
 for f in fcst:
     if f.tag == 'number':
         self.periods = f.text
     elif f.tag == 'date':
         self.date = f.text
     for subelement in f:
         if subelement.tag == 'period':
             self.period=int(subelement.text)
         if subelement.tag == 'fcttext':
             self.forecastText.append(subelement.text)
         elif subelement.tag == 'icon':
             self.icon.append( subelement.text)
         elif subelement.tag == 'title':
             self.Title.append(subelement.text)
             
class ForecastInfo:
    def __init__(self):
        self.forecastText = []  # Today/tonight forecast information
        self.Title = []         # Today/tonight
        self.date = ''         
        self.icon = []          # Icon to use for conditions today/tonight
        self.periods = 0
        self.period = 0
       #==============================================
       # Extended forecast information
       #==============================================      
        self.extIcon = []       # Icon to use for extended forecast
        self.extDay = []        # Day text for this forecast ("Monday", "Tuesday" etc)
        self.extHigh = []       # High Temp. (F)
        self.extHighC = []      # High Temp. (C)
        self.extLow = []        # Low Temp. (F)
        self.extLowC = []       # Low Temp. (C)
        self.extConditions = [] # Conditions text
        self.extPeriod = []     # Numerical Period information (counter)
        self.extpop = []        # Percent chance Of Precipitation
        
def GetForecastData(self,location):
        try:
            forecastdata = 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=%s' % location
            urllib.socket.setdefaulttimeout(8)
            usock = urllib.urlopen(forecastdata)
            tree = ET.parse(usock)
            usock.close()
        except:
            print 'ERROR - Forecast - Could not get information from server...'
            sys.exit(2)
            
#=================================
        # Now get the extended forecast
#=================================
    fcst = tree.find('.//simpleforecast')
    for f in fcst:
         for subelement in f:
            if subelement.tag == 'period':
                self.extPeriod.append(subelement.text)
            elif subelement.tag == 'conditions':
                self.extConditions.append(subelement.text)
            elif subelement.tag == 'icon':
                self.extIcon.append(subelement.text)
            elif subelement.tag == 'pop':
                self.extpop.append(subelement.text)
            elif subelement.tag == 'date':
                for child in subelement.getchildren():
                    if child.tag == 'weekday':
                        self.extDay.append(child.text)
            elif subelement.tag == 'high':
                for child in subelement.getchildren():
                    if child.tag == 'fahrenheit':
                        self.extHigh.append(child.text)
                    if child.tag == 'celsius':
                        self.extHighC.append(child.text)
            elif subelement.tag == 'low':
                for child in subelement.getchildren():
                    if child.tag == 'fahrenheit':
                        self.extLow.append(child.text)
                    if child.tag == 'celsius':
                        self.extLowC.append(child.text)