עקרונות בסיסיים בנושא דיווח

מבוא

במדריך הזה נסביר איך להפעיל ולהוריד דוח באמצעות ה-API. הוא עוסק גם בשימוש בשאילתת דוח שמור קיימת וגם ביצירת שאילתה של דוח אד-הוק.

דרישות מוקדמות

Primer

לא יודעים איך עובדים בדוחות ב-Ad Manager? מומלץ לקרוא את המאמר יצירת דוח חדש כדי לקבל סקירה כללית על הרצת דוח בממשק המשתמש של Ad Manager. ממשק המשתמש כולל תצוגה מקדימה של הפלט, וגם הסברים קצרים שמסבירים אילו שילובים של עמודות ומאפיינים נתמכים. כשיוצרים שאילתת דוח מורכבת, יכול להיות שקודם קל יותר ליצור אותה בממשק המשתמש ואז לאחזר אותה באמצעות ה-API.

אחזור של ReportQuery שמור

האובייקט ReportQuery מכיל את כל הפרטים של הדוח. אפשר ליצור שאילתות לגבי דוחות בממשק המשתמש של Ad Manager, ולאחזר אותן באמצעות method ReportService.getSavedQueriesByStatement. מזהה השאילתה השמורה נכלל בכתובת ה-URL כשמציגים שאילתה בממשק המשתמש. לדוגמה, בכתובת ה-URL https://www.google.com/admanager/1234#reports/report/detail/report_id=456789, מזהה השאילתה הוא 456789.

אם שאילתה לא תואמת לגרסת ה-API שבה אתם משתמשים, SavedQuery.reportQuery null ו-SavedQuery.isCompatibleWithApiVersion ייהיה false.

אפשר להריץ שאילתות שמורות תואמות עם או בלי שינויים.

Java


    StatementBuilder statementBuilder =
        new StatementBuilder()
            .where("id = :id")
            .orderBy("id ASC")
            .limit(1)
            .withBindVariableValue("id", savedQueryId);

    SavedQueryPage page = reportService.getSavedQueriesByStatement(statementBuilder.toStatement());
    SavedQuery savedQuery = Iterables.getOnlyElement(Arrays.asList(page.getResults()));

    if (!savedQuery.getIsCompatibleWithApiVersion()) {
      throw new IllegalStateException("The saved query is not compatible with this API version.");
    }

    ReportQuery reportQuery = savedQuery.getReportQuery();
    

Python


  statement = (ad_manager.StatementBuilder(version='v202402')
               .Where('id = :id')
               .WithBindVariable('id', int(saved_query_id))
               .Limit(1))

  response = report_service.getSavedQueriesByStatement(
      statement.ToStatement())

  if 'results' in response and len(response['results']):
    saved_query = response['results'][0]

    if saved_query['isCompatibleWithApiVersion']:
      report_job = {}

      # Set report query and optionally modify it.
      report_job['reportQuery'] = saved_query['reportQuery']
    

PHP


      $statementBuilder = (new StatementBuilder())->where('id = :id')
          ->orderBy('id ASC')
          ->limit(1)
          ->withBindVariableValue('id', $savedQueryId);

      $savedQueryPage = $reportService->getSavedQueriesByStatement(
          $statementBuilder->toStatement()
      );
      $savedQuery = $savedQueryPage->getResults()[0];

      if ($savedQuery->getIsCompatibleWithApiVersion() === false) {
          throw new UnexpectedValueException(
              'The saved query is not compatible with this API version.'
          );
      }

      $reportQuery = $savedQuery->getReportQuery();
    

C#


StatementBuilder statementBuilder = new StatementBuilder()
    .Where("id = :id")
    .OrderBy("id ASC")
    .Limit(1)
    .AddValue("id", savedQueryId);

SavedQueryPage page =
    reportService.getSavedQueriesByStatement(statementBuilder.ToStatement());
SavedQuery savedQuery = page.results[0];

if (!savedQuery.isCompatibleWithApiVersion)
{
    throw new InvalidOperationException("Saved query is not compatible with this " +
        "API version");
}

// Optionally modify the query.
ReportQuery reportQuery = savedQuery.reportQuery;
    

Ruby


  statement = ad_manager.new_statement_builder do |sb|
    sb.where = 'id = :saved_query_id'
    sb.with_bind_variable('saved_query_id', saved_query_id)
  end

  saved_query_page = report_service.get_saved_queries_by_statement(
      statement.to_statement()
  )

  unless saved_query_page[:results].nil?
    saved_query = saved_query_page[:results].first

    if saved_query[:is_compatible_with_api_version]
      # Create report job.
      report_job = {:report_query => saved_query[:report_query]}
    else
      raise StandardError, 'Report query is not compatible with the API'
    end
    

כדי להריץ את השאילתה, ראו יצירת משימת ReportJob.

בניית שאילתת ReportQuery

בנוסף לשימוש בשאילתות השמורות, ניתן גם ליצור שאילתת דוח אד-הוק. לשם כך, צריך להגדיר בדוח את המאפיינים, מאפייני המאפיינים, העמודות, המסנן וטווח התאריכים. הדוגמה הזו היא של דוח משלוח בסיסי של הזמנה בודדת.

Java


    // Create report query.
    ReportQuery reportQuery = new ReportQuery();
    reportQuery.setDimensions(new Dimension[] {Dimension.DATE, Dimension.ORDER_ID});
    reportQuery.setColumns(
        new Column[] {
          Column.AD_SERVER_IMPRESSIONS,
          Column.AD_SERVER_CLICKS,
          Column.AD_SERVER_CTR,
          Column.AD_SERVER_CPM_AND_CPC_REVENUE
        });
    reportQuery.setDimensionAttributes(
        new DimensionAttribute[] {
          DimensionAttribute.ORDER_TRAFFICKER,
          DimensionAttribute.ORDER_START_DATE_TIME,
          DimensionAttribute.ORDER_END_DATE_TIME
        });

    // Create statement to filter for an order.
    StatementBuilder statementBuilder =
        new StatementBuilder()
            .where("ORDER_ID = :orderId")
            .withBindVariableValue("orderId", orderId);

    // Set the filter statement.
    reportQuery.setStatement(statementBuilder.toStatement());

    // Set the start and end dates or choose a dynamic date range type.
    reportQuery.setDateRangeType(DateRangeType.CUSTOM_DATE);
    reportQuery.setStartDate(
        DateTimes.toDateTime("2013-05-01T00:00:00", "America/New_York").getDate());
    reportQuery.setEndDate(
        DateTimes.toDateTime("2013-05-31T00:00:00", "America/New_York").getDate());
    

Python


  # Create statement object to filter for an order.
  statement = (ad_manager.StatementBuilder(version='v202402')
               .Where('ORDER_ID = :id')
               .WithBindVariable('id', int(order_id))
               .Limit(None)  # No limit or offset for reports
               .Offset(None))

  # Set the start and end dates of the report to run (past 8 days).
  end_date = datetime.now().date()
  start_date = end_date - timedelta(days=8)

  # Create report job.
  report_job = {
      'reportQuery': {
          'dimensions': ['ORDER_ID', 'ORDER_NAME'],
          'dimensionAttributes': ['ORDER_TRAFFICKER', 'ORDER_START_DATE_TIME',
                                  'ORDER_END_DATE_TIME'],
          'statement': statement.ToStatement(),
          'columns': ['AD_SERVER_IMPRESSIONS', 'AD_SERVER_CLICKS',
                      'AD_SERVER_CTR', 'AD_SERVER_CPM_AND_CPC_REVENUE',
                      'AD_SERVER_WITHOUT_CPD_AVERAGE_ECPM'],
          'dateRangeType': 'CUSTOM_DATE',
          'startDate': start_date,
          'endDate': end_date
      }
  }
    

PHP


      // Create report query.
      $reportQuery = new ReportQuery();
      $reportQuery->setDimensions(
          [
              Dimension::ORDER_ID,
              Dimension::ORDER_NAME
          ]
      );
      $reportQuery->setDimensionAttributes(
          [
              DimensionAttribute::ORDER_TRAFFICKER,
              DimensionAttribute::ORDER_START_DATE_TIME,
              DimensionAttribute::ORDER_END_DATE_TIME
          ]
      );
      $reportQuery->setColumns(
          [
              Column::AD_SERVER_IMPRESSIONS,
              Column::AD_SERVER_CLICKS,
              Column::AD_SERVER_CTR,
              Column::AD_SERVER_CPM_AND_CPC_REVENUE,
              Column::AD_SERVER_WITHOUT_CPD_AVERAGE_ECPM
          ]
      );

      // Create statement to filter for an order.
      $statementBuilder = (new StatementBuilder())
          ->where('ORDER_ID = :orderId')
          ->withBindVariableValue(
              'orderId',
              $orderId
          );

      // Set the filter statement.
      $reportQuery->setStatement($statementBuilder->toStatement());

      // Set the start and end dates or choose a dynamic date range type.
      $reportQuery->setDateRangeType(DateRangeType::CUSTOM_DATE);
      $reportQuery->setStartDate(
          AdManagerDateTimes::fromDateTime(
              new DateTime(
                  '-10 days',
                  new DateTimeZone('America/New_York')
              )
          )
              ->getDate()
      );
      $reportQuery->setEndDate(
          AdManagerDateTimes::fromDateTime(
              new DateTime(
                  'now',
                  new DateTimeZone('America/New_York')
              )
          )
              ->getDate()
      );
    

C#


// Create report job.
ReportJob reportJob = new ReportJob();
reportJob.reportQuery = new ReportQuery();
reportJob.reportQuery.dimensions = new Dimension[]
{
    Dimension.ORDER_ID,
    Dimension.ORDER_NAME
};
reportJob.reportQuery.dimensionAttributes = new DimensionAttribute[]
{
    DimensionAttribute.ORDER_TRAFFICKER,
    DimensionAttribute.ORDER_START_DATE_TIME,
    DimensionAttribute.ORDER_END_DATE_TIME
};
reportJob.reportQuery.columns = new Column[]
{
    Column.AD_SERVER_IMPRESSIONS,
    Column.AD_SERVER_CLICKS,
    Column.AD_SERVER_CTR,
    Column.AD_SERVER_CPM_AND_CPC_REVENUE,
    Column.AD_SERVER_WITHOUT_CPD_AVERAGE_ECPM
};

// Set a custom date range for the last 8 days
reportJob.reportQuery.dateRangeType = DateRangeType.CUSTOM_DATE;
System.DateTime endDateTime = System.DateTime.Now;
reportJob.reportQuery.startDate = DateTimeUtilities
    .FromDateTime(endDateTime.AddDays(-8), "America/New_York").date;
reportJob.reportQuery.endDate = DateTimeUtilities
    .FromDateTime(endDateTime, "America/New_York").date;

// Create statement object to filter for an order.
StatementBuilder statementBuilder = new StatementBuilder().Where("ORDER_ID = :id")
    .AddValue("id", orderId);
reportJob.reportQuery.statement = statementBuilder.ToStatement();
    

Ruby


  # Specify a report to run for the last 7 days.
  report_end_date = ad_manager.today()
  report_start_date = report_end_date - 7

  # Create statement object to filter for an order.
  statement = ad_manager.new_report_statement_builder do |sb|
    sb.where = 'ORDER_ID = :order_id'
    sb.with_bind_variable('order_id', order_id)
  end

  # Create report query.
  report_query = {
    :date_range_type => 'CUSTOM_DATE',
    :start_date => report_start_date.to_h,
    :end_date => report_end_date.to_h,
    :dimensions => ['ORDER_ID', 'ORDER_NAME'],
    :dimension_attributes => ['ORDER_TRAFFICKER', 'ORDER_START_DATE_TIME',
        'ORDER_END_DATE_TIME'],
    :columns => ['AD_SERVER_IMPRESSIONS', 'AD_SERVER_CLICKS', 'AD_SERVER_CTR',
        'AD_SERVER_CPM_AND_CPC_REVENUE', 'AD_SERVER_WITHOUT_CPD_AVERAGE_ECPM'],
    :statement => statement.to_statement()
  }
    

יצירת משימת הדיווח

ברגע שיש לכם שאילתת דיווח, הגיע הזמן להריץ את הדוח. האובייקט ReportJob מכיל את הסטטוס של הדוח ומאפשר לכם לדעת מתי הוא מוכן להורדה. כדי להתחיל להריץ את הדוח, משתמשים ב-method ReportService.runReportJob.

Java


    // Create report job.
    ReportJob reportJob = new ReportJob();
    reportJob.setReportQuery(reportQuery);

    // Run report job.
    reportJob = reportService.runReportJob(reportJob);
    

Python


  # Initialize a DataDownloader.
  report_downloader = client.GetDataDownloader(version='v202402')

  try:
    # Run the report and wait for it to finish.
    report_job_id = report_downloader.WaitForReport(report_job)
  except errors.AdManagerReportError as e:
    print('Failed to generate report. Error was: %s' % e)
    

PHP


      // Create report job and start it.
      $reportJob = new ReportJob();
      $reportJob->setReportQuery($reportQuery);
      $reportJob = $reportService->runReportJob($reportJob);
    

C#


// Run report job.
reportJob = reportService.runReportJob(reportJob);
    

Ruby


  # Create report job.
  report_job = {:report_query => report_query}

  # Run report job.
  report_job = report_service.run_report_job(report_job);
    

הורדת הדוח

לאחר שתתחילו את משימת הדיווח, יהיה לה מזהה שהוגדר על ידי השרת. כדי לבדוק את הסטטוס של הדוח, משתמשים במזהה הזה ב-method ReportService.getReportJobStatus. כשהסטטוס הוא ReportJobStatus.COMPLETED, הדוח מוכן להורדה.

לחלק מספריות הלקוח שלנו יש כלי עזר שתפקידם לבדוק את ה-API ולהמתין להשלמת הדוח. כשהדוח מוכן, אפשר למצוא את כתובת ה-URL להורדה באמצעות method ReportService.getReportDownloadURL. ניתן להוריד דוח בפורמטים שונים. אם אתם רוצים לבצע עיבוד מכונה נוסף באמצעות הדוח, צריך להשתמש בפורמט CSV_DUMP.

Java


    // Create report downloader.
    ReportDownloader reportDownloader = new ReportDownloader(reportService, reportJob.getId());

    // Wait for the report to be ready.
    if (reportDownloader.waitForReportReady()) {
      // Change to your file location.
      File file = File.createTempFile("delivery-report-", ".csv.gz");

      System.out.printf("Downloading report to %s ...", file.toString());

      // Download the report.
      ReportDownloadOptions options = new ReportDownloadOptions();
      options.setExportFormat(ExportFormat.CSV_DUMP);
      options.setUseGzipCompression(true);
      URL url = reportDownloader.getDownloadUrl(options);
      Resources.asByteSource(url).copyTo(Files.asByteSink(file));

      System.out.println("done.");
    } else {
      System.out.printf("Report job %d failed.%n", reportJob.getId());
    }
    

Python


  # Change to your preferred export format.
  export_format = 'CSV_DUMP'

  report_file = tempfile.NamedTemporaryFile(suffix='.csv.gz', delete=False)

  # Download report data.
  report_downloader.DownloadReportToFile(
      report_job_id, export_format, report_file)

  report_file.close()

  # Display results.
  print('Report job with id "%s" downloaded to:\n%s' % (
      report_job_id, report_file.name))
    

PHP


      // Create report downloader to poll report's status and download when
      // ready.
      $reportDownloader = new ReportDownloader(
          $reportService,
          $reportJob->getId()
      );
      if ($reportDownloader->waitForReportToFinish()) {
          // Write to system temp directory by default.
          $filePath = sprintf(
              '%s.csv.gz',
              tempnam(sys_get_temp_dir(), 'delivery-report-')
          );
          printf("Downloading report to %s ...%s", $filePath, PHP_EOL);
          // Download the report.
          $reportDownloader->downloadReport(
              ExportFormat::CSV_DUMP,
              $filePath
          );
          print "done.\n";
      } else {
          print "Report failed.\n";
      }
    

C#


ReportUtilities reportUtilities =
    new ReportUtilities(reportService, reportJob.id);

// Set download options.
ReportDownloadOptions options = new ReportDownloadOptions();
options.exportFormat = ExportFormat.CSV_DUMP;
options.useGzipCompression = true;
reportUtilities.reportDownloadOptions = options;

// Download the report.
using (ReportResponse reportResponse = reportUtilities.GetResponse())
{
    reportResponse.Save(filePath);
}

Console.WriteLine("Report saved to \"{0}\".", filePath);
    

Ruby


  MAX_RETRIES.times do |retry_count|
    # Get the report job status.
    report_job_status = report_service.get_report_job_status(report_job[:id])

    break unless report_job_status == 'IN_PROGRESS'
    puts 'Report with ID %d is still running.' % report_job[:id]
    sleep(RETRY_INTERVAL)
  end

  puts 'Report job with ID %d finished with status "%s".' % [report_job[:id],
      report_service.get_report_job_status(report_job[:id])]

  # Get the report URL.
  download_url = report_service.get_report_download_url(
      report_job_id, export_format
  )

  puts 'Downloading "%s" to "%s"...' % [download_url, file_name]
  open(file_name, 'wb') do |local_file|
    local_file << open(download_url).read()
  end
    

קריאת נתוני הדוחות

רבות מספריות הלקוחות שלנו כוללות כלים לקריאת נתוני דוחות. זו אפשרות שימושית לעיבוד נוסף של נתוני הדוח או לשילוב דוחות מטווחי תאריכים שונים. שימו לב שהקוד לדוגמה מבוסס על ההנחה שהקובץ לא דחוס.

Java


  List rows = CsvFiles.getCsvDataArray(filePath, true);
  for (String[] row : rows) {
    // Additional row processing
    processReportRow(row);
  }
    

Python


  with open(report_file.name, 'rb') as report:
    report_reader = csv.reader(report)
    for row in report_reader:
      # Additional row processing
      process_row(row)
    

PHP


  $report = fopen($filePath, 'r');
  while (!feof($report)) {
    // Additional row processing
    processRow(fgetcsv($report));
  }
  fclose($report);
    

C#


  CsvFile file = new CsvFile();
  file.Read(fileName, true);
  for (String[] row : file.Records) {
    // Additional row processing
    ProcessReportRow(row);
  }
    

Ruby


    CSV.foreach(file_name, converters: :numeric, headers: true) do |row|
      # Additional row processing
      process_row(row)
    end
    

דוגמאות נוספות לדיווחים זמינות בספריות הלקוח ב-GitHub.

שאלות נפוצות

מדוע כל תוצאות הדוח ברשת הבדיקה שלי ריקות?
רשתות בדיקה לא מציגות מודעות, ולכן דוחות המסירה לא יכללו נתונים.
למה כל תוצאות הדוחות ברשת הייצור שלי ריקות?
למשתמש שאתם מבצעים את האימות אין גישה לנתונים שעליהם אתם מנסים לדווח. חשוב לוודא שהרשאות התפקיד והצוותים שלהם מוגדרים כמו שצריך.
למה מופיעה השגיאה ReportError.COLUMNS_NOT_SUPPORTED_FOR_REQUESTED_DIMENSIONS בדוח שלי?
לא כל שילובי העמודות והמאפיינים נתמכים ב-Ad Manager. בדוחות מורכבים, יכול להיות שיהיה קל יותר ליצור דוח תקין בממשק המשתמש ואז לאחזר אותו באמצעות method ReportService.getSavedQueriesByStatement.
למה הדוח השמור לא מוחזר ב-API?
חשוב לוודא שהבעלים של הדוח שיתף את הדוח עם המשתמש שבאמצעותו מתבצע האימות.
למה הדוח השמור לא תואם ל-API?
תכונות דיווח מסוימות לא זמינות ב-API. זה כולל עמודות, מאפייני מאפיינים, מימדים וסוגי טווח תאריכים. בסוגים של טווחי תאריכים לא תואמים, אפשר לשמור את הדוח בסוג נתמך כדי שיהיה אפשר לאחזר אותו, ולשנות את ReportQuery כך שיתאים לטווח התאריכים הקבוע הרצוי.
למה הקליקים/החשיפות בכל משך החיים לא תואמים לדוח שלי בממשק המשתמש?
חשיפות בכל משך החיים מתייחסות לכל משך החיים של הפריט, בלי קשר לטווח התאריכים של הדוח. אם פריט מסוים עדיין מוצג, סביר להניח שהערך ישתנה בין הרצת שני דוחות.
הדוחות שלי נמשך זמן רב מדי ולפעמים נפסקים מהם הזמן הקצוב לתפוגה. מה אפשר לעשות?
הקטנת טווח התאריכים או מספר המאפיינים תשפר את הביצועים. במקום זאת, אפשר לנסות להריץ כמה דוחות בטווחי תאריכים קצרים יותר. לאחר מכן תוכלו למזג את נתוני הדוח כדי לכסות את טווח התאריכים הרצוי.
מה ההבדל בין INVENTORY_LEVEL לבין עמודות LINE_ITEM_LEVEL? באיזה מהם כדאי להשתמש?

אפשר להשתמש בעמודות עם LINE_ITEM_LEVEL רק אם מופעלת הקצאה דינמית ברמת הפריט ברשת. העמודות האלה כוללות נתונים מהקצאה דינמית ברמת הפריט ל-AdSense או ל-Ad Exchange. באופן דומה, העמודות INVENTORY_LEVEL כוללות נתונים מהקצאה דינמית ברמת המלאי. מידע נוסף על הקצאה דינמית זמין במאמר פריטים של Ad Exchange.

אם אתם עדיין לא בטוחים באילו עמודות של API להשתמש, יוצרים שאילתה שמורה בממשק המשתמש של Ad Manager ומאחזרים אותה באמצעות method ReportService.getSavedQueriesByStatement.