gcrawl-sdk 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gramosoft
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.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcrawl-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for GCrawl — Scrape, crawl, screenshot, links, and search with ease
5
+ Author-email: Ramkumar <ramkumarlpm4@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Gramosoft
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/GramosoftAI/GCrawl
29
+ Keywords: crawl,scraper,screenshot,links,gcrawl,ai
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Requires-Python: >=3.10
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: requests>=2.28.0
37
+ Dynamic: license-file
38
+
39
+ # gcrawl-sdk
40
+
41
+ Official Python SDK for GCrawl — Scrape, crawl, screenshot, fetch links, and search web pages with ease.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install gcrawl-sdk
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from gcrawl_sdk import GcrawlClient, ScrapeOptions
53
+
54
+ client = GcrawlClient(api_key="your-api-key")
55
+
56
+ # Scrape a website (synchronous polling)
57
+ result = client.scrape(
58
+ url="https://gramosoft.tech",
59
+ options=ScrapeOptions(markdown_enabled=True, screenshot_enabled=True),
60
+ wait=True
61
+ )
62
+ print("Scraped Markdown:", result.markdown)
63
+ print("Screenshot URL:", result.screenshot_url)
64
+
65
+ # Search web synchronously
66
+ search_res = client.search("gramosoft", limit=5, geo="IN")
67
+ for res in search_res.results:
68
+ print(res.position, res.title, res.url)
69
+ ```
70
+
71
+ ## API Key
72
+
73
+ Set your API key as an environment variable:
74
+ ```bash
75
+ export GCRAWL_API_KEY="your-api-key"
76
+ ```
77
+
78
+ Or pass it directly to the client:
79
+ ```python
80
+ client = GcrawlClient(api_key="your-api-key")
81
+ ```
@@ -0,0 +1,43 @@
1
+ # gcrawl-sdk
2
+
3
+ Official Python SDK for GCrawl — Scrape, crawl, screenshot, fetch links, and search web pages with ease.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install gcrawl-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from gcrawl_sdk import GcrawlClient, ScrapeOptions
15
+
16
+ client = GcrawlClient(api_key="your-api-key")
17
+
18
+ # Scrape a website (synchronous polling)
19
+ result = client.scrape(
20
+ url="https://gramosoft.tech",
21
+ options=ScrapeOptions(markdown_enabled=True, screenshot_enabled=True),
22
+ wait=True
23
+ )
24
+ print("Scraped Markdown:", result.markdown)
25
+ print("Screenshot URL:", result.screenshot_url)
26
+
27
+ # Search web synchronously
28
+ search_res = client.search("gramosoft", limit=5, geo="IN")
29
+ for res in search_res.results:
30
+ print(res.position, res.title, res.url)
31
+ ```
32
+
33
+ ## API Key
34
+
35
+ Set your API key as an environment variable:
36
+ ```bash
37
+ export GCRAWL_API_KEY="your-api-key"
38
+ ```
39
+
40
+ Or pass it directly to the client:
41
+ ```python
42
+ client = GcrawlClient(api_key="your-api-key")
43
+ ```
@@ -0,0 +1,15 @@
1
+ from .client import GcrawlClient
2
+ from .scrape_options import ScrapeOptions
3
+ from .crawl_options import CrawlOptions
4
+ from .screenshot_options import ScreenshotOptions
5
+ from .links_options import LinksOptions
6
+ from .search_options import SearchOptions
7
+ from .task_init_result import GcrawlTaskInitResult
8
+ from .task_result import GcrawlTaskResult, GcrawlPageResult
9
+ from .search_result import SearchResult
10
+ from .exceptions import (
11
+ GcrawlAPIError,
12
+ GcrawlTimeoutError,
13
+ GcrawlValidationError,
14
+ GcrawlFileError,
15
+ )
@@ -0,0 +1,336 @@
1
+ import requests
2
+ import time
3
+ import os
4
+ from .exceptions import GcrawlAPIError, GcrawlTimeoutError, GcrawlValidationError
5
+ from .task_init_result import GcrawlTaskInitResult
6
+ from .task_result import GcrawlTaskResult
7
+ from .search_result import SearchResult
8
+ from .scrape_options import ScrapeOptions
9
+ from .crawl_options import CrawlOptions
10
+ from .screenshot_options import ScreenshotOptions
11
+ from .links_options import LinksOptions
12
+ from .search_options import SearchOptions
13
+
14
+ class GcrawlClient:
15
+ def __init__(self, api_key=None, base_url=None, timeout=300):
16
+ self.api_key = api_key or os.getenv("GCRAWL_API_KEY")
17
+ if not self.api_key:
18
+ raise GcrawlValidationError("API key required")
19
+
20
+ self.base_url = base_url or "https://gcrawlai.com/gc"
21
+ self.timeout = timeout
22
+
23
+ def _get_headers(self):
24
+ return {
25
+ "Content-Type": "application/json",
26
+ "X-API-Key": self.api_key
27
+ }
28
+
29
+ def scrape(self, url, options=None, formats=None, geo="default",
30
+ clean=False, remove_external_links=False, relative_to_absolute_links=True,
31
+ remove_data_images=False, ignore_tags=None,
32
+ full_page=False, format="png", quality=90, js_render=False,
33
+ render_timeout=30000, auto_scroll=True, scroll_delay=500, max_scrolls=2,
34
+ wait=False, poll_interval=2, timeout=300):
35
+ if options is not None:
36
+ if hasattr(options, "to_dict"):
37
+ payload = options.to_dict()
38
+ elif isinstance(options, dict):
39
+ payload = options.copy()
40
+ else:
41
+ raise GcrawlValidationError("options must be a ScrapeOptions instance or a dict")
42
+ else:
43
+ formats_list = formats if formats is not None else ["markdown"]
44
+ ignore_tags_list = ignore_tags if ignore_tags is not None else []
45
+
46
+ payload = {
47
+ "proxy": {
48
+ "geo": geo
49
+ },
50
+ "markdown": {
51
+ "enabled": "markdown" in formats_list,
52
+ "clean": clean
53
+ },
54
+ "html": {
55
+ "enabled": "html" in formats_list,
56
+ "clean": clean,
57
+ "remove_external_links": remove_external_links,
58
+ "relative_to_absolute_links": relative_to_absolute_links,
59
+ "remove_data_images": remove_data_images,
60
+ "ignore_tags": ignore_tags_list
61
+ },
62
+ "screenshot": {
63
+ "enabled": "screenshot" in formats_list,
64
+ "full_page": full_page,
65
+ "format": format,
66
+ "quality": quality,
67
+ "js_render": js_render,
68
+ "render_timeout": render_timeout,
69
+ "auto_scroll": auto_scroll,
70
+ "scroll_delay": scroll_delay,
71
+ "max_scrolls": max_scrolls
72
+ },
73
+ "seo": {
74
+ "enabled": "seo" in formats_list
75
+ },
76
+ "images": {
77
+ "enabled": "images" in formats_list
78
+ }
79
+ }
80
+
81
+ payload["url"] = url
82
+
83
+ try:
84
+ response = requests.post(
85
+ f"{self.base_url}/api/v1/scrape",
86
+ json=payload,
87
+ headers=self._get_headers(),
88
+ timeout=self.timeout
89
+ )
90
+ except requests.exceptions.Timeout:
91
+ raise GcrawlTimeoutError("Scrape request timed out")
92
+
93
+ if response.status_code != 200:
94
+ raise GcrawlAPIError(response.status_code, response.text)
95
+
96
+ init_result = GcrawlTaskInitResult(response.json())
97
+
98
+ if wait and init_result.crawl_id:
99
+ return self._poll_task_results(init_result.crawl_id, poll_interval, timeout)
100
+
101
+ return init_result
102
+
103
+ def crawl(self, url, options=None, limit=10, formats=None, geo="default",
104
+ same_domain_only=True, include_subdomains=True,
105
+ clean=False, remove_external_links=False, relative_to_absolute_links=True,
106
+ remove_data_images=False, ignore_tags=None,
107
+ full_page=False, format="png", quality=90, js_render=False,
108
+ render_timeout=30000, auto_scroll=True, scroll_delay=500, max_scrolls=2,
109
+ wait=False, poll_interval=2, timeout=300):
110
+ if options is not None:
111
+ if hasattr(options, "to_dict"):
112
+ payload = options.to_dict()
113
+ elif isinstance(options, dict):
114
+ payload = options.copy()
115
+ else:
116
+ raise GcrawlValidationError("options must be a CrawlOptions instance or a dict")
117
+ else:
118
+ formats_list = formats if formats is not None else ["markdown"]
119
+ ignore_tags_list = ignore_tags if ignore_tags is not None else []
120
+
121
+ payload = {
122
+ "crawl": {
123
+ "max_pages": limit,
124
+ "same_domain_only": same_domain_only,
125
+ "include_subdomains": include_subdomains
126
+ },
127
+ "proxy": {
128
+ "geo": geo
129
+ },
130
+ "markdown": {
131
+ "enabled": "markdown" in formats_list,
132
+ "clean": clean
133
+ },
134
+ "html": {
135
+ "enabled": "html" in formats_list,
136
+ "clean": clean,
137
+ "remove_external_links": remove_external_links,
138
+ "relative_to_absolute_links": relative_to_absolute_links,
139
+ "remove_data_images": remove_data_images,
140
+ "ignore_tags": ignore_tags_list
141
+ },
142
+ "screenshot": {
143
+ "enabled": "screenshot" in formats_list,
144
+ "full_page": full_page,
145
+ "format": format,
146
+ "quality": quality,
147
+ "js_render": js_render,
148
+ "render_timeout": render_timeout,
149
+ "auto_scroll": auto_scroll,
150
+ "scroll_delay": scroll_delay,
151
+ "max_scrolls": max_scrolls
152
+ },
153
+ "seo": {
154
+ "enabled": "seo" in formats_list
155
+ },
156
+ "images": {
157
+ "enabled": "images" in formats_list
158
+ }
159
+ }
160
+
161
+ payload["url"] = url
162
+
163
+ try:
164
+ response = requests.post(
165
+ f"{self.base_url}/api/v1/crawl",
166
+ json=payload,
167
+ headers=self._get_headers(),
168
+ timeout=self.timeout
169
+ )
170
+ except requests.exceptions.Timeout:
171
+ raise GcrawlTimeoutError("Crawl request timed out")
172
+
173
+ if response.status_code != 200:
174
+ raise GcrawlAPIError(response.status_code, response.text)
175
+
176
+ init_result = GcrawlTaskInitResult(response.json())
177
+
178
+ if wait and init_result.crawl_id:
179
+ return self._poll_task_results(init_result.crawl_id, poll_interval, timeout)
180
+
181
+ return init_result
182
+
183
+ def screenshot(self, url, options=None, geo="default",
184
+ full_page=True, format="png", quality=90, js_render=True,
185
+ render_timeout=30000, auto_scroll=True, scroll_delay=500, max_scrolls=2,
186
+ wait=False, poll_interval=2, timeout=300):
187
+ if options is not None:
188
+ if hasattr(options, "to_dict"):
189
+ payload = options.to_dict()
190
+ elif isinstance(options, dict):
191
+ payload = options.copy()
192
+ else:
193
+ raise GcrawlValidationError("options must be a ScreenshotOptions instance or a dict")
194
+ else:
195
+ payload = {
196
+ "screenshot": {
197
+ "enabled": True,
198
+ "full_page": full_page,
199
+ "format": format,
200
+ "quality": quality,
201
+ "js_render": js_render,
202
+ "render_timeout": render_timeout,
203
+ "auto_scroll": auto_scroll,
204
+ "scroll_delay": scroll_delay,
205
+ "max_scrolls": max_scrolls
206
+ }
207
+ }
208
+ if geo:
209
+ payload["proxy"] = {"geo": geo}
210
+
211
+ payload["url"] = url
212
+
213
+ try:
214
+ response = requests.post(
215
+ f"{self.base_url}/api/v1/screenshot",
216
+ json=payload,
217
+ headers=self._get_headers(),
218
+ timeout=self.timeout
219
+ )
220
+ except requests.exceptions.Timeout:
221
+ raise GcrawlTimeoutError("Screenshot request timed out")
222
+
223
+ if response.status_code != 200:
224
+ raise GcrawlAPIError(response.status_code, response.text)
225
+
226
+ init_result = GcrawlTaskInitResult(response.json())
227
+
228
+ if wait and init_result.crawl_id:
229
+ return self._poll_task_results(init_result.crawl_id, poll_interval, timeout)
230
+
231
+ return init_result
232
+
233
+ def links(self, url, options=None, limit=10, same_domain_only=True, include_subdomains=True, geo="default",
234
+ wait=False, poll_interval=2, timeout=300):
235
+ if options is not None:
236
+ if hasattr(options, "to_dict"):
237
+ payload = options.to_dict()
238
+ elif isinstance(options, dict):
239
+ payload = options.copy()
240
+ else:
241
+ raise GcrawlValidationError("options must be a LinksOptions instance or a dict")
242
+ else:
243
+ payload = {
244
+ "links": {
245
+ "limit": limit,
246
+ "same_domain_only": same_domain_only,
247
+ "include_subdomains": include_subdomains
248
+ }
249
+ }
250
+ if geo:
251
+ payload["proxy"] = {"geo": geo}
252
+
253
+ payload["url"] = url
254
+
255
+ try:
256
+ response = requests.post(
257
+ f"{self.base_url}/api/v1/links",
258
+ json=payload,
259
+ headers=self._get_headers(),
260
+ timeout=self.timeout
261
+ )
262
+ except requests.exceptions.Timeout:
263
+ raise GcrawlTimeoutError("Links request timed out")
264
+
265
+ if response.status_code != 200:
266
+ raise GcrawlAPIError(response.status_code, response.text)
267
+
268
+ init_result = GcrawlTaskInitResult(response.json())
269
+
270
+ if wait and init_result.crawl_id:
271
+ return self._poll_task_results(init_result.crawl_id, poll_interval, timeout)
272
+
273
+ return init_result
274
+
275
+ def search(self, query, options=None, limit=10, geo="IN"):
276
+ if options is not None:
277
+ if hasattr(options, "to_dict"):
278
+ payload = options.to_dict()
279
+ elif isinstance(options, dict):
280
+ payload = options.copy()
281
+ else:
282
+ raise GcrawlValidationError("options must be a SearchOptions instance or a dict")
283
+ else:
284
+ payload = {
285
+ "limit": limit,
286
+ "geo": geo
287
+ }
288
+
289
+ payload["query"] = query
290
+
291
+ try:
292
+ response = requests.post(
293
+ f"{self.base_url}/api/v1/search",
294
+ json=payload,
295
+ headers=self._get_headers(),
296
+ timeout=self.timeout
297
+ )
298
+ except requests.exceptions.Timeout:
299
+ raise GcrawlTimeoutError("Search request timed out")
300
+
301
+ if response.status_code != 200:
302
+ raise GcrawlAPIError(response.status_code, response.text)
303
+
304
+ return SearchResult(response.json())
305
+
306
+ def get_results(self, crawl_id):
307
+ headers = {
308
+ "accept": "application/json",
309
+ "X-API-Key": self.api_key
310
+ }
311
+
312
+ try:
313
+ response = requests.get(
314
+ f"{self.base_url}/crawler/results/{crawl_id}",
315
+ headers=headers,
316
+ timeout=self.timeout
317
+ )
318
+ except requests.exceptions.Timeout:
319
+ raise GcrawlTimeoutError("Get results request timed out")
320
+
321
+ if response.status_code != 200:
322
+ raise GcrawlAPIError(response.status_code, response.text)
323
+
324
+ return GcrawlTaskResult(response.json())
325
+
326
+ def _poll_task_results(self, crawl_id, poll_interval=2, timeout=600):
327
+ start_time = time.time()
328
+ while True:
329
+ result = self.get_results(crawl_id)
330
+ if result.status == "completed" or result.status == "failed" or result.is_completed or result.is_failed:
331
+ return result
332
+
333
+ if time.time() - start_time > timeout:
334
+ raise GcrawlTimeoutError(f"Polling task {crawl_id} timed out after {timeout} seconds")
335
+
336
+ time.sleep(poll_interval)
@@ -0,0 +1,94 @@
1
+ class CrawlOptions:
2
+ def __init__(self,
3
+ max_pages=10,
4
+ same_domain_only=True,
5
+ include_subdomains=True,
6
+ proxy_geo="default",
7
+ markdown_enabled=True,
8
+ markdown_clean=False,
9
+ html_enabled=False,
10
+ html_clean=False,
11
+ remove_external_links=False,
12
+ relative_to_absolute_links=True,
13
+ remove_data_images=False,
14
+ ignore_tags=[],
15
+ screenshot_enabled=False,
16
+ screenshot_full_page=False,
17
+ screenshot_format="png",
18
+ screenshot_quality=90,
19
+ js_render=False,
20
+ render_timeout=30000,
21
+ auto_scroll=True,
22
+ scroll_delay=500,
23
+ max_scrolls=2,
24
+ seo_enabled=False,
25
+ images_enabled=False):
26
+ self.max_pages = max_pages
27
+ self.same_domain_only = same_domain_only
28
+ self.include_subdomains = include_subdomains
29
+ self.proxy_geo = proxy_geo
30
+ self.markdown_enabled = markdown_enabled
31
+ self.markdown_clean = markdown_clean
32
+ self.html_enabled = html_enabled
33
+ self.html_clean = html_clean
34
+ self.remove_external_links = remove_external_links
35
+ self.relative_to_absolute_links = relative_to_absolute_links
36
+ self.remove_data_images = remove_data_images
37
+ self.ignore_tags = ignore_tags or []
38
+ self.screenshot_enabled = screenshot_enabled
39
+ self.screenshot_full_page = screenshot_full_page
40
+ self.screenshot_format = screenshot_format
41
+ self.screenshot_quality = screenshot_quality
42
+ self.js_render = js_render
43
+ self.render_timeout = render_timeout
44
+ self.auto_scroll = auto_scroll
45
+ self.scroll_delay = scroll_delay
46
+ self.max_scrolls = max_scrolls
47
+ self.seo_enabled = seo_enabled
48
+ self.images_enabled = images_enabled
49
+
50
+ def to_dict(self):
51
+ payload = {}
52
+ payload["crawl"] = {
53
+ "max_pages": self.max_pages,
54
+ "same_domain_only": self.same_domain_only,
55
+ "include_subdomains": self.include_subdomains
56
+ }
57
+
58
+ if self.proxy_geo:
59
+ payload["proxy"] = {"geo": self.proxy_geo}
60
+
61
+ payload["markdown"] = {
62
+ "enabled": self.markdown_enabled,
63
+ "clean": self.markdown_clean
64
+ }
65
+
66
+ payload["html"] = {
67
+ "enabled": self.html_enabled,
68
+ "clean": self.html_clean,
69
+ "remove_external_links": self.remove_external_links,
70
+ "relative_to_absolute_links": self.relative_to_absolute_links,
71
+ "remove_data_images": self.remove_data_images,
72
+ "ignore_tags": self.ignore_tags
73
+ }
74
+
75
+ payload["screenshot"] = {
76
+ "enabled": self.screenshot_enabled,
77
+ "full_page": self.screenshot_full_page,
78
+ "format": self.screenshot_format,
79
+ "quality": self.screenshot_quality,
80
+ "js_render": self.js_render,
81
+ "render_timeout": self.render_timeout,
82
+ "auto_scroll": self.auto_scroll,
83
+ "scroll_delay": self.scroll_delay,
84
+ "max_scrolls": self.max_scrolls
85
+ }
86
+
87
+ payload["seo"] = {
88
+ "enabled": self.seo_enabled
89
+ }
90
+
91
+ payload["images"] = {
92
+ "enabled": self.images_enabled
93
+ }
94
+ return payload
@@ -0,0 +1,14 @@
1
+ class GcrawlAPIError(Exception):
2
+ def __init__(self, status_code, response_data):
3
+ self.status_code = status_code
4
+ self.response_data = response_data
5
+ super().__init__(f"GCrawl API error {status_code}: {response_data}")
6
+
7
+ class GcrawlTimeoutError(Exception):
8
+ pass
9
+
10
+ class GcrawlFileError(Exception):
11
+ pass
12
+
13
+ class GcrawlValidationError(Exception):
14
+ pass
@@ -0,0 +1,22 @@
1
+ class LinksOptions:
2
+ def __init__(self,
3
+ limit=10,
4
+ same_domain_only=True,
5
+ include_subdomains=True,
6
+ proxy_geo="default"):
7
+ self.limit = limit
8
+ self.same_domain_only = same_domain_only
9
+ self.include_subdomains = include_subdomains
10
+ self.proxy_geo = proxy_geo
11
+
12
+ def to_dict(self):
13
+ payload = {
14
+ "links": {
15
+ "limit": self.limit,
16
+ "same_domain_only": self.same_domain_only,
17
+ "include_subdomains": self.include_subdomains
18
+ }
19
+ }
20
+ if self.proxy_geo:
21
+ payload["proxy"] = {"geo": self.proxy_geo}
22
+ return payload
@@ -0,0 +1,82 @@
1
+ class ScrapeOptions:
2
+ def __init__(self,
3
+ proxy_geo="default",
4
+ markdown_enabled=True,
5
+ markdown_clean=False,
6
+ html_enabled=False,
7
+ html_clean=False,
8
+ remove_external_links=False,
9
+ relative_to_absolute_links=True,
10
+ remove_data_images=False,
11
+ ignore_tags=[],
12
+ screenshot_enabled=False,
13
+ screenshot_full_page=True,
14
+ screenshot_format="png",
15
+ screenshot_quality=90,
16
+ js_render=False,
17
+ render_timeout=30000,
18
+ auto_scroll=True,
19
+ scroll_delay=1500,
20
+ max_scrolls=5,
21
+ seo_enabled=True,
22
+ images_enabled=True):
23
+ self.proxy_geo = proxy_geo
24
+ self.markdown_enabled = markdown_enabled
25
+ self.markdown_clean = markdown_clean
26
+ self.html_enabled = html_enabled
27
+ self.html_clean = html_clean
28
+ self.remove_external_links = remove_external_links
29
+ self.relative_to_absolute_links = relative_to_absolute_links
30
+ self.remove_data_images = remove_data_images
31
+ self.ignore_tags = ignore_tags or []
32
+ self.screenshot_enabled = screenshot_enabled
33
+ self.screenshot_full_page = screenshot_full_page
34
+ self.screenshot_format = screenshot_format
35
+ self.screenshot_quality = screenshot_quality
36
+ self.js_render = js_render
37
+ self.render_timeout = render_timeout
38
+ self.auto_scroll = auto_scroll
39
+ self.scroll_delay = scroll_delay
40
+ self.max_scrolls = max_scrolls
41
+ self.seo_enabled = seo_enabled
42
+ self.images_enabled = images_enabled
43
+
44
+ def to_dict(self):
45
+ payload = {}
46
+ if self.proxy_geo:
47
+ payload["proxy"] = {"geo": self.proxy_geo}
48
+
49
+ payload["markdown"] = {
50
+ "enabled": self.markdown_enabled,
51
+ "clean": self.markdown_clean
52
+ }
53
+
54
+ payload["html"] = {
55
+ "enabled": self.html_enabled,
56
+ "clean": self.html_clean,
57
+ "remove_external_links": self.remove_external_links,
58
+ "relative_to_absolute_links": self.relative_to_absolute_links,
59
+ "remove_data_images": self.remove_data_images,
60
+ "ignore_tags": self.ignore_tags
61
+ }
62
+
63
+ payload["screenshot"] = {
64
+ "enabled": self.screenshot_enabled,
65
+ "full_page": self.screenshot_full_page,
66
+ "format": self.screenshot_format,
67
+ "quality": self.screenshot_quality,
68
+ "js_render": self.js_render,
69
+ "render_timeout": self.render_timeout,
70
+ "auto_scroll": self.auto_scroll,
71
+ "scroll_delay": self.scroll_delay,
72
+ "max_scrolls": self.max_scrolls
73
+ }
74
+
75
+ payload["seo"] = {
76
+ "enabled": self.seo_enabled
77
+ }
78
+
79
+ payload["images"] = {
80
+ "enabled": self.images_enabled
81
+ }
82
+ return payload
@@ -0,0 +1,40 @@
1
+ class ScreenshotOptions:
2
+ def __init__(self,
3
+ enabled=True,
4
+ full_page=True,
5
+ format="png",
6
+ quality=90,
7
+ js_render=True,
8
+ render_timeout=30000,
9
+ auto_scroll=True,
10
+ scroll_delay=500,
11
+ max_scrolls=2,
12
+ proxy_geo="default"):
13
+ self.enabled = enabled
14
+ self.full_page = full_page
15
+ self.format = format
16
+ self.quality = quality
17
+ self.js_render = js_render
18
+ self.render_timeout = render_timeout
19
+ self.auto_scroll = auto_scroll
20
+ self.scroll_delay = scroll_delay
21
+ self.max_scrolls = max_scrolls
22
+ self.proxy_geo = proxy_geo
23
+
24
+ def to_dict(self):
25
+ payload = {
26
+ "screenshot": {
27
+ "enabled": self.enabled,
28
+ "full_page": self.full_page,
29
+ "format": self.format,
30
+ "quality": self.quality,
31
+ "js_render": self.js_render,
32
+ "render_timeout": self.render_timeout,
33
+ "auto_scroll": self.auto_scroll,
34
+ "scroll_delay": self.scroll_delay,
35
+ "max_scrolls": self.max_scrolls
36
+ }
37
+ }
38
+ if self.proxy_geo:
39
+ payload["proxy"] = {"geo": self.proxy_geo}
40
+ return payload
@@ -0,0 +1,10 @@
1
+ class SearchOptions:
2
+ def __init__(self, limit=20, geo="IN"):
3
+ self.limit = limit
4
+ self.geo = geo
5
+
6
+ def to_dict(self):
7
+ return {
8
+ "limit": self.limit,
9
+ "geo": self.geo
10
+ }
@@ -0,0 +1,17 @@
1
+ class SearchItem:
2
+ def __init__(self, data):
3
+ self.position = data.get("position")
4
+ self.url = data.get("url")
5
+ self.title = data.get("title")
6
+ self.description = data.get("description")
7
+ self.raw_data = data
8
+
9
+ class SearchResult:
10
+ def __init__(self, data):
11
+ self.query = data.get("query")
12
+ self.limit = data.get("limit")
13
+ self.count = data.get("count")
14
+
15
+ results_list = data.get("results", [])
16
+ self.results = [SearchItem(item) for item in results_list]
17
+ self.raw_data = data
@@ -0,0 +1,17 @@
1
+ class GcrawlTaskInitResult:
2
+ def __init__(self, data):
3
+ self.status_code = data.get("status_code")
4
+ self.crawl_id = data.get("crawl_id")
5
+ self.url = data.get("url")
6
+ self.crawl_mode = data.get("crawl_mode")
7
+ self.created_at = data.get("created_at")
8
+ self.task_id = data.get("task_id")
9
+ self.seo = data.get("SEO", False)
10
+ self.html = data.get("HTML", False)
11
+ self.screenshot = data.get("Screenshot", False)
12
+ self.markdown = data.get("Markdown", False)
13
+ self.images = data.get("Images", False)
14
+ self.status = data.get("status")
15
+ self.user_id = data.get("user_id")
16
+ self.task_url = data.get("task_url")
17
+ self.raw_data = data
@@ -0,0 +1,98 @@
1
+ class GcrawlPageResult:
2
+ def __init__(self, data):
3
+ self.raw_data = data
4
+ self.url = data.get("start_url") or data.get("url")
5
+ self.markdown = data.get("markdown_content") or data.get("markdown") or data.get("seo_md")
6
+ self.html = data.get("html_content") or data.get("html")
7
+ self.screenshot_url = data.get("screenshot_s3_url") or data.get("screenshot") or data.get("screenshot_url")
8
+ self.crawl_mode = data.get("crawl_mode")
9
+ self.started_at = data.get("started_at")
10
+ self.time_taken = data.get("time_taken")
11
+ self.links = data.get("links")
12
+ self.seo_json = data.get("seo_json")
13
+ self.seo_md = data.get("seo_md")
14
+ self.images_json = data.get("images_json")
15
+ self.seo_xlsx_url = data.get("seo_xlsx_s3_url")
16
+
17
+ class GcrawlTaskResult:
18
+ def __init__(self, data):
19
+ self.status = data.get("status")
20
+ self.crawl_id = data.get("crawl_id") or data.get("job_id")
21
+ self.url = data.get("url")
22
+ self.raw_data = data
23
+
24
+ @property
25
+ def is_completed(self):
26
+ return self.status in ["completed", "success"]
27
+
28
+ @property
29
+ def is_failed(self):
30
+ return self.status == "failed"
31
+
32
+ def _get_field_from_data(self, keys):
33
+ # 1. Search root level
34
+ for key in keys:
35
+ if key in self.raw_data:
36
+ return self.raw_data[key]
37
+
38
+ # 2. Search inside data (which could be a dict or a list of dicts)
39
+ data_field = self.raw_data.get("data")
40
+ if isinstance(data_field, dict):
41
+ for key in keys:
42
+ if key in data_field:
43
+ return data_field[key]
44
+ elif isinstance(data_field, list):
45
+ for item in data_field:
46
+ if isinstance(item, dict):
47
+ for key in keys:
48
+ if key in item:
49
+ return item[key]
50
+
51
+ # 3. Search inside results (list of dicts)
52
+ results = self.raw_data.get("results")
53
+ if isinstance(results, list):
54
+ for item in results:
55
+ if isinstance(item, dict):
56
+ for key in keys:
57
+ if key in item:
58
+ return item[key]
59
+ return None
60
+
61
+ @property
62
+ def markdown(self):
63
+ return self._get_field_from_data(["markdown", "markdown_content", "seo_md"])
64
+
65
+ @property
66
+ def html(self):
67
+ return self._get_field_from_data(["html", "html_content"])
68
+
69
+ @property
70
+ def screenshot_url(self):
71
+ return self._get_field_from_data(["screenshot_s3_url", "screenshot", "screenshot_url"])
72
+
73
+ @property
74
+ def links(self):
75
+ return self._get_field_from_data(["links"])
76
+
77
+ @property
78
+ def seo_json(self):
79
+ return self._get_field_from_data(["seo_json"])
80
+
81
+ @property
82
+ def seo_md(self):
83
+ return self._get_field_from_data(["seo_md"])
84
+
85
+ @property
86
+ def images_json(self):
87
+ return self._get_field_from_data(["images_json"])
88
+
89
+ @property
90
+ def seo_xlsx_url(self):
91
+ return self._get_field_from_data(["seo_xlsx_s3_url"])
92
+
93
+ @property
94
+ def pages(self):
95
+ data_field = self.raw_data.get("data")
96
+ if isinstance(data_field, list):
97
+ return [GcrawlPageResult(item) for item in data_field if isinstance(item, dict)]
98
+ return []
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcrawl-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for GCrawl — Scrape, crawl, screenshot, links, and search with ease
5
+ Author-email: Ramkumar <ramkumarlpm4@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Gramosoft
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/GramosoftAI/GCrawl
29
+ Keywords: crawl,scraper,screenshot,links,gcrawl,ai
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Requires-Python: >=3.10
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: requests>=2.28.0
37
+ Dynamic: license-file
38
+
39
+ # gcrawl-sdk
40
+
41
+ Official Python SDK for GCrawl — Scrape, crawl, screenshot, fetch links, and search web pages with ease.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install gcrawl-sdk
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from gcrawl_sdk import GcrawlClient, ScrapeOptions
53
+
54
+ client = GcrawlClient(api_key="your-api-key")
55
+
56
+ # Scrape a website (synchronous polling)
57
+ result = client.scrape(
58
+ url="https://gramosoft.tech",
59
+ options=ScrapeOptions(markdown_enabled=True, screenshot_enabled=True),
60
+ wait=True
61
+ )
62
+ print("Scraped Markdown:", result.markdown)
63
+ print("Screenshot URL:", result.screenshot_url)
64
+
65
+ # Search web synchronously
66
+ search_res = client.search("gramosoft", limit=5, geo="IN")
67
+ for res in search_res.results:
68
+ print(res.position, res.title, res.url)
69
+ ```
70
+
71
+ ## API Key
72
+
73
+ Set your API key as an environment variable:
74
+ ```bash
75
+ export GCRAWL_API_KEY="your-api-key"
76
+ ```
77
+
78
+ Or pass it directly to the client:
79
+ ```python
80
+ client = GcrawlClient(api_key="your-api-key")
81
+ ```
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ gcrawl_sdk/__init__.py
5
+ gcrawl_sdk/client.py
6
+ gcrawl_sdk/crawl_options.py
7
+ gcrawl_sdk/exceptions.py
8
+ gcrawl_sdk/links_options.py
9
+ gcrawl_sdk/scrape_options.py
10
+ gcrawl_sdk/screenshot_options.py
11
+ gcrawl_sdk/search_options.py
12
+ gcrawl_sdk/search_result.py
13
+ gcrawl_sdk/task_init_result.py
14
+ gcrawl_sdk/task_result.py
15
+ gcrawl_sdk.egg-info/PKG-INFO
16
+ gcrawl_sdk.egg-info/SOURCES.txt
17
+ gcrawl_sdk.egg-info/dependency_links.txt
18
+ gcrawl_sdk.egg-info/requires.txt
19
+ gcrawl_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.28.0
@@ -0,0 +1 @@
1
+ gcrawl_sdk
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gcrawl-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for GCrawl — Scrape, crawl, screenshot, links, and search with ease"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "Ramkumar", email = "ramkumarlpm4@gmail.com" }
13
+ ]
14
+ keywords = ["crawl", "scraper", "screenshot", "links", "gcrawl", "ai"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ requires-python = ">=3.10"
21
+ dependencies = [
22
+ "requests>=2.28.0"
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/GramosoftAI/GCrawl"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+