Spaces:
Running
Running
prince1604 commited on
Commit ·
3176f9b
1
Parent(s): 3c07190
Improve URL normalization logic and add API docs
Browse files- API_DOCUMENTATION.md +136 -0
- requirements.txt +0 -0
- src/crawler.py +11 -3
API_DOCUMENTATION.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Antigravity SEO Scaler - API Documentation
|
| 2 |
+
|
| 3 |
+
Base URL: `http://localhost:7860` (or your deployed URL)
|
| 4 |
+
|
| 5 |
+
## 1. Start a New Scan
|
| 6 |
+
Initiates an asynchronous crawling and analysis job for a target domain.
|
| 7 |
+
|
| 8 |
+
- **Endpoint**: `/api/scanstart`
|
| 9 |
+
- **Method**: `POST`
|
| 10 |
+
- **Headers**: `Content-Type: application/json`
|
| 11 |
+
- **Body Parameters**:
|
| 12 |
+
- `domain` (string, required): The URL of the website to scan (e.g., "https://example.com").
|
| 13 |
+
- `limit` (integer, optional): Maximum number of pages to crawl. Default is `25`.
|
| 14 |
+
|
| 15 |
+
**Example Request (curl):**
|
| 16 |
+
```bash
|
| 17 |
+
curl -X POST "http://localhost:7860/api/scanstart" \
|
| 18 |
+
-H "Content-Type: application/json" \
|
| 19 |
+
-d '{"domain": "https://example.com", "limit": 50}'
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
**Example Response:**
|
| 23 |
+
```json
|
| 24 |
+
{
|
| 25 |
+
"job_id": "8e8c40c5-77e0-4ad6-906a-53d038cd9fe7"
|
| 26 |
+
}
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 2. Check Scan Progress
|
| 32 |
+
Retrieves the real-time status of a running job. You can use either a path parameter or a query parameter.
|
| 33 |
+
|
| 34 |
+
### Option A: Path Parameter
|
| 35 |
+
- **Endpoint**: `/api/progress/{job_id}`
|
| 36 |
+
- **Method**: `GET`
|
| 37 |
+
|
| 38 |
+
### Option B: Query Parameter
|
| 39 |
+
- **Endpoint**: `/api/progress`
|
| 40 |
+
- **Method**: `GET`
|
| 41 |
+
- **Query Parameters**:
|
| 42 |
+
- `job_id` (string, required): The ID returned by the start endpoint.
|
| 43 |
+
|
| 44 |
+
**Example Request:**
|
| 45 |
+
```bash
|
| 46 |
+
curl "http://localhost:7860/api/progress/8e8c40c5-77e0-4ad6-906a-53d038cd9fe7"
|
| 47 |
+
# OR
|
| 48 |
+
curl "http://localhost:7860/api/progress?job_id=8e8c40c5-77e0-4ad6-906a-53d038cd9fe7"
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**Example Response:**
|
| 52 |
+
```json
|
| 53 |
+
{
|
| 54 |
+
"status": "running",
|
| 55 |
+
"percent": 45,
|
| 56 |
+
"pages_scanned": 12,
|
| 57 |
+
"images_found": 86,
|
| 58 |
+
"message": "Scanning: https://example.com/about",
|
| 59 |
+
"elapsed_seconds": 15,
|
| 60 |
+
"eta_seconds": 20,
|
| 61 |
+
"error": null
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
*Possible Statuses*: `pending`, `running`, `done`, `error`
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## 3. Get Scan Results
|
| 69 |
+
Retrieves the final detailed JSON report. This should be called when the progress status is `done`.
|
| 70 |
+
|
| 71 |
+
### Option A: Path Parameter
|
| 72 |
+
- **Endpoint**: `/api/result/{job_id}`
|
| 73 |
+
- **Method**: `GET`
|
| 74 |
+
|
| 75 |
+
### Option B: Query Parameter
|
| 76 |
+
- **Endpoint**: `/api/result`
|
| 77 |
+
- **Method**: `GET`
|
| 78 |
+
- **Query Parameters**:
|
| 79 |
+
- `job_id` (string, required): The ID of the completed job.
|
| 80 |
+
|
| 81 |
+
**Example Request:**
|
| 82 |
+
```bash
|
| 83 |
+
curl "http://localhost:7860/api/result/8e8c40c5-77e0-4ad6-906a-53d038cd9fe7"
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**Example Response:**
|
| 87 |
+
```json
|
| 88 |
+
{
|
| 89 |
+
"summary": {
|
| 90 |
+
"total_pages_scanned": 50,
|
| 91 |
+
"total_images_found": 320,
|
| 92 |
+
"total_images_missing_alt": 45,
|
| 93 |
+
"blocked_reason": null,
|
| 94 |
+
"crawl_blocked": false
|
| 95 |
+
},
|
| 96 |
+
"details": [
|
| 97 |
+
{
|
| 98 |
+
"page_url": "https://example.com",
|
| 99 |
+
"images": [ ... ]
|
| 100 |
+
}
|
| 101 |
+
]
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## 4. System Status
|
| 108 |
+
Checks the health and resource usage of the underlying server.
|
| 109 |
+
|
| 110 |
+
- **Endpoint**: `/api/status`
|
| 111 |
+
- **Method**: `GET`
|
| 112 |
+
- **Query Parameters**:
|
| 113 |
+
- `domain` (string, optional): A target URL to check reachability for.
|
| 114 |
+
|
| 115 |
+
**Example Response:**
|
| 116 |
+
```json
|
| 117 |
+
{
|
| 118 |
+
"cpu_percent": 12.5,
|
| 119 |
+
"memory_percent": 45.2,
|
| 120 |
+
"disk_usage": 60.1,
|
| 121 |
+
"target_reachable": true
|
| 122 |
+
}
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## 5. Health Check
|
| 128 |
+
Simple endpoint for load balancers or uptime monitors.
|
| 129 |
+
|
| 130 |
+
- **Endpoint**: `/health`
|
| 131 |
+
- **Method**: `GET`
|
| 132 |
+
|
| 133 |
+
**Example Response:**
|
| 134 |
+
```json
|
| 135 |
+
{ "status": "alive" }
|
| 136 |
+
```
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
src/crawler.py
CHANGED
|
@@ -406,10 +406,17 @@ class Crawler:
|
|
| 406 |
for href in raw_links:
|
| 407 |
full_url = urljoin(url, href)
|
| 408 |
parsed_url = urlparse(full_url)
|
| 409 |
-
|
|
|
|
|
|
|
| 410 |
link_domain = parsed_url.netloc
|
| 411 |
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
if is_internal:
|
| 415 |
path = parsed_url.path.lower()
|
|
@@ -419,7 +426,8 @@ class Crawler:
|
|
| 419 |
links_stats["skipped"] += 1
|
| 420 |
continue
|
| 421 |
|
| 422 |
-
|
|
|
|
| 423 |
if any(k in full_url.lower() for k in exclude_keywords):
|
| 424 |
links_stats["skipped"] += 1
|
| 425 |
continue
|
|
|
|
| 406 |
for href in raw_links:
|
| 407 |
full_url = urljoin(url, href)
|
| 408 |
parsed_url = urlparse(full_url)
|
| 409 |
+
# Normalize: Remove fragment, strip trailing slash to avoid duplicates (/about vs /about/)
|
| 410 |
+
full_url = parsed_url._replace(fragment="").geturl().rstrip('/')
|
| 411 |
+
|
| 412 |
link_domain = parsed_url.netloc
|
| 413 |
|
| 414 |
+
# Internal Check: Match base domain (handles www/non-www and subdomains)
|
| 415 |
+
is_internal = (
|
| 416 |
+
link_domain == start_domain or
|
| 417 |
+
link_domain.endswith('.' + base_domain) or
|
| 418 |
+
link_domain == base_domain
|
| 419 |
+
)
|
| 420 |
|
| 421 |
if is_internal:
|
| 422 |
path = parsed_url.path.lower()
|
|
|
|
| 426 |
links_stats["skipped"] += 1
|
| 427 |
continue
|
| 428 |
|
| 429 |
+
# Filter out non-content paths
|
| 430 |
+
exclude_keywords = ['/account', '/login', '/signin', '/signup', '/cart', '/checkout', '/wishlist', '/auth', 'javascript:', 'mailto:', 'tel:']
|
| 431 |
if any(k in full_url.lower() for k in exclude_keywords):
|
| 432 |
links_stats["skipped"] += 1
|
| 433 |
continue
|