Замут на Golang
Парсим курсы валют с ЦБ РФ
И вставляем на свой сайт
Вот как можно распарсить XML с курсами валют с сайта ЦБ РФ (https://cbr.ru/rss/RssCurrency) на Go:
Вступление
Да, давай напишем простую функцию на Go, которая:
- Загружает XML по адресу https://cbr.ru/rss/RssCurrency.
- Читает тело ответа.
- Выводит его содержимое в консоль.
Вот минимальный пример:
package main import ( "fmt" "io" "net/http" ) func main() { url := "https://cbr.ru/rss/RssCurrency" resp, err := http.Get(url) if err != nil { fmt.Println("Ошибка запроса:", err) return } defer resp.Body.Close() // Читаем тело ответа body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Ошибка чтения:", err) return } // Выводим XML в консоль fmt.Println(string(body)) }
Как запустить:
- Сохрани код в файл main.go.
- Выполни команду: go run main.go.
- В консоли появится XML, такой же, как ты получил в браузере.
Вот, что вышло в окно терминала:
Error 403
403 - Forbidden . That’s an error.
Client does not have access rights to the content so server is rejecting to give proper response. That’s all we know.
Ты получил ошибку 403 Forbidden, потому что сервер cbr.ru блокирует "подозрительные" запросы — такие, как от Go-http-client, который используется по умолчанию в http.Get().
На первый взгляд может показаться, что https.get из Node.js и http.Get из Go делают одно и то же, но поведение серверов часто зависит от заголовков, особенно от User-Agent.
📌 Почему Node.js не получает 403, а Go — получает?
Всё дело в заголовках, которые по умолчанию отправляются при запросе.
По умолчанию:
Go (http.Get): User-Agent: Go-http-client/1.1 Это часто считается "ботом", и многие сайты (включая CBR) блокируют такие User-Agent'ы.
- Node.js (https.get):Node.js не отправляет вообще никакого User-Agent по умолчанию, но некоторые сайты (включая cbr.ru) не блокируют пустой User-Agent или работают с curl-подобными клиентами нормально.
🧠 Что можно сделать в Go, чтобы было как в Node.js?
Если хочешь, можешь просто убрать User-Agent, но лучше подставить нормальный, например от Chrome:
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
💡 Резюме:
Клиент
Заголовок
User-Agent
по умолчанию
Блокируется CBR?
Go
Go-http-client/1.1
✅ Да
Node.js
Пустой / не указан
❌ Нет
Браузер
Полный User-Agent браузера (Chrome, etc.)
❌ Нет
Чтобы получить нормальный XML с https://cbr.ru/rss/RssCurrency без ошибки 403, надо в HTTP-запросе обязательно указать корректный заголовок User-Agent, имитируя обычный браузер.
Вот самый простой и надёжный вариант на Go:
package main import ( "fmt" "io" "net/http" ) func main() { url := "https://cbr.ru/rss/RssCurrency" req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println("Ошибка создания запроса:", err) return } // Устанавливаем User-Agent, как у браузера req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("Ошибка выполнения запроса:", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { fmt.Println("Ошибка: получен статус", resp.Status) return } body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Ошибка чтения тела:", err) return } fmt.Println(string(body)) }
Что тут важно:
- User-Agent — самый главный момент, без него сервер CBR даёт 403.
- Используем http.NewRequest и client.Do(), чтобы задать кастомные заголовки.
Да получил нормальный xml: go run main.go
Отлично, что получил корректный XML!Теперь можем сделать удобный парсер — извлечь из тега список валют и курсов в map[string]string или структуру.
Напишу пример, как распарсить именно это поле description из RSS и получить курс каждой валюты:
package main import ( "encoding/xml" "fmt" "io" "net/http" "strings" ) // Структуры для парсинга RSS с валютами type RSS struct { Channel Channel `xml:"channel"` } type Channel struct { Item Item `xml:"item"` } type Item struct { Description string `xml:"description"` } func main() { url := "https://cbr.ru/rss/RssCurrency" req, _ := http.NewRequest("GET", url, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } var rss RSS if err := xml.Unmarshal(body, &rss); err != nil { panic(err) } // description содержит много строк с валютами и курсами desc := rss.Channel.Item.Description // Разобьём по строкам lines := strings.Split(desc, "\n") // Словарь валют и курсов currencies := make(map[string]string) for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } // Каждая строка в формате "Валюта - курс" parts := strings.SplitN(line, " - ", 2) if len(parts) == 2 { currencies[parts[0]] = parts[1] } } // Выводим все курсы for cur, val := range currencies { fmt.Printf("%s: %s\n", cur, val) } }
Что делает код:
- Загружает RSS с CBR (с нужным User-Agent).
- Распарсивает XML в структуру.
- Извлекает поле description с курсами валют.
- Делит на строки, потом каждую строку — на валюту и курс.
- Сохраняет в map и выводит.
Сделаем по ключу — чтобы ты запускал программу с аргументом, например:
go run main.go usd
И программа выводила:
usd: 78,71
Для этого:
- Добавим мапу с соответствием длинных названий валют и их коротких кодов.
- Считаем аргумент из командной строки (код валюты в нижнем регистре).
- Если есть — выведем курс. Если нет — скажем, что не найден.
Вот полный пример:
package main import ( "encoding/xml" "fmt" "io" "net/http" "os" "strings" ) // Структуры для парсинга RSS type RSS struct { Channel Channel `xml:"channel"` } type Channel struct { Item Item `xml:"item"` } type Item struct { Description string `xml:"description"` } func main() { if len(os.Args) < 2 { fmt.Println("Использование: go run main.go <код валюты (например, usd)>") return } key := strings.ToLower(os.Args[1]) // Соответствие полного названия и короткого кода currencyMap := map[string]string{ "Австралийский доллар": "aud", "Азербайджанский манат": "azn", "Армянский драм": "amd", "Бат": "thb", "Белорусский рубль": "byn", "Болгарский лев": "bgn", "Бразильский реал": "brl", "Вона": "krw", "Гонконгский доллар": "hkd", "Гривна": "uah", "Датская крона": "dkk", "Дирхам ОАЭ": "aed", "Доллар США": "usd", "Донг": "vnd", "Евро": "eur", "Египетский фунт": "egp", "Злотый": "pln", "Иена": "jpy", "Индийская рупия": "inr", "Канадский доллар": "cad", "Катарский риал": "qar", "Лари": "gel", "Молдавский лей": "mdl", "Новозеландский доллар": "nzd", "Новый туркменский манат": "tmt", "Норвежская крона": "nok", "Румынский лей": "ron", "Рупия": "idr", "Рэнд": "zar", "СДР (специальные права заимствования)": "xdr", "Сербский динар": "rsd", "Сингапурский доллар": "sgd", "Сом": "kgs", "Сомони": "tjs", "Тенге": "kzt", "Турецкая лира": "try", "Узбекский сум": "uzs", "Форинт": "huf", "Фунт стерлингов": "gbp", "Чешская крона": "czk", "Шведская крона": "sek", "Швейцарский франк": "chf", "Юань": "cny", } url := "https://cbr.ru/rss/RssCurrency" req, _ := http.NewRequest("GET", url, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } var rss RSS if err := xml.Unmarshal(body, &rss); err != nil { panic(err) } desc := rss.Channel.Item.Description lines := strings.Split(desc, "\n") currencies := make(map[string]string) for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } parts := strings.SplitN(line, " - ", 2) if len(parts) == 2 { currencies[parts[0]] = parts[1] } } // Найдём полное название по коду var fullName string for name, code := range currencyMap { if code == key { fullName = name break } } if fullName == "" { fmt.Printf("Валюта с кодом '%s' не найдена.\n", key) return } val, ok := currencies[fullName] if !ok { fmt.Printf("Курс для валюты '%s' не найден.\n", fullName) return } fmt.Printf("%s: %s\n", key, val) }
Как использовать:
go run main.go usd
Вывод:
usd: 78,71
Вывести все по ключу list. И если запуск без ключей - вывести справку по правильному использованию проги. Корректировка. По ключу list выводить: "Полное название" короткое: курс
Давай по ключу list выводить все валюты в формате:Австралийский доллар aud: 51,41
Обновил код соответствующим образом — теперь list выводит полный текст названия, затем короткий код, двоеточие и курс:
package main import ( "encoding/xml" "fmt" "io" "net/http" "os" "strings" ) type RSS struct { Channel Channel `xml:"channel"` } type Channel struct { Item Item `xml:"item"` } type Item struct { Description string `xml:"description"` } func main() { if len(os.Args) < 2 { printHelp() return } key := strings.ToLower(os.Args[1]) currencyMap := map[string]string{ "Австралийский доллар": "aud", "Азербайджанский манат": "azn", "Армянский драм": "amd", "Бат": "thb", "Белорусский рубль": "byn", "Болгарский лев": "bgn", "Бразильский реал": "brl", "Вона": "krw", "Гонконгский доллар": "hkd", "Гривна": "uah", "Датская крона": "dkk", "Дирхам ОАЭ": "aed", "Доллар США": "usd", "Донг": "vnd", "Евро": "eur", "Египетский фунт": "egp", "Злотый": "pln", "Иена": "jpy", "Индийская рупия": "inr", "Канадский доллар": "cad", "Катарский риал": "qar", "Лари": "gel", "Молдавский лей": "mdl", "Новозеландский доллар": "nzd", "Новый туркменский манат": "tmt", "Норвежская крона": "nok", "Румынский лей": "ron", "Рупия": "idr", "Рэнд": "zar", "СДР (специальные права заимствования)": "xdr", "Сербский динар": "rsd", "Сингапурский доллар": "sgd", "Сом": "kgs", "Сомони": "tjs", "Тенге": "kzt", "Турецкая лира": "try", "Узбекский сум": "uzs", "Форинт": "huf", "Фунт стерлингов": "gbp", "Чешская крона": "czk", "Шведская крона": "sek", "Швейцарский франк": "chf", "Юань": "cny", } url := "https://cbr.ru/rss/RssCurrency" req, _ := http.NewRequest("GET", url, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("Ошибка запроса:", err) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Ошибка чтения ответа:", err) return } var rss RSS if err := xml.Unmarshal(body, &rss); err != nil { fmt.Println("Ошибка разбора XML:", err) return } desc := rss.Channel.Item.Description lines := strings.Split(desc, "\n") currencies := make(map[string]string) for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } parts := strings.SplitN(line, " - ", 2) if len(parts) == 2 { currencies[parts[0]] = parts[1] } } if key == "list" { // Выводим все валюты: Полное название + короткий код + курс for fullName, shortCode := range currencyMap { if val, ok := currencies[fullName]; ok { fmt.Printf("%s %s: %s\n", fullName, shortCode, val) } } return } // Вывод по ключу валюты var fullName string for name, code := range currencyMap { if code == key { fullName = name break } } if fullName == "" { fmt.Printf("Валюта с кодом '%s' не найдена.\n", key) return } val, ok := currencies[fullName] if !ok { fmt.Printf("Курс для валюты '%s' не найден.\n", fullName) return } fmt.Printf("%s: %s\n", key, val) } func printHelp() { fmt.Println("Использование:") fmt.Println(" go run main.go <код валюты> - вывести курс валюты по коду (например, usd, eur, gbp)") fmt.Println(" go run main.go list - вывести курсы всех валют (полное название + код + курс)") }
Пример вывода list:
Австралийский доллар aud: 51,41 Азербайджанский манат azn: 46,30 Армянский драм amd: 20,48 ... Доллар США usd: 78,71 Евро eur: 90,75 Фунт стерлингов gbp: 107,02