开始使用目标客户匹配

借助目标客户匹配功能,您可以利用您的线上及线下数据,在 Google 搜索、“购物”标签页、Gmail、YouTube 和展示广告网络上覆盖和重新吸引客户。目标客户匹配功能利用客户与您分享的信息,将广告定位到相应客户及其他类似客户。您可以批量上传客户关系管理 (CRM) 数据、附加或移除数据,也可以使用这些用户名单创建logical_user_list。

如需查看不同的受众群体细分类型,并将目标客户匹配与其他用户名单选项进行比较,请参阅受众群体管理概览。

详细了解目标客户匹配和受众群体定位。

前提条件

并非所有账号都符合使用目标客户匹配功能的条件。如需使用目标客户匹配功能,您的账号必须符合以下要求:

  • 政策遵从记录良好
  • 付款记录良好

根据您的账号满足哪些要求,您可以使用不同的功能。如需了解资格要求和限制,请参阅目标客户匹配政策。

开始前须知:规划实施

在开始编写任何代码之前,请务必先规划目标客户匹配功能的实现。本部分概述了整个流程和关键注意事项,可帮助您设计稳健的集成。制定好清晰的方案后,您就可以继续执行后续的实施步骤。

使用流程

以下是创建客户名单并定位到该名单的推荐流程:

  1. 创建空的客户名单。

  2. 创建 OfflineUserDataJob。创建一个大型作业比创建多个小型作业效率更高。

    确保在 OfflineUserDataJob create 请求中填充 customer_match_user_list_metadata 的 consent 字段。对于 remove 请求,无需征得用户同意。如果作业的 consent.ad_user_data 或 consent.ad_personalization 设置为 DENIED,则 API 会返回 OfflineUserDataJobError.CUSTOMER_NOT_ACCEPTED_CUSTOMER_DATA_TERMS。

    如果用户拒绝了同意声明,您可以创建一个包含 remove 操作的作业,以从用户列表中移除该用户的标识符。

    如果您缺少特定用户的意见征求数据,请创建一个单独的作业,在该作业的 customer_match_user_list_metadata 中不设置 consent 字段,然后使用 create 操作为该单独的作业添加这些用户的标识符。

  3. 使用 OfflineUserDataJobService.AddOfflineUserDataJobOperations 方法添加操作。建议您在一次调用中添加最多 10,000 个标识符,以实现最佳处理效果。单个 AddOfflineUserDataJobOperations 请求最多可包含操作列表中的所有 UserData 对象中的 10 万个标识符。

    例如,如果每个 UserData 对象都有一个针对 hashed_email 的 UserIdentifier 和另一个针对 hashed_phone_number 的 UserIdentifier,那么每个请求发送 5,000 个 UserData 对象是最佳做法,因为每个请求总共包含 10,000 个用户标识符。

  4. 重复上一步,直到添加完所有操作或作业达到容量上限。您可以向单个作业添加的操作数量没有限制;不过,为了获得最佳处理效果,我们建议每个作业的操作数量不超过 1,000,000 个。

  5. 运行作业。您必须在创建作业后的 5 天内运行该作业。 否则,相应作业将无法再运行。

  6. 轮询以确定上传是否成功。

  7. 验证匹配率。

  8. 定位到相应列表。

OfflineUserDataJobService 和 UserDataService

您可以使用以下两种服务上传目标客户匹配数据。根据您的使用情形选择服务,因为某些服务可能存在限制。

目标客户匹配数据上传服务
OfflineUserDataJobService (首选) 大多数开发者都会使用此服务。它针对高吞吐量的大型上传进行了优化,并在完成后返回成功指标。本指南主要介绍此服务。
UserDataService 此服务经过优化,可一次上传少量标识符,并进行零星更新,但未针对持续运行进行优化。每个请求最多只能包含 10 个操作。此外,单个请求中所有 user_identifiers 的总项数不能超过 100。

如需有关使用此服务进行上传的指导,请参阅目标客户匹配集成管理指南。

您应在 create 请求中填充 UploadUserDataRequest 的 customer_match_user_list_metadata 字段的 consent 字段。对于 remove 请求,无需征得用户同意。如果 consent.ad_user_data 或 consent.ad_personalization 为 DENIED,则 API 会返回一个将 received_operations_count 设置为零的响应,表示请求未得到处理。如果某个标识符的意见值为 DENIED,则您可以使用 UploadUserDataRequest 的 remove 操作请求从用户列表中移除该标识符。

最佳做法

在设计目标客户匹配集成时,请谨记以下最佳实践:

  • 请勿尝试使用多个账号来修改单个用户列表。用户名单只能由创建它的 Google Ads 账号或数据合作伙伴账号修改。

  • 最大限度地提高每个 AddOfflineUserDataJobOperationsRequest 的操作次数(最多 10 万个标识符),以避免 RESOURCE_EXHAUSTED 错误。

  • 请勿在同一 OfflineUserDataJob 中混用 create 和 remove 操作。这样做可能会导致 CONFLICTING_OPERATION 错误。

  • 在 AddOfflineUserDataJobOperationsRequest 中启用 partial_failure,以便在运行作业之前检测到任何有问题的操作。当操作上传到 OfflineUserDataJob 时,系统会对其进行验证。

  • 为防止更新用户列表时出现 CONCURRENT_MODIFICATION 错误,请执行以下操作:

    如果多个 OfflineUserDataJob 进程同时处于 RUNNING 状态,并且它们定位到 CustomerMatchUserListMetadata.user_list 中指定的同一用户列表资源名称,请避免这种情况。发生此错误的原因是,多个作业尝试同时修改同一列表。如果您在 API 作业处理期间通过 Google Ads 界面修改列表,也可能会出现此冲突。

    此限制适用于已启动(即处于 RUNNING 状态)的作业。相比之下,您可以安全地多次发出 AddOfflineUserDataJobOperations 请求,以将数据添加到仍处于 PENDING 状态的单个 OfflineUserDataJob。在调用 RunOfflineUserDataJob 以启动流程之前,您可以随时向作业添加操作。

  • 如果您有数千个要应用的操作,请创建一个包含所有操作的 OfflineUserDataJob。请勿创建多个作业,每个作业仅包含数百个操作,然后按顺序或同时运行这些作业。与多个小型作业相比,包含所有操作的单个大型作业效率更高,并且可以减少工作流中出现错误的可能性。

如需了解如何使用客户名单实现最佳定位,请参阅目标客户匹配最佳实践。

第 1 步:创建客户名单

使用 UserListService 创建客户名单。通过在 user_list 对象上设置 crm_based_user_list 字段来创建客户名单。crm_based_user_list 字段可针对支持定位客户名单的广告系列类型进行设置:

不同广告系列类型中的目标客户匹配
搜索网络 广告在 Google 搜索网络中展示。
展示广告网络 广告在 Google 展示广告网络中展示。
将展示广告网络也纳入投放范围的搜索广告系列 广告在 Google 搜索网络和 Google 展示广告网络中展示。
需求开发广告系列 广告会展示在 YouTube、Google 探索和 Gmail 中(使用 AdGroupCriterion.audience 上的 Audience 资源进行定位)。
效果最大化广告系列 广告在所有 Google 渠道中展示(用作 AssetGroupSignal.audience 上的受众群体信号)。
视频广告系列 广告在 YouTube 上展示。
购物广告系列 广告在“购物”标签页中展示。

crm_based_user_list 包含三个字段:

  • app_id:一个字符串,用于唯一标识收集数据的移动应用。创建用于上传移动广告 ID 的 CrmBasedUserList 时,必须提供此信息。

  • upload_key_type:列表的匹配密钥类型,可以是 CONTACT_INFO、CRM_ID 或 MOBILE_ADVERTISING_ID。同一列表中不允许使用混合数据类型。所有客户名单都必须包含此字段。

  • data_source_type:列表的数据源。默认值为 FIRST_PARTY。在许可名单中的客户可以创建第三方来源的客户名单(THIRD_PARTY_CREDIT_BUREAU、THIRD_PARTY_VOTER_FILE 或 THIRD_PARTY_PARTNER_DATA)。只有 FIRST_PARTY 来源的名单可以与其他名单组合。

用户名单的 membership_life_span 属性可让您定义用户被视为在名单中的时间段(以天为单位)。目标客户匹配用户名单的 membership_life_span 不得超过 540,这也是默认值。

membership_status 属性用于定义列表是否接受新用户。

用于创建客户名单的代码示例

Java

private String createCustomerMatchUserList(GoogleAdsClient googleAdsClient, long customerId) {
  // Creates the new user list.
  UserList userList =
      UserList.newBuilder()
          .setName("Customer Match list #" + getPrintableDateTime())
          .setDescription("A list of customers that originated from email addresses")
          // Membership life span must be between 0 and 540 days inclusive. See:
          // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
          // Sets the membership life span to 30 days.
          .setMembershipLifeSpan(30)
          // Sets the upload key type to indicate the type of identifier that will be used to
          // add users to the list. This field is immutable and required for a CREATE operation.
          .setCrmBasedUserList(
              CrmBasedUserListInfo.newBuilder()
                  .setUploadKeyType(CustomerMatchUploadKeyType.CONTACT_INFO))
          .build();

  // Creates the operation.
  UserListOperation operation = UserListOperation.newBuilder().setCreate(userList).build();

  // Creates the service client.
  try (UserListServiceClient userListServiceClient =
      googleAdsClient.getLatestVersion().createUserListServiceClient()) {
    // Adds the user list.
    MutateUserListsResponse response =
        userListServiceClient.mutateUserLists(
            Long.toString(customerId), ImmutableList.of(operation));
    // Prints the response.
    System.out.printf(
        "Created Customer Match user list with resource name: %s.%n",
        response.getResults(0).getResourceName());
    return response.getResults(0).getResourceName();
  }
}

      

C#

private string CreateCustomerMatchUserList(GoogleAdsClient client, long customerId)
{
    // Get the UserListService.
    UserListServiceClient service = client.GetService(Services.V25.UserListService);

    // Creates the user list.
    UserList userList = new UserList()
    {
        Name = $"Customer Match list# {ExampleUtilities.GetShortRandomString()}",
        Description = "A list of customers that originated from email and physical" +
            " addresses",
        // Membership life span must be between 0 and 540 days inclusive. See:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
        // Sets the membership life span to 30 days.
        MembershipLifeSpan = 30,
        CrmBasedUserList = new CrmBasedUserListInfo()
        {
            UploadKeyType = CustomerMatchUploadKeyType.ContactInfo
        }
    };
    // Creates the user list operation.
    UserListOperation operation = new UserListOperation()
    {
        Create = userList
    };

    // Issues a mutate request to add the user list and prints some information.
    MutateUserListsResponse response = service.MutateUserLists(
        customerId.ToString(), new[] { operation });
    string userListResourceName = response.Results[0].ResourceName;
    Console.WriteLine($"User list with resource name '{userListResourceName}' " +
        $"was created.");
    return userListResourceName;
}
      

PHP

private static function createCustomerMatchUserList(
    GoogleAdsClient $googleAdsClient,
    int $customerId
): string {
    // Creates the user list.
    $userList = new UserList(
        [
        'name' => 'Customer Match list #' . Helper::getPrintableDatetime(),
        'description' => 'A list of customers that originated from email '
            . 'and physical addresses',
        // Membership life span must be between 0 and 540 days inclusive. See:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
        // Sets the membership life span to 30 days.
        'membership_life_span' => 30,
        'crm_based_user_list' => new CrmBasedUserListInfo(
            [
            // Sets the upload key type to indicate the type of identifier that will be used to
            // add users to the list. This field is immutable and required for a CREATE
            // operation.
            'upload_key_type' => CustomerMatchUploadKeyType::CONTACT_INFO
            ]
        )
        ]
    );

    // Creates the user list operation.
    $operation = new UserListOperation();
    $operation->setCreate($userList);

    // Issues a mutate request to add the user list and prints some information.
    $userListServiceClient = $googleAdsClient->getUserListServiceClient();
    $response = $userListServiceClient->mutateUserLists(
        MutateUserListsRequest::build($customerId, [$operation])
    );
    $userListResourceName = $response->getResults()[0]->getResourceName();
    printf("User list with resource name '%s' was created.%s", $userListResourceName, PHP_EOL);

    return $userListResourceName;
}
      

Python

def create_customer_match_user_list(
    client: GoogleAdsClient, customer_id: str
) -> str:
    """Creates a Customer Match user list.

    Args:
        client: The Google Ads client.
        customer_id: The ID for the customer that owns the user list.

    Returns:
        The string resource name of the newly created user list.
    """
    # Creates the UserListService client.
    user_list_service_client: UserListServiceClient = client.get_service(
        "UserListService"
    )

    # Creates the user list operation.
    user_list_operation: UserListOperation = client.get_type(
        "UserListOperation"
    )

    # Creates the new user list.
    user_list: UserList = user_list_operation.create
    user_list.name = f"Customer Match list #{uuid.uuid4()}"
    user_list.description = (
        "A list of customers that originated from email and physical addresses"
    )
    # Sets the upload key type to indicate the type of identifier that is used
    # to add users to the list. This field is immutable and required for a
    # CREATE operation.
    user_list.crm_based_user_list.upload_key_type = (
        client.enums.CustomerMatchUploadKeyTypeEnum.CONTACT_INFO
    )
    # Membership life span must be between 0 and 540 days inclusive. See:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
    # Sets the membership life span to 30 days.
    user_list.membership_life_span = 30

    response: MutateUserListsResponse = (
        user_list_service_client.mutate_user_lists(
            customer_id=customer_id, operations=[user_list_operation]
        )
    )
    user_list_resource_name: str = response.results[0].resource_name
    print(
        f"User list with resource name '{user_list_resource_name}' was created."
    )

    return user_list_resource_name
      

Ruby

def create_customer_match_user_list(client, customer_id)
  # Creates the user list.
  operation = client.operation.create_resource.user_list do |ul|
    ul.name = "Customer Match List #{(Time.new.to_f * 1000).to_i}"
    ul.description = "A list of customers that originated from email and " \
      "physical addresses"
    # Membership life span must be between 0 and 540 days inclusive. See:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
    # Sets the membership life span to 30 days.
    ul.membership_life_span = 30
    ul.crm_based_user_list = client.resource.crm_based_user_list_info do |crm|
      crm.upload_key_type = :CONTACT_INFO
    end
  end

  # Issues a mutate request to add the user list and prints some information.
  response = client.service.user_list.mutate_user_lists(
    customer_id: customer_id,
    operations: [operation],
  )

  # Prints out some information about the newly created user list.
  resource_name = response.results.first.resource_name
  puts "User list with resource name #{resource_name} was created."

  resource_name
end
      

Perl

sub create_customer_match_user_list {
  my ($api_client, $customer_id) = @_;

  # Create the user list.
  my $user_list = Google::Ads::GoogleAds::V25::Resources::UserList->new({
      name        => "Customer Match list #" . uniqid(),
      description =>
        "A list of customers that originated from email and physical addresses",
      # Membership life span must be between 0 and 540 days inclusive. See:
      # https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
      # Set the membership life span to 30 days.
      membershipLifeSpan => 30,
      # Set the upload key type to indicate the type of identifier that will be
      # used to add users to the list. This field is immutable and required for
      # a CREATE operation.
      crmBasedUserList =>
        Google::Ads::GoogleAds::V25::Common::CrmBasedUserListInfo->new({
          uploadKeyType => CONTACT_INFO
        })});

  # Create the user list operation.
  my $user_list_operation =
    Google::Ads::GoogleAds::V25::Services::UserListService::UserListOperation->
    new({
      create => $user_list
    });

  # Issue a mutate request to add the user list and print some information.
  my $user_lists_response = $api_client->UserListService()->mutate({
      customerId => $customer_id,
      operations => [$user_list_operation]});
  my $user_list_resource_name =
    $user_lists_response->{results}[0]{resourceName};
  printf "User list with resource name '%s' was created.\n",
    $user_list_resource_name;

  return $user_list_resource_name;
}
      

curl

第 2 步:向用户列表添加成员

三个主要匹配键是电子邮件地址、邮寄地址和电话号码。您可以使用用户 ID 和移动设备 ID 作为匹配键,但由于这些解决方案依赖于 Cookie 和设备 ID,因此不太适合未来发展趋势。 我们建议您尽可能上传用户联系信息(例如电子邮件地址、邮寄地址和电话号码),而不是 CRM 或移动 ID。

每个用户名单只能包含一种客户数据,如 CrmBasedUserListInfo.upload_key_type 字段所指定。此外,表示单个用户的 UserData 对象最多可以包含 20 个用户标识符,每个标识符都有自己的 UserIdentifier 对象。如果标识符超过 20 个,则会导致 TOO_MANY_USER_IDENTIFIERS 错误。

只有当目标客户匹配用户名单在广告投放时达到活跃用户数最低阈值时,Google Ads 才会使用该名单进行定位;活跃用户是指名单中在 Gmail、Google 搜索、YouTube 或展示广告网络中活跃的用户数。上传至少 5,000 位会员,以提高获得足够多的匹配活跃用户进行定位的可能性。

上传用户联系信息

如需上传用户电子邮件地址、邮寄地址或电话号码,请将 upload_key_type 设置为 CONTACT_INFO。联系信息必须与 Google 账号相关联才能进行匹配,并且不能定位到公司账号(例如 Google Workspace)。

出于隐私问题方面的考虑,在上传电子邮件地址、名字、姓氏和电话号码之前,必须使用 SHA-256 算法对这些数据进行哈希处理。为了使哈希结果实现标准化,在对其中某个值进行哈希处理之前,请确保您执行以下任务:

如果联系信息的格式在哈希处理之前不正确,API 仍会接受哈希处理后的信息,但无法将其与客户匹配。

如果您想上传邮寄地址数据,则必须至少包含以下信息:

  • 国家/地区代码
  • 邮编
  • 经过哈希处理的名字
  • 经过哈希处理的姓氏

如果缺少任何这些字段,则无法匹配地址。

虽然客户名单只能包含一种 upload_key_type,但您可以为 CONTACT_INFO 的 upload_key_type 上传多种类型的联系信息。 建议您启用此选项,以提高匹配率。

增强型转化的电子邮件地址格式

为确保增强型转化的匹配率最高,您必须按照以下步骤正确设置电子邮件地址的格式并进行哈希处理:

  1. 应用标准标准化处理(所有电子邮件地址):

    • 移除所有开头和结尾处的 ASCII 空白字符。
    • 将电子邮件地址中的所有字符转换为小写。
  2. 应用特定于网域的标准化(仅限 gmail.com 和 googlemail.com):

    • 将电子邮件地址转换为小写后,检查网域(@ 符号后面的部分)是否为 gmail.com 或 googlemail.com。
    • 移除句点 (.):从用户名部分(@ 符号之前)移除所有句点字符。
    • 移除加号后缀 (+):从用户名部分移除第一个加号 (+) 以及后续所有字符(直到 @ 符号)。
      • 示例:Jane.Doe+Shopping@googlemail.com 先变为 jane.doe+shopping@googlemail.com(小写),然后变为 janedoe@googlemail.com(Gmail 和 Googlemail 规则)。
    • 其他网域:对于 gmail.com 或 googlemail.com 以外的任何网域,请勿从用户名中移除句点或加号后缀。仅应用标准归一化(去除空格并转换为小写)。
      • 示例:user.name+NYC@Example.com 会归一化为 user.name+nyc@example.com。
  3. 对标准化电子邮件地址进行哈希处理:

    • 将规范化的电子邮件字符串编码为 UTF-8 字节。
    • 使用 SHA-256 算法对 UTF-8 字节进行哈希处理。
    • 将生成的摘要格式设置为小写十六进制字符串(64 个字符)。
import hashlib
import re


def normalize_and_hash_email(email: str) -> str:
    """Normalizes and SHA-256 hashes an email address for enhanced conversions."""
    normalized = email.strip().lower()
    parts = normalized.split("@")
    if len(parts) != 2:
        raise ValueError("Invalid email address format.")
    username, domain = parts
    if domain in ("gmail.com", "googlemail.com"):
        username = username.replace(".", "")
        username = re.sub(r"\+.*$", "", username)
    normalized = f"{username}@{domain}"
    return hashlib.sha256(normalized.encode("utf-8")).hexdigest()

上传 CRM ID

如需使用 CRM ID 填充客户名单,请将 upload_key_type 设置为 CRM_ID。CRM ID 是根据广告客户生成和分配的用户 ID 进行匹配的。这与上传 MOBILE_ADVERTISING_ID 个实例类似,但您需要填充 UserIdentifier 对象的 third_party_user_id 字段。

上传移动设备 ID

与使用电子邮件地址的目标客户匹配类似,您可以使用广告标识符 (IDFA) 或 Google 广告 ID (AAID) 移动设备 ID 执行目标客户匹配。为此,请在将用户名单用于目标客户匹配(使用移动设备 ID)之前,指定 app_id 属性并将 upload_key_type 设置为 MOBILE_ADVERTISING_ID。

代码示例

以下示例使用 OfflineUserDataJobOperation 将客户联系信息附加到客户名单。

Java

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys "email",
// "phone", "firstName", "lastName", "countryCode", and "postalCode". In your application, this
// data might come from a file or a database.
List<Map<String, String>> rawRecords = new ArrayList<>();
// The first user data has an email address and a phone number.
Map<String, String> rawRecord1 =
    ImmutableMap.<String, String>builder()
        .put("email", "dana@example.com")
        // Phone number to be converted to E.164 format, with a leading '+' as required. This
        // includes whitespace, dashes, and parentheses that will be removed later.
        .put("phone", "+1 (800) 555-0101")
        .build();
// The second user data has an email address, a mailing address, and a phone number.
Map<String, String> rawRecord2 =
    ImmutableMap.<String, String>builder()
        // Email address that includes a period (.) and plus (+) suffix before the Gmail domain.
        .put("email", "alex.2+myalias@gmail.com")
        // Address that includes all four required elements: first name, last name, country
        // code, and postal code.
        .put("firstName", "Alex")
        .put("lastName", "Quinn")
        .put("countryCode", "US")
        .put("postalCode", "94045")
        // Phone number to be converted to E.164 format, with a leading '+' as required.
        .put("phone", "+1 800-555-0102")
        .build();
// The third user data only has an email address.
Map<String, String> rawRecord3 =
    ImmutableMap.<String, String>builder().put("email", "charlie@example.com").build();
// Adds the raw records to the raw input list.
rawRecords.add(rawRecord1);
rawRecords.add(rawRecord2);
rawRecords.add(rawRecord3);

// Iterates over the raw input list and creates a UserData object for each record.
List<UserData> userDataList = new ArrayList<>();
for (Map<String, String> rawRecord : rawRecords) {
  // Creates a builder for the UserData object that represents a member of the user list.
  UserData.Builder userDataBuilder = UserData.newBuilder();
  // Checks if the record has email, phone, or address information, and adds a SEPARATE
  // UserIdentifier object for each one found. For example, a record with an email address and a
  // phone number will result in a UserData with two UserIdentifiers.

  // IMPORTANT: Since the identifier attribute of UserIdentifier
  // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is a
  // oneof
  // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only ONE of
  // hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId, or addressInfo. Setting more
  // than one of these attributes on the same UserIdentifier will clear all the other members
  // of the oneof. For example, the following code is INCORRECT and will result in a
  // UserIdentifier with ONLY a hashedPhoneNumber.
  //
  // UserIdentifier incorrectlyPopulatedUserIdentifier =
  //     UserIdentifier.newBuilder()
  //         .setHashedEmail("...")
  //         .setHashedPhoneNumber("...")
  //         .build();
  //
  // The separate 'if' statements below demonstrate the correct approach for creating a UserData
  // for a member with multiple UserIdentifiers.

  // Checks if the record has an email address, and if so, adds a UserIdentifier for it.
  if (rawRecord.containsKey("email")) {
    UserIdentifier hashedEmailIdentifier =
        UserIdentifier.newBuilder()
            .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, rawRecord.get("email")))
            .build();
    // Adds the hashed email identifier to the UserData object's list.
    userDataBuilder.addUserIdentifiers(hashedEmailIdentifier);
  }

  // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
  if (rawRecord.containsKey("phone")) {
    UserIdentifier hashedPhoneNumberIdentifier =
        UserIdentifier.newBuilder()
            .setHashedPhoneNumber(
                normalizeAndHashPhoneNumber(sha256Digest, rawRecord.get("phone")))
            .build();
    // Adds the hashed phone number identifier to the UserData object's list.
    userDataBuilder.addUserIdentifiers(hashedPhoneNumberIdentifier);
  }

  // Checks if the record has all the required mailing address elements, and if so, adds a
  // UserIdentifier for the mailing address.
  if (rawRecord.containsKey("firstName")) {
    // Checks if the record contains all the other required elements of a mailing address.
    Set<String> missingAddressKeys = new HashSet<>();
    for (String addressKey : new String[] {"lastName", "countryCode", "postalCode"}) {
      if (!rawRecord.containsKey(addressKey)) {
        missingAddressKeys.add(addressKey);
      }
    }

    if (!missingAddressKeys.isEmpty()) {
      System.out.printf(
          "Skipping addition of mailing address information because the following required keys"
              + " are missing: %s%n",
          missingAddressKeys);
    } else {
      // Creates an OfflineUserAddressInfo object that contains all the required elements of a
      // mailing address.
      OfflineUserAddressInfo addressInfo =
          OfflineUserAddressInfo.newBuilder()
              .setHashedFirstName(
                  normalizeAndHash(sha256Digest, rawRecord.get("firstName"), false))
              .setHashedLastName(
                  normalizeAndHash(sha256Digest, rawRecord.get("lastName"), false))
              .setCountryCode(rawRecord.get("countryCode"))
              .setPostalCode(rawRecord.get("postalCode"))
              .build();
      UserIdentifier addressIdentifier =
          UserIdentifier.newBuilder().setAddressInfo(addressInfo).build();
      // Adds the address identifier to the UserData object's list.
      userDataBuilder.addUserIdentifiers(addressIdentifier);
    }
  }

  if (!userDataBuilder.getUserIdentifiersList().isEmpty()) {
    // Builds the UserData and adds it to the list.
    userDataList.add(userDataBuilder.build());
  }
}

// Creates the operations to add users.
List<OfflineUserDataJobOperation> operations = new ArrayList<>();
for (UserData userData : userDataList) {
  operations.add(OfflineUserDataJobOperation.newBuilder().setCreate(userData).build());
}
      

C#

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys
// "email", "phone", "firstName", "lastName", "countryCode", and "postalCode".
// In your application, this data might come from a file or a database.
List<Dictionary<string, string>> rawRecords = new List<Dictionary<string, string>>();

// The first user data has an email address and a phone number.
Dictionary<string, string> rawRecord1 = new Dictionary<string, string>();
rawRecord1.Add("email", "dana@example.com");
// Phone number to be converted to E.164 format, with a leading '+' as required.
// This includes whitespace that will be removed later.
rawRecord1.Add("phone", "+1 800 5550101");

// The second user data has an email address, a mailing address, and a phone number.
Dictionary<string, string> rawRecord2 = new Dictionary<string, string>();
// Email address that includes a period (.) before the Gmail domain.
rawRecord2.Add("email", "alex.2@example.com");
// Address that includes all four required elements: first name, last name, country
// code, and postal code.
rawRecord2.Add("firstName", "Alex");
rawRecord2.Add("lastName", "Quinn");
rawRecord2.Add("countryCode", "US");
rawRecord2.Add("postalCode", "94045");
// Phone number to be converted to E.164 format, with a leading '+' as required.
// This includes whitespace that will be removed later.
rawRecord2.Add("phone", "+1 800 5550102");

// The third user data only has an email address.
Dictionary<string, string> rawRecord3 = new Dictionary<string, string>();
rawRecord3.Add("email", "charlie@example.com");

// Adds the raw records to the raw input list.
rawRecords.Add(rawRecord1);
rawRecords.Add(rawRecord2);
rawRecords.Add(rawRecord3);

// Iterates over the raw input list and creates a UserData object for each record.
List<UserData> userDataList = new List<UserData>();
foreach (Dictionary<string, string> rawRecord in rawRecords) {
    // Creates a UserData object that represents a member of the user list.
    UserData userData = new UserData();
    // Checks if the record has email, phone, or address information, and adds a
    // SEPARATE UserIdentifier object for each one found.
    // For example, a record with an email address and a phone number will result in a
    // UserData with two UserIdentifiers.

    // IMPORTANT: Since the identifier attribute of UserIdentifier
    // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
    // is a oneof
    // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set
    // only ONE of hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId,
    // or addressInfo.
    // Setting more than one of these attributes on the same UserIdentifier will clear
    // all the other members of the oneof.
    // For example, the following code is INCORRECT and will result in a UserIdentifier
    // with ONLY a hashedPhoneNumber.
    //
    // UserIdentifier incorrectlyPopulatedUserIdentifier = new UserIdentifier()
    // {
    //      HashedEmail = "...",
    //      HashedPhoneNumber = "..."
    // };
    //
    // The separate 'if' statements below demonstrate the correct approach for creating
    // a UserData for a member with multiple UserIdentifiers.

    // Checks if the record has an email address, and if so, adds a UserIdentifier
    // for it.
    if (rawRecord.ContainsKey("email")) {
        UserIdentifier hashedEmailIdentifier = new UserIdentifier()
        {
            HashedEmail = NormalizeAndHash(rawRecord["email"], true)
        };

        userData.UserIdentifiers.Add(hashedEmailIdentifier);
    }

    // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
    if (rawRecord.ContainsKey("phone")) {
        UserIdentifier hashedPhoneNumberIdentifier = new UserIdentifier()
        {
            HashedPhoneNumber = NormalizeAndHash(rawRecord["phone"], true)
        };

        // Adds the hashed phone number identifier to the UserData object's list.
        userData.UserIdentifiers.Add(hashedPhoneNumberIdentifier);
    }

    // Checks if the record has all the required mailing address elements, and if so,
    // adds a UserIdentifier for the mailing address.
    if (rawRecord.ContainsKey("firstName")) {
        // Checks if the record contains all the other required elements of a mailing
        // address.
        HashSet<string> missingAddressKeys = new HashSet<string>();
        foreach (string addressKey in new string[] {"lastName", "countryCode",
            "postalCode"}) {
        if (!rawRecord.ContainsKey(addressKey)) {
            missingAddressKeys.Add(addressKey);
        }
        }

        if (!missingAddressKeys.Any()) {
        Console.WriteLine(
            $"Skipping addition of mailing address information because the following " +
                "required keys are missing: {missingAddressKeys}");
        } else {
            // Creates an OfflineUserAddressInfo object that contains all the required
            // elements of a mailing address.
            OfflineUserAddressInfo addressInfo = new OfflineUserAddressInfo()
            {
                HashedFirstName = NormalizeAndHash(rawRecord["firstName"]),
                HashedLastName = NormalizeAndHash(rawRecord["lastName"]),
                CountryCode = rawRecord["countryCode"],
                PostalCode = rawRecord["postalCode"]
            };

            UserIdentifier addressIdentifier = new UserIdentifier()
            {
                AddressInfo = addressInfo
            };

            // Adds the address identifier to the UserData object's list.
            userData.UserIdentifiers.Add(addressIdentifier);
        }
    }

    if (userData.UserIdentifiers.Any())
    {
        userDataList.Add(userData);
    }
}

// Creates the operations to add the users.
List<OfflineUserDataJobOperation> operations = new List<OfflineUserDataJobOperation>();
foreach(UserData userData in userDataList)
{
    operations.Add(new OfflineUserDataJobOperation()
    {
        Create = userData
    });
}
      

PHP

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys 'email',
// 'phone', 'firstName', 'lastName', 'countryCode', and 'postalCode'. In your application,
// this data might come from a file or a database.
$rawRecords = [];
// The first user data has an email address and a phone number.
$rawRecord1 = [
    // The first user data has an email address and a phone number.
    'email' => 'dana@example.com',
    // Phone number to be converted to E.164 format, with a leading '+' as required. This
    // includes whitespace that will be removed later.
    'phone' => '+1 800 5550101'
];
$rawRecords[] = $rawRecord1;

// The second user data has an email address, a mailing address, and a phone number.
$rawRecord2 = [
    // Email address that includes a period (.) before the Gmail domain.
    'email' => 'alex.2@example.com',
    // Address that includes all four required elements: first name, last name, country
    // code, and postal code.
    'firstName' => 'Alex',
    'lastName' => 'Quinn',
    'countryCode' => 'US',
    'postalCode' => '94045',
    // Phone number to be converted to E.164 format, with a leading '+' as required.
    'phone' => '+1 800 5550102',
];
$rawRecords[] = $rawRecord2;

// The third user data only has an email address.
$rawRecord3 = ['email' => 'charlie@example.com'];
$rawRecords[] = $rawRecord3;

// Iterates over the raw input list and creates a UserData object for each record.
$userDataList = [];
foreach ($rawRecords as $rawRecord) {
    // Checks if the record has email, phone, or address information, and adds a SEPARATE
    // UserIdentifier object for each one found. For example, a record with an email address
    // and a phone number will result in a UserData with two UserIdentifiers.

    // IMPORTANT: Since the identifier attribute of UserIdentifier
    // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is
    // a oneof
    // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only
    // ONE of 'hashed_email, 'hashed_phone_number', 'mobile_id', 'third_party_user_id', or
    // 'address_info'.
    // Setting more than one of these attributes on the same UserIdentifier will clear all
    // the other members of the oneof. For example, the following code is INCORRECT and will
    // result in a UserIdentifier with ONLY a 'hashed_phone_number'.
    //
    // $incorrectlyPopulatedUserIdentifier = new UserIdentifier();
    // $incorrectlyPopulatedUserIdentifier->setHashedEmail('...');
    // $incorrectlyPopulatedUserIdentifier->setHashedPhoneNumber('...');
    //
    // The separate 'if' statements below demonstrate the correct approach for creating a
    // UserData for a member with multiple UserIdentifiers.

    $userIdentifiers = [];
    // Checks if the record has an email address, and if so, adds a UserIdentifier for it.
    if (array_key_exists('email', $rawRecord)) {
        $hashedEmailIdentifier = new UserIdentifier(
            [
            'hashed_email' => self::normalizeAndHash($rawRecord['email'], true)
            ]
        );
        // Adds the hashed email identifier to the user identifiers list.
        $userIdentifiers[] = $hashedEmailIdentifier;
    }

    // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
    if (array_key_exists('phone', $rawRecord)) {
        $hashedPhoneNumberIdentifier = new UserIdentifier(
            [
            'hashed_phone_number' => self::normalizeAndHash($rawRecord['phone'], true)
            ]
        );
        // Adds the hashed email identifier to the user identifiers list.
        $userIdentifiers[] = $hashedPhoneNumberIdentifier;
    }

    // Checks if the record has all the required mailing address elements, and if so, adds a
    // UserIdentifier for the mailing address.
    if (array_key_exists('firstName', $rawRecord)) {
        // Checks if the record contains all the other required elements of a mailing
        // address.
        $missingAddressKeys = [];
        foreach (['lastName', 'countryCode', 'postalCode'] as $addressKey) {
            if (!array_key_exists($addressKey, $rawRecord)) {
                $missingAddressKeys[] = $addressKey;
            }
        }
        if (!empty($missingAddressKeys)) {
            printf(
                "Skipping addition of mailing address information because the "
                . "following required keys are missing: %s%s",
                json_encode($missingAddressKeys),
                PHP_EOL
            );
        } else {
            // Creates an OfflineUserAddressInfo object that contains all the required
            // elements of a mailing address.
            $addressIdentifier = new UserIdentifier(
                [
                'address_info' => new OfflineUserAddressInfo(
                    [
                    'hashed_first_name' => self::normalizeAndHash(
                        $rawRecord['firstName'],
                        false
                    ),
                    'hashed_last_name' => self::normalizeAndHash(
                        $rawRecord['lastName'],
                        false
                    ),
                    'country_code' => $rawRecord['countryCode'],
                    'postal_code' => $rawRecord['postalCode']
                    ]
                )
                ]
            );
            // Adds the address identifier to the user identifiers list.
            $userIdentifiers[] = $addressIdentifier;
        }
    }
    if (!empty($userIdentifiers)) {
        // Builds the UserData and adds it to the list.
        $userDataList[] = new UserData(['user_identifiers' => $userIdentifiers]);
    }
}

// Creates the operations to add users.
$operations = array_map(
    function (UserData $userData) {
        return new OfflineUserDataJobOperation(['create' => $userData]);
    },
    $userDataList
);
      

Python

def build_offline_user_data_job_operations(
    client: GoogleAdsClient,
) -> List[OfflineUserDataJobOperation]:
    """Creates a raw input list of unhashed user information.

    Each element of the list represents a single user and is a dict containing a
    separate entry for the keys "email", "phone", "first_name", "last_name",
    "country_code", and "postal_code". In your application, this data might come
    from a file or a database.

    Args:
        client: The Google Ads client.

    Returns:
        A list containing the operations.
    """
    # The first user data has an email address and a phone number.
    raw_record_1: Dict[str, str] = {
        "email": "dana@example.com",
        # Phone number to be converted to E.164 format, with a leading '+' as
        # required. This includes whitespace that will be removed later.
        "phone": "+1 800 5550101",
    }

    # The second user data has an email address, a mailing address, and a phone
    # number.
    raw_record_2: Dict[str, str] = {
        # Email address that includes a period (.) before the email domain.
        "email": "alex.2@example.com",
        # Address that includes all four required elements: first name, last
        # name, country code, and postal code.
        "first_name": "Alex",
        "last_name": "Quinn",
        "country_code": "US",
        "postal_code": "94045",
        # Phone number to be converted to E.164 format, with a leading '+' as
        # required.
        "phone": "+1 800 5550102",
    }

    # The third user data only has an email address.
    raw_record_3: Dict[str, str] = {"email": "charlie@example.com"}

    # Adds the raw records to a raw input list.
    raw_records: List[Dict[str, str]] = [
        raw_record_1,
        raw_record_2,
        raw_record_3,
    ]

    operations: List[OfflineUserDataJobOperation] = []
    # Iterates over the raw input list and creates a UserData object for each
    # record.
    for record in raw_records:
        # Creates a UserData object that represents a member of the user list.
        user_data: UserData = client.get_type("UserData")

        # Checks if the record has email, phone, or address information, and
        # adds a SEPARATE UserIdentifier object for each one found. For example,
        # a record with an email address and a phone number will result in a
        # UserData with two UserIdentifiers.

        # IMPORTANT: Since the identifier attribute of UserIdentifier
        # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
        # is a oneof
        # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you
        # must set only ONE of hashed_email, hashed_phone_number, mobile_id,
        # third_party_user_id, or address-info. Setting more than one of these
        # attributes on the same UserIdentifier will clear all the other members
        # of the oneof. For example, the following code is INCORRECT and will
        # result in a UserIdentifier with ONLY a hashed_phone_number:

        # incorrect_user_identifier = client.get_type("UserIdentifier")
        # incorrect_user_identifier.hashed_email = "..."
        # incorrect_user_identifier.hashed_phone_number = "..."

        # The separate 'if' statements below demonstrate the correct approach
        # for creating a UserData object for a member with multiple
        # UserIdentifiers.

        # Checks if the record has an email address, and if so, adds a
        # UserIdentifier for it.
        if "email" in record:
            user_identifier: UserIdentifier = client.get_type("UserIdentifier")
            user_identifier.hashed_email = normalize_and_hash(
                record["email"], True
            )
            # Adds the hashed email identifier to the UserData object's list.
            user_data.user_identifiers.append(user_identifier)

        # Checks if the record has a phone number, and if so, adds a
        # UserIdentifier for it.
        if "phone" in record:
            user_identifier: UserIdentifier = client.get_type("UserIdentifier")
            user_identifier.hashed_phone_number = normalize_and_hash(
                record["phone"], True
            )
            # Adds the hashed phone number identifier to the UserData object's
            # list.
            user_data.user_identifiers.append(user_identifier)

        # Checks if the record has all the required mailing address elements,
        # and if so, adds a UserIdentifier for the mailing address.
        if "first_name" in record:
            required_keys = ("last_name", "country_code", "postal_code")
            # Checks if the record contains all the other required elements of
            # a mailing address.
            if not all(key in record for key in required_keys):
                # Determines which required elements are missing from the
                # record.
                missing_keys = record.keys() - required_keys
                print(
                    "Skipping addition of mailing address information "
                    "because the following required keys are missing: "
                    f"{missing_keys}"
                )
            else:
                user_identifier: UserIdentifier = client.get_type(
                    "UserIdentifier"
                )
                address_info: AddressInfo = user_identifier.address_info
                address_info.hashed_first_name = normalize_and_hash(
                    record["first_name"], False
                )
                address_info.hashed_last_name = normalize_and_hash(
                    record["last_name"], False
                )
                address_info.country_code = record["country_code"]
                address_info.postal_code = record["postal_code"]
                user_data.user_identifiers.append(user_identifier)

        # If the user_identifiers repeated field is not empty, create a new
        # OfflineUserDataJobOperation and add the UserData to it.
        if user_data.user_identifiers:
            operation: OfflineUserDataJobOperation = client.get_type(
                "OfflineUserDataJobOperation"
            )
            operation.create = user_data
            operations.append(operation)
      

Ruby

# Create a list of unhashed user data records that we will format in the
# following steps to prepare for the API.
raw_records = [
  # The first user data has an email address and a phone number.
  {
    email: 'dana@example.com',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required. This includes whitespace that will be removed later.
    phone: '+1 800 5550100',
  },
  # The second user data has an email address, a phone number, and an address.
  {
    # Email address that includes a period (.) before the Gmail domain.
    email: 'alex.2@example.com',
    # Address that includes all four required elements: first name, last
    # name, country code, and postal code.
    first_name: 'Alex',
    last_name: 'Quinn',
    country_code: 'US',
    postal_code: '94045',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    phone: '+1 800 5550102',
  },
  # The third user data only has an email address.
  {
    email: 'charlie@example.com',
  },
]

# Create a UserData for each entry in the raw records.
user_data_list = raw_records.map do |record|
  client.resource.user_data do |data|
    if record[:email]
      data.user_identifiers << client.resource.user_identifier do |ui|
        ui.hashed_email = normalize_and_hash(record[:email], true)
      end
    end
    if record[:phone]
      data.user_identifiers << client.resource.user_identifier do |ui|
        ui.hashed_phone_number = normalize_and_hash(record[:phone], true)
      end
    end
    if record[:first_name]
      # Check that we have all the required information.
      missing_keys = [:last_name, :country_code, :postal_code].reject {|key|
        record[key].nil?
      }
      if missing_keys.empty?
        # If nothing is missing, add the address.
        data.user_identifiers << client.resource.user_identifier do |ui|
          ui.address_identifier = client.resource.offline_user_address_info do |address|
            address.hashed_first_name = normalize_and_hash(record[:first_name])
            address.hashed_last_name = normalize_and_hash(record[:last_name])
            address.country_code = record[:country_code]
            address.postal_code = record[:postal_code]
          end
        end
      else
        # If some data is missing, skip this entry.
        puts "Skipping addition of mailing information because the following keys are missing:" \
          "#{missing_keys}"
      end
    end
  end
end

operations = user_data_list.map do |user_data|
  client.operation.create_resource.offline_user_data_job(user_data)
end
      

Perl

  # The first user data has an email address and a phone number.
  my $raw_record_1 = {
    email => 'dana@example.com',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required. This includes whitespace that will be removed later.
    phone => '+1 800 5550101',
  };

  # The second user data has an email address, a mailing address, and a phone
  # number.
  my $raw_record_2 = {
    # Email address that includes a period (.) before the Gmail domain.
    email => 'alex.2@example.com',
    # Address that includes all four required elements: first name, last
    # name, country code, and postal code.
    firstName   => 'Alex',
    lastName    => 'Quinn',
    countryCode => 'US',
    postalCode  => '94045',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    phone => '+1 800 5550102',
  };

  # The third user data only has an email address.
  my $raw_record_3 = {email => 'charlie@example.com',};

  my $raw_records = [$raw_record_1, $raw_record_2, $raw_record_3];

  my $operations = [];
  foreach my $record (@$raw_records) {
    # Check if the record has email, phone, or address information, and adds a
    # SEPARATE UserIdentifier object for each one found. For example, a record
    # with an email address and a phone number will result in a UserData with two
    # UserIdentifiers.
    #
    # IMPORTANT: Since the identifier attribute of UserIdentifier
    # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
    # is a oneof
    # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set
    # only ONE of hashed_email, hashed_phone_number, mobile_id, third_party_user_id,
    # or address-info. Setting more than one of these attributes on the same UserIdentifier
    # will clear all the other members of the oneof. For example, the following code is
    # INCORRECT and will result in a UserIdentifier with ONLY a hashed_phone_number:
    #
    # my $incorrect_user_identifier = Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
    #   hashedEmail => '...',
    #   hashedPhoneNumber => '...',
    # });
    #
    # The separate 'if' statements below demonstrate the correct approach for creating a
    # UserData object for a member with multiple UserIdentifiers.

    my $user_identifiers = [];

    # Check if the record has an email address, and if so, add a UserIdentifier for it.
    if (defined $record->{email}) {
      # Add the hashed email identifier to the list of UserIdentifiers.
      push(
        @$user_identifiers,
        Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
            hashedEmail => normalize_and_hash($record->{email}, 1)}));
    }

    # Check if the record has a phone number, and if so, add a UserIdentifier for it.
    if (defined $record->{phone}) {
      # Add the hashed phone number identifier to the list of UserIdentifiers.
      push(
        @$user_identifiers,
        Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
            hashedPhoneNumber => normalize_and_hash($record->{phone}, 1)}));
    }

    # Check if the record has all the required mailing address elements, and if so, add
    # a UserIdentifier for the mailing address.
    if (defined $record->{firstName}) {
      my $required_keys = ["lastName", "countryCode", "postalCode"];
      my $missing_keys  = [];

      foreach my $key (@$required_keys) {
        if (!defined $record->{$key}) {
          push(@$missing_keys, $key);
        }
      }

      if (@$missing_keys) {
        print
"Skipping addition of mailing address information because the following"
          . "keys are missing: "
          . join(",", @$missing_keys);
      } else {
        push(
          @$user_identifiers,
          Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
              addressInfo =>
                Google::Ads::GoogleAds::V25::Common::OfflineUserAddressInfo->
                new({
                  # First and last name must be normalized and hashed.
                  hashedFirstName => normalize_and_hash($record->{firstName}),
                  hashedLastName  => normalize_and_hash($record->{lastName}),
                  # Country code and zip code are sent in plain text.
                  countryCode => $record->{countryCode},
                  postalCode  => $record->{postalCode},
                })}));
      }
    }

    # If the user_identifiers array is not empty, create a new
    # OfflineUserDataJobOperation and add the UserData to it.
    if (@$user_identifiers) {
      my $user_data = Google::Ads::GoogleAds::V25::Common::UserData->new({
          userIdentifiers => [$user_identifiers]});
      push(
        @$operations,
        Google::Ads::GoogleAds::V25::Services::OfflineUserDataJobService::OfflineUserDataJobOperation
          ->new({
            create => $user_data
          }));
    }
  }
      

curl

第 3 步:验证名单上传和匹配率

当 OfflineUserDataJob 具有 SUCCESS 状态后,您可以在 operation_metadata.match_rate_range 字段中查看估计的匹配率。如果在作业完成之前查询此字段,此字段中的值可能为零。为确保匹配率已准备好进行验证,并且名单已准备好用于定位,我们建议您轮询作业以了解其完成情况。作业可能只需 10 分钟即可完成,也可能需要长达 24 小时。

用于检查作业状态的代码示例

Java

private void checkJobStatus(
    GoogleAdsClient googleAdsClient, long customerId, String offlineUserDataJobResourceName) {
  try (GoogleAdsServiceClient googleAdsServiceClient =
      googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
    String query =
        String.format(
            "SELECT offline_user_data_job.resource_name, "
                + "offline_user_data_job.id, "
                + "offline_user_data_job.status, "
                + "offline_user_data_job.type, "
                + "offline_user_data_job.failure_reason, "
                + "offline_user_data_job.customer_match_user_list_metadata.user_list "
                + "FROM offline_user_data_job "
                + "WHERE offline_user_data_job.resource_name = '%s'",
            offlineUserDataJobResourceName);
    // Issues the query and gets the GoogleAdsRow containing the job from the response.
    GoogleAdsRow googleAdsRow =
        googleAdsServiceClient
            .search(Long.toString(customerId), query)
            .iterateAll()
            .iterator()
            .next();
    OfflineUserDataJob offlineUserDataJob = googleAdsRow.getOfflineUserDataJob();
    System.out.printf(
        "Offline user data job ID %d with type '%s' has status: %s%n",
        offlineUserDataJob.getId(), offlineUserDataJob.getType(), offlineUserDataJob.getStatus());
    OfflineUserDataJobStatus jobStatus = offlineUserDataJob.getStatus();
    if (OfflineUserDataJobStatus.SUCCESS == jobStatus) {
      // Prints information about the user list.
      printCustomerMatchUserListInfo(
          googleAdsClient,
          customerId,
          offlineUserDataJob.getCustomerMatchUserListMetadata().getUserList());
    } else if (OfflineUserDataJobStatus.FAILED == jobStatus) {
      System.out.printf("  Failure reason: %s%n", offlineUserDataJob.getFailureReason());
    } else if (OfflineUserDataJobStatus.PENDING == jobStatus
        || OfflineUserDataJobStatus.RUNNING == jobStatus) {
      System.out.println();
      System.out.printf(
          "To check the status of the job periodically, use the following GAQL query with"
              + " GoogleAdsService.search:%n%s%n",
          query);
    }
  }
}

      

C#

private static void CheckJobStatusAndPrintResults(GoogleAdsClient client, long customerId,
    string offlineUserDataJobResourceName)
{
    // Get the GoogleAdsService.
    GoogleAdsServiceClient service = client.GetService(Services.V25.GoogleAdsService);

    string query = "SELECT offline_user_data_job.resource_name, " +
        "offline_user_data_job.id, offline_user_data_job.status, " +
        "offline_user_data_job.type, offline_user_data_job.failure_reason, " +
        "offline_user_data_job.customer_match_user_list_metadata.user_list " +
        "FROM offline_user_data_job WHERE " +
        $"offline_user_data_job.resource_name = '{offlineUserDataJobResourceName}'";

    // Issues the query and gets the GoogleAdsRow containing the job from the response.
    GoogleAdsRow googleAdsRow = service.Search(customerId.ToString(), query).First();

    OfflineUserDataJob offlineUserDataJob = googleAdsRow.OfflineUserDataJob;
    Console.WriteLine($"Offline user data job ID {offlineUserDataJob.Id} with type " +
        $"'{offlineUserDataJob.Type}' has status: {offlineUserDataJob.Status}");

    switch (offlineUserDataJob.Status)
    {
        case OfflineUserDataJobStatus.Success:
            // Prints information about the user list.
            PrintCustomerMatchUserListInfo(client, customerId,
                offlineUserDataJob.CustomerMatchUserListMetadata.UserList);
            break;

        case OfflineUserDataJobStatus.Failed:
            Console.WriteLine($"  Failure reason: {offlineUserDataJob.FailureReason}");
            break;

        case OfflineUserDataJobStatus.Pending:
        case OfflineUserDataJobStatus.Running:
            Console.WriteLine("To check the status of the job periodically, use the " +
                $"following GAQL query with GoogleAdsService.search:\n\n{query}");
            break;
    }
}
      

PHP

    private static function checkJobStatus(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $offlineUserDataJobResourceName
    ) {
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();

        // Creates a query that retrieves the offline user data job.
        $query = "SELECT offline_user_data_job.resource_name, "
              . "offline_user_data_job.id, "
              . "offline_user_data_job.status, "
              . "offline_user_data_job.type, "
              . "offline_user_data_job.failure_reason, "
              . "offline_user_data_job.customer_match_user_list_metadata.user_list "
              . "FROM offline_user_data_job "
              . "WHERE offline_user_data_job.resource_name = '$offlineUserDataJobResourceName'";

        // Issues a search request to get the GoogleAdsRow containing the job from the response.
        /**
 * @var GoogleAdsRow $googleAdsRow
*/
        $googleAdsRow =
            $googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query))
            ->getIterator()
            ->current();
        $offlineUserDataJob = $googleAdsRow->getOfflineUserDataJob();

        // Prints out some information about the offline user data job.
        $offlineUserDataJobStatus = $offlineUserDataJob->getStatus();
        printf(
            "Offline user data job ID %d with type '%s' has status: %s.%s",
            $offlineUserDataJob->getId(),
            OfflineUserDataJobType::name($offlineUserDataJob->getType()),
            OfflineUserDataJobStatus::name($offlineUserDataJobStatus),
            PHP_EOL
        );

        if ($offlineUserDataJobStatus === OfflineUserDataJobStatus::SUCCESS) {
            // Prints information about the user list.
            self::printCustomerMatchUserListInfo(
                $googleAdsClient,
                $customerId,
                $offlineUserDataJob->getCustomerMatchUserListMetadata()->getUserList()
            );
        } elseif ($offlineUserDataJobStatus === OfflineUserDataJobStatus::FAILED) {
            printf("  Failure reason: %s.%s", $offlineUserDataJob->getFailureReason(), PHP_EOL);
        } elseif (
            $offlineUserDataJobStatus === OfflineUserDataJobStatus::PENDING
            || $offlineUserDataJobStatus === OfflineUserDataJobStatus::RUNNING
        ) {
            printf(
                '%1$sTo check the status of the job periodically, use the following GAQL query with'
                . ' GoogleAdsService.search:%1$s%2$s%1$s',
                PHP_EOL,
                $query
            );
        }
    }
      

Python

def check_job_status(
    client: GoogleAdsClient,
    customer_id: str,
    offline_user_data_job_resource_name: str,
) -> None:
    """Retrieves, checks, and prints the status of the offline user data job.

    If the job is completed successfully, information about the user list is
    printed. Otherwise, a GAQL query will be printed, which can be used to
    check the job status at a later date.

    Offline user data jobs may take 6 hours or more to complete, so checking the
    status periodically, instead of waiting, can be more efficient.

    Args:
        client: The Google Ads client.
        customer_id: The ID for the customer that owns the user list.
        offline_user_data_job_resource_name: The resource name of the offline
            user data job to get the status of.
    """
    query: str = f"""
        SELECT
          offline_user_data_job.resource_name,
          offline_user_data_job.id,
          offline_user_data_job.status,
          offline_user_data_job.type,
          offline_user_data_job.failure_reason,
          offline_user_data_job.customer_match_user_list_metadata.user_list
        FROM offline_user_data_job
        WHERE offline_user_data_job.resource_name =
          '{offline_user_data_job_resource_name}'
        LIMIT 1"""

    # Issues a search request using streaming.
    google_ads_service: GoogleAdsServiceClient = client.get_service(
        "GoogleAdsService"
    )
    results: SearchGoogleAdsStreamResponse = google_ads_service.search(
        customer_id=customer_id, query=query
    )
    offline_user_data_job_result: OfflineUserDataJob = next(
        iter(results)
    ).offline_user_data_job
    status_name: str = offline_user_data_job_result.status.name
    user_list_resource_name: str = (
        offline_user_data_job_result.customer_match_user_list_metadata.user_list
    )

    print(
        f"Offline user data job ID '{offline_user_data_job_result.id}' with type "
        f"'{offline_user_data_job_result.type_.name}' has status: {status_name}"
    )

    if status_name == "SUCCESS":
        print_customer_match_user_list_info(
            client, customer_id, user_list_resource_name
        )
    elif status_name == "FAILED":
        print(
            f"\tFailure Reason: {offline_user_data_job_result.failure_reason}"
        )
    elif status_name in ("PENDING", "RUNNING"):
        print(
            "To check the status of the job periodically, use the following "
            f"GAQL query with GoogleAdsService.Search: {query}"
        )
      

Ruby

def check_job_status(client, customer_id, offline_user_data_job)
  query = <<~QUERY
    SELECT
      offline_user_data_job.id,
      offline_user_data_job.status,
      offline_user_data_job.type,
      offline_user_data_job.failure_reason,
      offline_user_data_job.customer_match_user_list_metadata.user_list
    FROM
      offline_user_data_job
    WHERE
      offline_user_data_job.resource_name = '#{offline_user_data_job}'
  QUERY

  row = client.service.google_ads.search(
    customer_id: customer_id,
    query: query,
  ).first

  job = row.offline_user_data_job
  puts "Offline user data job ID #{job.id} with type '#{job.type}' has status: #{job.status}."

  case job.status
  when :SUCCESS
    print_customer_match_user_list(client, customer_id, job.customer_match_user_list_metadata.user_list)
  when :FAILED
    puts "  Failure reason: #{job.failure_reason}"
  else
    puts "  To check the status of the job periodically, use the following GAQL " \
      "query with GoogleAdsService.search:"
    puts query
  end
end
      

Perl

sub check_job_status {
  my ($api_client, $customer_id, $offline_user_data_job_resource_name) = @_;

  my $search_query =
    "SELECT offline_user_data_job.resource_name, " .
    "offline_user_data_job.id, offline_user_data_job.status, " .
    "offline_user_data_job.type, offline_user_data_job.failure_reason, " .
    "offline_user_data_job.customer_match_user_list_metadata.user_list " .
    "FROM offline_user_data_job " .
    "WHERE offline_user_data_job.resource_name = " .
    "'$offline_user_data_job_resource_name' LIMIT 1";

  my $search_request =
    Google::Ads::GoogleAds::V25::Services::GoogleAdsService::SearchGoogleAdsRequest
    ->new({
      customerId => $customer_id,
      query      => $search_query
    });

  # Get the GoogleAdsService.
  my $google_ads_service = $api_client->GoogleAdsService();

  my $iterator = Google::Ads::GoogleAds::Utils::SearchGoogleAdsIterator->new({
    service => $google_ads_service,
    request => $search_request
  });

  # The results have exactly one row.
  my $google_ads_row        = $iterator->next;
  my $offline_user_data_job = $google_ads_row->{offlineUserDataJob};
  my $status                = $offline_user_data_job->{status};

  printf
    "Offline user data job ID %d with type %s has status: %s.\n",
    $offline_user_data_job->{id},
    $offline_user_data_job->{type},
    $status;

  if ($status eq SUCCESS) {
    print_customer_match_user_list_info($api_client, $customer_id,
      $offline_user_data_job->{customerMatchUserListMetadata}{userList});
  } elsif ($status eq FAILED) {
    print "Failure reason: $offline_user_data_job->{failureReason}";
  } elsif (grep /$status/, (PENDING, RUNNING)) {
    print
      "To check the status of the job periodically, use the following GAQL " .
      "query with the GoogleAdsService->search() method:\n$search_query\n";
  }

  return 1;
}
      

curl

如需验证列表大小,您可以查询 user_list 资源。

用于查询 user_list 资源的代码示例

Java

try (GoogleAdsServiceClient googleAdsServiceClient =
    googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
  // Creates a query that retrieves the user list.
  String query =
      String.format(
          "SELECT user_list.size_for_display, user_list.size_for_search "
              + "FROM user_list "
              + "WHERE user_list.resource_name = '%s'",
          userListResourceName);

  // Constructs the SearchGoogleAdsStreamRequest.
  SearchGoogleAdsStreamRequest request =
      SearchGoogleAdsStreamRequest.newBuilder()
          .setCustomerId(Long.toString(customerId))
          .setQuery(query)
          .build();

  // Issues the search stream request.
  ServerStream<SearchGoogleAdsStreamResponse> stream =
      googleAdsServiceClient.searchStreamCallable().call(request);
      

C#

 // Get the GoogleAdsService.
 GoogleAdsServiceClient service =
     client.GetService(Services.V25.GoogleAdsService);

 // Creates a query that retrieves the user list.
 string query =
     "SELECT user_list.size_for_display, user_list.size_for_search " +
     "FROM user_list " +
     $"WHERE user_list.resource_name = '{userListResourceName}'";
 // Issues a search stream request.
 service.SearchStream(customerId.ToString(), query,
    delegate (SearchGoogleAdsStreamResponse resp)
    {
        // Display the results.
        foreach (GoogleAdsRow userListRow in resp.Results)
        {
            UserList userList = userListRow.UserList;
            Console.WriteLine("The estimated number of users that the user list " +
                $"'{userList.ResourceName}' has is {userList.SizeForDisplay}" +
                $" for Display and {userList.SizeForSearch} for Search.");
        }
    }
);
      

PHP

        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();

        // Creates a query that retrieves the user list.
        $query =
            "SELECT user_list.size_for_display, user_list.size_for_search " .
            "FROM user_list " .
            "WHERE user_list.resource_name = '$userListResourceName'";

        // Issues a search stream request.
        /**
 * @var GoogleAdsServerStreamDecorator $stream
*/
        $stream = $googleAdsServiceClient->searchStream(
            SearchGoogleAdsStreamRequest::build($customerId, $query)
        );
      

Python

googleads_service_client: GoogleAdsServiceClient = client.get_service(
    "GoogleAdsService"
)

# Creates a query that retrieves the user list.
query: str = f"""
    SELECT
      user_list.size_for_display,
      user_list.size_for_search
    FROM user_list
    WHERE user_list.resource_name = '{user_list_resource_name}'"""

# Issues a search request.
search_results: SearchGoogleAdsStreamResponse = (
    googleads_service_client.search(customer_id=customer_id, query=query)
)
      

Ruby

query = <<~EOQUERY
  SELECT user_list.size_for_display, user_list.size_for_search
  FROM user_list
  WHERE user_list.resource_name = #{user_list}
EOQUERY

response = client.service.google_ads.search_stream(
  customer_id: customer_id,
  query: query,
)
      

Perl

# Create a query that retrieves the user list.
my $search_query =
  "SELECT user_list.size_for_display, user_list.size_for_search " .
  "FROM user_list " .
  "WHERE user_list.resource_name = '$user_list_resource_name'";

# Create a search Google Ads stream request that will retrieve the user list.
my $search_stream_request =
  Google::Ads::GoogleAds::V25::Services::GoogleAdsService::SearchGoogleAdsStreamRequest
  ->new({
    customerId => $customer_id,
    query      => $search_query,
  });

# Get the GoogleAdsService.
my $google_ads_service = $api_client->GoogleAdsService();

my $search_stream_handler =
  Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
    service => $google_ads_service,
    request => $search_stream_request
  });
      

curl

出于隐私保护目的,在名单至少有 100 名成员之前,用户名单规模会显示为零。之后,系统会将大小四舍五入到两个最重要的数字。

通过 Google Ads 查询语言,使用 offline_user_data_job 资源可以提取执行 OfflineUserDataJob 期间发生的错误。不过,由于在执行匹配时仅比较哈希值,因此该报告不包含有关任何匹配失败的信息。如果您在客户名单方面遇到问题,请参阅问题排查指南。

与 Google Ads 界面进行比较

在 Google Ads 界面的受众群体管理器中查看时,名单的规模可能会低于预期。此视图会显示列表中的活跃用户数。 如需了解详情,请参阅此问题排查指南。

由于名单最多可能需要 24 小时才能填充成员,因此如果您上传到受众群体名单的频率高于每 12 小时一次,则可能会在 Google Ads 界面中看到In Progress 状态。

第 4 步:定位列表

您可以在广告组一级或广告系列一级定位到您的名单。此过程与 API 中的其他类型的定位条件类似。

将广告组中的广告定位到用户名单的代码示例

Java

private String targetAdsInAdGroupToUserList(
    GoogleAdsClient googleAdsClient, long customerId, long adGroupId, String userList) {
  // Creates the ad group criterion targeting members of the user list.
  AdGroupCriterion adGroupCriterion =
      AdGroupCriterion.newBuilder()
          .setAdGroup(ResourceNames.adGroup(customerId, adGroupId))
          .setUserList(UserListInfo.newBuilder().setUserList(userList).build())
          .build();

  // Creates the operation.
  AdGroupCriterionOperation operation =
      AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterion).build();

  // Creates the ad group criterion service.
  try (AdGroupCriterionServiceClient adGroupCriterionServiceClient =
      googleAdsClient.getLatestVersion().createAdGroupCriterionServiceClient()) {
    // Adds the ad group criterion.
    MutateAdGroupCriteriaResponse response =
        adGroupCriterionServiceClient.mutateAdGroupCriteria(
            Long.toString(customerId), ImmutableList.of(operation));
    // Gets and prints the results.
    String adGroupCriterionResourceName = response.getResults(0).getResourceName();
    System.out.printf(
        "Successfully created ad group criterion with resource name '%s' "
            + "targeting user list with resource name '%s' with ad group with ID %d.%n",
        adGroupCriterionResourceName, userList, adGroupId);
    return adGroupCriterionResourceName;
  }
}

      

C#

private string TargetAdsInAdGroupToUserList(
    GoogleAdsClient client, long customerId, long adGroupId, string userListResourceName)
{
    // Get the AdGroupCriterionService client.
    AdGroupCriterionServiceClient adGroupCriterionServiceClient = client.GetService
        (Services.V25.AdGroupCriterionService);

    // Create the ad group criterion targeting members of the user list.
    AdGroupCriterion adGroupCriterion = new AdGroupCriterion
    {
        AdGroup = ResourceNames.AdGroup(customerId, adGroupId),
        UserList = new UserListInfo
        {
            UserList = userListResourceName
        }
    };

    // Create the operation.
    AdGroupCriterionOperation adGroupCriterionOperation = new AdGroupCriterionOperation
    {
        Create = adGroupCriterion
    };

    // Add the ad group criterion, then print and return the new criterion's resource name.
    MutateAdGroupCriteriaResponse mutateAdGroupCriteriaResponse =
        adGroupCriterionServiceClient.MutateAdGroupCriteria(customerId.ToString(),
            new[] { adGroupCriterionOperation });

    string adGroupCriterionResourceName =
        mutateAdGroupCriteriaResponse.Results.First().ResourceName;
    Console.WriteLine("Successfully created ad group criterion with resource name " +
        $"'{adGroupCriterionResourceName}' targeting user list with resource name " +
        $"'{userListResourceName}' with ad group with ID {adGroupId}.");
    return adGroupCriterionResourceName;
}
      

PHP

    private static function targetAdsInAdGroupToUserList(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $adGroupId,
        string $userListResourceName
    ): string {
        // Creates the ad group criterion targeting members of the user list.
        $adGroupCriterion = new AdGroupCriterion(
            [
            'ad_group' => ResourceNames::forAdGroup($customerId, $adGroupId),
            'user_list' => new UserListInfo(['user_list' => $userListResourceName])
            ]
        );

        // Creates the operation.
        $operation = new AdGroupCriterionOperation();
        $operation->setCreate($adGroupCriterion);

        // Issues a mutate request to add an ad group criterion.
        $adGroupCriterionServiceClient = $googleAdsClient->getAdGroupCriterionServiceClient();
        /**
 * @var MutateAdGroupCriteriaResponse $adGroupCriterionResponse
*/
        $adGroupCriterionResponse = $adGroupCriterionServiceClient->mutateAdGroupCriteria(
            MutateAdGroupCriteriaRequest::build($customerId, [$operation])
        );

        $adGroupCriterionResourceName =
            $adGroupCriterionResponse->getResults()[0]->getResourceName();
        printf(
            "Successfully created ad group criterion with resource name '%s' " .
            "targeting user list with resource name '%s' with ad group with ID %d.%s",
            $adGroupCriterionResourceName,
            $userListResourceName,
            $adGroupId,
            PHP_EOL
        );

        return $adGroupCriterionResourceName;
    }
      

Python

def target_ads_in_ad_group_to_user_list(
    client: GoogleAdsClient,
    customer_id: str,
    ad_group_id: str,
    user_list_resource_name: str,
) -> str:
    """Creates an ad group criterion that targets a user list with an ad group.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a str client customer ID used to create an ad group
            criterion.
        ad_group_id: a str ID for an ad group used to create an ad group
            criterion that targets members of a user list.
        user_list_resource_name: a str resource name for a user list.

    Returns:
        a str resource name for an ad group criterion.
    """
    ad_group_criterion_operation: AdGroupCriterionOperation = client.get_type(
        "AdGroupCriterionOperation"
    )
    # Creates the ad group criterion targeting members of the user list.
    ad_group_criterion: AdGroupCriterion = ad_group_criterion_operation.create
    ad_group_criterion.ad_group = client.get_service(
        "AdGroupService"
    ).ad_group_path(customer_id, ad_group_id)
    ad_group_criterion.user_list.user_list = user_list_resource_name

    ad_group_criterion_service: AdGroupCriterionServiceClient = (
        client.get_service("AdGroupCriterionService")
    )
    response: MutateAdGroupCriteriaResponse = (
        ad_group_criterion_service.mutate_ad_group_criteria(
            customer_id=customer_id, operations=[ad_group_criterion_operation]
        )
    )
    resource_name: str = response.results[0].resource_name
    print(
        "Successfully created ad group criterion with resource name: "
        f"'{resource_name}' targeting user list with resource name: "
        f"'{user_list_resource_name}' and with ad group with ID "
        f"{ad_group_id}."
    )
    return resource_name
      

Ruby

def target_ads_in_ad_group_to_user_list(
  client,
  customer_id,
  ad_group_id,
  user_list
)
  # Creates the ad group criterion targeting members of the user list.
  operation = client.operation.create_resource.ad_group_criterion do |agc|
    agc.ad_group = client.path.ad_group(customer_id, ad_group_id)
    agc.user_list = client.resource.user_list_info do |info|
      info.user_list = user_list
    end
  end

  # Issues a mutate request to create the ad group criterion.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  ad_group_criterion_resource_name = response.results.first.resource_name
  puts "Successfully created ad group criterion with resource name " \
    "'#{ad_group_criterion_resource_name}' targeting user list with resource name " \
    "'#{user_list}' with ad group with ID #{ad_group_id}"

  ad_group_criterion_resource_name
end
      

Perl

sub target_ads_in_ad_group_to_user_list {
  my ($api_client, $customer_id, $ad_group_id, $user_list_resource_name) = @_;

  # Create the ad group criterion targeting members of the user list.
  my $ad_group_criterion =
    Google::Ads::GoogleAds::V25::Resources::AdGroupCriterion->new({
      adGroup => Google::Ads::GoogleAds::V25::Utils::ResourceNames::ad_group(
        $customer_id, $ad_group_id
      ),
      userList => Google::Ads::GoogleAds::V25::Common::UserListInfo->new({
          userList => $user_list_resource_name
        })});

  # Create the operation.
  my $ad_group_criterion_operation =
    Google::Ads::GoogleAds::V25::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion
    });

  # Add the ad group criterion, then print and return the new criterion's resource name.
  my $ad_group_criteria_response =
    $api_client->AdGroupCriterionService()->mutate({
      customerId => $customer_id,
      operations => [$ad_group_criterion_operation]});

  my $ad_group_criterion_resource_name =
    $ad_group_criteria_response->{results}[0]{resourceName};
  printf "Successfully created ad group criterion with resource name '%s' " .
    "targeting user list with resource name '%s' with ad group with ID %d.\n",
    $ad_group_criterion_resource_name, $user_list_resource_name, $ad_group_id;

  return $ad_group_criterion_resource_name;
}
      

curl

定位多个客户名单

使用 logical_user_list 时,crm_based_user_list 只能与另一个 crm_based_user_list 结合使用。crm_based_user_list 的所有政策都适用于生成的用户列表。

管理和更新列表

创建客户名单后,您可能需要定期更新这些名单,以反映客户群的变化。借助 Google Ads API,您可以添加或移除会员,从而管理现有列表。

更新列表的关键操作包括:

  • 附加新用户:向现有名单添加新客户数据。
  • 移除特定用户:根据标识符从列表中移除个别用户。
  • 替换所有成员:清除现有列表并完全替换其内容,不过建议改为附加和移除。

请参阅管理客户名单。