Курс доллара ЦБ РФ в Excel на Mac: почему это не «два клика»
Казалось бы, простая задача: подтягивать в Excel официальный курс USD/RUB с сайта Банка России. На Windows это решается за пару минут — Power Query «Из интернета» или функция WEBSERVICE. На Mac всё иначе, и путь до рабочего решения оказался куда интереснее, чем ожидалось.
Почему привычные способы не работают на Mac
Первое, что приходит в голову, — функция =WEBSERVICE(url). Она действительно есть в списке функций Excel для Mac и даже не выдаёт ошибку при вводе формулы. Но по документации Microsoft она «полагается на возможности операционной системы Windows» и на Mac попросту не возвращает результат. Формально функция существует, реально — не работает.
Второй кандидат — Power Query, вкладка «Данные → Получить данные». На Windows там есть коннектор «Из интернета» (From Web). На Mac его нет вообще: список источников ограничен Excel Workbook, Text/CSV, XML, JSON, SQL Server, SharePoint Online List и OData. Веб-адреса среди них нет.
Получается, оба штатных инструмента, которыми это делается на Windows, на Mac отсутствуют или не функциональны. Нужен обходной путь.
Какие варианты вообще есть
Прежде чем садиться за код, полезно видеть всю карту вариантов.
Ручной способ. Официальный XML с курсами лежит по адресу https://www.cbr.ru/scripts/XML_daily.asp (без даты отдаёт последний курс; доллар — валюта с кодом ID="R01235"). Можно открыть эту ссылку в браузере и вручную вписывать значение в ячейку. Ноль автоматизации, зато ноль настройки.
Скрипт на самой системе + Power Query из локального файла. Отдельный shell-скрипт на Mac (curl + разбор XML) сам скачивает курс и сохраняет его в CSV на диске, а launchd (аналог cron для macOS) запускает этот скрипт по расписанию. В Excel при этом ничего экзотического не нужно — просто подключаете CSV через Power Query «Из текста/CSV», который на Mac работает штатно, поскольку источник теперь локальный файл, а не веб-адрес. Из всех вариантов этот самый надёжный: никаких макросов, предупреждений безопасности и капризов VBA.
VBA-макрос внутри самого файла Excel. Один файл, никаких внешних скриптов — но на Mac у VBA нет доступа к сетевым объектам Windows, так что придётся звать curl в обход, через AppleScript. Именно этот путь я и прошёл целиком — и набил на нём все возможные шишки.
xlwings. Python-библиотека, официально работающая и на Windows, и на Mac: можно написать Python-функцию, которая ходит в интернет, и вызывать её из ячейки как обычную формулу. Хороший вариант, если вы и так работаете с Python.
Office Scripts + Power Automate. Полностью облачный путь: скрипт выполняется на серверах Microsoft, а не на вашем Mac, поэтому все локальные ограничения macOS вообще ни при чём. Плата за это — обязательная подписка Microsoft 365, файл должен жить в OneDrive/SharePoint, а лимиты бесплатного Power Automate могут поджимать при частых обновлениях.
Дальше — история о том, как я прошёл путь VBA-макроса от идеи до рабочего кода, наступив по дороге на три разные грабли.
Заход первый: сырые системные вызовы через libc
Первая идея была в лоб: раз на Mac нет WEBSERVICE, дёрнем curl напрямую из VBA через системную библиотеку libc.dylib — функции popen, fread, pclose, как будто пишем на Си:
Private Declare PtrSafe Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As LongPtr
Private Declare PtrSafe Function pclose Lib "libc.dylib" (ByVal file As LongPtr) As LongPtr
Private Declare PtrSafe Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As LongPtr, ByVal num As LongPtr, ByVal stream As LongPtr) As LongPtr
Function ExecShell(command As String) As String
Dim f As LongPtr, chunk As String * 512, r As LongPtr, res As String
f = popen(command, "r")
If f <> 0 Then
Do
r = fread(chunk, 1, Len(chunk) - 1, f)
If r <= 0 Then Exit Do
res = res & Left$(chunk, r)
Loop
pclose f
End If
ExecShell = res
End Function
Идея рабочая, и такой приём действительно встречается в старых блогах про Excel на Mac. Но на практике за него пришлось заплатить несколькими раундами ошибок компиляции подряд:
- «Only comments may appear after End Sub». Код случайно оказался вставлен внутрь пустого макроса
Sub USD() ... End Sub, который Excel сам создаёт, если попросить создать макрос с именем. VBA не разрешает объявлятьFunction/Sub/Declareвнутри другой процедуры. Решение простое: стереть всё содержимое модуля и вставить код заново без лишней обёртки. - «Type mismatch» на этапе компиляции. Переменная
r(число прочитанных байт изfread) была объявлена какLongPtr, а встроеннаяLeft$во втором аргументе жёстко ожидаетLong. На 64-битной архитектуре это разные типы, и компилятор ругается ещё до запуска. Лечится заменойr As LongPtrнаr As Long.
После этого код действительно скомпилировался. Но ощущение осталось: слишком много ручной возни с указателями и типами ради, по сути, одной команды в терминале.
Заход второй: MacScript вместо ручных указателей
В какой-то момент стало ясно, что бороться с LongPtr дальше — не лучшее вложение времени. У Excel для Mac есть встроенная функция именно для таких случаев — MacScript, которая выполняет произвольный код AppleScript и возвращает результат строкой. А в AppleScript есть команда do shell script, которая как раз умеет запускать curl и отдавать его вывод текстом. Никаких Declare, никаких указателей:
Function ExecShell(command As String) As String
Dim escaped As String
escaped = Replace(command, "\", "\\")
escaped = Replace(escaped, Chr(34), "\" & Chr(34))
ExecShell = MacScript("do shell script " & Chr(34) & escaped & Chr(34))
End Function
Вся сложность свелась к одной вещи — правильно экранировать кавычки внутри команды для AppleScript (сначала обратные слэши, потом двойные кавычки), а дальше MacScript делает всё остальное сам. Код стал короче, надёжнее и куда легче читается.
Финальный баг: локаль решает
Полный код макроса, который разбирает XML и кладёт курс в ячейку, выглядел так:
Sub GetUSDRate()
Dim xml As String, p As Long, p2 As Long, v As String
xml = ExecShell("curl -s ""https://www.cbr.ru/scripts/XML_daily.asp""")
p = InStr(xml, "Valute ID=""R01235""")
If p = 0 Then
MsgBox "Не удалось найти курс USD в ответе ЦБ РФ"
Exit Sub
End If
p = InStr(p, xml, "<Value>") + Len("<Value>")
p2 = InStr(p, xml, "</Value>")
v = Replace(Mid$(xml, p, p2 - p), ",", ".")
Range("A1").Value2 = "USD/RUB (ЦБ РФ):"
Range("B1").Value2 = CDbl(v)
End Sub
Компилировалось без единой ошибки, запускалось, курс из ЦБ РФ успешно скачивался и разбирался — и всё равно падало с run-time error 13: Type mismatch, уже во время выполнения. Причина пряталась в самой последней строке: CDbl(v).
ЦБ РФ отдаёт курс с запятой в качестве десятичного разделителя («80,1234»), поэтому строкой раньше мы меняем запятую на точку. Но CDbl — локале-зависимая функция: она смотрит на региональные настройки системы, чтобы понять, какой символ считать разделителем дробной части. На Mac с русской локалью система как раз ожидает запятую — и, получив строку с точкой, CDbl не может распознать её как число.
Решение — заменить CDbl на Val. В отличие от CDbl, функция Val всегда трактует точку как десятичный разделитель, независимо от языка и региона системы:
Range("B1").Value2 = Val(v)
После этой правки всё наконец заработало стабильно.
Рабочий макрос целиком
Function ExecShell(command As String) As String
Dim escaped As String
escaped = Replace(command, "\", "\\")
escaped = Replace(escaped, Chr(34), "\" & Chr(34))
ExecShell = MacScript("do shell script " & Chr(34) & escaped & Chr(34))
End Function
Sub GetUSDRate()
Dim xml As String, p As Long, p2 As Long, v As String
xml = ExecShell("curl -s ""https://www.cbr.ru/scripts/XML_daily.asp""")
p = InStr(xml, "Valute ID=""R01235""")
If p = 0 Then
MsgBox "Не удалось найти курс USD в ответе ЦБ РФ"
Exit Sub
End If
p = InStr(p, xml, "<Value>") + Len("<Value>")
p2 = InStr(p, xml, "</Value>")
v = Replace(Mid$(xml, p, p2 - p), ",", ".")
Range("A1").Value2 = "USD/RUB (ЦБ РФ):"
Range("B1").Value2 = Val(v)
End Sub
Вставляется в модуль VBA (Alt+F11 → Insert → Module), запускается макрос GetUSDRate через Вид → Макросы. При первом запуске macOS может спросить разрешение на «Automation» для Excel — это нормально, do shell script идёт именно через этот механизм.
Несколько выводов напоследок
Excel для Mac и Excel для Windows — два похожих, но не идентичных продукта: часть возможностей Power Query и функций вроде WEBSERVICE тихо не работает на Mac, хотя в интерфейсе выглядит так, будто должна.
Если задача — просто «получать курс раз в час, даже когда Excel закрыт», то честнее выбрать отдельный shell-скрипт с launchd и подключение готового CSV через Power Query: меньше движущихся частей и не нужно давать файлу разрешение на запуск макросов. А если хочется решения в одном файле — рабочий макрос выше, с поправкой на локаль, теперь у вас есть.