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

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

Селекторы

Фильтрация с помощью селекторов

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

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

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

Подход к программированию Фрагмент кода
Фильтрация с использованием селекторов (рекомендуется)
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.
    }
  }
}

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

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

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

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

Подход к программированию Фрагмент кода
Фильтрация по ID (рекомендуется)
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();

Несмотря на то, что оба фрагмента кода дают идентичные результаты, дополнительная фильтрация в первом фрагменте кода с использованием родительского идентификатора (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-пункте.

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

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

В таких условиях предпочтительнее присвоить сущностям метку, а затем отфильтровать их по 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 Ads, скрипты Google Ads не выполняют изменения немедленно. Вместо этого они пытаются объединить несколько изменений в пакеты, чтобы отправить один запрос, который выполнит несколько изменений. Такой подход ускоряет работу скриптов и снижает нагрузку на серверы Google Ads. Однако существуют некоторые шаблоны кода, которые заставляют скрипты Google Ads часто запускать пакет операций, что приводит к замедлению работы скрипта.

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

Подход к программированию Фрагмент кода
Отслеживайте обновленные элементы (рекомендуется).
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 Ads завершать операцию setCpc() и выполнять только одну операцию за раз. Первый подход, хотя и похож на второй, имеет дополнительное преимущество в поддержке пакетной обработки, поскольку вызов getCpc() выполняется в отдельном цикле от цикла, в котором вызывается setCpc() .

По возможности пользуйтесь услугами строителей.

Скрипты Google Ads поддерживают два способа создания новых объектов — конструкторы и методы создания. Конструкторы более гибкие, чем методы создания, поскольку они предоставляют доступ к объекту, созданному в результате вызова 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 Ads.

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

Подход к программированию Фрагмент кода
Отслеживайте обновленные элементы (рекомендуется).
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 Ads выполнять операцию по одной за раз. Первый подход, хотя и похож, позволяет выполнять пакетную обработку, поскольку мы вызываем operation.getResult() в другом цикле, нежели тот, в котором она была создана.

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

Одной из распространенных задач разработчиков является создание отчетов и обновление свойств сущностей (например, ставок по ключевым словам) на основе текущих значений эффективности. При необходимости обновления большого количества сущностей пакетная загрузка обычно обеспечивает лучшую производительность. Например, рассмотрим следующие скрипты, которые увеличивают максимальное значение CPC для ключевых слов, у которых 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();
Выбирать и обновлять ключевые слова по ID (менее оптимальный вариант)
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 Ads существует ограничение на количество объектов, которые можно получить или обновить за один запуск, и операции выбора и обновления во втором подходе учитываются в этом ограничении.

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

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

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

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

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

Отчетность

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

Когда вам нужно получить большое количество объектов и их статистику, часто лучше использовать отчеты, а не стандартные методы 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);
  }
Используйте итераторы AdApp (не рекомендуется).
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 , его использование не рекомендуется. Для полного контроля над запросами убедитесь, что вы используете вместо него GAQL .

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

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

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

Подход к программированию Фрагмент кода
Используйте два запроса (рекомендуется).
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)

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

При написании скриптов для учетных записей менеджеров, по возможности используйте 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.