apple-app-store-scraper-lite 0.1.0__tar.gz

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.
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agents Kit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ and associated documentation files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or
12
+ substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
15
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: apple-app-store-scraper-lite
3
+ Version: 0.1.0
4
+ Summary: A lightweight, pure-Python library for scraping Apple App Store data via iTunes API
5
+ Author: Apple App Store Scraper Lite Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://apify.com/Redbaron/apple-app-store-scraper-lite
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Internet :: WWW/HTTP
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Apple App Store Scraper Lite
27
+
28
+ > **Run it hosted, no setup:** [Apple App Store Scraper Lite on Apify](https://apify.com/Redbaron/apple-app-store-scraper-lite) — pay per result, nothing to install or maintain.
29
+
30
+ A lightweight, pure-Python library for scraping Apple App Store data via the iTunes API. Supports searching for apps, getting app details, extracting reviews, and discovering top charts — all without a browser, using only standard library HTTP requests.
31
+
32
+ ## Features
33
+
34
+ - **Search apps** by keyword with full metadata (name, developer, price, rating, description, screenshots, supported devices)
35
+ - **Get app details** by App Store ID or URL
36
+ - **Extract reviews** with ratings, titles, content, author, date, and app version
37
+ - **Discover top charts** (free, paid, grossing, new releases) by country
38
+ - **Pure Python** — no external dependencies, no browser automation
39
+ - **Pagination support** — automatically follows next-page links
40
+ - **Privacy-safe** — never returns personal data (emails, phones, usernames beyond public review author names)
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install apple-app-store-scraper-lite-0.1.0-py3-none-any.whl
46
+ ```
47
+
48
+ Or install from source:
49
+
50
+ ```bash
51
+ pip install -e .
52
+ ```
53
+
54
+ Requires Python 3.9+.
55
+
56
+ ## Quickstart
57
+
58
+ ```python
59
+ from apple_app_store_scraper_lite import start_urls, process, next_urls
60
+ import urllib.request
61
+ import json
62
+
63
+ # 1. Build start URLs from your parameters
64
+ params = {
65
+ "mode": "search",
66
+ "searchTerm": "photo editor",
67
+ "country": "us"
68
+ }
69
+ urls = start_urls(params)
70
+ print(f"Fetching: {urls[0]}")
71
+
72
+ # 2. Fetch the response (using stdlib urllib)
73
+ with urllib.request.urlopen(urls[0]) as response:
74
+ text = response.read().decode("utf-8")
75
+
76
+ # 3. Parse into structured records
77
+ records = process(text, urls[0])
78
+ print(f"Found {len(records)} apps")
79
+ for app in records[:3]:
80
+ print(f" - {app['appName']} ({app['appId']}) by {app['developer']} — {app['price']['formatted']} — ⭐ {app['rating']['value']}")
81
+
82
+ # 4. Follow pagination if needed
83
+ for next_url in next_urls(text, urls[0]):
84
+ with urllib.request.urlopen(next_url) as response:
85
+ more_text = response.read().decode("utf-8")
86
+ more_records = process(more_text, next_url)
87
+ print(f"Next page: {len(more_records)} more apps")
88
+ ```
89
+
90
+ ## Modes
91
+
92
+ | Mode | Description | Required Params | Optional Params |
93
+ |------|-------------|-----------------|-----------------|
94
+ | `search` | Search apps by keyword | `mode`, `searchTerm` | `country` (default: `us`) |
95
+ | `details` | Get full app metadata | `mode`, `appIds` | `country` (default: `us`) |
96
+ | `reviews` | Extract user reviews | `mode`, `appIds` | `country` (default: `us`) |
97
+ | `charts` | Top charts by category | `mode` | `chartType` (default: `top-free`), `country` (default: `us`) |
98
+
99
+ ### Chart Types
100
+ - `top-free` — Top Free Apps
101
+ - `top-paid` — Top Paid Apps
102
+ - `top-grossing` — Top Grossing Apps
103
+ - `new-free` — New Free Apps
104
+ - `new-paid` — New Paid Apps
105
+
106
+ ## API Reference
107
+
108
+ ### `start_urls(params: dict) -> list[str]`
109
+
110
+ Build initial URL(s) to fetch from user parameters.
111
+
112
+ **Parameters:**
113
+ - `params` (dict): Configuration dictionary with keys:
114
+ - `mode` (str, required): One of `"search"`, `"details"`, `"reviews"`, `"charts"`
115
+ - `searchTerm` (str, optional): Keyword for search mode (default: `"photo editor"`)
116
+ - `appIds` (list[str], optional): App IDs or App Store URLs for details/reviews modes (default: `["389801252"]`)
117
+ - `chartType` (str, optional): Chart type for charts mode (default: `"top-free"`)
118
+ - `country` (str, optional): Two-letter country code (default: `"us"`)
119
+
120
+ **Returns:** List of URLs to fetch initially.
121
+
122
+ **Example:**
123
+ ```python
124
+ start_urls({"mode": "search", "searchTerm": "fitness", "country": "gb"})
125
+ # ["https://itunes.apple.com/search?term=fitness&country=gb&entity=software&limit=200"]
126
+ ```
127
+
128
+ ---
129
+
130
+ ### `process(text: str, url: str) -> list[dict]`
131
+
132
+ Parse a single fetched response into structured records.
133
+
134
+ **Parameters:**
135
+ - `text` (str): Raw response body (JSON)
136
+ - `url` (str): The URL that was fetched (used to determine response type)
137
+
138
+ **Returns:** List of record dictionaries. Each record has exactly these fields:
139
+
140
+ **App Records (search, details, charts):**
141
+ | Field | Type | Description |
142
+ |-------|------|-------------|
143
+ | `appId` | str | Numeric App Store ID |
144
+ | `appName` | str | App display name |
145
+ | `developer` | str | Developer/seller name |
146
+ | `price` | dict | `{"amount": float, "currency": str, "formatted": str}` |
147
+ | `rating` | dict | `{"value": float, "count": int}` |
148
+ | `description` | str | App description |
149
+ | `screenshotUrls` | list[str] | Screenshot image URLs |
150
+ | `supportedDevices` | list[str] | e.g., `["iphone", "ipad", "mac"]` |
151
+ | `country` | str | Two-letter country code |
152
+
153
+ **Review Records (reviews mode):**
154
+ | Field | Type | Description |
155
+ |-------|------|-------------|
156
+ | `reviewId` | str | Unique review identifier |
157
+ | `title` | str | Review title |
158
+ | `content` | str | Review text |
159
+ | `rating` | int | 1–5 star rating |
160
+ | `author` | str | Review author name (public) |
161
+ | `date` | str | ISO 8601 timestamp |
162
+ | `appVersion` | str | App version reviewed |
163
+
164
+ **Example:**
165
+ ```python
166
+ records = process(json_text, "https://itunes.apple.com/search?term=photo+editor&country=us")
167
+ # [{
168
+ # "appId": "389801252",
169
+ # "appName": "Photo Editor Pro",
170
+ # "developer": "Example Inc.",
171
+ # "price": {"amount": 0.0, "currency": "USD", "formatted": "Free"},
172
+ # "rating": {"value": 4.7, "count": 12345},
173
+ # "description": "Edit photos like a pro...",
174
+ # "screenshotUrls": ["https://.../1.jpg", "https://.../2.jpg"],
175
+ # "supportedDevices": ["iphone", "ipad"],
176
+ # "country": "us"
177
+ # }]
178
+ ```
179
+
180
+ ---
181
+
182
+ ### `next_urls(text: str, url: str) -> list[str]`
183
+
184
+ Extract further URLs to fetch for pagination.
185
+
186
+ **Parameters:**
187
+ - `text` (str): Raw response body (JSON)
188
+ - `url` (str): The URL that was fetched
189
+
190
+ **Returns:** List of next-page URLs, empty if no more pages.
191
+
192
+ **Behavior by mode:**
193
+ - **search**: Uses `offset`/`limit` pagination until `resultCount` is reached
194
+ - **details**: No pagination (single request per app ID)
195
+ - **reviews**: Follows RSS `link[rel="next"]` for paginated review feeds
196
+ - **charts**: No pagination (fixed limit feed)
197
+
198
+ **Example:**
199
+ ```python
200
+ next_pages = next_urls(search_response_text, search_url)
201
+ # ["https://itunes.apple.com/search?term=photo+editor&country=us&limit=200&offset=200"]
202
+ ```
203
+
204
+ ## Running Tests
205
+
206
+ ```bash
207
+ pip install -e ".[dev]"
208
+ pytest -v
209
+ ```
210
+
211
+ ## License
212
+
213
+ MIT License — see LICENSE for details.
@@ -0,0 +1,188 @@
1
+ # Apple App Store Scraper Lite
2
+
3
+ > **Run it hosted, no setup:** [Apple App Store Scraper Lite on Apify](https://apify.com/Redbaron/apple-app-store-scraper-lite) — pay per result, nothing to install or maintain.
4
+
5
+ A lightweight, pure-Python library for scraping Apple App Store data via the iTunes API. Supports searching for apps, getting app details, extracting reviews, and discovering top charts — all without a browser, using only standard library HTTP requests.
6
+
7
+ ## Features
8
+
9
+ - **Search apps** by keyword with full metadata (name, developer, price, rating, description, screenshots, supported devices)
10
+ - **Get app details** by App Store ID or URL
11
+ - **Extract reviews** with ratings, titles, content, author, date, and app version
12
+ - **Discover top charts** (free, paid, grossing, new releases) by country
13
+ - **Pure Python** — no external dependencies, no browser automation
14
+ - **Pagination support** — automatically follows next-page links
15
+ - **Privacy-safe** — never returns personal data (emails, phones, usernames beyond public review author names)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install apple-app-store-scraper-lite-0.1.0-py3-none-any.whl
21
+ ```
22
+
23
+ Or install from source:
24
+
25
+ ```bash
26
+ pip install -e .
27
+ ```
28
+
29
+ Requires Python 3.9+.
30
+
31
+ ## Quickstart
32
+
33
+ ```python
34
+ from apple_app_store_scraper_lite import start_urls, process, next_urls
35
+ import urllib.request
36
+ import json
37
+
38
+ # 1. Build start URLs from your parameters
39
+ params = {
40
+ "mode": "search",
41
+ "searchTerm": "photo editor",
42
+ "country": "us"
43
+ }
44
+ urls = start_urls(params)
45
+ print(f"Fetching: {urls[0]}")
46
+
47
+ # 2. Fetch the response (using stdlib urllib)
48
+ with urllib.request.urlopen(urls[0]) as response:
49
+ text = response.read().decode("utf-8")
50
+
51
+ # 3. Parse into structured records
52
+ records = process(text, urls[0])
53
+ print(f"Found {len(records)} apps")
54
+ for app in records[:3]:
55
+ print(f" - {app['appName']} ({app['appId']}) by {app['developer']} — {app['price']['formatted']} — ⭐ {app['rating']['value']}")
56
+
57
+ # 4. Follow pagination if needed
58
+ for next_url in next_urls(text, urls[0]):
59
+ with urllib.request.urlopen(next_url) as response:
60
+ more_text = response.read().decode("utf-8")
61
+ more_records = process(more_text, next_url)
62
+ print(f"Next page: {len(more_records)} more apps")
63
+ ```
64
+
65
+ ## Modes
66
+
67
+ | Mode | Description | Required Params | Optional Params |
68
+ |------|-------------|-----------------|-----------------|
69
+ | `search` | Search apps by keyword | `mode`, `searchTerm` | `country` (default: `us`) |
70
+ | `details` | Get full app metadata | `mode`, `appIds` | `country` (default: `us`) |
71
+ | `reviews` | Extract user reviews | `mode`, `appIds` | `country` (default: `us`) |
72
+ | `charts` | Top charts by category | `mode` | `chartType` (default: `top-free`), `country` (default: `us`) |
73
+
74
+ ### Chart Types
75
+ - `top-free` — Top Free Apps
76
+ - `top-paid` — Top Paid Apps
77
+ - `top-grossing` — Top Grossing Apps
78
+ - `new-free` — New Free Apps
79
+ - `new-paid` — New Paid Apps
80
+
81
+ ## API Reference
82
+
83
+ ### `start_urls(params: dict) -> list[str]`
84
+
85
+ Build initial URL(s) to fetch from user parameters.
86
+
87
+ **Parameters:**
88
+ - `params` (dict): Configuration dictionary with keys:
89
+ - `mode` (str, required): One of `"search"`, `"details"`, `"reviews"`, `"charts"`
90
+ - `searchTerm` (str, optional): Keyword for search mode (default: `"photo editor"`)
91
+ - `appIds` (list[str], optional): App IDs or App Store URLs for details/reviews modes (default: `["389801252"]`)
92
+ - `chartType` (str, optional): Chart type for charts mode (default: `"top-free"`)
93
+ - `country` (str, optional): Two-letter country code (default: `"us"`)
94
+
95
+ **Returns:** List of URLs to fetch initially.
96
+
97
+ **Example:**
98
+ ```python
99
+ start_urls({"mode": "search", "searchTerm": "fitness", "country": "gb"})
100
+ # ["https://itunes.apple.com/search?term=fitness&country=gb&entity=software&limit=200"]
101
+ ```
102
+
103
+ ---
104
+
105
+ ### `process(text: str, url: str) -> list[dict]`
106
+
107
+ Parse a single fetched response into structured records.
108
+
109
+ **Parameters:**
110
+ - `text` (str): Raw response body (JSON)
111
+ - `url` (str): The URL that was fetched (used to determine response type)
112
+
113
+ **Returns:** List of record dictionaries. Each record has exactly these fields:
114
+
115
+ **App Records (search, details, charts):**
116
+ | Field | Type | Description |
117
+ |-------|------|-------------|
118
+ | `appId` | str | Numeric App Store ID |
119
+ | `appName` | str | App display name |
120
+ | `developer` | str | Developer/seller name |
121
+ | `price` | dict | `{"amount": float, "currency": str, "formatted": str}` |
122
+ | `rating` | dict | `{"value": float, "count": int}` |
123
+ | `description` | str | App description |
124
+ | `screenshotUrls` | list[str] | Screenshot image URLs |
125
+ | `supportedDevices` | list[str] | e.g., `["iphone", "ipad", "mac"]` |
126
+ | `country` | str | Two-letter country code |
127
+
128
+ **Review Records (reviews mode):**
129
+ | Field | Type | Description |
130
+ |-------|------|-------------|
131
+ | `reviewId` | str | Unique review identifier |
132
+ | `title` | str | Review title |
133
+ | `content` | str | Review text |
134
+ | `rating` | int | 1–5 star rating |
135
+ | `author` | str | Review author name (public) |
136
+ | `date` | str | ISO 8601 timestamp |
137
+ | `appVersion` | str | App version reviewed |
138
+
139
+ **Example:**
140
+ ```python
141
+ records = process(json_text, "https://itunes.apple.com/search?term=photo+editor&country=us")
142
+ # [{
143
+ # "appId": "389801252",
144
+ # "appName": "Photo Editor Pro",
145
+ # "developer": "Example Inc.",
146
+ # "price": {"amount": 0.0, "currency": "USD", "formatted": "Free"},
147
+ # "rating": {"value": 4.7, "count": 12345},
148
+ # "description": "Edit photos like a pro...",
149
+ # "screenshotUrls": ["https://.../1.jpg", "https://.../2.jpg"],
150
+ # "supportedDevices": ["iphone", "ipad"],
151
+ # "country": "us"
152
+ # }]
153
+ ```
154
+
155
+ ---
156
+
157
+ ### `next_urls(text: str, url: str) -> list[str]`
158
+
159
+ Extract further URLs to fetch for pagination.
160
+
161
+ **Parameters:**
162
+ - `text` (str): Raw response body (JSON)
163
+ - `url` (str): The URL that was fetched
164
+
165
+ **Returns:** List of next-page URLs, empty if no more pages.
166
+
167
+ **Behavior by mode:**
168
+ - **search**: Uses `offset`/`limit` pagination until `resultCount` is reached
169
+ - **details**: No pagination (single request per app ID)
170
+ - **reviews**: Follows RSS `link[rel="next"]` for paginated review feeds
171
+ - **charts**: No pagination (fixed limit feed)
172
+
173
+ **Example:**
174
+ ```python
175
+ next_pages = next_urls(search_response_text, search_url)
176
+ # ["https://itunes.apple.com/search?term=photo+editor&country=us&limit=200&offset=200"]
177
+ ```
178
+
179
+ ## Running Tests
180
+
181
+ ```bash
182
+ pip install -e ".[dev]"
183
+ pytest -v
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT License — see LICENSE for details.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "apple-app-store-scraper-lite"
7
+ version = "0.1.0"
8
+ description = "A lightweight, pure-Python library for scraping Apple App Store data via iTunes API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Apple App Store Scraper Lite Contributors"}
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Internet :: WWW/HTTP",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+ dependencies = []
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
31
+ include = ["apple_app_store_scraper_lite*"]
32
+
33
+ [tool.setuptools.package-data]
34
+ "*" = ["fixtures/*.json"]
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["pytest>=7.0", "pytest-cov>=4.0"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://apify.com/Redbaron/apple-app-store-scraper-lite"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """
2
+ Apple App Store Scraper Lite
3
+
4
+ A lightweight, pure-Python library for scraping Apple App Store data via the iTunes API.
5
+ Supports searching for apps, getting app details, extracting reviews, and discovering top charts.
6
+
7
+ Public API:
8
+ - start_urls: Build initial URLs from user parameters
9
+ - process: Parse a fetched response into structured records
10
+ - next_urls: Get pagination URLs for the next page of results
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .parsers import process
16
+ from .pagination import next_urls
17
+ from .urls import start_urls
18
+
19
+ __all__ = ["start_urls", "process", "next_urls"]
20
+ __version__ = "0.1.0"
@@ -0,0 +1,116 @@
1
+ """
2
+ Pagination handling for Apple App Store iTunes API responses.
3
+
4
+ Pure functions that extract next-page URLs from responses.
5
+ No network I/O, no side effects.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import urllib.parse
12
+ from typing import Any
13
+
14
+
15
+ def _extract_next_from_search(text: str, url: str) -> list[str]:
16
+ """Extract next page URL from search API response."""
17
+ try:
18
+ data = json.loads(text)
19
+ except json.JSONDecodeError:
20
+ return []
21
+
22
+ # iTunes search API doesn't have standard pagination in response
23
+ # But we can paginate by offset if resultCount > limit
24
+ result_count = data.get("resultCount", 0)
25
+ results = data.get("results", [])
26
+
27
+ if not isinstance(results, list) or len(results) == 0:
28
+ return []
29
+
30
+ # If we got fewer results than limit, we're done
31
+ # Default limit is 200, but could be overridden
32
+ parsed = urllib.parse.urlparse(url)
33
+ query = urllib.parse.parse_qs(parsed.query)
34
+ limit = int(query.get("limit", ["200"])[0])
35
+
36
+ if len(results) < limit:
37
+ return []
38
+
39
+ # Calculate next offset
40
+ offset = int(query.get("offset", ["0"])[0])
41
+ next_offset = offset + limit
42
+
43
+ # Don't paginate beyond resultCount
44
+ if next_offset >= result_count:
45
+ return []
46
+
47
+ # Build next URL with updated offset
48
+ next_query = dict(query)
49
+ next_query["offset"] = [str(next_offset)]
50
+ next_url = urllib.parse.urlunparse((
51
+ parsed.scheme,
52
+ parsed.netloc,
53
+ parsed.path,
54
+ parsed.params,
55
+ urllib.parse.urlencode(next_query, doseq=True),
56
+ parsed.fragment
57
+ ))
58
+ return [next_url]
59
+
60
+
61
+ def _extract_next_from_lookup(text: str, url: str) -> list[str]:
62
+ """Lookup API doesn't paginate - single request per app ID."""
63
+ return []
64
+
65
+
66
+ def _extract_next_from_reviews(text: str, url: str) -> list[str]:
67
+ """Extract next page URL from reviews RSS feed."""
68
+ try:
69
+ data = json.loads(text)
70
+ except json.JSONDecodeError:
71
+ return []
72
+
73
+ feed = data.get("feed", {})
74
+ links = feed.get("link", [])
75
+ if not isinstance(links, list):
76
+ return []
77
+
78
+ # Find the 'next' link
79
+ for link in links:
80
+ if isinstance(link, dict):
81
+ rel = link.get("rel")
82
+ href = link.get("href")
83
+ if rel == "next" and href:
84
+ return [href]
85
+ return []
86
+
87
+
88
+ def _extract_next_from_charts(text: str, url: str) -> list[str]:
89
+ """Charts RSS feed doesn't typically paginate (fixed limit)."""
90
+ return []
91
+
92
+
93
+ def next_urls(text: str, url: str) -> list[str]:
94
+ """
95
+ Extract further URLs to fetch for pagination.
96
+
97
+ Args:
98
+ text: Raw response text (JSON)
99
+ url: The URL that was fetched
100
+
101
+ Returns:
102
+ List of next page URLs, empty if no more pages.
103
+ """
104
+ if not text or not isinstance(text, str):
105
+ return []
106
+
107
+ if "/search?" in url:
108
+ return _extract_next_from_search(text, url)
109
+ elif "/lookup?" in url:
110
+ return _extract_next_from_lookup(text, url)
111
+ elif "/customerreviews/" in url:
112
+ return _extract_next_from_reviews(text, url)
113
+ elif "/rss/" in url:
114
+ return _extract_next_from_charts(text, url)
115
+ else:
116
+ return []