@mradex77/google-play-scraper 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +508 -0
- package/dist/index.cjs +3306 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +593 -0
- package/dist/index.d.ts +593 -0
- package/dist/index.js +3253 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MrAdex77
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @mradex77/google-play-scraper
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mradex77/google-play-scraper)
|
|
4
|
+
[](https://github.com/MrAdex77/google-play-scraper/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/MrAdex77/google-play-scraper/actions/workflows/e2e.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
A modern TypeScript rewrite of the unmaintained [`google-play-scraper`](https://github.com/facundoolano/google-play-scraper). It scrapes public app data from Google Play — app details, search, suggestions, lists, developer pages, similar apps, reviews, permissions, and data safety. Ships as both ESM and CommonJS, returns fully typed results validated with [zod](https://zod.dev), runs on native `fetch` with no runtime HTTP dependency, and isolates every fragile Google Play array path behind a resilient spec layer that is exercised by daily live contract tests.
|
|
9
|
+
|
|
10
|
+
The public method names, options, and constants match the original library, so migrating is mostly a matter of swapping the import.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
npm install @mradex77/google-play-scraper
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Node.js 22.12 or newer.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
The package exposes named exports and an aggregate default export. Use whichever style you prefer.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import gplay from '@mradex77/google-play-scraper';
|
|
26
|
+
|
|
27
|
+
const app = await gplay.app({ appId: 'com.google.android.apps.translate' });
|
|
28
|
+
console.log(app.title, app.score);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { app, type App } from '@mradex77/google-play-scraper';
|
|
33
|
+
|
|
34
|
+
const details: App = await app({ appId: 'com.google.android.apps.translate' });
|
|
35
|
+
console.log(details.installs);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
CommonJS works too:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const gplay = require('@mradex77/google-play-scraper').default;
|
|
42
|
+
|
|
43
|
+
gplay.search({ term: 'panda' }).then((results) => {
|
|
44
|
+
console.log(results.length);
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Common options
|
|
49
|
+
|
|
50
|
+
Every method accepts a single options object. These options are available on all of them:
|
|
51
|
+
|
|
52
|
+
| Option | Type | Default | Description |
|
|
53
|
+
| ---------------- | -------- | ------- | ------------------------------------------------------------------------------------ |
|
|
54
|
+
| `lang` | `string` | `'en'` | Two letter language code used to fetch the page. |
|
|
55
|
+
| `country` | `string` | `'us'` | Two letter country code. Needed for apps available only in some regions. |
|
|
56
|
+
| `throttle` | `number` | none | Maximum requests per second across a single call. |
|
|
57
|
+
| `requestOptions` | `object` | none | HTTP overrides. See [Throttling and requestOptions](#throttling-and-requestoptions). |
|
|
58
|
+
|
|
59
|
+
## Methods
|
|
60
|
+
|
|
61
|
+
- [app](#app) — full detail of a single application
|
|
62
|
+
- [search](#search) — apps matching a search term
|
|
63
|
+
- [suggest](#suggest) — search term autocompletions
|
|
64
|
+
- [list](#list) — a ranked collection of apps
|
|
65
|
+
- [developer](#developer) — other apps by the same developer
|
|
66
|
+
- [similar](#similar) — apps related to a given app
|
|
67
|
+
- [reviews](#reviews) — user reviews for an app
|
|
68
|
+
- [permissions](#permissions) — permissions an app requests
|
|
69
|
+
- [datasafety](#datasafety) — the data safety section of an app
|
|
70
|
+
- [categories](#categories) — the Google Play category taxonomy
|
|
71
|
+
- [memoized](#memoized) — a client that caches identical calls
|
|
72
|
+
|
|
73
|
+
### app
|
|
74
|
+
|
|
75
|
+
Retrieves the full detail of an application.
|
|
76
|
+
|
|
77
|
+
| Option | Type | Default | Description |
|
|
78
|
+
| ------- | -------- | -------- | ---------------------------------------------- |
|
|
79
|
+
| `appId` | `string` | required | The Google Play id (the `?id=` url parameter). |
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { app } from '@mradex77/google-play-scraper';
|
|
83
|
+
|
|
84
|
+
const details = await app({ appId: 'com.google.android.apps.translate' });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Returns an `App` with 55 fields. Trimmed:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
{
|
|
91
|
+
title: 'Google Translate',
|
|
92
|
+
description: 'Translate between up to 133 languages...',
|
|
93
|
+
descriptionHTML: 'Translate between up to 133 languages...<br>...',
|
|
94
|
+
summary: 'Instantly translate text, speech and images in over 100 languages',
|
|
95
|
+
installs: '1,000,000,000+',
|
|
96
|
+
minInstalls: 1000000000,
|
|
97
|
+
score: 4.48,
|
|
98
|
+
scoreText: '4.5',
|
|
99
|
+
ratings: 8765432,
|
|
100
|
+
reviews: 5678901,
|
|
101
|
+
histogram: { '1': 123456, '2': 45678, '3': 90123, '4': 234567, '5': 4567890 },
|
|
102
|
+
price: 0,
|
|
103
|
+
free: true,
|
|
104
|
+
currency: 'USD',
|
|
105
|
+
priceText: 'Free',
|
|
106
|
+
available: true,
|
|
107
|
+
offersIAP: false,
|
|
108
|
+
androidVersion: '8.0',
|
|
109
|
+
androidVersionText: '8.0 and up',
|
|
110
|
+
developer: 'Google LLC',
|
|
111
|
+
developerId: '5700313618786177705',
|
|
112
|
+
developerEmail: 'apps-help@google.com',
|
|
113
|
+
developerWebsite: 'http://support.google.com/translate',
|
|
114
|
+
genre: 'Tools',
|
|
115
|
+
genreId: 'TOOLS',
|
|
116
|
+
categories: [{ name: 'Tools', id: 'TOOLS' }],
|
|
117
|
+
icon: 'https://play-lh.googleusercontent.com/...',
|
|
118
|
+
screenshots: ['https://play-lh.googleusercontent.com/...'],
|
|
119
|
+
contentRating: 'Everyone',
|
|
120
|
+
adSupported: false,
|
|
121
|
+
updated: 1719878400000,
|
|
122
|
+
version: 'Varies with device',
|
|
123
|
+
comments: [],
|
|
124
|
+
appId: 'com.google.android.apps.translate',
|
|
125
|
+
url: 'https://play.google.com/store/apps/details?id=com.google.android.apps.translate'
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### search
|
|
130
|
+
|
|
131
|
+
Retrieves apps that match a search term.
|
|
132
|
+
|
|
133
|
+
| Option | Type | Default | Description |
|
|
134
|
+
| ------------ | --------------------------- | -------- | ------------------------------------------------------------- |
|
|
135
|
+
| `term` | `string` | required | The search query. |
|
|
136
|
+
| `num` | `number` | `20` | Number of results, up to `250`. |
|
|
137
|
+
| `price` | `'all' \| 'free' \| 'paid'` | `'all'` | Filter results by price. |
|
|
138
|
+
| `fullDetail` | `boolean` | `false` | When `true`, fetch and return the full `App` for each result. |
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { search } from '@mradex77/google-play-scraper';
|
|
142
|
+
|
|
143
|
+
const results = await search({ term: 'panda', num: 5 });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Returns `SearchResult[]` (or `App[]` when `fullDetail` is `true`). Trimmed:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
[
|
|
150
|
+
{
|
|
151
|
+
title: 'Panda VPN',
|
|
152
|
+
appId: 'com.example.pandavpn',
|
|
153
|
+
url: 'https://play.google.com/store/apps/details?id=com.example.pandavpn',
|
|
154
|
+
icon: 'https://play-lh.googleusercontent.com/...',
|
|
155
|
+
developer: 'Panda Labs',
|
|
156
|
+
developerId: '1234567890',
|
|
157
|
+
currency: 'USD',
|
|
158
|
+
price: 0,
|
|
159
|
+
free: true,
|
|
160
|
+
summary: 'Fast and secure VPN',
|
|
161
|
+
scoreText: '4.3',
|
|
162
|
+
score: 4.3,
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### suggest
|
|
168
|
+
|
|
169
|
+
Given a partial term, returns up to five search completions.
|
|
170
|
+
|
|
171
|
+
| Option | Type | Default | Description |
|
|
172
|
+
| ------ | -------- | -------- | ------------------------- |
|
|
173
|
+
| `term` | `string` | required | The partial search query. |
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { suggest } from '@mradex77/google-play-scraper';
|
|
177
|
+
|
|
178
|
+
const suggestions = await suggest({ term: 'pand' });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Returns `string[]`:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
['panda', 'pandora', 'panda vpn', 'panda pop', 'pandora music'];
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### list
|
|
188
|
+
|
|
189
|
+
Retrieves a ranked collection of apps, optionally scoped to a category and an age bracket.
|
|
190
|
+
|
|
191
|
+
| Option | Type | Default | Description |
|
|
192
|
+
| ------------ | ------------ | ---------------------- | -------------------------------------------------------- |
|
|
193
|
+
| `collection` | `Collection` | `collection.TOP_FREE` | One of `TOP_FREE`, `TOP_PAID`, `GROSSING`. |
|
|
194
|
+
| `category` | `Category` | `category.APPLICATION` | Any [category](#constants) constant. |
|
|
195
|
+
| `age` | `Age` | none | One of `age.FIVE_UNDER`, `age.SIX_EIGHT`, `age.NINE_UP`. |
|
|
196
|
+
| `num` | `number` | `500` | Number of results. |
|
|
197
|
+
| `fullDetail` | `boolean` | `false` | When `true`, return the full `App` for each result. |
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { list, collection, category } from '@mradex77/google-play-scraper';
|
|
201
|
+
|
|
202
|
+
const items = await list({
|
|
203
|
+
collection: collection.TOP_FREE,
|
|
204
|
+
category: category.GAME,
|
|
205
|
+
num: 5,
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Returns `ListItem[]` (or `App[]` when `fullDetail` is `true`), each shaped like a [search](#search) result.
|
|
210
|
+
|
|
211
|
+
### developer
|
|
212
|
+
|
|
213
|
+
Returns other apps published by the same developer. The `devId` is either the numeric developer id or the developer name, exactly as it appears on Google Play.
|
|
214
|
+
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
| ------------ | --------- | -------- | --------------------------------------------------- |
|
|
217
|
+
| `devId` | `string` | required | Numeric developer id or developer name. |
|
|
218
|
+
| `num` | `number` | `60` | Number of results. |
|
|
219
|
+
| `fullDetail` | `boolean` | `false` | When `true`, return the full `App` for each result. |
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { developer } from '@mradex77/google-play-scraper';
|
|
223
|
+
|
|
224
|
+
const apps = await developer({ devId: '5700313618786177705' });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Returns `DeveloperApp[]` (or `App[]` when `fullDetail` is `true`), each shaped like a [search](#search) result.
|
|
228
|
+
|
|
229
|
+
### similar
|
|
230
|
+
|
|
231
|
+
Returns apps related to a given app.
|
|
232
|
+
|
|
233
|
+
| Option | Type | Default | Description |
|
|
234
|
+
| ------------ | --------- | -------- | --------------------------------------------------- |
|
|
235
|
+
| `appId` | `string` | required | The Google Play id of the reference app. |
|
|
236
|
+
| `fullDetail` | `boolean` | `false` | When `true`, return the full `App` for each result. |
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { similar } from '@mradex77/google-play-scraper';
|
|
240
|
+
|
|
241
|
+
const apps = await similar({ appId: 'com.google.android.apps.translate' });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Returns `SimilarApp[]` (or `App[]` when `fullDetail` is `true`), each shaped like a [search](#search) result.
|
|
245
|
+
|
|
246
|
+
### reviews
|
|
247
|
+
|
|
248
|
+
Retrieves reviews for an app. Reviews always come back inside a `{ data, nextPaginationToken }` envelope so paging is uniform.
|
|
249
|
+
|
|
250
|
+
| Option | Type | Default | Description |
|
|
251
|
+
| --------------------- | --------- | ------------- | -------------------------------------------------------- |
|
|
252
|
+
| `appId` | `string` | required | The Google Play id of the app. |
|
|
253
|
+
| `sort` | `Sort` | `sort.NEWEST` | One of `sort.NEWEST`, `sort.RATING`, `sort.HELPFULNESS`. |
|
|
254
|
+
| `num` | `number` | `150` | Number of reviews to fetch. |
|
|
255
|
+
| `paginate` | `boolean` | `false` | When `true`, fetch a single page and return its token. |
|
|
256
|
+
| `nextPaginationToken` | `string` | none | Continue from a token returned by a previous call. |
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { reviews, sort } from '@mradex77/google-play-scraper';
|
|
260
|
+
|
|
261
|
+
const first = await reviews({ appId: 'com.google.android.apps.translate', sort: sort.NEWEST });
|
|
262
|
+
|
|
263
|
+
if (first.nextPaginationToken) {
|
|
264
|
+
const next = await reviews({
|
|
265
|
+
appId: 'com.google.android.apps.translate',
|
|
266
|
+
paginate: true,
|
|
267
|
+
nextPaginationToken: first.nextPaginationToken,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Returns `ReviewsResult`. Trimmed:
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
{
|
|
276
|
+
data: [
|
|
277
|
+
{
|
|
278
|
+
id: 'gp:AOqpTOH...',
|
|
279
|
+
userName: 'Ada Lovelace',
|
|
280
|
+
userImage: 'https://play-lh.googleusercontent.com/...',
|
|
281
|
+
date: '2026-06-30T12:00:00.000Z',
|
|
282
|
+
score: 5,
|
|
283
|
+
title: null,
|
|
284
|
+
text: 'Works offline and the camera translation is great.',
|
|
285
|
+
thumbsUp: 42,
|
|
286
|
+
version: '8.9.0',
|
|
287
|
+
criterias: []
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
nextPaginationToken: 'CqYBCqMB...'
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### permissions
|
|
295
|
+
|
|
296
|
+
Returns the permissions an app requests.
|
|
297
|
+
|
|
298
|
+
| Option | Type | Default | Description |
|
|
299
|
+
| ------- | --------- | -------- | ----------------------------------------------------------------- |
|
|
300
|
+
| `appId` | `string` | required | The Google Play id of the app. |
|
|
301
|
+
| `short` | `boolean` | `false` | When `true`, return a flat `string[]` of common permission names. |
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { permissions } from '@mradex77/google-play-scraper';
|
|
305
|
+
|
|
306
|
+
const detailed = await permissions({ appId: 'com.google.android.apps.translate' });
|
|
307
|
+
const names = await permissions({ appId: 'com.google.android.apps.translate', short: true });
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Returns `AppPermission[]` (or `string[]` when `short` is `true`):
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
[
|
|
314
|
+
{ permission: 'take pictures and videos', type: 0 },
|
|
315
|
+
{ permission: 'view network connections', type: 1 },
|
|
316
|
+
];
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The `type` is `permission.COMMON` (`0`) or `permission.OTHER` (`1`).
|
|
320
|
+
|
|
321
|
+
### datasafety
|
|
322
|
+
|
|
323
|
+
Returns the data safety section of an app.
|
|
324
|
+
|
|
325
|
+
| Option | Type | Default | Description |
|
|
326
|
+
| ------- | -------- | -------- | ------------------------------ |
|
|
327
|
+
| `appId` | `string` | required | The Google Play id of the app. |
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { datasafety } from '@mradex77/google-play-scraper';
|
|
331
|
+
|
|
332
|
+
const safety = await datasafety({ appId: 'com.google.android.apps.translate' });
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Returns `DataSafety`. Trimmed:
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
{
|
|
339
|
+
sharedData: [
|
|
340
|
+
{ data: 'Approximate location', optional: false, purpose: 'App functionality', type: 'Location' }
|
|
341
|
+
],
|
|
342
|
+
collectedData: [
|
|
343
|
+
{ data: 'Email address', optional: false, purpose: 'Account management', type: 'Personal info' }
|
|
344
|
+
],
|
|
345
|
+
securityPractices: [
|
|
346
|
+
{ practice: 'Data is encrypted in transit', description: 'Your data is transferred over a secure connection' }
|
|
347
|
+
],
|
|
348
|
+
privacyPolicyUrl: 'https://policies.google.com/privacy'
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### categories
|
|
353
|
+
|
|
354
|
+
Returns the Google Play category taxonomy as a list of category ids.
|
|
355
|
+
|
|
356
|
+
| Option | Type | Default | Description |
|
|
357
|
+
| ---------------- | -------- | ------- | -------------------------------------- |
|
|
358
|
+
| `throttle` | `number` | none | See [common options](#common-options). |
|
|
359
|
+
| `requestOptions` | `object` | none | See [common options](#common-options). |
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { categories } from '@mradex77/google-play-scraper';
|
|
363
|
+
|
|
364
|
+
const ids = await categories();
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Returns `string[]`:
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
[
|
|
371
|
+
'APPLICATION',
|
|
372
|
+
'ANDROID_WEAR',
|
|
373
|
+
'ART_AND_DESIGN',
|
|
374
|
+
'AUTO_AND_VEHICLES',
|
|
375
|
+
'GAME',
|
|
376
|
+
'GAME_ACTION',
|
|
377
|
+
'FAMILY',
|
|
378
|
+
];
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### memoized
|
|
382
|
+
|
|
383
|
+
Returns a client whose methods share an in-memory LRU cache, so identical calls made within the TTL resolve from cache instead of hitting Google Play again.
|
|
384
|
+
|
|
385
|
+
| Option | Type | Default | Description |
|
|
386
|
+
| ---------- | -------- | -------- | ---------------------------------------------- |
|
|
387
|
+
| `maxAgeMs` | `number` | `300000` | Time to live per cache entry, in milliseconds. |
|
|
388
|
+
| `max` | `number` | `1000` | Maximum number of cached entries. |
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { memoized } from '@mradex77/google-play-scraper';
|
|
392
|
+
|
|
393
|
+
const client = memoized({ maxAgeMs: 60000, max: 500 });
|
|
394
|
+
|
|
395
|
+
await client.app({ appId: 'com.google.android.apps.translate' });
|
|
396
|
+
await client.app({ appId: 'com.google.android.apps.translate' });
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The returned client exposes every method above plus the exported constants.
|
|
400
|
+
|
|
401
|
+
## Constants
|
|
402
|
+
|
|
403
|
+
The library re-exports the same constant sets as the original, frozen and typed.
|
|
404
|
+
|
|
405
|
+
| Constant | Values |
|
|
406
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
407
|
+
| `category` | All app and game categories plus the `FAMILY` set (e.g. `APPLICATION`, `TOOLS`, `GAME`, `GAME_PUZZLE`, `FAMILY`). |
|
|
408
|
+
| `collection` | `TOP_FREE`, `TOP_PAID`, `GROSSING`. |
|
|
409
|
+
| `sort` | `NEWEST` (`2`), `RATING` (`3`), `HELPFULNESS` (`1`). |
|
|
410
|
+
| `age` | `FIVE_UNDER` (`'AGE_RANGE1'`), `SIX_EIGHT` (`'AGE_RANGE2'`), `NINE_UP` (`'AGE_RANGE3'`). |
|
|
411
|
+
| `permission` | `COMMON` (`0`), `OTHER` (`1`). |
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { category, collection, sort, age, permission } from '@mradex77/google-play-scraper';
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Error handling
|
|
418
|
+
|
|
419
|
+
Every failure surfaces as a typed subclass of `GooglePlayError`, so you can branch on the exact cause instead of parsing message strings.
|
|
420
|
+
|
|
421
|
+
| Error | Extends | Thrown when |
|
|
422
|
+
| ----------------- | ----------------- | ----------------------------------------------------------------------------------------- |
|
|
423
|
+
| `GooglePlayError` | `Error` | Base class for every error the library throws. |
|
|
424
|
+
| `ValidationError` | `GooglePlayError` | The options you passed fail their zod schema. |
|
|
425
|
+
| `HttpError` | `GooglePlayError` | A request fails with a non-success status or a network error. Carries `status` and `url`. |
|
|
426
|
+
| `NotFoundError` | `HttpError` | Google Play responds `404`, e.g. an unknown `appId`. |
|
|
427
|
+
| `RateLimitError` | `HttpError` | Google Play responds `429` after retries are exhausted. |
|
|
428
|
+
| `BlockedError` | `GooglePlayError` | A consent wall or captcha interstitial is detected. |
|
|
429
|
+
| `ParseError` | `GooglePlayError` | A batchexecute response cannot be parsed. |
|
|
430
|
+
| `SpecError` | `ParseError` | Extraction fails; lists every failing field and the paths that were tried. |
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { app, NotFoundError, SpecError } from '@mradex77/google-play-scraper';
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const details = await app({ appId: 'com.does.not.exist' });
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (error instanceof NotFoundError) {
|
|
439
|
+
console.error('No such app');
|
|
440
|
+
} else if (error instanceof SpecError) {
|
|
441
|
+
console.error('Google Play changed its layout:', error.failures);
|
|
442
|
+
} else {
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Throttling and requestOptions
|
|
449
|
+
|
|
450
|
+
Pass `throttle` to cap requests per second, and `requestOptions` to override the HTTP layer:
|
|
451
|
+
|
|
452
|
+
| requestOptions field | Type | Description |
|
|
453
|
+
| -------------------- | ------------------------ | -------------------------------------------------------------- |
|
|
454
|
+
| `headers` | `Record<string, string>` | Extra headers merged into every request. |
|
|
455
|
+
| `fetchImpl` | `typeof fetch` | A custom `fetch` implementation, useful for proxies and tests. |
|
|
456
|
+
| `timeoutMs` | `number` | Per-request timeout, up to `120000`. Default `30000`. |
|
|
457
|
+
| `retries` | `number` | Retry count for `429` and `5xx`, `0` to `5`. Default `2`. |
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import { app } from '@mradex77/google-play-scraper';
|
|
461
|
+
|
|
462
|
+
const details = await app({
|
|
463
|
+
appId: 'com.google.android.apps.translate',
|
|
464
|
+
throttle: 5,
|
|
465
|
+
requestOptions: {
|
|
466
|
+
timeoutMs: 15000,
|
|
467
|
+
retries: 3,
|
|
468
|
+
headers: { 'Accept-Language': 'de' },
|
|
469
|
+
fetchImpl: myProxiedFetch,
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Retries use exponential backoff and honor a `Retry-After` header when present.
|
|
475
|
+
|
|
476
|
+
## Resilience
|
|
477
|
+
|
|
478
|
+
Google Play serves its data as deeply nested, unlabeled arrays whose positions shift a few times a year. That is what breaks scrapers. Every positional path in this library lives as a typed constant in a per-feature `specs.ts` file, never inline in logic, and each field is resolved through an ordered list of candidate paths so a single moved index does not take the whole call down. Extraction collects all field failures in one pass and throws a single `SpecError` naming every broken field and the paths that were tried, which is exactly the input the maintenance runbook needs. Unknown data enters as `unknown` and only leaves through a zod schema, so a layout change fails loudly at the boundary rather than three layers up.
|
|
479
|
+
|
|
480
|
+
To catch breakage before users do, the `e2e/` suite runs against live Google Play on a daily GitHub Actions schedule and opens a labeled issue on failure. Repairing a break is a one-file spec diff, walked through step by step in [docs/RUNBOOK.md](docs/RUNBOOK.md).
|
|
481
|
+
|
|
482
|
+
## Migrating from google-play-scraper
|
|
483
|
+
|
|
484
|
+
The method names, options, and constants are the same, so most code keeps working after swapping the import. Watch for these differences:
|
|
485
|
+
|
|
486
|
+
- `reviews` always returns the `{ data, nextPaginationToken }` envelope, never a bare array.
|
|
487
|
+
- Dates are ISO 8601 strings (review `date`, `replyDate`), and `updated` is a millisecond timestamp.
|
|
488
|
+
- Errors are the typed classes above instead of plain `Error`.
|
|
489
|
+
- The package is ESM-first with a CommonJS build; the default export is the aggregate client and named exports are also available.
|
|
490
|
+
|
|
491
|
+
## Contributing
|
|
492
|
+
|
|
493
|
+
This project uses [pnpm](https://pnpm.io), [Conventional Commits](https://www.conventionalcommits.org), and the conventional branch spec (`feature/`, `bugfix/`, `chore/`, ...). Versioning, changelog, and npm publishing are automated with Release Please.
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
pnpm install
|
|
497
|
+
pnpm lint eslint on the whole repo
|
|
498
|
+
pnpm typecheck tsc --noEmit
|
|
499
|
+
pnpm test unit tests, offline against recorded fixtures
|
|
500
|
+
pnpm test:coverage unit tests with coverage thresholds
|
|
501
|
+
pnpm test:e2e live contract tests against play.google.com
|
|
502
|
+
pnpm build emit dist/ with esm, cjs, and d.ts
|
|
503
|
+
pnpm check:package build then verify the published package
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## License
|
|
507
|
+
|
|
508
|
+
MIT
|