GO: Прогноз с meteoinfo.ru

Пример получения XML данных с http ресурса с обработкой строк, реализованный на языке go.

Опубликовано 20-10-2015
Эксперементы
Теги go-lang, http, xml

Пример получения XML данных с http ресурса с обработкой строк, реализованный на языке go.

Задача нашего примера получить данные о погоде из RSS Гидрометцентра России и разобрать полученные данные. Для этого мы скачаем XML по ссылке при помощи библиотеки net/http. Далее идет код загрузки XML с проверкой ошибок, статуса HTTP ответа и с отложенным уничтожением defer r.Body.Close() тела HTTP ответа.

...
fmt.Println("Download")
r, err := http.Get("http://meteoinfo.ru/rss/moscow/")
if (err != nil) {
	fmt.Println(err.Error())
	return
}
defer r.Body.Close()
if (r.StatusCode != 200) {
	fmt.Println("Network error")
	return
}
fmt.Println("Content length:",r.ContentLength)
...

Приведу полученный XML.

<?xml version="1.0" encoding="windows-1251" ?> 
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
   <channel>
       <title>Гидрометцентр России: О погоде - из первых рук</title>
       <generator>Meteoinfo</generator>
       <link>http://www.meteoinfo.ru</link>
       <description>Новости о погоде</description>
       <lastBuildDate>Tue, 20 Oct 2015 10:45:39 +0300</lastBuildDate>
       <language>ru</language>
       <pubDate>Tue, 20 Oct 2015 10:45:39 +0300</pubDate>
       <docs>http://www.meteoinfo.ru/rss</docs>
       <managingEditor>web@mecom.ru</managingEditor>
       <webMaster>kiktev@mecom.ru</webMaster>
       <atom:link href="http://www.meteoinfo.ru/rss" rel="self" type="application/rss+xml" />
              <item>
                <title>Москва и область, 20 октября, ночью
</title>
                 <link>http://meteoinfo.ru/forecasts#201510190</link>
                 <description><![CDATA[без осадков, облачно с прояснениями, утром местами туман, температура -1..1°, по области -3..2°, ветер переменных направлений 2-7 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#201510190</guid>
              </item>
              <item>
                <title>Москва и область, 20 октября, днём
</title>
                 <link>http://meteoinfo.ru/forecasts#201510192</link>
                 <description><![CDATA[без осадков, переменная облачность, температура 4..6°, по области 1..6°, ветер переменных направлений 2-7 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#201510192</guid>
              </item>
              <item>
                <title>Москва и область, 21 октября, ночью
</title>
                 <link>http://meteoinfo.ru/forecasts#201510194</link>
                 <description><![CDATA[переменная облачность, без осадков, утром местами туман, температура -3..-1°, по области -5..0°, ветер восточный, ю-в 3-8 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#201510194</guid>
              </item>
              <item>
                <title>Москва и область, 21 октября, днём
</title>
                 <link>http://meteoinfo.ru/forecasts#201510196</link>
                 <description><![CDATA[переменная облачность, без осадков, температура 4..6°, по области 1..6°, ветер восточный, ю-в 3-8 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#201510196</guid>
              </item>
              <item>
                <title>Москва и область, 22 октября, ночью
</title>
                 <link>http://meteoinfo.ru/forecasts#201510198</link>
                 <description><![CDATA[переменная облачность, без осадков, температура -3..-1°, по области -6..-1°, ветер ю-в, южный 5-10 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#201510198</guid>
              </item>
              <item>
                <title>Москва и область, 22 октября, днём
</title>
                 <link>http://meteoinfo.ru/forecasts#2015101910</link>
                 <description><![CDATA[переменная облачность, без осадков, температура 4..6°, по области 2..7°, ветер ю-в, южный 5-10 м/с
]]></description>
                 <category>Прогноз на неделю для 5000 городов мира</category>
                 <pubDate>Mon, 19 Oct 2015 23:32:01 +0300</pubDate>
                 <guid>http://meteoinfo.ru/forecasts#2015101910</guid>
              </item>
   </channel>
</rss>

Нам потребуется вытащить содержание двух тегов title, description и поместим в структуру Item.Для разбора XML используем библиотеку encoding/xml.Парсер XML умеет работать только с XML в кодировке UTF-8, для преобразования на лету кодировки из windows-1251 в UTF-8 воспользуемся библиотеками golang.org/x/text/transform и golang.org/x/text/encoding/charmap.

...
type Item struct {
	title string
  	description string
}
...
if (r.Header["Content-Type"][0] == "application/xml" && r.ContentLength > 0) {
	fmt.Println("Parse XML")
	d := xml.NewDecoder(r.Body)
	d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
		if (charset == "windows-1251") {
			return transform.NewReader(input, charmap.Windows1251.NewDecoder()),nil
		}
		return nil, fmt.Errorf("unsupported charset: %q", charset)
	}
	var buffer bytes.Buffer
	item := false
	witem := new(Item)
	for {
		t,err := d.Token()
		if (err != nil) {
			fmt.Println(err)
			return
		}
		
		switch t := t.(type) {
			case xml.StartElement:
				buffer.Reset()
				if (t.Name.Local == "item") {
					witem = new(Item)
					if (!item) {
						item = true
					}
				}
			case xml.EndElement:
				if (item) {
					if (t.Name.Local == "description") {
						witem.description = buffer.String()
						buffer.Reset()
					}
					if (t.Name.Local == "title") {
						witem.title = buffer.String()
						buffer.Reset()
					}
					if (t.Name.Local == "item") {
						fmt.Print("Title:",witem.title)
						fmt.Print("Desc:",witem.description)
					}
				}
			case xml.CharData:
				buffer.Write(t)
		}
	}
}
...

Далее разбираем строки для получения погоды. Для этого расширим нашу структуру Item и реализуем при помощи пакета strings разбор строк.

...
type Item struct {
	title string
  	description string

  	city string
  	date_day int
  	date_month string
  	date_night bool

  	temp_min int
  	temp_max int
}
...
func parseStr(witem Item) {
	_title := strings.Split(witem.title,",")
	witem.city = _title[0]
	witem.date_night = (strings.Trim(_title[2],"\n ") == "ночью")
	_date := strings.Split(strings.Trim(_title[1]," ")," ")
	witem.date_day,_ = strconv.Atoi(_date[0])
	witem.date_month = _date[1]

	_temp := strings.Split(strings.Trim(strings.Split(strings.Split(witem.description,"температура")[1],",")[0],"° "),"..")
	witem.temp_min,_ = strconv.Atoi(_temp[0])
	witem.temp_max,_ = strconv.Atoi(_temp[1])

	fmt.Println(witem)
}
...

Конечный вариант нашего скрипта.

package main

import "fmt"
import "net/http"
import "encoding/xml"
import "golang.org/x/text/transform"
import "golang.org/x/text/encoding/charmap"
import "io"
import "bytes"
import "strings"
import "strconv"

type Item struct {
	title string
  	description string

  	city string
  	date_day int
  	date_month string
  	date_night bool

  	temp_min int
  	temp_max int
}


func main() {
	fmt.Println("Download")
	r, err := http.Get("http://meteoinfo.ru/rss/moscow/")
	if (err != nil) {
		fmt.Println(err.Error())
		return
	}
	defer r.Body.Close()
	if (r.StatusCode != 200) {
		fmt.Println("Network error")
		return
	}
	fmt.Println("Content length:",r.ContentLength)
	if (r.Header["Content-Type"][0] == "application/xml" && r.ContentLength > 0) {
		fmt.Println("Parse XML")
		d := xml.NewDecoder(r.Body)
		d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
			if (charset == "windows-1251") {
				return transform.NewReader(input, charmap.Windows1251.NewDecoder()),nil
			}
			return nil, fmt.Errorf("unsupported charset: %q", charset)
		}
		var buffer bytes.Buffer
		item := false
		witem := new(Item)
		for {
			t,err := d.Token()
			if (err != nil) {
				fmt.Println(err)
				return
			}
			
			switch t := t.(type) {
				case xml.StartElement:
					buffer.Reset()
					if (t.Name.Local == "item") {
						witem = new(Item)
						if (!item) {
							item = true
						}
					}
				case xml.EndElement:
					if (item) {
						if (t.Name.Local == "description") {
							witem.description = buffer.String()
							buffer.Reset()
						}
						if (t.Name.Local == "title") {
							witem.title = buffer.String()
							buffer.Reset()
						}
						if (t.Name.Local == "item") {
							parseStr(*witem)
						}
					}
				case xml.CharData:
					buffer.Write(t)
			}
		}
	}
	fmt.Println("Finish")
}

func parseStr(witem Item) {
	_title := strings.Split(witem.title,",")
	witem.city = _title[0]
	witem.date_night = (strings.Trim(_title[2],"\n ") == "ночью")
	_date := strings.Split(strings.Trim(_title[1]," ")," ")
	witem.date_day,_ = strconv.Atoi(_date[0])
	witem.date_month = _date[1]

	_temp := strings.Split(strings.Trim(strings.Split(strings.Split(witem.description,"температура")[1],",")[0],"° "),"..")
	witem.temp_min,_ = strconv.Atoi(_temp[0])
	witem.temp_max,_ = strconv.Atoi(_temp[1])

	fmt.Println(witem)
}