Skip to content

Commit 621444c

Browse files
authored
Merge pull request #53 from urbanisierung/feat-add-retry-for-read-requests-if-rate-limited
feat(get-publish-metadata): optional retries if rate limited
2 parents c2dd31c + ab5657f commit 621444c

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

README.md

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Use this script to get your entire site indexed on Google in less than 48 hours.
44

55
You can read more about the motivation behind it and how it works in this blog post https://seogets.com/blog/google-indexing-script
66

7-
> [!IMPORTANT]
7+
> [!IMPORTANT]
8+
>
89
> 1. Indexing != Ranking. This will not help your page rank on Google, it'll just let Google know about the existence of your pages.
910
> 2. This script uses [Google Indexing API](https://developers.google.com/search/apis/indexing-api/v3/quickstart). We do not recommend using this script on spam/low-quality content.
1011
@@ -80,6 +81,7 @@ google-indexing-script seogets.com
8081
# cloned repository
8182
npm run index seogets.com
8283
```
84+
8385
</details>
8486

8587
<details>
@@ -92,6 +94,7 @@ Run the script with the domain or url you want to index.
9294
```bash
9395
GIS_CLIENT_EMAIL=your-client-email GIS_PRIVATE_KEY=your-private-key gis seogets.com
9496
```
97+
9598
</details>
9699

97100
<details>
@@ -104,6 +107,7 @@ Once you have the values, run the script with the domain or url you want to inde
104107
```bash
105108
gis seogets.com --client-email your-client-email --private-key your-private-key
106109
```
110+
107111
</details>
108112

109113
<details>
@@ -116,29 +120,64 @@ npm i google-indexing-script
116120
```
117121

118122
```javascript
119-
import { index } from 'google-indexing-script'
120-
import serviceAccount from './service_account.json'
123+
import { index } from "google-indexing-script";
124+
import serviceAccount from "./service_account.json";
121125

122-
index('seogets.com', {
126+
index("seogets.com", {
123127
client_email: serviceAccount.client_email,
124-
private_key: serviceAccount.private_key
128+
private_key: serviceAccount.private_key,
125129
})
126130
.then(console.log)
127-
.catch(console.error)
131+
.catch(console.error);
128132
```
129133

130134
Read the [API documentation](https://paka.dev/npm/google-indexing-script) for more details.
135+
131136
</details>
132137

133138
Here's an example of what you should expect:
134139

135140
![](./output.png)
136141

137142
> [!IMPORTANT]
143+
>
138144
> - Your site must have 1 or more sitemaps submitted to Google Search Console. Otherwise, the script will not be able to find the pages to index.
139145
> - You can run the script as many times as you want. It will only index the pages that are not already indexed.
140146
> - Sites with a large number of pages might take a while to index, be patient.
141147
148+
## Quota
149+
150+
Depending on your account several quotas are configured for the API (see [docs](https://developers.google.com/search/apis/indexing-api/v3/quota-pricing#quota)). By default the script exits as soon as the rate limit is exceeded. You can configure a retry mechanism for the read requests that apply on a per minute time frame.
151+
152+
<details>
153+
<summary>With environment variables</summary>
154+
155+
```bash
156+
export GIS_QUOTA_RPM_RETRY=true
157+
```
158+
159+
</details>
160+
161+
<details>
162+
<summary>As a npm module</summary>
163+
164+
```javascript
165+
import { index } from 'google-indexing-script'
166+
import serviceAccount from './service_account.json'
167+
168+
index('seogets.com', {
169+
client_email: serviceAccount.client_email,
170+
private_key: serviceAccount.private_key
171+
quota: {
172+
rpmRetry: true
173+
}
174+
})
175+
.then(console.log)
176+
.catch(console.error)
177+
```
178+
179+
</details>
180+
142181
## 🔀 Alternative
143182

144183
If you prefer a hands-free, and less technical solution, you can use a SaaS platform like [TagParrot](https://tagparrot.com/?via=goenning).

src/index.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,28 @@ import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
1515
import path from "path";
1616

1717
const CACHE_TIMEOUT = 1000 * 60 * 60 * 24 * 14; // 14 days
18+
export const QUOTA = {
19+
rpm: {
20+
retries: 3,
21+
waitingTime: 60000, // 1 minute
22+
},
23+
};
1824

1925
export type IndexOptions = {
2026
client_email?: string;
2127
private_key?: string;
2228
path?: string;
29+
quota?: {
30+
rpmRetry?: boolean; // read requests per minute: retry after waiting time
31+
};
2332
};
2433

2534
/**
2635
* Indexes the specified domain or site URL.
2736
* @param input - The domain or site URL to index.
2837
* @param options - (Optional) Additional options for indexing.
2938
*/
30-
export const index = async (
31-
input: string = process.argv[2],
32-
options: IndexOptions = {},
33-
) => {
39+
export const index = async (input: string = process.argv[2], options: IndexOptions = {}) => {
3440
if (!input) {
3541
console.error("❌ Please provide a domain or site URL as the first argument.");
3642
console.error("");
@@ -47,6 +53,11 @@ export const index = async (
4753
if (!options.path) {
4854
options.path = args["path"] || process.env.GIS_PATH;
4955
}
56+
if (!options.quota) {
57+
options.quota = {
58+
rpmRetry: args["rpm-retry"] === "true" || process.env.GIS_QUOTA_RPM_RETRY === "true",
59+
};
60+
}
5061

5162
const accessToken = await getAccessToken(options.client_email, options.private_key, options.path);
5263
let siteUrl = convertToSiteUrl(input);
@@ -145,7 +156,9 @@ export const index = async (
145156

146157
for (const url of indexablePages) {
147158
console.log(`📄 Processing url: ${url}`);
148-
const status = await getPublishMetadata(accessToken, url);
159+
const status = await getPublishMetadata(accessToken, url, {
160+
retriesOnRateLimit: options.quota.rpmRetry ? QUOTA.rpm.retries : 0,
161+
});
149162
if (status === 404) {
150163
await requestIndexing(accessToken, url);
151164
console.log("🚀 Indexing requested successfully. It may take a few days for Google to process it.");

src/shared/gsc.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { webmasters_v3 } from "googleapis";
2+
import { QUOTA } from "..";
23
import { Status } from "./types";
34
import { fetchRetry } from "./utils";
45

@@ -202,9 +203,10 @@ export function getEmojiForStatus(status: Status) {
202203
* Retrieves metadata for publishing from the given URL.
203204
* @param accessToken - The access token for authentication.
204205
* @param url - The URL for which to retrieve metadata.
206+
* @param options - The options for the request.
205207
* @returns The status of the request.
206208
*/
207-
export async function getPublishMetadata(accessToken: string, url: string) {
209+
export async function getPublishMetadata(accessToken: string, url: string, options?: { retriesOnRateLimit: number }) {
208210
const response = await fetchRetry(
209211
`https://indexing.googleapis.com/v3/urlNotifications/metadata?url=${encodeURIComponent(url)}`,
210212
{
@@ -223,12 +225,23 @@ export async function getPublishMetadata(accessToken: string, url: string) {
223225
}
224226

225227
if (response.status === 429) {
226-
console.error("🚦 Rate limit exceeded, try again later.");
227-
console.error("");
228-
console.error(" Quota: https://developers.google.com/search/apis/indexing-api/v3/quota-pricing#quota");
229-
console.error(" Usage: https://console.cloud.google.com/apis/enabled");
230-
console.error("");
231-
process.exit(1);
228+
if (options?.retriesOnRateLimit && options?.retriesOnRateLimit > 0) {
229+
const RPM_WATING_TIME = (QUOTA.rpm.retries - options.retriesOnRateLimit + 1) * QUOTA.rpm.waitingTime; // increase waiting time for each retry
230+
console.log(
231+
`🚦 Rate limit exceeded for read requests. Retries left: ${options.retriesOnRateLimit}. Waiting for ${
232+
RPM_WATING_TIME / 1000
233+
}sec.`
234+
);
235+
await new Promise((resolve) => setTimeout(resolve, RPM_WATING_TIME));
236+
await getPublishMetadata(accessToken, url, { retriesOnRateLimit: options.retriesOnRateLimit - 1 });
237+
} else {
238+
console.error("🚦 Rate limit exceeded, try again later.");
239+
console.error("");
240+
console.error(" Quota: https://developers.google.com/search/apis/indexing-api/v3/quota-pricing#quota");
241+
console.error(" Usage: https://console.cloud.google.com/apis/enabled");
242+
console.error("");
243+
process.exit(1);
244+
}
232245
}
233246

234247
if (response.status >= 500) {

0 commit comments

Comments
 (0)