В большинстве служб реализован синхронный механизм вызова API, требующий ожидания ответа на запрос. BatchJobService отличается от них тем, что позволяет выполнять операции с несколькими службами, не дожидаясь завершения операций.
В отличие от специальных операций mutate для служб, одно задание в BatchJobService обеспечивает работу со смешанным набором кампаний, групп объявлений, объявлений, критериев, ярлыков и элементов фида. Задания обрабатываются параллельно, и служба BatchJobService автоматически повторяет операции, при выполнении которых произошла временная ошибка, например RateExceededError
.
Кроме того, BatchJobService позволяет использовать в запросах временные идентификаторы, благодаря чему вы можете передавать зависимые операции одному заданию.
Поддерживаемые операции
BatchJobService поддерживает следующие операции:
Схема
Утилиты для XML-сериализации загружаемых операций и XML-десериализации полученных результатов есть в каждой клиентской библиотеке. Полную схему для запросов и ответов пакетных заданий можно найти по следующему адресу:
https://adwords.google.com/api/adwords/cm/v201702/BatchJobOpsService?wsdl
Процесс выполнения пакетных заданий
Чтобы выполнить пакетное задание:
- Создайте объект
BatchJob
и получите егоuploadUrl
из ответа методаmutate()
. - Загрузите на
uploadUrl
список операций, которые необходимо выполнить. - Периодически проверяйте статус (
status
) пакетного задания, пока он не примет значениеCANCELED
илиDONE
. - Скачайте результаты задания с
downloadUrl
и проверьте их на наличие ошибок обработки (processingErrors
).
Вы также можете отменить BatchJob
на стадии выполнения AWAITING_FILE
или ACTIVE
, задав в поле status
значение CANCELING
.
Создание пакетного задания
Чтобы создать пакетное задание, отправьте операцию ADD
с новым объектом BatchJob
.
// Create a BatchJob. BatchJobOperation addOp = new BatchJobOperation(); addOp.setOperator(Operator.ADD); addOp.setOperand(new BatchJob()); BatchJob batchJob = batchJobService.mutate(new BatchJobOperation[] {addOp}).getValue(0); // Get the upload URL from the new job. String uploadUrl = batchJob.getUploadUrl().getUrl(); System.out.printf("Created BatchJob with ID %d, status '%s' and upload URL %s.%n", batchJob.getId(), batchJob.getStatus(), uploadUrl);
Статус задания (status
) на этом этапе будет AWAITING_FILE
.
Создание операций для пакетного задания
Операции для пакетного задания создаются так же, как и при использовании синхронных служб API. Например, следующий код создает объекты CampaignOperation
для добавления новых кампаний:
List<CampaignOperation> operations = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_CAMPAIGNS_TO_ADD; i++) { Campaign campaign = new Campaign(); campaign.setName(String.format("Batch Campaign %s.%s", namePrefix, i)); // Recommendation: Set the campaign to PAUSED when creating it to prevent // the ads from immediately serving. Set to ENABLED once you've added // targeting and the ads are ready to serve. campaign.setStatus(CampaignStatus.PAUSED); campaign.setId(tempIdGenerator.next()); campaign.setAdvertisingChannelType(AdvertisingChannelType.SEARCH); Budget budget = new Budget(); budget.setBudgetId(budgetId); campaign.setBudget(budget); BiddingStrategyConfiguration biddingStrategyConfiguration = new BiddingStrategyConfiguration(); biddingStrategyConfiguration.setBiddingStrategyType(BiddingStrategyType.MANUAL_CPC); // You can optionally provide a bidding scheme in place of the type. ManualCpcBiddingScheme cpcBiddingScheme = new ManualCpcBiddingScheme(); cpcBiddingScheme.setEnhancedCpcEnabled(false); biddingStrategyConfiguration.setBiddingScheme(cpcBiddingScheme); campaign.setBiddingStrategyConfiguration(biddingStrategyConfiguration); CampaignOperation operation = new CampaignOperation(); operation.setOperand(campaign); operation.setOperator(Operator.ADD); operations.add(operation); } return operations;
Если вы создаете зависимые объекты (например, кампанию с группами объявлений, объявлениями и ключевыми словами), можно использовать временные идентификаторы в операциях ADD
.
// Create a temporary ID generator that will produce a sequence of descending negative numbers. Iterator<Long> tempIdGenerator = new AbstractSequentialIterator<Long>(-1L) { @Override protected Long computeNext(Long previous) { return Long.MIN_VALUE == previous ? null : previous - 1; } }; // Use a random UUID name prefix to avoid name collisions. String namePrefix = UUID.randomUUID().toString(); // Create the mutate request that will be sent to the upload URL. List<Operation> operations = new ArrayList<>(); // Create and add an operation to create a new budget. BudgetOperation budgetOperation = buildBudgetOperation(tempIdGenerator, namePrefix); operations.add(budgetOperation); // Create and add operations to create new campaigns. List<CampaignOperation> campaignOperations = buildCampaignOperations(tempIdGenerator, namePrefix, budgetOperation); operations.addAll(campaignOperations); // Create and add operations to create new negative keyword criteria for each campaign. operations.addAll(buildCampaignCriterionOperations(campaignOperations)); // Create and add operations to create new ad groups. List<AdGroupOperation> adGroupOperations = new ArrayList<>(buildAdGroupOperations(tempIdGenerator, namePrefix, campaignOperations)); operations.addAll(adGroupOperations); // Create and add operations to create new ad group criteria (keywords). operations.addAll(buildAdGroupCriterionOperations(adGroupOperations)); // Create and add operations to create new ad group ads (text ads). operations.addAll(buildAdGroupAdOperations(adGroupOperations));
Отправка операций на URL загрузки
Создав набор операций для пакетного задания, отправьте их на URL загрузки.
Если вы используете утилиту из клиентской библиотеки, вам не придется беспокоиться обо всех тонкостях процесса. Утилита будет создавать и отправлять запросы за вас. Вам нужно будет только предоставить методы для следующих двух вариантов действий:
- Загрузка сразу всех операций.
- Загрузка операций с использованием нескольких вызовов утилиты.
Вариант 1. Загрузка сразу всех операций
В приведенном ниже примере используется утилита BatchJobHelper
из клиентской библиотеки Java для загрузки сразу всех операций.
// Use a BatchJobHelper to upload all operations. BatchJobHelper batchJobHelper = adWordsServices.getUtility(session, BatchJobHelper.class); batchJobHelper.uploadBatchJobOperations(operations, uploadUrl);
Вариант 2. Загрузка операций с использованием нескольких вызовов утилиты
В приведенном ниже примере используется утилита BatchJobHelper
из клиентской библиотеки Java для загрузки операций путем нескольких вызовов метода uploadIncrementalBatchJobOperations()
.
// Use a BatchJobUploadHelper to upload all operations.
BatchJobHelper batchJobUploadHelper = new BatchJobHelper(session);
BatchJobUploadStatus startingUploadStatus =
new BatchJobUploadStatus(0, URI.create(batchJob.getUploadUrl().getUrl()));
BatchJobUploadResponse uploadResponse;
// Create and upload the first operation to create a new budget.
BudgetOperation budgetOperation = buildBudgetOperation(tempIdGenerator, namePrefix);
uploadResponse = batchJobUploadHelper.uploadIncrementalBatchJobOperations(
Lists.newArrayList(budgetOperation),
false, /* pass isLastRequest = false */
startingUploadStatus);
System.out.printf("First upload response: %s%n", uploadResponse);
// Create and upload intermediate operations to create new campaigns.
List<CampaignOperation> campaignOperations =
buildCampaignOperations(budgetOperation, tempIdGenerator, namePrefix);
uploadResponse = batchJobUploadHelper.uploadIncrementalBatchJobOperations(
campaignOperations,
false, /* pass isLastRequest = false */
uploadResponse.getBatchJobUploadStatus());
System.out.printf("Intermediate upload response: %s%n", uploadResponse);
// Upload more intermediate requests...
// Create and upload operations to create new ad group ads (text ads).
// This is the final upload request for the BatchJob.
uploadResponse = batchJobUploadHelper.uploadIncrementalBatchJobOperations(
buildAdGroupAdOperations(adGroupOperations),
true, /* pass isLastRequest = true */
uploadResponse.getBatchJobUploadStatus());
System.out.printf("Last upload response: %s%n", uploadResponse);
Проверка статуса пакетного задания
После загрузки операций пакетное задание будет поставлено в очередь на обработку. Вам нужно будет периодически проверять его статус, пока он не примет значение CANCELED
или DONE
. Чтобы избежать слишком частой проверки, используйте алгоритм экспоненциальной отсрочки. В приведенном ниже примере после первой попытки система будет выжидать 30 секунд, после второй – 60, после третьей – 120 и т. д.
int pollAttempts = 0; boolean isPending; Selector selector = new SelectorBuilder() .fields(BatchJobField.Id, BatchJobField.Status, BatchJobField.DownloadUrl, BatchJobField.ProcessingErrors, BatchJobField.ProgressStats) .equalsId(batchJob.getId()) .build(); do { long sleepSeconds = (long) Math.scalb(30, pollAttempts); System.out.printf("Sleeping %d seconds...%n", sleepSeconds); Thread.sleep(sleepSeconds * 1000); batchJob = batchJobService.get(selector).getEntries(0); System.out.printf( "Batch job ID %d has status '%s'.%n", batchJob.getId(), batchJob.getStatus()); pollAttempts++; isPending = PENDING_STATUSES.contains(batchJob.getStatus()); } while (isPending && pollAttempts < MAX_POLL_ATTEMPTS);
Скачивание результатов выполнения пакетного задания и проверка на наличие ошибок
На этом этапе задание будет иметь один из следующих двух статусов:
Статус | Описание | Действия |
---|---|---|
DONE |
Служба BatchJobService успешно произвела синтаксический разбор и попыталась выполнить каждую из загруженных операций. |
|
CANCELED |
При синтаксическом разборе загруженных операций произошла ошибка. |
|
URL скачивания (downloadURL) возвратит элемент mutateResult
для всех выполненных операций. У каждого результата будут следующие атрибуты, как определено в файле BatchJobOpsService.wsdl:
Атрибут | Тип | Описание |
---|---|---|
result |
Operand |
Если операция была успешно выполнена, у этого атрибута будет только один из следующих дочерних элементов:
index . Например, если операция CampaignOperation была выполнена успешно, будет возвращен элемент Campaign .
|
errorList |
ErrorList |
Если же она закончилась неудачей, вы увидите один или несколько элементов errors . Каждый из них будет представлять собой экземпляр ApiError или одного из его подклассов. |
index |
long |
Номер операции (на основе 0). Используйте его, чтобы соотнести результат с соответствующей операцией из пакета. |
В примере ниже показан один из способов обработки результатов, полученных с URL скачивания.
if (batchJob.getDownloadUrl() != null && batchJob.getDownloadUrl().getUrl() != null) { BatchJobMutateResponse mutateResponse = batchJobHelper.downloadBatchJobMutateResponse(batchJob.getDownloadUrl().getUrl()); System.out.printf("Downloaded results from %s:%n", batchJob.getDownloadUrl().getUrl()); for (MutateResult mutateResult : mutateResponse.getMutateResults()) { String outcome = mutateResult.getErrorList() == null ? "SUCCESS" : "FAILURE"; System.out.printf(" Operation [%d] - %s%n", mutateResult.getIndex(), outcome); } } else { System.out.println("No results available for download."); }
Поле processingErrors
будет содержать все ошибки, произошедшие во время предварительной обработки загруженных операций (например, вызванные повреждением входного файла). Вот пример обработки такого рода ошибок:
if (batchJob.getProcessingErrors() != null) { int i = 0; for (BatchJobProcessingError processingError : batchJob.getProcessingErrors()) { System.out.printf( " Processing error [%d]: errorType=%s, trigger=%s, errorString=%s, fieldPath=%s" + ", reason=%s%n", i++, processingError.getApiErrorType(), processingError.getTrigger(), processingError.getErrorString(), processingError.getFieldPath(), processingError.getReason()); } } else { System.out.println("No processing errors found."); }
Использование временных идентификаторов
Одним из важнейших достоинств службы BatchJobService является поддержка временных идентификаторов.
Временный идентификатор – это отрицательное число (long
), которое позволяет операциям из пакетного задания ссылаться на результат выполнения операции ADD
из предыдущей операции в том же задании. Просто укажите отрицательное число в операции ADD
для родительского объекта, а затем используйте этот идентификатор в последующих операциях mutate()
для других зависимых объектов в том же пакетном задании.
Обычно временные идентификаторы используются для создания полной кампании в одном пакетном задании. Например, можно создать одно задание, содержащее операции ADD
, со следующими идентификаторами для каждого operand
:
Вот к каким результатам приведет эта последовательность:
- Будет добавлена кампания с временным идентификатором
-1
.- Будет добавлена группа объявлений с временным идентификатором
-2
для кампании-1
.- Будет добавлено объявление для группы объявлений с временным идентификатором
-2
. - Будут добавлены критерии (например, ключевые слова) для группы объявлений с временным идентификатором
-2
.
- Будет добавлено объявление для группы объявлений с временным идентификатором
- Будет добавлена группа объявлений с временным идентификатором
- Будет применен ярлык к кампании
-1
. - Будет добавлен минус-критерий (минус-слово) для кампании
-1
.
Отмена пакетного задания
Пакетное задание BatchJob
можно отменить, если его статус (status
) – AWAITING_FILE
или ACTIVE
. Просто отправьте запрос методу BatchJobService.mutate()
и передайте объект BatchJobOperation
со следующими настройками:
operator
=SET
operand
=BatchJob
со следующими параметрами:id
= идентификатор пакетного заданияstatus
=CANCELING
Если во время выполнения этого запроса поле status
объекта BatchJob
не будет иметь значение AWAITING_FILE
или ACTIVE
, вы получите ошибку BatchJobError.INVALID_STATE_CHANGE
.
Отмена задания выполняется асинхронно, поэтому после отправки запроса mutate()
проверяйте статус пакетного задания до тех пор, пока не получите значение CANCELED
или DONE
. Кроме того, обязательно скачайте результаты и проверьте их на наличие ошибок, поскольку некоторые операции могли быть начаты до отмены задания.
Требования к загрузке
Полная загрузка за один прием
В каждой клиентской библиотеке есть утилита, позволяющая загружать операции за один прием. Если вы не используете клиентскую библиотеку, учтите, что полная загрузка за один прием не поддерживается.
Загрузка по частям
В этом случае вы отправляете несколько запросов на uploadUrl
пакетного задания. Задания начнут выполняться только после того, как будет загружен последний набор операций.
Замена uploadURL на URL возобновляемой загрузки
Загрузка выполняется в соответствии с рекомендациями Google Cloud Storage по возобновляемой загрузке с помощью XML API.
uploadUrl
объекта BatchJob
необходимо заменить на URL возобновляемой загрузки. Для этого отправьте на
uploadUrl
запрос, соответствующий следующим спецификациям:
Атрибуты запроса | |
---|---|
Метод запроса | POST |
URL | URL загрузки, возвращаемый методом BatchJobService.mutate |
HTTP-заголовок Content-Type |
application/xml |
HTTP-заголовок Content-Length |
0 |
HTTP-заголовок x-goog-resumable |
start |
Тело запроса | Тело запроса не требуется |
Если запрос окажется успешным, в ответе вы увидите статус 201 Created
и заголовок Location
, значением которого и будет URL возобновляемой загрузки.
Отправка операций на URL возобновляемой загрузки
Теперь можно приступать к отправке операций. Каждый запрос, передаваемый на URL возобновляемой загрузки, должен соответствовать следующим требованиям:
Атрибуты запроса | |
---|---|
Метод запроса | PUT |
URL | URL возобновляемой загрузки, полученный на шаге инициализации. |
HTTP-заголовок Content-Type |
application/xml |
HTTP-заголовок Content-Length |
Число байтов в содержимом текущего запроса. |
HTTP-запрос Content-Range |
Диапазон байтов в запросе, за которым следует общее количество байтов. Общее число байтов будет обозначено звездочкой (
* ) для первого и промежуточного запросов, однако при отправке последнего запроса оно должно быть указано.Примеры:
bytes 0-262143/* bytes 262144-524287/* bytes 524288-786431/786432 |
Тело запроса |
Операции в формате XML, как определено в файле BatchJobOpsService.wsdl.
|
Тело запроса для URL возобновляемой загрузки
Служба BatchJobService объединит все XML, загруженные на uploadUrl
, в один запрос и произведет его синтаксический анализ. Поэтому вам нужно будет включить только начальные и конечные элементы mutate
в первый и последний запрос соответственно.
Запрос | Начальный элемент mutate |
Конечный элемент mutate |
---|---|---|
Первый | ||
Промежуточный | ||
Последний |
Кроме того, поскольку служба BatchJobService производит синтаксический разбор всех загруженных XML как одного документа, в тело одного запроса не требуется включать полный XML-документ. Например, при загрузке 524305 байтов (256 K + 256 K + 17) запросы могут выглядеть следующим образом:
Запрос 1
<?xml version="1.0" encoding="UTF-8"?> <ns1:mutate xmlns:ns1="https://adwords.google.com/api/adwords/cm/v201702"> <operations xsi:type="ns1:CampaignOperation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <operator xsi:type="ns1:Operator">ADD</operator> <operand xsi:type="ns1:Campaign"> ... </operations> <operations> ... </operat
Длина содержимого – 262144 байта, где t
в последней строке – 262144-й байт.
Запрос 2
ions> <operations xsi:type="ns1:AdGroupOperation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <operator xsi:type="ns1:Operator">ADD</operator> <operand xsi:type="ns1:AdGroup"> ... </operations> <operations> ... </ope
Длина содержимого – 262144 байта, где e
в последней строке – 262144-й байт.
Запрос 3
rations></mutate> ... (с заполнением до 262144 байта)
Длина содержимого без заполнения – 17 байтов, где заключительный символ >
в </mutate>
– 17-й байт. Длина содержимого с заполнением – 262144 байта.
Рекомендации
При использовании службы BatchJobService следуйте этим советам.
- Чем меньше число заданий и больше их размер, тем лучше.
- При отправке большого числа параллельных заданий для одного идентификатора clientCustomerId необходимо свести к минимуму вероятность одновременного выполнения операций над одними и теми же объектами. Задания по возможности должны иметь большой размер. Наличие множества незавершенных заданий со статусом
ACTIVE
илиCANCELING
, пытающихся модифицировать один набор объектов, может привести к их взаимной блокировке и стать причиной заметного снижения производительности или даже сбоев. - Старайтесь не выполнять несколько операций одновременно над одним и тем же объектом из того же задания.
- Для наилучших результатов упорядочивайте операции по типу. Например, если задание содержит операции по добавлению кампаний, групп объявлений и критериев, первыми должны идти все операции
CampaignOperations
, затем – все операцииAdGroupOperations
и наконец – все операцииAdGroupCriterionOperations
. - Не проверяйте статус задания слишком часто, чтобы не превысить ограничения на количество запросов.
Работа с торговыми кампаниями
При изменении дерева групп товаров с помощью BatchJobService действуют следующие ограничения:
-
Если результатом списка операций над деревом групп товаров является дерево с недопустимой структурой (например, узел разделен без создания узла Все остальное), то весь список операций завершается с ошибкой.
-
Операции, не изменяющие структуру дерева (например, содержащие только изменения ставок в существующих узлах), выполняются независимо.
-
Если вы удаляете узел дерева, то в качестве значения поля
criterion
объектаAdGroupCriterion
укажите экземпляр классаProductPartition
. Если указать экземпляр классаCriterion
, то операция завершится с ошибкойAdGroupCriterionError.CONCRETE_TYPE_REQUIRED
.
Ограничения
- В любой момент времени в аккаунте AdWords может быть не более 1 ГБ загруженных операций во всех незавершенных пакетных заданиях. Если по достижении этого лимита вы попытаетесь добавить новые пакетные задания, то получите ошибку
BatchJobError
с причинойDISK_QUOTA_EXCEEDED
. В этом случае подождите, пока размер невыполненных операций не станет ниже максимального порога, прежде чем создавать новые задания.
Примеры кода
В следующих клиентских библиотеках вы найдете полный пример кода, иллюстрирующий все функции, о которых говорилось выше: