Обработка ошибки 404–Not Found функцией Web.Contents() в Power Query и Power BI
Текст представляет собой адаптированный перевод статьи Chris Webb (Крис Вебб), оригинал – Handling 404–Not Found Errors With Web.Contents() In Power Query And Power BI. Рассматривается англоязычный Power Query.
Крис Вебб (Chris Webb) — независимый эксперт, консультант по технологиям Analysis Services, MDX, Power Pivot, DAX, Power Query и Power BI. Его блог — это кладезь информации на тему перечисленных технологий. Вот уже более 10 лет он пишет про BI-решения от Microsoft. Количество его статей перевалило за 1000! Также Крис выступает на большом количестве различных конференций вроде SQLBits, PASS Summit, PASS BA Conference, SQL Saturdays и участвует в различных сообществах.
Крис любезно разрешил нам переводить его статьи на русский язык. И это одна из них.

Обработка ошибки 404–Not Found функцией Web.Contents() в Power Query и Power BI

Одно из странных свойств функции Web.Contents() в Power Query и Power BI это то, что она не обрабатывает стандартные ошибки привычным в языке М способом. Трудно сказать баг это или фича, но подобная ситуация встречается довольно часто. Поэтому мы решили поделиться описанием проблемы и способом обойти её.

Во-первых, так в чём проблема? Представим, что мы хотим импортировать в Power Query или список курсов UK Microsoft BI and SQL Server компании Technitrain. Для этого можно воспользоваться функцией Web.Contents() и получить данные с RSS feed. Например, так:
let  
   Source = Web.Contents("http://technitrain.com/feed/")  
in  
   Source
Но что произойдёт, если будет ошибка в адресе URL или другие проблемы на сайте? Следующая ссылка вернёт ошибку 404 – Not Found, т.к. страница не существует:

http://technitrain.com/blahblah

Если мы сделаем такой запрос:
let  
   Source = Web.Contents("http://technitrain.com/blahblah")  
in  
   Source
Естественно, получим ошибку:

DataSource.Error: Web.Contents failed to get contents from 'http://technitrain.com/blahblah' (404): Not Found

(Ошибка источника данных: Web.Contents не удалось получить данные с 'http://technitrain.com/blahblah' (404): Не найдено)
Хоть это и реальная проблема, попробуем обработать ошибку с помощью оператора try/otherwise:
let  
   Source = try  
               Web.Contents("http://technitrain.com/blahblah")  
            otherwise  
               "Error!"  
in  
   Source
И это не работает, мы получаем ту же самую ошибку. Что удивительно, в некоторых случаях с более сложным кодом блок try/otherwise срабатывает. Например, здесь:
let  
   Source = try  
               Xml.Tables(  
                Web.Contents("http://technitrain.com/blahblah")  
               )  
            otherwise  
               "Error!"  
in  
   Source
Ошибка перехватывается:
На этом форуме высказывается предположение, что это связано с ленивыми вычислениями. Но мы так и не смогли определить в каких ситуациях это работает, а в каких нет.

Вместо этого, можно обрабатывать конкретные ошибки HTTP, используя опцию ManualStatusHandling функции Web.Contents():
let  
   Source = Web.Contents(  
        "http://technitrain.com/blahblah",  
        [ManualStatusHandling={404}])  
in  
   Source
Опция ManualStatusHandling принимает список кодов HTTP ошибок. Если запустить пример выше, то мы увидим, что запрос перестал генерировать ошибку.

Следующая проблема – как узнать сработал запрос или нет? Оказывается, определить это можно, просматривая метаданные, связанные с переменной Source. Вот пример как использовать функцию Value.Metadata() применительно к переменной Source:
let  
   Source = Web.Contents(  
       "http://technitrain.com/blahblah",  
       [ManualStatusHandling={404}]),  
   GetMetadata = Value.Metadata(Source)  
in  
   GetMetadata
Запрос возвращает запись, которая среди прочего содержит HTTP код ответа:
Подытожим, для перехвата ошибки 404 можно использовать такой шаблон:
let  
   Source = Web.Contents(  
       "http://technitrain.com/blahblah",  
       [ManualStatusHandling={404}]),  
   GetMetadata = Value.Metadata(Source),  
   GetResponseStatus = GetMetadata[Response.Status],  
   Output = if GetResponseStatus=404 then "Error!" else Source  
in  
   Output