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>

Files changed (5) hide show
  1. README.md +25 -3
  2. google_auth.py +107 -0
  3. pagespeed_client.py +22 -24
  4. requirements.txt +4 -1
  5. 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. Configure sua `OPENAI_API_KEY` nos Settings do Space
28
- 2. Cole duas URLs de análises do PageSpeed Insights
29
- 3. Compare e receba análise detalhada
 
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 web scraping."""
2
  import requests
3
  import json
4
- import re
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 scraping PageSpeed Insights web interface."""
14
  def __init__(self):
15
- self.session = requests.Session()
16
- self.session.headers.update({
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
- lighthouse_data = self._get_lighthouse_data(site_url, strategy)
 
 
 
 
 
 
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 _get_lighthouse_data(self, site_url: str, strategy: str) -> Optional[dict]:
58
- """Get Lighthouse data from an existing PageSpeed report URL."""
59
  try:
60
- # For this demo, we'll create mock data based on the URL and strategy
61
- # In a real implementation, you would need to:
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
- gr.Markdown("""
48
- # 🚀 PageSpeed Insights Comparator
49
 
50
- Compare duas análises do PageSpeed Insights para monitorar melhorias em Core Web Vitals.
51
-
52
- **⚠️ DEMO MODE:** Atualmente usando dados simulados para demonstração, pois a API do PageSpeed Insights agora requer autenticação OAuth2 complexa.
53
-
54
- **Como usar:**
55
- 1. Cole a URL da primeira análise (baseline)
56
- 2. Cole a URL da segunda análise (atual)
57
- 3. Clique em "Comparar Análises"
58
-
59
- **Exemplo de URL:** `https://pagespeed.web.dev/analysis/https-www-example-com/abc123?form_factor=mobile`
60
-
61
- **Nota:** Os dados mostrados são simulados mas realistas, demonstrando a funcionalidade da ferramenta.
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