Рекомендации

На этой странице представлены различные рекомендации по разработке сценариев Google Рекламы.

Селекторы

Фильтр с селекторами

По возможности используйте фильтры, чтобы запрашивать только те сущности, которые вам нужны. Использование правильных фильтров дает следующие преимущества:

  • Код проще и понятнее.
  • Скрипт будет выполняться намного быстрее.

Сравните следующие фрагменты кода:

Подход к кодированию Фрагмент кода
Фильтровать с помощью селекторов (рекомендуется)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 10')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  // Do work here.
}
Фильтровать по коду (не рекомендуется)
var keywords = AdsApp.keywords().get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor(
      'LAST_MONTH');
  if (stats.getClicks() > 10) {
    // Do work here.
  }
}

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

Избегайте пересечения иерархии кампании

Если вы хотите получить объекты на определенном уровне, используйте метод сбора на этом уровне вместо того, чтобы просматривать всю иерархию кампании. Помимо того, что это будет проще, это также будет работать намного лучше: системе не придется без необходимости читать информацию обо всех кампаниях и группах объявлений.

Сравните следующие фрагменты кода, которые извлекают все объявления в вашем аккаунте:

Подход к кодированию Фрагмент кода
Используйте соответствующий метод сбора (рекомендуется)

var ads = AdsApp.ads();

Пересечение иерархии (не рекомендуется)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next().
      adGroups().get();
  while (adGroups.hasNext()) {
    var ads = adGroups.next().ads().get();
    // Do your work here.
  }
}

Второй подход не рекомендуется, поскольку он пытается получить всю иерархию объектов (кампаний, групп объявлений), тогда как требуются только объявления.

Используйте определенные родительские методы доступа.

Иногда вам необходимо получить родительскую сущность полученного объекта. В этом случае вам следует использовать предоставленный метод доступа вместо получения целых иерархий.

Сравните следующие фрагменты кода, которые извлекают группы объявлений с текстовыми объявлениями, получившими более 50 кликов в прошлом месяце:

Подход к кодированию Фрагмент кода
Используйте соответствующий родительский метод доступа (рекомендуется)
var ads = AdsApp.ads()
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup) to an array.
}
Пересечение иерархии (не рекомендуется)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    if (ads.totalNumEntities() > 0) {
      // Store (campaign, adGroup) to an array.
    }
  }
}

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

Используйте определенные родительские фильтры

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

Сравните следующие фрагменты кода, которые извлекают список текстовых объявлений в указанной кампании и группе объявлений, получивших более 50 кликов в прошлом месяце.

Подход к кодированию Фрагмент кода
Используйте соответствующие фильтры родительского уровня (рекомендуется).
var ads = AdsApp.ads()
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup, ad) to
  // an array.
}
Пересечение иерархии (не рекомендуется)
var campaigns = AdsApp.campaigns()
    .withCondition('Name = "Campaign 1"')
    .get();

while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .withCondition('Name = "AdGroup 1"')
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    while (ads.hasNext()) {
      var ad = ads.next();
      // Store (campaign, adGroup, ad) to
      // an array.
    }
  }
}

Второй подход не рекомендуется, поскольку он повторяет иерархию кампаний и групп объявлений в вашем аккаунте, тогда как вам нужен только выбранный набор объявлений, а также их родительские кампании и группы объявлений. Первый подход ограничивает итерацию списком объявлений, применяя специальный фильтр для родительских объектов в селекторе.

По возможности используйте идентификаторы для фильтрации.

При фильтрации сущностей предпочтительно фильтровать сущности по их идентификаторам, а не по другим полям.

Рассмотрим следующие фрагменты кода, которые выбирают кампанию.

Подход к кодированию Фрагмент кода
Фильтровать по идентификатору (рекомендуется)
var campaign = AdsApp.campaigns()
    .withIds([12345])
    .get()
    .next();
Фильтровать по имени (менее оптимально)
var campaign = AdsApp.campaigns()
    .withCondition('Name="foo"')
    .get()
    .next();

Второй подход менее оптимален, поскольку мы фильтруем по полю, не имеющему идентификатора.

По возможности фильтруйте по родительским идентификаторам

При выборе объекта по возможности фильтруйте его по родительским идентификаторам. Это ускорит ваши запросы за счет ограничения списка объектов, извлекаемых серверами при фильтрации результатов.

Рассмотрим следующий фрагмент кода, который извлекает группу объявлений по ее идентификатору. Предположим, что идентификатор родительской кампании известен.

Подход к кодированию Фрагмент кода
Фильтровать по идентификаторам кампании и группы объявлений (рекомендуется)
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .withCondition('CampaignId="54678"')
    .get()
    .next();
Фильтровать только по идентификатору группы объявлений (менее оптимально).
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .get()
    .next();

Несмотря на то, что оба фрагмента кода дают одинаковые результаты, дополнительная фильтрация во фрагменте кода 1 с использованием родительского идентификатора (CampaignId="54678") делает код более эффективным, ограничивая список сущностей, которые серверу приходится перебирать при фильтрации результатов.

Используйте метки, если условий фильтрации слишком много.

Если у вас слишком много условий фильтрации, рекомендуется создать метку для обрабатываемых сущностей и использовать ее для фильтрации сущностей.

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

Подход к кодированию Фрагмент кода
Использовать ярлык (рекомендуется)
var label = AdsApp.labels()
    .withCondition('Name = "My Label"')
    .get()
    .next();
var campaigns = label.campaigns.get();
while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work
}
Создание сложных селекторов (не рекомендуется)
var campaignNames = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < campaignNames.length; i++) {
  campaignNames[i] = '"' + campaignNames[i] + '"';
}

var campaigns = AdsApp.campaigns
    .withCondition('CampaignName in [' + campaignNames.join(',') + ']')
    .get();

while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work.
}

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

Ограничьте количество условий в предложении IN.

При запуске сценариев обычно используется создание отчета по списку сущностей. Разработчики обычно достигают этого, создавая очень длинный запрос AWQL, который фильтрует идентификаторы объектов с помощью предложения IN. Этот подход отлично работает, когда количество сущностей ограничено. Однако по мере увеличения длины вашего запроса производительность вашего скрипта ухудшается по двум причинам:

  • Более длинный запрос требует больше времени для анализа.
  • Каждый идентификатор, который вы добавляете в предложение IN, представляет собой дополнительное условие для оценки и, следовательно, занимает больше времени.

В таких условиях предпочтительнее применить к сущностям метку, а затем фильтровать по LabelId .

Подход к кодированию Фрагмент кода
Примените метку и отфильтруйте по labelID (рекомендуется)
// The label applied to the entity is "Report Entities"
var label = AdsApp.labels()
    .withCondition('LabelName contains "Report Entities"')
    .get()
    .next();

var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT ' +
    'WHERE LabelId = "' + label.getId() + '"');
Создайте длинный запрос, используя предложение IN (не рекомендуется).
var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT WHERE ' +
    'AdGroupId IN (123, 456) and Id in (123,345, 456…)');

Обновления аккаунта

Пакетные изменения

Когда вы вносите изменения в объект Google Рекламы, скрипты Google Рекламы не выполняют изменения немедленно. Вместо этого он пытается объединить несколько изменений в пакеты, чтобы можно было выдать один запрос, выполняющий несколько изменений. Такой подход ускоряет работу ваших скриптов и снижает нагрузку на серверы Google Ads. Однако существуют некоторые шаблоны кода, которые вынуждают сценарии Google Рекламы часто сбрасывать пакет операций, что приводит к замедлению работы вашего сценария.

Рассмотрим следующий скрипт, который обновляет ставки для списка ключевых слов.

Подход к кодированию Фрагмент кода
Следите за обновленными элементами (рекомендуется)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

var list = [];
while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  list.push(keyword);
}

for (var i = 0; i < list.length; i++) {
  var keyword = list[i];
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}
Получение обновленных элементов в тесном цикле (не рекомендуется)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}

Второй подход не рекомендуется, поскольку вызов keyword.bidding().getCpc() заставляет скрипты Google Рекламы сбрасывать операцию setCpc() и выполнять только одну операцию за раз. Первый подход, хотя и похож на второй, имеет дополнительное преимущество, заключающееся в поддержке пакетной обработки, поскольку вызов getCpc() выполняется в отдельном цикле, отличном от того, в котором вызывается setCpc() .

Используйте строителей, когда это возможно

Скрипты Google Рекламы поддерживают два способа создания новых объектов: построители и методы создания. Построители более гибки, чем методы создания, поскольку они предоставляют доступ к объекту, созданному с помощью вызова API.

Рассмотрим следующие фрагменты кода:

Подход к кодированию Фрагмент кода
Используйте конструкторы (рекомендуется)
var operation = adGroup.newKeywordBuilder()
    .withText('shoes')
    .build();
var keyword = operation.getResult();
Использовать методы создания (не рекомендуется)
adGroup.createKeyword('shoes');
var keyword = adGroup.keywords()
    .withCondition('KeywordText="shoes"')
    .get()
    .next();

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

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

Рассмотрим следующие фрагменты кода, которые создают список ключевых слов и печатают идентификаторы вновь созданных ключевых слов:

Подход к кодированию Фрагмент кода
Следите за обновленными элементами (рекомендуется)
var keywords = [‘foo’, ‘bar’, ‘baz’];

var list = [];
for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  list.push(operation);
}

for (var i = 0; i < list.length; i++) {
  var operation = list[i];
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}
Получение обновленных элементов в тесном цикле (не рекомендуется)
var keywords = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}

Второй подход не является предпочтительным, поскольку он вызывает operation.getResult() в том же цикле, в котором создается операция, что заставляет скрипты Google Рекламы выполнять одну операцию за раз. Первый подход, хотя и похож, позволяет выполнять пакетную обработку, поскольку мы вызываем Operation.getResult() в другом цикле, отличном от того, в котором он был создан.

Рассмотрите возможность использования массовой загрузки для больших обновлений.

Общая задача, которую выполняют разработчики, — создание отчетов и обновление свойств объектов (например, ставок по ключевым словам) на основе текущих значений производительности. Когда вам необходимо обновить большое количество объектов, массовая загрузка обычно повышает производительность. Например, рассмотрим следующие скрипты, которые увеличивают максимальную цену за клик для ключевых слов, у которых TopImpressionPercentage > 0.4 ​​за последний месяц:

Подход к кодированию Фрагмент кода
Использовать массовую загрузку (рекомендуется)

var report = AdsApp.report(
  'SELECT AdGroupId, Id, CpcBid FROM KEYWORDS_PERFORMANCE_REPORT ' +
  'WHERE TopImpressionPercentage > 0.4 DURING LAST_MONTH');

var upload = AdsApp.bulkUploads().newCsvUpload([
  report.getColumnHeader('AdGroupId').getBulkUploadColumnName(),
  report.getColumnHeader('Id').getBulkUploadColumnName(),
  report.getColumnHeader('CpcBid').getBulkUploadColumnName()]);
upload.forCampaignManagement();

var reportRows = report.rows();
while (reportRows.hasNext()) {
  var row = reportRows.next();
  row['CpcBid'] = row['CpcBid'] + 0.02;
  upload.append(row.formatForUpload());
}

upload.apply();
Выберите и обновите ключевые слова по идентификатору (менее оптимально).
var reportRows = AdsApp.report('SELECT AdGroupId, Id, CpcBid FROM ' +
    'KEYWORDS_PERFORMANCE_REPORT WHERE TopImpressionPercentage > 0.4 ' +
    ' DURING LAST_MONTH')
    .rows();

var map = {
};

while (reportRows.hasNext()) {
  var row = reportRows.next();
  var adGroupId = row['AdGroupId'];
  var id = row['Id'];

  if (map[adGroupId] == null) {
    map[adGroupId] = [];
  }
  map[adGroupId].push([adGroupId, id]);
}

for (var key in map) {
  var keywords = AdsApp.keywords()
      .withCondition('AdGroupId="' + key + '"')
      .withIds(map[key])
      .get();

  while (keywords.hasNext()) {
    var keyword = keywords.next();
    keyword.bidding().setCpc(keyword.bidding().getCpc() + 0.02);
  }
}

Хотя второй подход дает довольно хорошую производительность, первый подход в данном случае предпочтительнее, поскольку

  • Скрипты Google Рекламы имеют ограничение на количество объектов, которые можно получить или обновить за один запуск, а операции выбора и обновления во втором подходе учитываются в этом пределе.

  • Массовая загрузка имеет более высокие ограничения как по количеству объектов, которые она может обновлять, так и по общему времени выполнения.

Группируйте массовые загрузки по кампаниям.

Создавая массовую загрузку, попробуйте сгруппировать операции по родительской кампании. Это повышает эффективность и снижает вероятность возникновения конфликтующих изменений/ошибок параллелизма.

Рассмотрим две задачи массовой загрузки, выполняемые параллельно. Один приостанавливает рекламу в группе объявлений; другой корректирует ставки по ключевым словам. Несмотря на то, что операции не связаны между собой, они могут применяться к объектам одной и той же группы объявлений (или к двум разным группам объявлений в одной кампании). В этом случае система заблокирует родительский объект (общую группу объявлений или кампанию), что приведет к блокировке задач массовой загрузки друг на друге.

Скрипты Google Рекламы могут оптимизировать выполнение одной задачи массовой загрузки, поэтому проще всего одновременно запускать только одну задачу массовой загрузки для каждого аккаунта. Если вы решите запустить более одной массовой загрузки для каждого аккаунта, убедитесь, что массовые загрузки выполняются во взаимоисключающем списке кампаний (и их дочерних объектов) для достижения оптимальной производительности.

Составление отчетов

Используйте отчеты для получения статистики

Если вы хотите получить большое количество объектов и их статистику, часто лучше использовать отчеты, а не стандартные методы AdsApp. Использование отчетов является предпочтительным по следующим причинам:

  • Отчеты повышают производительность при выполнении больших запросов.
  • Отчеты не будут превышать обычные квоты на получение.

Сравните следующие фрагменты кода, которые получают данные о кликах, показах, стоимости и тексте для всех ключевых слов, получивших более 50 кликов в прошлом месяце:

Подход к кодированию Фрагмент кода
Использовать отчеты (рекомендуется)
  report = AdsApp.search(
      'SELECT ' +
      '   ad_group_criterion.keyword.text, ' +
      '   metrics.clicks, ' +
      '   metrics.cost_micros, ' +
      '   metrics.impressions ' +
      'FROM ' +
      '   keyword_view ' +
      'WHERE ' +
      '   segments.date DURING LAST_MONTH ' +
      '   AND metrics.clicks > 50');
  while (report.hasNext()) {
    var row = report.next();
    Logger.log('Keyword: %s Impressions: %s ' +
        'Clicks: %s Cost: %s',
        row.adGroupCriterion.keyword.text,
        row.metrics.impressions,
        row.metrics.clicks,
        row.metrics.cost);
  }
Используйте итераторы AdsApp (не рекомендуется)
var keywords = AdsApp.keywords()
    .withCondition('metrics.clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor('LAST_MONTH');
  Logger.log('Keyword: %s Impressions: %s ' +
      'Clicks: %s Cost: %s',
      keyword.getText(),
      stats.getImpressions(),
      stats.getClicks(),
      stats.getCost());
}

Второй подход не является предпочтительным, поскольку он перебирает ключевые слова и извлекает статистику по одному объекту за раз. В этом случае отчеты выполняются быстрее, поскольку все данные извлекаются за один вызов и передаются по мере необходимости. Кроме того, ключевые слова, полученные при втором подходе, учитываются в квоте вашего скрипта на количество объектов, полученных с помощью вызова get() .

Используйте поиск вместо отчета

Метод отчета был создан для старой инфраструктуры и выводит результаты в плоском формате, даже если вы используете GAQL. Это означает, что ему приходится преобразовывать результаты запроса в соответствии со старым стилем, который поддерживается не для всех полей и увеличивает издержки при каждом вызове.

Вместо этого мы предлагаем вам использовать поиск, чтобы воспользоваться всеми возможностями новых отчетов Google Ads API.

Предпочитайте GAQL AWQL

Хотя AWQL по-прежнему поддерживается в запросах отчетов и вызовах withCondition , он выполняется через уровень перевода, который не имеет полной совместимости с настоящим AWQL. Чтобы иметь полный контроль над своими запросами, убедитесь, что вы используете GAQL .

Если у вас есть существующие запросы AWQL, которые вы хотите перевести, у нас есть инструмент миграции запросов .

Не выбирайте больше строк, чем вам нужно

Скорость выполнения отчетов (и селекторов) зависит от общего количества строк, которые будет возвращен отчетом, независимо от того, перебираете ли вы их . Это означает, что вам всегда следует использовать определенные фильтры, чтобы максимально минимизировать набор результатов в соответствии с вашим вариантом использования.

Например, предположим, что вы хотите найти группы объявлений со ставками, выходящими за пределы определенного диапазона. Было бы быстрее сделать два отдельных запроса: один для ставок ниже нижнего порога, а другой для ставок выше верхнего порога, чем получать все группы объявлений и игнорировать те, которые вас не интересуют.

Подход к кодированию Фрагмент кода
Используйте два запроса (рекомендуется)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros < 1000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros > 2000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
Фильтрация по общему запросу (не рекомендуется)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group');

while (report.hasNext()) {
  var row = report.next();
  var cpcBidMicros = row.adGroup.cpcBidMicros;
  if (cpcBidMicros < 1000000 || cpcBidMicros > 2000000) {
    adGroups.push(row.adGroup);
  }
}

Скрипты Ads Manager (MCC)

Предпочитайте выполнение InParallel вместо последовательного выполнения.

При написании скриптов для управляющих учетных записей по возможности используйте executeInParallel() вместо последовательного выполнения. executeInParallel() увеличивает время обработки вашего скрипта (до одного часа) и до 30 минут на каждую обработанную учетную запись (вместо 30 минут в совокупности для последовательного выполнения). Для получения более подробной информации посетите нашу страницу лимитов .

Таблицы

Используйте пакетные операции при обновлении электронных таблиц

При обновлении электронных таблиц старайтесь использовать методы массовых операций (например, getRange() ), а не методы, которые обновляют одну ячейку за раз.

Рассмотрим следующий фрагмент кода, который генерирует фрактальный узор в электронной таблице.

Подход к кодированию Фрагмент кода
Обновить диапазон ячеек за один вызов (рекомендуется)
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  colors[y] = new Array(100);
  for (var x = 0; x < 100; x++) {
    colors[y][x] = getColor_(xcoord, ycoord);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
Обновляйте по одной ячейке (не рекомендуется)
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  for (var x = 0; x < 100; x++) {
    var c = getColor_(xcoord, ycoord);
    cell.offset(y, x).setBackgroundColor(c);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
  SpreadsheetApp.flush();
}

Хотя таблицы Google пытаются оптимизировать второй фрагмент кода путем кэширования значений, он по-прежнему дает низкую производительность по сравнению с первым фрагментом из-за количества выполняемых вызовов API.