openplanning

Đánh chỉ mục trang với Java Google Indexing API

  1. Các yều cầu đòi hỏi
  2. Thư viện
  3. HttpRequestInitializer
  4. Thông báo cập nhập một URL
  5. Yêu cầu xoá một URL
  6. Lấy thông tin của một URL
  7. Yêu cầu đánh chỉ số hàng loạt URL
  8. Xử lý lỗi
Google Indexing API cho phép bất kỳ chủ sở hữu trang web nào thông báo trực tiếp cho Google khi các trang được thêm hoặc xóa. Điều này cho phép Google lên lịch thu thập dữ liệu mới cho các trang, điều này có thể mang lại lưu lượng truy cập chất lượng cao hơn cho người dùng. Đối với các website có nhiều trang tồn tại trong thời gian ngắn như tin tuyển dụng hoặc video phát trực tiếp, Indexing API giúp nội dung luôn mới trong kết quả tìm kiếm vì nó cho phép các bản cập nhật được đẩy lên một cách riêng lẻ.
Hiện tại, Indexing API chỉ có thể được sử dụng để thu thập dữ liệu các trang với một trong hai "JobPosting" hoặc "BroadcastEvent được nhúng trong VideoObject".
Dưới đây là một số điều bạn có thể thực hiện với Google Indexing API:
Supported
Description
Update a URL
Thông báo cho Google về URL mới để thu thập dữ liệu hoặc nội dung tại URL gửi trước đó đã được cập nhật.
Remove a URL
Sau khi bạn xóa một trang khỏi máy chủ của mình, hãy thông báo cho Google để chúng tôi có thể xóa trang đó khỏi chỉ mục của mình và để chúng tôi không cố gắng thu thập lại dữ liệu URL này.
Get the status of a request
Kiểm tra lần cuối cùng Google nhận được từng loại thông báo cho một URL nhất định.
Send batch indexing requests
Thay vì gửi yêu cầu đánh chỉ mục cho từng URL riêng lẻ, bạn có thể gộp tối đa 100 URLs để thông báo cho Google trong một lần gửi yêu cầu.

1. Các yều cầu đòi hỏi

Trước hết bạn cần đăng ký và tạo một dự án trên Google Cloud Console.
Bật dịch vụ "Google Indexing" cho tài khoản của bạn.
Tạo một thông tin xác thực (credentials) kiểu "Service Account" và download nó dưới dạng một file JSON.
Sau bước trên, bạn được cung cấp một email có định dạng "xxx@yyy.iam.gserviceaccount.com".
Trên Google Search Console, thêm email mà bạn có ở bước trên như một chủ sở hữu cho website của bạn.

2. Thư viện

<!-- SEE ALL VERSIONS -->
<!-- https://libraries.io/maven/com.google.apis:google-api-services-indexing/versions -->
<dependency>
  <groupId>com.google.apis</groupId>
  <artifactId>google-api-services-indexing</artifactId> 
  <version>v3-rev20230927-2.0.0</version> 
</dependency>  

<!-- https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http -->
<dependency>
  <groupId>com.google.auth</groupId>
  <artifactId>google-auth-library-oauth2-http</artifactId>
  <version>1.20.0</version>
</dependency> 
    
<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client-gson --> 
<dependency>
  <groupId>com.google.api-client</groupId>
  <artifactId>google-api-client-gson</artifactId>
  <version>2.2.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>  

3. HttpRequestInitializer

Để gửi các yêu cầu (request) tới các dịch vụ của Google thông qua Google Java API, bạn cần tạo đối tượng HttpRequestInitializer.
HttpRequestInitializer sẽ khởi tạo các thông tin cần thiết đính kèm trong các yêu cầu mỗi khi chúng được gửi đi. Trong đó, quan trọng nhất là thông tin xác thực (credentials), để đảm bảo rằng bạn có quyền truy cập truy cập vào dịch vụ và tài nguyên có liên quan.
Ở bước trước bạn đã tạo một thông tin xác thực (credentials) và download nó dưới dạng một file JSON. Bây giờ chúng ta sẽ viết một lớp tiện ích để tạo đối tượng HttpRequestInitializer .
MyUtils.java
package org.o7planning.googleapis.utils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.FileUtils;

import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.ObjectParser;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;

public class MyUtils {

	public static final String SERVICE_ACCOUNT_FILE_PATH = "/Volumes/New/_gsc/test-google-search-console-key.json";

	private static byte[] serviceAccountBytes;

	public static HttpRequestInitializer createHttpRequestInitializer(String... scopes) throws IOException {
		InputStream serviceAccountInputStream = getServiceAccountInputStream();

		GoogleCredentials credentials = ServiceAccountCredentials //
				.fromStream(serviceAccountInputStream) //
				.createScoped(scopes);

		HttpRequestInitializer requestInitializer = new HttpRequestInitializer() {

			@Override
			public void initialize(HttpRequest request) throws IOException {
				HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials);
				adapter.initialize(request);
				//
				if (request.getParser() == null) {
					ObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance());
					request.setParser(parser);
				}
				//
				request.setConnectTimeout(60000); // 1 minute connect timeout
				request.setReadTimeout(60000); // 1 minute read timeout
			}

		};
		return requestInitializer;
	}

	public static synchronized InputStream getServiceAccountInputStream() throws IOException {
		if (serviceAccountBytes == null) {
			serviceAccountBytes = FileUtils.readFileToByteArray(new File(SERVICE_ACCOUNT_FILE_PATH));
		}
		return new ByteArrayInputStream(serviceAccountBytes);
	}
}
Bạn cũng có thể tạo GoogleCredentials với "API Key" hoặc "OAuth Client ID",...
  • Java Google APIs commons Credentials

4. Thông báo cập nhập một URL

Google Indexing API cho phép bạn gửi các yêu cầu đánh chỉ mục cho một URL nào đó trên website của bạn. Lưu ý rằng yêu cầu này sẽ được đưa vào một hàng đợi, nó sẽ không ngay lập tức được thực hiện cho dù bạn có gửi bao nhiêu yêu cầu như vậy đi nữa. Thời điểm lần cuối cùng mà bạn yêu cầu cập nhập một URL sẽ được Google lưu lại.
Mỗi website sẽ có một hạn ngạch mỗi ngày để gửi các yêu cầu tới Google. Nếu quá hạn ngạch đó yêu cầu đó sẽ bị từ chối. Hạn ngạch có thể khác nhau với những website khác nhau, tuỳ thuộc vào chất lượng website của bạn, cụ thể là lưu lượng người dùng.
Một lớp tiện ích để gửi một yêu cầu đánh chỉ mục tới Google.
NotifyToUpdateUrlUtils.java
package org.o7planning.java_14285_google_apis;

import org.o7planning.googleapis.utils.MyUtils;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.indexing.v3.Indexing;
import com.google.api.services.indexing.v3.Indexing.UrlNotifications;
import com.google.api.services.indexing.v3.IndexingScopes;
import com.google.api.services.indexing.v3.model.PublishUrlNotificationResponse;
import com.google.api.services.indexing.v3.model.UrlNotification;
import com.google.api.services.indexing.v3.model.UrlNotificationMetadata;

public class NotifyToUpdateUrlUtils {

	private static final String MY_APPLICATION = "My-Application";

	/**
	 * <pre>
	 * {
	 *  url: "http://foo.com/page",
	 *  latestUpdate: {
	 *    url: "http://foo.com/page",
	 *    type: "URL_UPDATED",
	 *    notifyTime: "2017-09-31T19:30:54.524457662Z"
	 *  },
	 *  latestRemove: {
	 *    url: "http://foo.com/page",
	 *    type: "URL_DELETED",
	 *    notifyTime: "2017-08-31T19:30:54.524457662Z"
	 *  }
	 * }
	 * </pre>
	 */
	public static UrlNotificationMetadata requestToUpdatePageUrl(String applicationName, String pageUrl)
			throws Exception {
		HttpRequestInitializer httpRequestInitializer //
				= MyUtils.createHttpRequestInitializer(IndexingScopes.INDEXING);
		//
		HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
		//

		Indexing indexing = new Indexing.Builder(httpTransport, //
				GsonFactory.getDefaultInstance(), //  
				httpRequestInitializer) //
				.setApplicationName(applicationName) //
				.build();

		UrlNotification content = new UrlNotification();
		content.setType("URL_UPDATED");
		content.setUrl(pageUrl);
		//
		UrlNotifications.Publish publish = indexing.urlNotifications().publish(content);
		PublishUrlNotificationResponse respone = publish.execute();

		UrlNotificationMetadata urlNotification = respone.getUrlNotificationMetadata(); 
		return urlNotification;
	}

	public static UrlNotificationMetadata updatePageUrlIndexing(String pageUrl) throws Exception {
		return requestToUpdatePageUrl(MY_APPLICATION, pageUrl);
	}

	public static void main(String[] args) throws Exception {
		// Test:
		UrlNotificationMetadata metadata = updatePageUrlIndexing(MyTestConstants.URL_TO_TEST);
		System.out.println("Return: " + metadata.toPrettyString());
	}
}
Output:
{
  url: "http://foo.com/page",
  latestUpdate: {
    url: "http://foo.com/page",
    type: "URL_UPDATED",
    notifyTime: "2017-09-31T19:30:54.524457662Z"
  },
  latestRemove: {
    url: "http://foo.com/page",
    type: "URL_DELETED",
    notifyTime: "2017-08-31T19:30:54.524457662Z"
  }
 }

5. Yêu cầu xoá một URL

Đôi khi có một vài URLs trong website của bạn đã không còn cần thiết và bạn muốn xoá nó ra khỏi chỉ mục của Google Search, bạn có thể gửi yêu cầu xoá tới Google, đồng thời URL này phải trả về mã lỗi 404.
NotifyToDeleteUrlUtils.java
package org.o7planning.java_14285_google_apis;

import org.o7planning.googleapis.utils.MyUtils;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.indexing.v3.Indexing;
import com.google.api.services.indexing.v3.Indexing.UrlNotifications;
import com.google.api.services.indexing.v3.IndexingScopes;
import com.google.api.services.indexing.v3.model.PublishUrlNotificationResponse;
import com.google.api.services.indexing.v3.model.UrlNotification;
import com.google.api.services.indexing.v3.model.UrlNotificationMetadata;

public class NotifyToDeleteUrlUtils {

	private static final String MY_APPLICATION = "My-Application";

	/**
	 * <pre>
	 * {
	 *  "urlNotificationMetadata": {
	 *   "url": "http://foo.com/path",
	 *  "latestUpdate": {
	 *    "url": "http://foo.com/path",
	 *    "type": "URL_UPDATED",
	 *     "notifyTime": "2023-11-28T12:49:16.868776828Z"
	 *   },
	 *   "latestRemove": {
	 *     "url": "http://foo.com/path",
	 *     "type": "URL_DELETED",
	 *     "notifyTime": "2023-11-28T13:50:29.662337626Z"
	 *     }
	 *   }
	 * }
	 * </pre>
	 */
	public static PublishUrlNotificationResponse requestToDeletePageUrl(String applicationName, String pageUrl)
			throws Exception {

		HttpRequestInitializer httpRequestInitializer //
				= MyUtils.createHttpRequestInitializer(IndexingScopes.INDEXING);

		//
		HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
		//

		Indexing indexing = new Indexing.Builder(httpTransport, //
				GsonFactory.getDefaultInstance(), //  
				httpRequestInitializer) //
				.setApplicationName(applicationName) //
				.build();

		UrlNotification content = new UrlNotification();
		content.setType("URL_DELETED");
		content.setUrl(pageUrl);
		//
		UrlNotifications.Publish publish = indexing.urlNotifications().publish(content);
		PublishUrlNotificationResponse respone = publish.execute();
		return respone;
	}

	public static PublishUrlNotificationResponse deletePageUrlIndexing(String pageUrl) throws Exception {
		return requestToDeletePageUrl(MY_APPLICATION, pageUrl);
	}

	public static void main(String[] args) throws Exception {
		// Test:
		PublishUrlNotificationResponse response = deletePageUrlIndexing(MyTestConstants.URL_TO_TEST);
		System.out.println("Delete Response: " + response.toPrettyString());
		UrlNotificationMetadata meta = response.getUrlNotificationMetadata();
		System.out.println("\nMetadata: " + meta.toPrettyString());
	}
}
Output:
{
  "urlNotificationMetadata": {
   "url": "http://foo.com/path",
  "latestUpdate": {
    "url": "http://foo.com/path",
    "type": "URL_UPDATED",
     "notifyTime": "2023-11-28T12:49:16.868776828Z"
   },
   "latestRemove": {
     "url": "http://foo.com/path",
     "type": "URL_DELETED",
     "notifyTime": "2023-11-28T13:50:29.662337626Z"
     }
   }
 }

6. Lấy thông tin của một URL

Google Indexing API cho phép bạn xem thông tin lần cuối cùng bạn đã gửi yêu cầu đánh chỉ số hoặc xoá một URL cụ thể. Thông tin này không bao gồm trạng thái thực sự của URL (đã cập nhập hoặc đã xoá).
Nếu bạn muốn biết trạng thái thực sự của một URL, hãy tiếp tục với bài viết dưới đây:
GetUrlNotificationMetaDataUtils.java
package org.o7planning.java_14285_google_apis;

import org.o7planning.googleapis.utils.MyUtils;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.indexing.v3.Indexing;
import com.google.api.services.indexing.v3.IndexingScopes;
import com.google.api.services.indexing.v3.model.UrlNotificationMetadata;

public class GetUrlNotificationMetaDataUtils {

	private static final String MY_APPLICATION = "My-Application";

	/**
	 * <pre>
	 * {
	 *  url: "http://foo.com/page",
	 *  latestUpdate: {
	 *    url: "http://foo.com/page",
	 *    type: "URL_UPDATED",
	 *    notifyTime: "2017-07-31T19:30:54.524457662Z"
	 *  },
	 *  latestRemove: {
	 *    url: "http://foo.com/page",
	 *    type: "URL_DELETED",
	 *    notifyTime: "2017-08-31T19:30:54.524457662Z"
	 *  }
	 * }
	 * </pre>
	 */
	public static UrlNotificationMetadata getUrlNotifications(String applicationName, String pageUrl) throws Exception {
		HttpRequestInitializer httpRequestInitializer //
				= MyUtils.createHttpRequestInitializer(IndexingScopes.INDEXING); 
		 
		HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
		//

		Indexing indexing = new Indexing.Builder(httpTransport, //
				GsonFactory.getDefaultInstance(), //  
				httpRequestInitializer) //
				.setApplicationName(applicationName) //
				.build();

		Indexing.UrlNotifications.GetMetadata getMetadataRequest = indexing.urlNotifications().getMetadata();
		getMetadataRequest.setUrl(pageUrl);

		UrlNotificationMetadata metadata = getMetadataRequest.execute(); 
		return metadata;
	}

	public static UrlNotificationMetadata getUrlNotifications(String pageUrl) throws Exception {
		return getUrlNotifications(MY_APPLICATION, pageUrl);
	}

	public static void main(String[] args) throws Exception {
		// Test:
		UrlNotificationMetadata infos = getUrlNotifications(MyTestConstants.URL_TO_TEST);
		System.out.println("Return: " + infos.toPrettyString());
	}
}
Output:
{
  url: "http://foo.com/page",
  latestUpdate: {
    url: "http://foo.com/page",
    type: "URL_UPDATED",
    notifyTime: "2017-09-31T19:30:54.524457662Z"
  },
  latestRemove: {
    url: "http://foo.com/page",
    type: "URL_DELETED",
    notifyTime: "2017-08-31T19:30:54.524457662Z"
  }
 }

7. Yêu cầu đánh chỉ số hàng loạt URL

Thay vì gửi yêu cầu đánh chỉ mục cho từng URL riêng lẻ, bạn có thể gộp tối đa 100 URLs để thông báo cho Google trong một lần yêu cầu được gửi đi.
BatchUpdateUrlUtils.java
package org.o7planning.java_14285_google_apis;

import java.io.IOException;

import org.o7planning.googleapis.utils.MyUtils;

import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.indexing.v3.Indexing;
import com.google.api.services.indexing.v3.IndexingScopes;
import com.google.api.services.indexing.v3.model.PublishUrlNotificationResponse;
import com.google.api.services.indexing.v3.model.UrlNotification;

public class BatchUpdateUrlUtils {

	private static final String MY_APPLICATION = "My-Application";

	public static void executeBatchIndexing(String applicationName, String... pageUrls) throws Exception {
		HttpRequestInitializer httpRequestInitializer //
				= MyUtils.createHttpRequestInitializer(IndexingScopes.INDEXING);
		//
		HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();

		Indexing indexing = new Indexing.Builder(httpTransport, //
				GsonFactory.getDefaultInstance(), //
				httpRequestInitializer) //
				.setApplicationName(applicationName) //
				.build();
		//

		JsonBatchCallback<PublishUrlNotificationResponse> callback = new JsonBatchCallback<PublishUrlNotificationResponse>() {
			@Override
			public void onSuccess(PublishUrlNotificationResponse res, HttpHeaders responseHeaders) {
				try {
					System.out.println(res.toPrettyString());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			@Override
			public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
				System.out.println("Error Message: " + e.getMessage());
			}
		};

		BatchRequest batch = indexing.batch();

		for (String url : pageUrls) {
			UrlNotification unf = new UrlNotification();
			unf.setUrl(url);
			unf.setType("URL_UPDATED");
			indexing.urlNotifications().publish(unf).queue(batch, callback);
		}
		batch.execute();
	}

	public static void updatePageUrlIndexing(String... pageUrls) throws Exception {
		executeBatchIndexing(MY_APPLICATION, pageUrls);
	}

	public static void main(String[] args) throws Exception {
		// Test:
		updatePageUrlIndexing(MyTestConstants.URL_TO_TEST, MyTestConstants.URL2_TO_TEST);
	}
}
Output:
{
  "urlNotificationMetadata": {
    "latestRemove": {
      "notifyTime": "2023-12-01T14:37:38.721117189Z",
      "type": "URL_DELETED",
      "url": "http://foo.com/page"
    },
    "latestUpdate": {
      "notifyTime": "2023-12-02T12:43:43.074657919Z",
      "type": "URL_UPDATED",
      "url": "http://foo.com/page"
    },
    "url": "http://foo.com/page"
  }
}
{
  "urlNotificationMetadata": { 
    "latestUpdate": {
      "notifyTime": "2023-12-02T12:43:43.074745791Z",
      "type": "URL_UPDATED",
      "url": "http://foo.com/page2"
    },
    "url": "http://foo.com/page2"
  }
}

8. Xử lý lỗi

Lỗi có thể phát sinh khi bạn gửi một yêu cầu tới Google. Chẳng hạn khi bạn đã vượt quá hạn ngạch cho phép, hoặc gửi yêu cầu đánh chỉ số một URL của một website không phải của bạn,..
{
  "code": 403,
  "errors": [
    {
      "domain": "global",
      "message": "Permission denied. Failed to verify the URL ownership.",
      "reason": "forbidden"
    }
  ],
  "message": "Permission denied. Failed to verify the URL ownership.",
  "status": "PERMISSION_DENIED"
}
Trong trường hợp lỗi, Google Indexing API sẽ ném ra HttpResponseException.
HandleError.java
package org.o7planning.java_14285_google_apis;

import java.util.List;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpResponseException;
import com.google.api.services.indexing.v3.model.UrlNotificationMetadata;

public class HandleError {

	public static void main(String[] args) throws Exception {
		// Test:
		try {
			UrlNotificationMetadata infos = GetUrlNotificationMetaDataUtils
					.getUrlNotifications("https://notmywebsite.com");
			System.out.println("Return: " + infos.toPrettyString());
		} catch (Exception e) {
			if (e instanceof HttpResponseException) {
				HttpResponseException ex = (HttpResponseException) e;
				int statusCode = ex.getStatusCode();
				String content = ex.getContent();
				System.err.println("statusCode: " + statusCode);
				System.err.println("content: " + content);
				if (e instanceof GoogleJsonResponseException) {
					GoogleJsonResponseException ex2 = (GoogleJsonResponseException) e;
					GoogleJsonError detailError = ex2.getDetails();
					//
					List<ErrorInfo> errorInfos = detailError.getErrors();
					for (ErrorInfo einfo : errorInfos) {
						System.out.println("Error: domain: " + einfo.getDomain());
						System.out.println("Error: message: " + einfo.getMessage());
						System.out.println("Error: reason: " + einfo.getReason());
					}
				}
			} else {
				// Other Error
				System.out.println("Error: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}
}
Output:
statusCode: 403
content: {
  "code": 403,
  "errors": [
    {
      "domain": "global",
      "message": "Permission denied. Failed to verify the URL ownership.",
      "reason": "forbidden"
    }
  ],
  "message": "Permission denied. Failed to verify the URL ownership.",
  "status": "PERMISSION_DENIED"
}
Error: domain: global
Error: message: Permission denied. Failed to verify the URL ownership.
Error: reason: forbidden