This commit is contained in:
@@ -2,7 +2,9 @@ from fastapi import FastAPI, HTTPException, Header
|
|||||||
from pyppeteer import launch
|
from pyppeteer import launch
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
import json
|
||||||
|
import re
|
||||||
|
from typing import Optional, Dict, List, Any
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
@@ -67,6 +69,179 @@ async def visit_url(url: str, x_api_key: Optional[str] = Header(None)):
|
|||||||
if browser:
|
if browser:
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|
||||||
|
@app.get("/seo")
|
||||||
|
async def extract_seo(url: str, x_api_key: Optional[str] = Header(None)):
|
||||||
|
"""Extract SEO information from a website"""
|
||||||
|
# Validate API key
|
||||||
|
if not x_api_key or x_api_key != API_KEY:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||||
|
|
||||||
|
browser = None
|
||||||
|
try:
|
||||||
|
# Decode URL if it's encoded
|
||||||
|
decoded_url = unquote(url)
|
||||||
|
print(f"Extracting SEO from: {decoded_url}")
|
||||||
|
|
||||||
|
# Launch browser using installed Chrome
|
||||||
|
browser = await launch(
|
||||||
|
headless=True,
|
||||||
|
executablePath='/usr/bin/google-chrome',
|
||||||
|
args=['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new page
|
||||||
|
page = await browser.newPage()
|
||||||
|
|
||||||
|
# Set custom user agent
|
||||||
|
await page.setUserAgent(CUSTOM_USER_AGENT)
|
||||||
|
|
||||||
|
# Navigate to URL and wait for network idle
|
||||||
|
await page.goto(decoded_url, waitUntil='networkidle2')
|
||||||
|
|
||||||
|
# Extract JSON-LD data
|
||||||
|
json_ld_data = await page.evaluate('''() => {
|
||||||
|
const jsonLdScripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]'));
|
||||||
|
return jsonLdScripts.map(script => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(script.textContent);
|
||||||
|
} catch (e) {
|
||||||
|
return { error: "Failed to parse JSON-LD", content: script.textContent };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Extract meta tags
|
||||||
|
meta_tags = await page.evaluate('''() => {
|
||||||
|
const metaTags = Array.from(document.querySelectorAll('meta'));
|
||||||
|
return metaTags.map(tag => {
|
||||||
|
const attributes = Array.from(tag.attributes);
|
||||||
|
const result = {};
|
||||||
|
attributes.forEach(attr => {
|
||||||
|
result[attr.name] = attr.value;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Extract title
|
||||||
|
title = await page.evaluate('document.title')
|
||||||
|
|
||||||
|
# Extract canonical URL
|
||||||
|
canonical_url = await page.evaluate('''() => {
|
||||||
|
const link = document.querySelector('link[rel="canonical"]');
|
||||||
|
return link ? link.href : null;
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Extract Open Graph data
|
||||||
|
og_data = await page.evaluate('''() => {
|
||||||
|
const ogTags = Array.from(document.querySelectorAll('meta[property^="og:"]'));
|
||||||
|
const result = {};
|
||||||
|
ogTags.forEach(tag => {
|
||||||
|
const property = tag.getAttribute('property');
|
||||||
|
const content = tag.getAttribute('content');
|
||||||
|
result[property] = content;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Extract Twitter Card data
|
||||||
|
twitter_data = await page.evaluate('''() => {
|
||||||
|
const twitterTags = Array.from(document.querySelectorAll('meta[name^="twitter:"]'));
|
||||||
|
const result = {};
|
||||||
|
twitterTags.forEach(tag => {
|
||||||
|
const name = tag.getAttribute('name');
|
||||||
|
const content = tag.getAttribute('content');
|
||||||
|
result[name] = content;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Close page
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
# Compile all SEO data
|
||||||
|
seo_data = {
|
||||||
|
"title": title,
|
||||||
|
"canonical_url": canonical_url,
|
||||||
|
"json_ld": json_ld_data,
|
||||||
|
"meta_tags": meta_tags,
|
||||||
|
"open_graph": og_data,
|
||||||
|
"twitter_card": twitter_data
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"status": "success", "seo_data": seo_data}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Ensure browser is closed even if an error occurs
|
||||||
|
if browser:
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
@app.get("/meta")
|
||||||
|
async def extract_meta_tags(url: str, x_api_key: Optional[str] = Header(None)):
|
||||||
|
"""Extract meta tags from a website"""
|
||||||
|
# Validate API key
|
||||||
|
if not x_api_key or x_api_key != API_KEY:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||||
|
|
||||||
|
browser = None
|
||||||
|
try:
|
||||||
|
# Decode URL if it's encoded
|
||||||
|
decoded_url = unquote(url)
|
||||||
|
print(f"Extracting meta tags from: {decoded_url}")
|
||||||
|
|
||||||
|
# Launch browser using installed Chrome
|
||||||
|
browser = await launch(
|
||||||
|
headless=True,
|
||||||
|
executablePath='/usr/bin/google-chrome',
|
||||||
|
args=['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new page
|
||||||
|
page = await browser.newPage()
|
||||||
|
|
||||||
|
# Set custom user agent
|
||||||
|
await page.setUserAgent(CUSTOM_USER_AGENT)
|
||||||
|
|
||||||
|
# Navigate to URL and wait for network idle
|
||||||
|
await page.goto(decoded_url, waitUntil='networkidle2')
|
||||||
|
|
||||||
|
# Extract meta tags
|
||||||
|
meta_tags = await page.evaluate('''() => {
|
||||||
|
const metaTags = Array.from(document.querySelectorAll('meta'));
|
||||||
|
return metaTags.map(tag => {
|
||||||
|
const attributes = Array.from(tag.attributes);
|
||||||
|
const result = {};
|
||||||
|
attributes.forEach(attr => {
|
||||||
|
result[attr.name] = attr.value;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}''')
|
||||||
|
|
||||||
|
# Extract title
|
||||||
|
title = await page.evaluate('document.title')
|
||||||
|
|
||||||
|
# Close page
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"url": decoded_url,
|
||||||
|
"title": title,
|
||||||
|
"meta_tags": meta_tags
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Ensure browser is closed even if an error occurs
|
||||||
|
if browser:
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|||||||
Reference in New Issue
Block a user