Spaces:
Sleeping
Sleeping
Guilherme Favaron Claude commited on
Commit ·
69cd712
1
Parent(s): ba9733f
Implement OAuth2 authentication for Google PageSpeed Insights API
Browse files- Added Google Service Account authentication with OAuth2
- Created comprehensive setup instructions in UI and README
- Implemented fallback to demo mode when not configured
- Added proper error handling and authentication validation
- Updated dependencies for Google auth libraries
- App now supports both real API data and demo mode seamlessly
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- README.md +25 -3
- google_auth.py +107 -0
- pagespeed_client.py +22 -24
- requirements.txt +4 -1
- ui.py +70 -15
README.md
CHANGED
|
@@ -21,9 +21,31 @@ Compare análises do PageSpeed Insights para monitorar melhorias em Core Web Vit
|
|
| 21 |
- 🤖 Análise inteligente com OpenAI GPT-o1
|
| 22 |
- 📈 Visualização de melhorias e regressões
|
| 23 |
- 💡 Recomendações priorizadas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
## Como usar
|
| 26 |
|
| 27 |
-
1.
|
| 28 |
-
2.
|
| 29 |
-
3.
|
|
|
|
| 21 |
- 🤖 Análise inteligente com OpenAI GPT-o1
|
| 22 |
- 📈 Visualização de melhorias e regressões
|
| 23 |
- 💡 Recomendações priorizadas
|
| 24 |
+
- 🔐 Autenticação OAuth2 com Google PageSpeed Insights API
|
| 25 |
+
- 🔄 Modo demo quando não configurado
|
| 26 |
+
|
| 27 |
+
## Configuração para Dados Reais
|
| 28 |
+
|
| 29 |
+
### 1. Criar Projeto Google Cloud
|
| 30 |
+
1. Acesse [Google Cloud Console](https://console.cloud.google.com/)
|
| 31 |
+
2. Crie um novo projeto
|
| 32 |
+
3. Ative a **PageSpeed Insights API**
|
| 33 |
+
|
| 34 |
+
### 2. Criar Service Account
|
| 35 |
+
1. Vá para **IAM & Admin** → **Service Accounts**
|
| 36 |
+
2. Clique em **Create Service Account**
|
| 37 |
+
3. Conceda papel **Viewer** ou **Editor**
|
| 38 |
+
4. Baixe a chave JSON
|
| 39 |
+
|
| 40 |
+
### 3. Configurar no Hugging Face Space
|
| 41 |
+
1. **Settings** → **Variables and secrets**
|
| 42 |
+
2. Adicione:
|
| 43 |
+
- `GOOGLE_SERVICE_ACCOUNT_JSON`: JSON completo da service account
|
| 44 |
+
- `OPENAI_API_KEY`: Sua chave OpenAI (opcional)
|
| 45 |
+
3. Reinicie o Space
|
| 46 |
|
| 47 |
## Como usar
|
| 48 |
|
| 49 |
+
1. Cole duas URLs de análises do PageSpeed Insights
|
| 50 |
+
2. Compare e receba análise detalhada
|
| 51 |
+
3. Se não configurado, funciona em modo demo
|
google_auth.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Google OAuth2 and Service Account authentication for PageSpeed Insights API."""
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from typing import Optional
|
| 5 |
+
from google.auth.transport.requests import Request
|
| 6 |
+
from google.oauth2 import service_account
|
| 7 |
+
import requests
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class GoogleAuthenticator:
|
| 11 |
+
"""Handles Google API authentication using service account credentials."""
|
| 12 |
+
|
| 13 |
+
def __init__(self):
|
| 14 |
+
self.credentials = None
|
| 15 |
+
self.setup_credentials()
|
| 16 |
+
|
| 17 |
+
def setup_credentials(self):
|
| 18 |
+
"""Setup Google service account credentials from environment variables."""
|
| 19 |
+
try:
|
| 20 |
+
# Try to get service account JSON from environment variable
|
| 21 |
+
service_account_json = os.environ.get('GOOGLE_SERVICE_ACCOUNT_JSON')
|
| 22 |
+
|
| 23 |
+
if service_account_json:
|
| 24 |
+
# Parse the JSON string
|
| 25 |
+
service_account_info = json.loads(service_account_json)
|
| 26 |
+
|
| 27 |
+
# Create credentials from service account info
|
| 28 |
+
self.credentials = service_account.Credentials.from_service_account_info(
|
| 29 |
+
service_account_info,
|
| 30 |
+
scopes=['https://www.googleapis.com/auth/cloud-platform']
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Refresh the credentials to get access token
|
| 34 |
+
self.credentials.refresh(Request())
|
| 35 |
+
|
| 36 |
+
else:
|
| 37 |
+
print("⚠️ GOOGLE_SERVICE_ACCOUNT_JSON não encontrado nas variáveis de ambiente")
|
| 38 |
+
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"❌ Erro ao configurar credenciais do Google: {str(e)}")
|
| 41 |
+
self.credentials = None
|
| 42 |
+
|
| 43 |
+
def get_access_token(self) -> Optional[str]:
|
| 44 |
+
"""Get a valid access token for Google APIs."""
|
| 45 |
+
if not self.credentials:
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
# Refresh token if needed
|
| 50 |
+
if not self.credentials.valid:
|
| 51 |
+
self.credentials.refresh(Request())
|
| 52 |
+
|
| 53 |
+
return self.credentials.token
|
| 54 |
+
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"❌ Erro ao obter token de acesso: {str(e)}")
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
def is_authenticated(self) -> bool:
|
| 60 |
+
"""Check if we have valid authentication."""
|
| 61 |
+
return self.credentials is not None and self.credentials.valid
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class PageSpeedAPI:
|
| 65 |
+
"""PageSpeed Insights API client with OAuth2 authentication."""
|
| 66 |
+
|
| 67 |
+
def __init__(self):
|
| 68 |
+
self.authenticator = GoogleAuthenticator()
|
| 69 |
+
self.base_url = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
|
| 70 |
+
|
| 71 |
+
def analyze_url(self, url: str, strategy: str = 'mobile') -> Optional[dict]:
|
| 72 |
+
"""Analyze a URL using PageSpeed Insights API with OAuth2."""
|
| 73 |
+
if not self.authenticator.is_authenticated():
|
| 74 |
+
raise ValueError("Google authentication not configured. Please set up service account credentials.")
|
| 75 |
+
|
| 76 |
+
access_token = self.authenticator.get_access_token()
|
| 77 |
+
if not access_token:
|
| 78 |
+
raise ValueError("Could not obtain valid access token")
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
headers = {
|
| 82 |
+
'Authorization': f'Bearer {access_token}',
|
| 83 |
+
'Content-Type': 'application/json'
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
params = {
|
| 87 |
+
'url': url,
|
| 88 |
+
'strategy': strategy,
|
| 89 |
+
'category': ['performance', 'accessibility', 'best-practices', 'seo']
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
response = requests.get(
|
| 93 |
+
self.base_url,
|
| 94 |
+
headers=headers,
|
| 95 |
+
params=params,
|
| 96 |
+
timeout=60
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
response.raise_for_status()
|
| 100 |
+
return response.json()
|
| 101 |
+
|
| 102 |
+
except requests.exceptions.RequestException as e:
|
| 103 |
+
raise Exception(f"Error calling PageSpeed API: {str(e)}")
|
| 104 |
+
|
| 105 |
+
def is_available(self) -> bool:
|
| 106 |
+
"""Check if the PageSpeed API is available with current authentication."""
|
| 107 |
+
return self.authenticator.is_authenticated()
|
pagespeed_client.py
CHANGED
|
@@ -1,22 +1,18 @@
|
|
| 1 |
-
"""Client for PageSpeed Insights
|
| 2 |
import requests
|
| 3 |
import json
|
| 4 |
-
import
|
| 5 |
-
from urllib.parse import urlparse, parse_qs, quote
|
| 6 |
from typing import Optional
|
| 7 |
-
from bs4 import BeautifulSoup
|
| 8 |
|
| 9 |
from models import PageSpeedData
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
class PageSpeedClient:
|
| 13 |
-
"""Client for
|
| 14 |
def __init__(self):
|
| 15 |
-
self.
|
| 16 |
-
self.
|
| 17 |
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
| 18 |
-
})
|
| 19 |
-
# No API key validation needed for web scraping
|
| 20 |
|
| 21 |
def extract_pagespeed_data(self, report_url: str) -> Optional[PageSpeedData]:
|
| 22 |
"""Extract PageSpeed data from a PageSpeed Insights report URL."""
|
|
@@ -25,7 +21,13 @@ class PageSpeedClient:
|
|
| 25 |
|
| 26 |
try:
|
| 27 |
site_url, strategy = self._parse_report_url(report_url)
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
if not lighthouse_data:
|
| 31 |
raise ValueError("Dados do Lighthouse não encontrados")
|
|
@@ -54,21 +56,17 @@ class PageSpeedClient:
|
|
| 54 |
|
| 55 |
return site_url, strategy
|
| 56 |
|
| 57 |
-
def
|
| 58 |
-
"""Get Lighthouse data
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
# 1. Extract data from the original report URL
|
| 63 |
-
# 2. Use a different API service
|
| 64 |
-
# 3. Or implement proper OAuth2 authentication
|
| 65 |
-
|
| 66 |
-
# Create realistic mock data for demonstration
|
| 67 |
-
mock_data = self._create_mock_lighthouse_data(site_url, strategy)
|
| 68 |
-
return mock_data
|
| 69 |
-
|
| 70 |
except Exception as e:
|
| 71 |
-
raise Exception(f"Erro ao obter dados do PageSpeed: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
def _create_mock_lighthouse_data(self, site_url: str, strategy: str) -> dict:
|
| 74 |
"""Create mock Lighthouse data for demonstration purposes."""
|
|
|
|
| 1 |
+
"""Client for PageSpeed Insights API with OAuth2 authentication."""
|
| 2 |
import requests
|
| 3 |
import json
|
| 4 |
+
from urllib.parse import urlparse, parse_qs
|
|
|
|
| 5 |
from typing import Optional
|
|
|
|
| 6 |
|
| 7 |
from models import PageSpeedData
|
| 8 |
+
from google_auth import PageSpeedAPI
|
| 9 |
|
| 10 |
|
| 11 |
class PageSpeedClient:
|
| 12 |
+
"""Client for PageSpeed Insights API with OAuth2 authentication."""
|
| 13 |
def __init__(self):
|
| 14 |
+
self.api_client = PageSpeedAPI()
|
| 15 |
+
self.use_mock_data = not self.api_client.is_available()
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def extract_pagespeed_data(self, report_url: str) -> Optional[PageSpeedData]:
|
| 18 |
"""Extract PageSpeed data from a PageSpeed Insights report URL."""
|
|
|
|
| 21 |
|
| 22 |
try:
|
| 23 |
site_url, strategy = self._parse_report_url(report_url)
|
| 24 |
+
|
| 25 |
+
if self.use_mock_data:
|
| 26 |
+
# Use mock data if OAuth2 not configured
|
| 27 |
+
lighthouse_data = self._create_mock_lighthouse_data(site_url, strategy)
|
| 28 |
+
else:
|
| 29 |
+
# Use real API with OAuth2
|
| 30 |
+
lighthouse_data = self._get_real_lighthouse_data(site_url, strategy)
|
| 31 |
|
| 32 |
if not lighthouse_data:
|
| 33 |
raise ValueError("Dados do Lighthouse não encontrados")
|
|
|
|
| 56 |
|
| 57 |
return site_url, strategy
|
| 58 |
|
| 59 |
+
def _get_real_lighthouse_data(self, site_url: str, strategy: str) -> Optional[dict]:
|
| 60 |
+
"""Get real Lighthouse data using OAuth2 authenticated API."""
|
| 61 |
try:
|
| 62 |
+
response = self.api_client.analyze_url(site_url, strategy)
|
| 63 |
+
return response.get('lighthouseResult')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
except Exception as e:
|
| 65 |
+
raise Exception(f"Erro ao obter dados do PageSpeed API: {str(e)}")
|
| 66 |
+
|
| 67 |
+
def is_using_real_data(self) -> bool:
|
| 68 |
+
"""Check if using real API data or mock data."""
|
| 69 |
+
return not self.use_mock_data
|
| 70 |
|
| 71 |
def _create_mock_lighthouse_data(self, site_url: str, strategy: str) -> dict:
|
| 72 |
"""Create mock Lighthouse data for demonstration purposes."""
|
requirements.txt
CHANGED
|
@@ -3,4 +3,7 @@ requests>=2.31.0
|
|
| 3 |
beautifulsoup4>=4.12.0
|
| 4 |
pandas>=2.0.0
|
| 5 |
openai>=1.12.0
|
| 6 |
-
lxml>=4.9.0
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
beautifulsoup4>=4.12.0
|
| 4 |
pandas>=2.0.0
|
| 5 |
openai>=1.12.0
|
| 6 |
+
lxml>=4.9.0
|
| 7 |
+
google-auth>=2.0.0
|
| 8 |
+
google-auth-oauthlib>=1.0.0
|
| 9 |
+
google-auth-httplib2>=0.2.0
|
ui.py
CHANGED
|
@@ -44,22 +44,41 @@ class PageSpeedUI:
|
|
| 44 |
""")
|
| 45 |
return demo
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
with gr.Row():
|
| 65 |
with gr.Column():
|
|
@@ -104,6 +123,42 @@ class PageSpeedUI:
|
|
| 104 |
inputs=[url1_input, url2_input],
|
| 105 |
outputs=[summary_output, table_output, ai_output]
|
| 106 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
return demo
|
| 109 |
|
|
|
|
| 44 |
""")
|
| 45 |
return demo
|
| 46 |
|
| 47 |
+
# Check if we're using real data or mock data
|
| 48 |
+
using_real_data = self.client and self.client.is_using_real_data()
|
| 49 |
|
| 50 |
+
if using_real_data:
|
| 51 |
+
gr.Markdown("""
|
| 52 |
+
# 🚀 PageSpeed Insights Comparator
|
| 53 |
+
|
| 54 |
+
Compare duas análises do PageSpeed Insights para monitorar melhorias em Core Web Vitals.
|
| 55 |
+
|
| 56 |
+
**✅ API REAL:** Conectado com Google PageSpeed Insights API usando OAuth2.
|
| 57 |
+
|
| 58 |
+
**Como usar:**
|
| 59 |
+
1. Cole a URL da primeira análise (baseline)
|
| 60 |
+
2. Cole a URL da segunda análise (atual)
|
| 61 |
+
3. Clique em "Comparar Análises"
|
| 62 |
+
|
| 63 |
+
**Exemplo de URL:** `https://pagespeed.web.dev/analysis/https-www-example-com/abc123?form_factor=mobile`
|
| 64 |
+
""")
|
| 65 |
+
else:
|
| 66 |
+
gr.Markdown("""
|
| 67 |
+
# 🚀 PageSpeed Insights Comparator
|
| 68 |
+
|
| 69 |
+
Compare duas análises do PageSpeed Insights para monitorar melhorias em Core Web Vitals.
|
| 70 |
+
|
| 71 |
+
**⚠️ DEMO MODE:** Usando dados simulados. Para dados reais, configure Google Service Account.
|
| 72 |
+
|
| 73 |
+
**Como usar:**
|
| 74 |
+
1. Cole a URL da primeira análise (baseline)
|
| 75 |
+
2. Cole a URL da segunda análise (atual)
|
| 76 |
+
3. Clique em "Comparar Análises"
|
| 77 |
+
|
| 78 |
+
**Exemplo de URL:** `https://pagespeed.web.dev/analysis/https-www-example-com/abc123?form_factor=mobile`
|
| 79 |
+
|
| 80 |
+
**Configuração para dados reais:** Veja as instruções abaixo para configurar Google Service Account.
|
| 81 |
+
""")
|
| 82 |
|
| 83 |
with gr.Row():
|
| 84 |
with gr.Column():
|
|
|
|
| 123 |
inputs=[url1_input, url2_input],
|
| 124 |
outputs=[summary_output, table_output, ai_output]
|
| 125 |
)
|
| 126 |
+
|
| 127 |
+
# Add setup instructions if not using real data
|
| 128 |
+
if not using_real_data:
|
| 129 |
+
gr.Markdown("""
|
| 130 |
+
---
|
| 131 |
+
## 🔧 Configuração para Dados Reais
|
| 132 |
+
|
| 133 |
+
Para usar dados reais do PageSpeed Insights API, siga estes passos:
|
| 134 |
+
|
| 135 |
+
### 1. Criar um Projeto Google Cloud
|
| 136 |
+
1. Acesse [Google Cloud Console](https://console.cloud.google.com/)
|
| 137 |
+
2. Crie um novo projeto ou selecione um existente
|
| 138 |
+
3. Ative a **PageSpeed Insights API**
|
| 139 |
+
|
| 140 |
+
### 2. Criar Service Account
|
| 141 |
+
1. Vá para **IAM & Admin** → **Service Accounts**
|
| 142 |
+
2. Clique em **Create Service Account**
|
| 143 |
+
3. Dê um nome (ex: `pagespeed-analyzer`)
|
| 144 |
+
4. Conceda o papel **Viewer** ou **Editor**
|
| 145 |
+
5. Clique em **Create Key** → **JSON**
|
| 146 |
+
6. Baixe o arquivo JSON
|
| 147 |
+
|
| 148 |
+
### 3. Configurar no Hugging Face Space
|
| 149 |
+
1. Vá para **Settings** → **Variables and secrets**
|
| 150 |
+
2. Adicione um novo secret:
|
| 151 |
+
- **Name:** `GOOGLE_SERVICE_ACCOUNT_JSON`
|
| 152 |
+
- **Value:** Cole todo o conteúdo do arquivo JSON baixado
|
| 153 |
+
3. Reinicie o Space
|
| 154 |
+
|
| 155 |
+
### 4. APIs Necessárias
|
| 156 |
+
Certifique-se que estas APIs estão ativadas no seu projeto:
|
| 157 |
+
- PageSpeed Insights API
|
| 158 |
+
- Cloud Resource Manager API (opcional)
|
| 159 |
+
|
| 160 |
+
**Nota:** Pode levar alguns minutos para as mudanças fazerem efeito.
|
| 161 |
+
""")
|
| 162 |
|
| 163 |
return demo
|
| 164 |
|