Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.planning;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.lib.GoogleAdsException;
import com.google.ads.googleads.lib.utils.ResourceNames;
import com.google.ads.googleads.v0.enums.KeywordPlanNetworkEnum.KeywordPlanNetwork;
import com.google.ads.googleads.v0.errors.GoogleAdsError;
import com.google.ads.googleads.v0.services.GenerateKeywordIdeaResponse;
import com.google.ads.googleads.v0.services.GenerateKeywordIdeaResult;
import com.google.ads.googleads.v0.services.GenerateKeywordIdeasRequest;
import com.google.ads.googleads.v0.services.KeywordPlanIdeaServiceClient;
import com.google.protobuf.StringValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/** This example generates keyword ideas from a list of seed keywords. */
public class GenerateKeywordIdeas {

private static class GenerateKeywordIdeasParams extends CodeSampleParams {

@Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
private Long customerId;

@Parameter(
names = ArgumentNames.LOCATION_ID,
required = true,
description =
"Location criteria IDs. For example, specify 21167 for New York. For more information"
+ " on determining this value, see: "
+ " https://developers.google.com/adwords/api/docs/appendix/geotargeting.")
private List<Long> locationIds;

@Parameter(
names = ArgumentNames.LANGUAGE_ID,
required = true,
description =
"A language criterion ID. For example, specify 1000 for English. For more information"
+ " on determining this value, see: "
+ " https://developers.google.com/adwords/api/docs/appendix/codes-formats#languages")
private Long languageId;

@Parameter(names = ArgumentNames.KEYWORD_TEXT)
private List<String> keywords = new ArrayList<>();

@Parameter(
names = ArgumentNames.PAGE_URL,
description = "URL of a page related to your business")
private String pageUrl;
}

public static void main(String[] args) throws IOException {
GenerateKeywordIdeasParams params = new GenerateKeywordIdeasParams();
if (!params.parseArguments(args)) {
// Either pass the required parameters for this example on the command line, or insert them
// into the code here. See the parameter class definition above for descriptions.
params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
params.locationIds =
Arrays.asList(
Long.parseLong("INSERT_LOCATION_ID_1_HERE"),
Long.parseLong("INSERT_LOCATION_ID_2_HERE"));
params.languageId = Long.parseLong("INSERT_LANGUAGE_ID_HERE");
params.keywords = Arrays.asList("INSERT_KEYWORD_1_HERE", "INSERT_KEYWORD_2_HERE");
// Optional: Use a URL related to your business to generate ideas.
params.pageUrl = null;
}

GoogleAdsClient googleAdsClient;
try {
googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
} catch (FileNotFoundException fnfe) {
System.err.printf(
"Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
return;
} catch (IOException ioe) {
System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
return;
}

try {
new GenerateKeywordIdeas()
.runExample(
googleAdsClient,
params.customerId,
params.languageId,
params.locationIds,
params.keywords,
params.pageUrl);
} catch (GoogleAdsException gae) {
// GoogleAdsException is the base class for most exceptions thrown by an API request.
// Instances of this exception have a message and a GoogleAdsFailure that contains a
// collection of GoogleAdsErrors that indicate the underlying causes of the
// GoogleAdsException.
System.err.printf(
"Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
gae.getRequestId());
int i = 0;
for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
System.err.printf(" Error %d: %s%n", i++, googleAdsError);
}
}
}

/**
* Runs the example.
*
* @param googleAdsClient the Google Ads API client.
* @param customerId the client customer ID.
* @param languageId the language ID.
* @param locationIds the location IDs.
* @param keywords the list of keywords to use as a seed for ideas.
* @param pageUrl optional URL related to your business to use as a seed for ideas.
* @throws GoogleAdsException if an API request failed with one or more service errors.
* @throws IllegalArgumentException if {@code keywords} is empty and {@code pageUrl} is null.
* @throws Exception if the example failed due to other errors.
*/
private void runExample(
GoogleAdsClient googleAdsClient,
long customerId,
long languageId,
List<Long> locationIds,
List<String> keywords,
@Nullable String pageUrl) {
try (KeywordPlanIdeaServiceClient keywordPlanServiceClient =
googleAdsClient.getKeywordPlanIdeaServiceClient()) {
GenerateKeywordIdeasRequest.Builder requestBuilder =
GenerateKeywordIdeasRequest.newBuilder()
.setCustomerId(Long.toString(customerId))
// Set the language resource using the provided language ID.
.setLanguage(StringValue.of(ResourceNames.languageConstant(languageId)))
// Set the network. To restrict to only Google Search, change the parameter below to
// KeywordPlanNetwork.GOOGLE_SEARCH.
.setKeywordPlanNetwork(KeywordPlanNetwork.GOOGLE_SEARCH_AND_PARTNERS);

// Add the resource name of each location ID to the request.
for (Long locationId : locationIds) {
requestBuilder.addGeoTargetConstants(
StringValue.of(ResourceNames.geoTargetConstant(locationId)));
}

// Make sure that keywords and/or page URL were specified. The request must have exactly one
// of urlSeed, keywordSeed, or keywordAndUrlSeed set.
if (keywords.isEmpty() && pageUrl == null) {
throw new IllegalArgumentException(
"At least one of keywords or page URL is required, but neither was specified.");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment here explaining the oneof semantics.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified the existing comment on lines 162-163

if (keywords.isEmpty()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be clearer to reduce the nesting here, as in

if (keywords.isEmpty() && pageUrl != null) {
} else if (pageUrl != null) {
} else {
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also add the above error branch to bring the entire oneof handling into a single block.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked to reduce nesting. I did not include the error case here because if that's true, the example will throw an exception. I personally prefer separating out the error case, but LMK if you feel strongly about combining them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point, thanks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read this correctly, it looks like the keyword texts will always be at least ['INSERT_KEYWORD_TEXT_1', 'INSERT_KEYWORD_TEXT_2']?
In that case, this condition would never be true?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values are only used when the flags fail to parse (e.g. missing required, invalid number formats etc.)

Also this flag is optional, so specifying --locationId 123 --languageId abc should be sufficient to clear the value here.

// Only page URL was specified, so use a UrlSeed.
requestBuilder.getUrlSeedBuilder().setUrl(StringValue.of(pageUrl));
} else if (pageUrl == null) {
// Only keywords were specified, so use a KeywordSeed.
requestBuilder
.getKeywordSeedBuilder()
.addAllKeywords(keywords.stream().map(StringValue::of).collect(Collectors.toList()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated code for the stream().map(), maybe make a local variable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, if I do that I'd want to move back to the 2nd level of nesting. With the current nesting layout, creating the local variable is unnecessary for the if case. I'm concerned that it will just confuse readers.

I like the current nesting you suggested, so I'd prefer to keep it with the tradeoff of a bit of duplication. Also, the duplication means the else if and else blocks are a bit more amenable to copy/pasting.

@AnashOommen , what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Josh, I prefer the ease of copy-pasting over a bit of duplication here.

} else {
// Both page URL and keywords were specified, so use a KeywordAndUrlSeed.
requestBuilder
.getKeywordAndUrlSeedBuilder()
.setUrl(StringValue.of(pageUrl))
.addAllKeywords(keywords.stream().map(StringValue::of).collect(Collectors.toList()));
}

// Send the keyword ideas request.
GenerateKeywordIdeaResponse response =
keywordPlanServiceClient.generateKeywordIdeas(requestBuilder.build());
// Print each result in the response.
for (GenerateKeywordIdeaResult result : response.getResultsList()) {
System.out.printf(
"Keyword idea text '%s' has %d average monthly searches and '%s' competition.%n",
result.getText().getValue(),
result.getKeywordIdeaMetrics().getAvgMonthlySearches().getValue(),
result.getKeywordIdeaMetrics().getCompetition());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ public final class ArgumentNames {
public static final String CPC_BID_CEILING_MICRO_AMOUNT = "--cpcBidCeilingMicroAmount";
public static final String ARTIFACT_NAME = "--artifactName";
public static final String KEYWORD_TEXT = "--keywordText";
public static final String LANGUAGE_ID = "--languageId";
public static final String LOCATION_ID = "--locationId";
public static final String RECOMMENDATION_ID = "--recommendationId";
public static final String HOTEL_CENTER_ACCOUNT_ID = "--hotelCenterAccountId";
public static final String MERCHANT_CENTER_ACCOUNT_ID = "--merchantCenterAccountId";
public static final String CREATE_DEFAULT_LISTING_GROUP = "--createDefaultListingGroup";
public static final String REPLACE_EXISTING_TREE = "--replaceExistingTree";
public static final String PAGE_URL = "--pageUrl";
}