
    "mj8                     N   d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
 ddlmZmZmZ ddlZddlmZ dZdZdZd	Zd
ZdZdZdZej4                  j7                  d      Zg dZdZg dZddZ d Z!d Z"d Z#d Z$d Z%d dZ&d Z'd Z(d Z)d Z*d Z+d Z,e-dk(  r e,        yy)!a9  
sitereview - revisao final de sites.

Faz o crawl de um dominio (apenas HTML estatico), registra o status HTTP de
todos os links (internos e externos) e usa uma LLM para avaliar a clareza das
paginas internas que retornaram 200. Resultado: um CSV.

Uso: python sitereview.py https://exemplo.com -o relatorio.csv
    N)defaultdictdeque)urljoin	urldefragurlparse)BeautifulSoupzgpt-5.4-mini      g      ?   ip  z'sitereview/1.0 (+revisao final de site)z~/.config/sitereview/env)scriptstylenoscriptnavfooterheaderasideformaL  Voce vai avaliar uma pagina de um site brasileiro sob duas personas.

1) USUARIO DESAVISADO: leu apenas o conteudo abaixo, sem nenhum contexto previo.
   Responda em 1a pessoa, curto: sobre o que e esta pagina? O que eu deveria
   fazer aqui? Ficou claro?
2) PUBLICITARIO: avalie se a pagina comunica bem a proposta de valor, o CTA
   (chamada para acao) e o tom.

Depois responda objetivamente as duas perguntas-ancora, com nota de 1 a 5:
- O objetivo da pagina esta claro?
- A mensagem sobre o que a pagina comunica esta clara?

Use a regua inteira, ancorada assim:
1 = nao da pra saber do que se trata nem o que fazer; o visitante desiste.
2 = da pra adivinhar o tema, mas objetivo e proximo passo ficam vagos.
3 = compreensivel depois de ler tudo; exige esforco do visitante.
4 = claro na primeira leitura; falta pouco (destaque, concisao).
5 = obvio em segundos, com proximo passo inequivoco.

Nao empilhe tudo em 3 e 4: se a pagina cumpre o basico sem falha real, de 5; se
voce precisou reler pra entender, de 2. Na justificativa cite algo concreto da
pagina (um trecho, um titulo, o texto do CTA) em vez de conselhos genericos do
tipo "poderia ser mais direto".

Responda SOMENTE com um JSON valido, sem markdown, sem cercas de codigo, com
exatamente estas chaves:
{{
  "visao_usuario_desavisado": "texto",
  "visao_publicitario": "texto",
  "nota_objetivo": 1-5,
  "justificativa_objetivo": "texto curto",
  "nota_mensagem": 1-5,
  "justificativa_mensagem": "texto curto"
}}

--- DADOS DA PAGINA ---
URL: {url}
TITLE: {title}
META DESCRIPTION: {description}
HEADINGS: {headings}
TEXTO PRINCIPAL:
{texto}
)urlstatus_codetipoalertaencontrado_emqtd_paginasnota_objetivonota_mensagemjustificativa_objetivojustificativa_mensagemvisao_usuario_desavisadovisao_publicitariotitleerroc                     t        |       \  } }t        |       }|j                  j                  d      xs d}|xs |j                  j                         }|j                  ||      j                         S )zRemove fragmento (#) e barra final redundante, pra dedupe funcionar.

    host_canonico troca o host da URL (usado nos links internos, pra que
    www.ex.com/a e ex.com/a nao sejam visitados como se fossem duas paginas).
    /)netlocpath)r   r   r%   rstripr$   lower_replacegeturl)r   host_canonico_parsedcaminhor$   s         J/var/www/projetos5.exentdev.com.br/ws/8.3/shimada/sitereview/sitereview.py	normalizar/   d   sf     s^FCc]Fkk  %,G3fmm113F??&w?7>>@@    c                    t        |       j                  j                         }|j                  d      |k7  ry|dk(  ryt        |       j                  j                  d      xs d}||k(  xs |j                  |dz         S )a*  O crawl fica no mesmo host E dentro do path da URL semente.

    O path importa porque um dominio pode hospedar varios sites em subpastas
    (ex: /ws/8.3/prosperi/dev). Semente na raiz ("/") = dominio inteiro no escopo.
    Links fora do escopo nao sao rastreados, so tem o status verificado.
    www.Fr#   T)r   r$   r'   removeprefixr%   r&   
startswith)r   dominioprefixohostr-   s        r.   	no_escopor8   q   sz     C=%%'D G+#~sm  '',3GgB!3!3GcM!BBr0   c                 0    t        |       j                  dv S )z@So seguimos http(s); mailto, tel, javascript etc. ficam de fora.)httphttps)r   scheme)r   s    r.   
rastreavelr=      s    C=#444r0   c           	      D   | j                   r| j                   j                  d      nd}| j                  dddi      xs | j                  ddd	i      }|r#|j                  d
      xs dj	                         nd}| j                  g d      D cg c]$  }|j                   d|j                  dd       & }}| j                  t              D ]  }|j                           t        j                  dd| j                  dd            }||||dt         fS c c}w )zDDevolve title, meta description, headings h1-h3 e o texto principal.T)strip metanamedescription)attrspropertyzog:descriptioncontent)h1h2h3z:  z\s+N)r    get_textfindgetr?   find_allrB   
TAGS_RUIDO	decomposeresubMAX_TEXT_CHARS)soupr    rA   rC   hheadingstagtextos           r.   extrai_conteudorY      s    /3zzDJJd+rE99VFM#:9; tyyz#34 @I @D :>488I&,"3352K 12 66("QZZ4Z012H  }}Z( FF63c >?E+x)???s   )Dc                     t               }| j                  dd      D ]7  }t        t        ||d               }t	        |      s'|j                  |       9 |S )z3Todos os hrefs da pagina, absolutos e normalizados.aT)hrefr\   )setrN   r/   r   r=   add)rT   base_urllinksr[   r   s        r.   extrai_linksra      sS    EE]]3T]* !F)45c?IIcN Lr0   c                     t        j                  d      ry	 t        t        d      5 } | D ]  }|j	                         j                  d      }|j                  d      s4|j                  dd      d   j	                         j	                  d	      }|si|t         j                  d<    d
d
d
       y 	 d
d
d
       y# 1 sw Y   yxY w# t        $ r Y yw xY w)zMGarante OPENAI_API_KEY no ambiente. A env var vence; senao, le ARQUIVO_CHAVE.OPENAI_API_KEYTzutf-8)encodingzexport zOPENAI_API_KEY==   z"'NF)
osgetenvopenARQUIVO_CHAVEr?   r3   r4   splitenvironOSError)flinhachaves      r.   carrega_chaverq      s    	yy!"
-'2 	$a $229=##$56!KKQ/288:@@GE7<

#34#	$ 	$$	$ 	$   sF   C	 6B= 4B=B=)C	 2B=4C	 =CC	 C	 		CCc                 z    |rddini } | j                   j                  j                  dt        d|dgddid|S )	z*Uma chamada de chat pedindo JSON de volta.temperaturer   user)rolerF   typejson_object)modelmessagesresponse_format )chatcompletionscreateMODEL)clientpromptcom_temperaturaextras       r.   chama_modelor      sT    "1]ArE)6;;"")) !f56/ 	 r0   c                    t         j                  ||xs d|xs ddj                  |      xs d|xs d      }	 	 t        | |      }t        j                  |j                  d
   j                  j                        S # t        $ r&}dt        |      vr t        | |d      }Y d	}~`d	}~ww xY w# t        $ r}dd| icY d	}~S d	}~ww xY w)zGUma chamada por pagina. Devolve dict com a avaliacao, ou {'erro': ...}.z(vazio)z | z(nenhum)z(sem texto))r   r    rC   rV   rX   rs   F)r   Nr   r!   zllm: )PROMPTformatjoinr   	ExceptionstrjsonloadschoicesmessagerF   )	r   r   r    rC   rV   rX   r   respostaes	            r.   avalia_com_llmr      s    ]] y,9H%3$}  F
%	K#FF3H zz(**1-55==>>  	KCF*#FFEJH		K  %%s$$%sA   A> 5B0 >	B-B(#B0 (B--B0 0	C
9C?C
C
c                 .   	 | j                  |t        d      }|j                  dk\  r)| j                  |t        dd      }|j	                          |j                  dfS # t
        j                  $ r!}dt        |      j                  fcY d}~S d}~ww xY w)zEExternos: so o status code. HEAD primeiro, GET se o servidor recusar.Ttimeoutallow_redirects  )r   r   streamr@   r   N)	headTIMEOUTr   rM   closerequestsRequestExceptionrv   __name__)sessaor   rr   s       r.   checa_externor      s    #KKWdKC==C

3d
SAGGI}}b  $$ #$q'""""#s   AA   B3B	BBc           
      >   t        |       j                  j                         }|j                  d      }t	        j
                         }t        |j                  d<   d }|rddlm	}  |       }t        | |      }t        |      j                  }t        |dfg      }	|h}
t               }t        t              }g }d}|	r|t         k  r|	j#                         \  }}|dz  }|dddd}|j%                  |       t'        d	|d
d| d| t(        j*                         t-        j.                  t0               	 |j3                  |t4        d      }|j<                  |d<   d|j                  j3                  dd      v }|j<                  dk7  s|st?        |j@                  d      }tC        ||jD                        }tG        |      \  }}}}||d<   |r|jI                  tK        ||||||             |D ]  }tM        |||      }|rt        ||      }||   j%                  |       |r7||
vs9|tN        k  sC|
jQ                  |       |	j%                  ||dz   f       k||vsp|jQ                  |       tS        ||      \  }}|j%                  |d||d        |	r|t         k  r|D ]7  }|j3                  |d   g       }|d   |k(  rdn|d   |d<   tU        |      |d<   9 |S # t        j6                  $ r#}t9        |      j:                  |d<   Y d }~[d }~ww xY w)Nr2   z
User-Agentr   )OpenAIrf   internor@   )r   r   r   r!   [3z] drJ   fileTr   r!   r   z	text/htmlzContent-Type   zhtml.parserr    externor   z	(semente)r   r   )+r   r$   r'   r3   r   Session
USER_AGENTheadersopenair   r/   r%   r   r]   r   list	MAX_PAGESpopleftappendprintsysstderrtimesleepREQUEST_DELAYrM   r   r   rv   r   r   r   textra   r   rY   updater   r8   	MAX_DEPTHr^   r   len) seedusar_llm	host_seedr5   r   r   r   inicior6   filavistosexternos_vistosorigens
resultadosinternos_visitadosr   profundidadero   r   r   eh_htmlrT   r`   r    rC   rV   rX   linkr   statusr!   paginass                                    r.   rastreiar      s   %%++-I$$V,GF#-FNN< F!tY'Fv##G61+DXFeO$GJ
%	1 LLN\aYq"M% $Q's<.#?cjjQ

=!	

3
FA
  !}}m~r!BB==CwQVV]3T155).=d.C+{Heg LLUKSXYZ  	Dgw7G y1DM  %v%,*BJJt$KK|a'7 89_,##D),VT:!! )FTXY	; %	1^  ,++eElB/ 16e0FGTUJo"7|m, Y (( 	 G,,E&M	s   8K& &L9LLc                 8   g }| d   dk(  r|j                  d       n| d   dk\  r|j                  d       dD ]K  \  }}| j                  |      }t        |t        t        f      s.|t
        k  s8|j                  d|        M dj                  |      | d	<   | S )
z:Coluna 'alerta' pra achar rapido o que precisa de atencao.r   r   SEM_RESPOSTAr   LINK_QUEBRADO))r   OBJETIVO)r   MENSAGEMNOTA_BAIXA_rJ   r   )r   rM   
isinstanceintfloatNOTA_MINIMAr   )ro   alertascamporotulonotas        r.   marca_alertasr   >  s    G]q ~&	}		$'W 3vyydS%L)dk.ANN[123 hhw'E(OLr0   c                    | D ]  }t        |        | j                  d        t        |ddd      5 }t        j                  |t
        d      }|j                          |j                  |        d d d        y # 1 sw Y   y xY w)	Nc                      | d    | d   | d   fS )Nr   r   r   r{   )ls    r.   <lambda>zescreve_csv.<locals>.<lambda>Q  s    q{?AfIqx"H r0   )keywr@   z	utf-8-sig)newlinerd   ignore)
fieldnamesextrasaction)r   sortri   csv
DictWriterCOLUNASwriteheader	writerows)r   r-   ro   rn   writers        r.   escreve_csvr   M  s{     e OOHOI	gsB	= %gHM$% % %s   >A<<Bc                     t        j                  d      } | j                  dd       | j                  dddd	
       | j                  ddd       | j                         }|j                   }|r't               st        j                  dt         d       t        |j                  |      }t        ||j                         t        d |D              }t        dt        |       d| d|j                   t        j                          y )Nz/Revisao final de site: links + clareza por LLM.)rC   r   z$URL semente, ex: https://exemplo.com)helpz-oz--outputzrelatorio.csvzCSV de saida)defaultr   z--no-llm
store_truez$so checa links, sem avaliar conteudo)actionr   z(Sem chave da OpenAI. Coloque a chave em zM (linha OPENAI_API_KEY=...), ou exporte OPENAI_API_KEY, ou rode com --no-llm.c              3   ,   K   | ]  }|d    s	d  yw)r   rf   Nr{   ).0r   s     r.   	<genexpr>zmain.<locals>.<genexpr>j  s     71X;!7s   

z URLs verificadas, z com alerta -> r   )argparseArgumentParseradd_argument
parse_argsno_llmrq   r   exitrj   r   r   r   outputsumr   r   r   )apargsr   r   r   s        r.   mainr   Y  s    		 	 -^	_BOOE FOGOOD*oNOSOOJ|:`Oa==?D;;H6}o F[ [	

 $((H-J
DKK(7Z77G	
S_0	VZZr0   __main__)N)T).__doc__r   r   r   rg   rQ   r   r   collectionsr   r   urllib.parser   r   r   r   bs4r   r   r   r   r   r   rS   r   r   r%   
expanduserrj   rO   r   r   r/   r8   r=   rY   ra   rq   r   r   r   r   r   r   r   r   r{   r0   r.   <module>r     s     
  	 	 
  * 5 5  
 			
6
 ""#=> Y
*
X
AC 5@,$%2	#J`	%0 zF r0   