From e388183601d3b074b997cb4d9a4ba6552459ac82 Mon Sep 17 00:00:00 2001 From: WanderMotta Date: Fri, 13 Feb 2026 01:31:03 -0300 Subject: [PATCH] Commit inicial - upload de todos os arquivos da pasta --- .env | 2 + .env.example | 2 + Campanha-LP-Civil-Otimizada.md | 120 ++++++ Dockerfile | 13 + Links Uteis.txt | 5 + Prompt.md | 55 +++ app.py | 361 +++++++++++++++++++ app.zip | Bin 0 -> 36095 bytes github.bat | 30 ++ google_ads_assets.xlsx | Bin 0 -> 8896 bytes google_ads_keywords (1).csv | 21 ++ google_ads_keywords.csv | 16 + planejamento.md | 56 +++ requirements.txt | 10 + src/__init__.py | 0 src/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 137 bytes src/__pycache__/ai_generator.cpython-310.pyc | Bin 0 -> 7759 bytes src/__pycache__/exporter.cpython-310.pyc | Bin 0 -> 4057 bytes src/__pycache__/scraper.cpython-310.pyc | Bin 0 -> 7896 bytes src/ai_generator.py | 263 ++++++++++++++ src/exporter.py | 163 +++++++++ src/scraper.py | 265 ++++++++++++++ 22 files changed, 1382 insertions(+) create mode 100644 .env create mode 100644 .env.example create mode 100644 Campanha-LP-Civil-Otimizada.md create mode 100644 Dockerfile create mode 100644 Links Uteis.txt create mode 100644 Prompt.md create mode 100644 app.py create mode 100644 app.zip create mode 100644 github.bat create mode 100644 google_ads_assets.xlsx create mode 100644 google_ads_keywords (1).csv create mode 100644 google_ads_keywords.csv create mode 100644 planejamento.md create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 src/__pycache__/__init__.cpython-310.pyc create mode 100644 src/__pycache__/ai_generator.cpython-310.pyc create mode 100644 src/__pycache__/exporter.cpython-310.pyc create mode 100644 src/__pycache__/scraper.cpython-310.pyc create mode 100644 src/ai_generator.py create mode 100644 src/exporter.py create mode 100644 src/scraper.py diff --git a/.env b/.env new file mode 100644 index 0000000..22a45ac --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +OPENAI_API_KEY=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 +GEMINI_API_KEY=AIzaSyBEtSE6SpdOYXc0p5b5aepdcRuu53jHaFA \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a902301 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +OPENAI_API_KEY=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 +GEMINI_API_KEY=AIzaSyBEtSE6SpdOYXc0p5b5aepdcRuu53jHaFA diff --git a/Campanha-LP-Civil-Otimizada.md b/Campanha-LP-Civil-Otimizada.md new file mode 100644 index 0000000..fef5b0f --- /dev/null +++ b/Campanha-LP-Civil-Otimizada.md @@ -0,0 +1,120 @@ +REGRAS OTIMIZADAS PARA GOOGLE ADS: + +1. PALAVRAS-CHAVE (Fundo de Funil): Gere 20 termos com alta intenção de contratação. Use [Exata] e "Frase". Limite os termos a no máximo 25 caracteres para viabilizar o uso nos títulos. +2. NEGATIVAS: Gere 20 termos que filtrem estudantes, curiosos e buscas gratuitas (ex: pdf, curso, modelo, jurisprudência, tcc). +3. TÍTULOS (Headlines): Gere 15 títulos (máx. 30 caracteres). Regra de Ouro: A palavra-chave da Regra 1 deve aparecer de forma INTEGRAL e IDENTICA no título. Se a keyword for longa, o título será apenas ela. +4. DESCRIÇÕES: Gere 4 descrições (máx. 90 caracteres). Devem terminar obrigatoriamente com um ponto final ou exclamação. Inclua uma Proposta Única de Valor (UVP). +5. EXTENSÕES (Sitelinks & Callouts): + - 4 Sitelinks (Título 25ch / Desc 35ch). + - 6 Callouts (Frases de destaque, máx 25ch) focadas em autoridade e agilidade. +6. POLÍTICAS EDITORIAIS: + - Proibido: "Grátis" e sinônimos. + - Proibido: CAIXA ALTA em palavras inteiras (exceto siglas como SP, OAB). + - Proibido: Uso excessivo de pontuação (ex: !!!). + +RETORNO: Apenas o JSON estruturado. + + + +///////////////////////// +{ + "campanha_google_ads": { + "palavras_chave_fundo_de_funil": [ + "[advogado divórcio online]", + "\"contratar advogado família\"", + "[advogado guarda filhos]", + "\"advogado pensão alimentícia\"", + "[advogado inventário SP]", + "\"advogado união estável\"", + "[dissolução união estável]", + "\"escritório direito civil\"", + "[advogado para inventário]", + "\"especialista em divórcio\"", + "[ação de pensão alimentícia]", + "\"advogado guarda de menores\"", + "[advogado partilha de bens]", + "\"consultoria jurídica família\"", + "[divórcio consensual online]", + "\"advogado civilista SP\"", + "[inventário extrajudicial]", + "\"advogado direito de família\"", + "[pedir pensão alimentícia]", + "\"advogado separação judicial\"" + ], + "palavras_chave_negativas": [ + "grátis (evitar buscas sem orçamento)", + "gratuito (evitar defensoria pública)", + "estágio (evitar candidatos a vaga)", + "vagas (evitar candidatos a vaga)", + "curso (evitar estudantes)", + "faculdade (evitar estudantes)", + "tcc (evitar estudantes)", + "modelo de petição (evitar curiosos)", + "jurisprudência (evitar estudantes)", + "pdf (evitar buscas informativas)", + "concurso (evitar concurseiros)", + "trabalhista (fora do escopo civil)", + "criminal (fora do escopo civil)", + "previdenciário (fora do escopo civil)", + "defensoria pública (baixa intenção)", + "fórum (busca informativa)", + "como fazer (busca informativa)", + "lei (busca informativa)", + "livro (evitar estudantes)", + "apostila (evitar estudantes)" + ], + "titulos_headlines": [ + "Advogado divórcio online", + "Contratar advogado família", + "Advogado guarda filhos", + "Advogado pensão alimentícia", + "Advogado inventário SP", + "Advogado união estável", + "Dissolução união estável", + "Escritório direito civil", + "Advogado para inventário", + "Especialista em divórcio", + "Ação de pensão alimentícia", + "Advogado guarda de menores", + "Advogado partilha de bens", + "Consultoria jurídica família", + "Divórcio consensual online" + ], + "descricoes": [ + "Advocacia especializada em Direito Civil e Família. Atendimento 100% online e sigiloso.", + "Resolva seu divórcio, guarda ou inventário de forma rápida e segura. Fale com um expert.", + "Proteja seus direitos e sua família com especialistas. Soluções ágeis e atendimento humanizado.", + "Mais de 10 anos de experiência em Direito de Família. Avalie seu caso com total sigilo." + ], + "sitelinks": [ + { + "titulo": "Divórcio e Separação", + "desc_linha_1": "Encerre o caso de forma rápida.", + "desc_linha_2": "Consensual ou litigioso online." + }, + { + "titulo": "Guarda e Pensão", + "desc_linha_1": "Priorizando o bem-estar dos filhos.", + "desc_linha_2": "Definição e revisão de valores." + }, + { + "titulo": "Inventário e Herança", + "desc_linha_1": "Solução ágil na partilha de bens.", + "desc_linha_2": "Via judicial ou extrajudicial." + }, + { + "titulo": "Fale com Especialista", + "desc_linha_1": "Atendimento rápido via WhatsApp.", + "desc_linha_2": "Tire suas dúvidas agora mesmo." + } + ], + "callouts": [ + "Atendimento 100% Online", + "Sigilo Total Garantido", + "10 Anos de Experiência", + "Resposta Rápida", + "Especialista em Família", + "Solução Sem Burocracia" + ] + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..06de387 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8501 + +CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"] diff --git a/Links Uteis.txt b/Links Uteis.txt new file mode 100644 index 0000000..ac11c16 --- /dev/null +++ b/Links Uteis.txt @@ -0,0 +1,5 @@ +# Como criar uma Campanha - adriano gianini +https://www.youtube.com/watch?v=4HMDAhl15cA + +#Amanda Agostinho +https://www.youtube.com/watch?v=XU1MWVk6HvQ \ No newline at end of file diff --git a/Prompt.md b/Prompt.md new file mode 100644 index 0000000..4eeaa21 --- /dev/null +++ b/Prompt.md @@ -0,0 +1,55 @@ +Objetivo: Landing Page (LP) já está bem otimizada para conversão, ela é a melhor fonte de "verdade" para o Google Ads. Usar Python para automatizar isso não só economiza tempo, mas garante que o anúncio seja um reflexo fiel do que o usuário vai encontrar na página (o que aumenta o seu **Índice de Qualidade**). + +Aqui está como eu estruturaria essa aplicação Python para você: + +--- + +## 🛠️ Arquitetura da Aplicação + +Para construir isso "do zero", você pode dividir o script em quatro módulos principais: + +### 1. Scraping (Extração de Conteúdo) + +Use as bibliotecas `BeautifulSoup` ou `Selenium`. + +* **O que pegar:** Títulos (`h1`, `h2`), textos de benefícios, CTAs (chamadas para ação) e a meta-description. +* **Dica:** Foque em extrair a **proposta de valor** central. + +### 2. Processamento com IA (O "Cérebro") + +Em vez de tentar criar lógica de palavras-chave na mão, conecte o Python à **API do OpenAI** + +* **Prompt de Ouro:** "Com base no texto desta LP [solicitar URL], gere: 10 variações de palavras-chave (fundo de funil), 3 títulos de 30 caracteres, 2 descrições de 90 caracteres e 4 sitelinks." + +### 3. Estruturação dos Ativos + +Organize os dados extraídos no formato que o Google Ads aceita (planilhas de upload em massa ou via API). + +* **Palavras-chave:** Classifique por tipos de correspondência (Exata e Frase). +* **Anúncios Responsivos (RSA):** Garanta que você tenha variações suficientes para o Google testar. + +### 4. Exportação + +Gere um arquivo `.csv` formatado para o **Google Ads Editor**. É muito mais seguro do que subir direto via API se você está começando do zero, pois permite uma revisão final antes de "dar o play". + +--- + +## 🐍 Sugestão de Stack Tecnológica + +| Componente | Ferramenta Recomendada | +| --- | --- | +| **Linguagem** | Python 3.10+ | +| **Scraping** | `requests` + `BeautifulSoup4` | +| **Inteligência** | `api oPENai` | +| **Interface** | `Streamlit` (para você colar a URL e ver o resultado na tela) | +| **Saída** | `pandas` (para gerar o CSV/Excel) | + +--- + +## 💡 Meu "Pulo do Gato" para você + +Não peça apenas palavras-chave óbvias. Peça para a IA identificar **"Dores do Cliente"** no texto da sua LP e transformar essas dores em **Extensões de Frase de Destaque (Callouts)**. + +**Exemplo:** Se na sua LP diz "Suporte em 5 minutos", o Python deve extrair isso automaticamente como um diferencial do anúncio. + +--- diff --git a/app.py b/app.py new file mode 100644 index 0000000..8aa38f6 --- /dev/null +++ b/app.py @@ -0,0 +1,361 @@ +""" +Google Ads Generator - Interface Streamlit + +Aplicação que extrai conteúdo de Landing Pages e gera automaticamente +ativos de campanha para Google Ads usando IA (OpenAI ou Gemini). +""" + +import os +import re +import streamlit as st +from dotenv import load_dotenv + +from src.scraper import scrape_landing_page +from src.ai_generator import generate_google_ads_assets, MODELS +from src.exporter import ( + create_keywords_df, + create_negative_keywords_df, + create_ads_df, + create_sitelinks_df, + create_callouts_df, + export_all_to_excel, +) + +# ─── Carregar variáveis de ambiente ─────────────────────────────── +load_dotenv() + + +def _highlight_keywords(text: str, keywords: list[str]) -> str: + """Destaca em negrito as palavras-chave encontradas no texto.""" + result = text + for kw in keywords: + pattern = re.compile(re.escape(kw), re.IGNORECASE) + match = pattern.search(result) + if match: + result = result[:match.start()] + f"**{match.group()}**" + result[match.end():] + break # Destacar apenas a primeira keyword encontrada + return result + +# ─── Configuração da Página ─────────────────────────────────────── +st.set_page_config( + page_title="Google Ads Generator", + page_icon="📊", + layout="wide", +) + +# ─── CSS Customizado ────────────────────────────────────────────── +st.markdown(""" + +""", unsafe_allow_html=True) + +# ─── Header ─────────────────────────────────────────────────────── +st.markdown('

Google Ads Generator

', unsafe_allow_html=True) +st.markdown( + '

Gere ativos de campanha automaticamente a partir da sua Landing Page

', + unsafe_allow_html=True, +) + +# ─── Sidebar ────────────────────────────────────────────────────── +with st.sidebar: + st.header("Configurações") + + provider = st.selectbox( + "Provider de IA", + ["OpenAI", "Gemini"], + index=0, + help="Escolha o provider de IA. As chaves devem estar configuradas no arquivo .env", + ) + + model = st.selectbox( + "Modelo", + MODELS.get(provider, []), + index=0, + help="Modelo a ser utilizado para geração dos ativos.", + ) + + # Status das chaves configuradas + st.divider() + openai_ok = bool(os.environ.get("OPENAI_API_KEY")) + gemini_ok = bool(os.environ.get("GEMINI_API_KEY")) + st.caption("Status das API Keys (.env):") + st.markdown(f"- OpenAI: {'✅ Configurada' if openai_ok else '❌ Não encontrada'}") + st.markdown(f"- Gemini: {'✅ Configurada' if gemini_ok else '❌ Não encontrada'}") + + st.divider() + + campaign_name = st.text_input( + "Nome da Campanha", + value="Campanha LP", + help="Nome que aparecerá na coluna 'Campaign' do CSV.", + ) + + ad_group = st.text_input( + "Nome do Grupo de Anúncios", + value="Grupo 1", + help="Nome que aparecerá na coluna 'Ad Group' do CSV.", + ) + + st.divider() + st.caption("Desenvolvido com Streamlit + OpenAI + Gemini") + +# ─── Área Principal ─────────────────────────────────────────────── +url = st.text_input( + "URL da Landing Page", + placeholder="https://www.seusite.com.br/landing-page", + help="Cole a URL completa da sua Landing Page aqui.", +) + +col_btn, col_status = st.columns([1, 3]) + +with col_btn: + generate_btn = st.button("Gerar Campanha", type="primary", use_container_width=True) + +# ─── Lógica Principal ──────────────────────────────────────────── +if generate_btn: + # Validações + if not url: + st.error("Por favor, insira a URL da Landing Page.") + st.stop() + + if not url.startswith(("http://", "https://")): + st.error("A URL deve começar com http:// ou https://") + st.stop() + + # Verificar se a chave do provider selecionado está configurada + if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"): + st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"): + st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + + # Step 1: Scraping + with st.status("Processando...", expanded=True) as status: + st.write("Extraindo conteúdo da Landing Page...") + try: + lp_data = scrape_landing_page(url) + except Exception as e: + st.error(f"Erro ao acessar a URL: {e}") + st.stop() + + st.write(f"Conteúdo extraído: {len(lp_data['paragraphs'])} parágrafos, " + f"{len(lp_data['h1'])} H1, {len(lp_data['h2'])} H2, " + f"{len(lp_data.get('list_items', []))} itens de lista, " + f"{len(lp_data['ctas'])} CTAs") + + # Step 2: IA + st.write(f"Gerando ativos com {provider} ({model})...") + try: + assets, prompts = generate_google_ads_assets( + lp_content=lp_data["full_text"], + provider=provider, + model=model, + ) + except ValueError as e: + st.error(str(e)) + st.stop() + except Exception as e: + st.error(f"Erro ao gerar ativos: {e}") + st.stop() + + status.update(label="Concluído!", state="complete", expanded=False) + + # Salvar no session_state + st.session_state["assets"] = assets + st.session_state["prompts"] = prompts + st.session_state["lp_data"] = lp_data + st.session_state["campaign_name"] = campaign_name + st.session_state["ad_group"] = ad_group + st.session_state["provider_used"] = provider + st.session_state["model_used"] = model + +# ─── Exibição dos Resultados ───────────────────────────────────── +if "assets" in st.session_state: + assets = st.session_state["assets"] + prompts = st.session_state.get("prompts", {}) + camp = st.session_state.get("campaign_name", "Campanha LP") + adg = st.session_state.get("ad_group", "Grupo 1") + prov_used = st.session_state.get("provider_used", "") + model_used = st.session_state.get("model_used", "") + + st.divider() + + # Métricas resumo + col1, col2, col3, col4, col5, col6 = st.columns(6) + + with col1: + st.metric("Keywords", len(assets.get("keywords", []))) + with col2: + st.metric("Negativas", len(assets.get("negative_keywords", []))) + with col3: + st.metric("Títulos", len(assets.get("headlines", []))) + with col4: + st.metric("Descrições", len(assets.get("descriptions", []))) + with col5: + st.metric("Sitelinks", len(assets.get("sitelinks", []))) + with col6: + st.metric("Callouts", len(assets.get("callouts", []))) + + st.divider() + + # Abas com resultados + tab_kw, tab_neg, tab_ads, tab_sl, tab_co, tab_prompts, tab_raw = st.tabs([ + "Keywords", "Negativas", "Anúncios RSA", "Sitelinks", "Callouts", + "Prompts Utilizados", "Dados da LP", + ]) + + with tab_kw: + st.subheader("Palavras-chave") + kw_df = create_keywords_df(assets, camp, adg) + if not kw_df.empty: + st.dataframe(kw_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhuma palavra-chave gerada.") + + with tab_neg: + st.subheader("Palavras-chave Negativas") + nkw_df = create_negative_keywords_df(assets, camp) + if not nkw_df.empty: + st.dataframe(nkw_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhuma palavra-chave negativa gerada.") + + with tab_ads: + st.subheader("Anúncio Responsivo de Pesquisa (RSA)") + + st.write("**Títulos (Headlines):**") + # Coletar palavras-chave para destacar nos títulos + kw_list = sorted( + [kw["keyword"].lower() for kw in assets.get("keywords", []) if kw.get("keyword")], + key=len, reverse=True, # Maior primeiro para evitar match parcial + ) + for i, h in enumerate(assets.get("headlines", []), 1): + char_count = len(h) + color = "green" if char_count <= 30 else "red" + h_highlighted = _highlight_keywords(h, kw_list) + st.markdown(f"{i}. {h_highlighted} — :{color}[{char_count} chars]") + + st.write("**Descrições:**") + for i, d in enumerate(assets.get("descriptions", []), 1): + char_count = len(d) + color = "green" if char_count <= 90 else "red" + st.markdown(f"{i}. {d} — :{color}[{char_count} chars]") + + st.divider() + ads_df = create_ads_df(assets, camp, adg) + if not ads_df.empty: + st.dataframe(ads_df, use_container_width=True, hide_index=True) + + with tab_sl: + st.subheader("Sitelinks") + sl_df = create_sitelinks_df(assets, camp) + if not sl_df.empty: + st.dataframe(sl_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhum sitelink gerado.") + + with tab_co: + st.subheader("Callouts (Frases de Destaque)") + co_df = create_callouts_df(assets, camp) + if not co_df.empty: + st.dataframe(co_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhum callout gerado.") + + with tab_prompts: + st.subheader("Prompts Utilizados") + st.caption(f"Provider: **{prov_used}** | Modelo: **{model_used}**") + + st.markdown("#### Prompt de Sistema (System Prompt)") + st.code(prompts.get("system_prompt", "N/A"), language=None) + + st.markdown("#### Prompt do Usuário (User Prompt)") + st.code(prompts.get("user_prompt", "N/A"), language=None) + + with tab_raw: + st.subheader("Dados Extraídos da Landing Page") + lp_data = st.session_state.get("lp_data", {}) + if lp_data: + st.write(f"**URL:** {lp_data.get('url', '')}") + st.write(f"**Título:** {lp_data.get('title', '')}") + st.write(f"**Meta Description:** {lp_data.get('meta_description', '')}") + + with st.expander("Headings (H1, H2, H3)"): + for h in lp_data.get("h1", []): + st.write(f"**H1:** {h}") + for h in lp_data.get("h2", []): + st.write(f"**H2:** {h}") + for h in lp_data.get("h3", []): + st.write(f"**H3:** {h}") + + with st.expander("Itens de Lista / Benefícios"): + for li in lp_data.get("list_items", []): + st.write(f"- {li}") + + with st.expander("CTAs Encontrados"): + for c in lp_data.get("ctas", []): + st.write(f"- {c}") + + with st.expander("Texto Completo Enviado à IA"): + st.code(lp_data.get("full_text", ""), language=None) + + # ─── Botões de Download ─────────────────────────────────────── + st.divider() + st.subheader("Download dos Ativos") + + col_dl1, col_dl2 = st.columns(2) + + with col_dl1: + excel_data = export_all_to_excel(assets, camp, adg) + st.download_button( + label="Baixar Excel (.xlsx)", + data=excel_data, + file_name="google_ads_assets.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + use_container_width=True, + ) + + with col_dl2: + kw_csv = create_keywords_df(assets, camp, adg).to_csv(index=False) + st.download_button( + label="Baixar Keywords (.csv)", + data=kw_csv, + file_name="google_ads_keywords.csv", + mime="text/csv", + use_container_width=True, + ) diff --git a/app.zip b/app.zip new file mode 100644 index 0000000000000000000000000000000000000000..9d5bcf3ed94085591c220ae6b1fb700e9eb2c022 GIT binary patch literal 36095 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 R@I*;rhBn}WkmObn4**$X4p0C9 literal 0 HcmV?d00001 diff --git a/github.bat b/github.bat new file mode 100644 index 0000000..8a458f7 --- /dev/null +++ b/github.bat @@ -0,0 +1,30 @@ +@echo off +echo === INICIANDO UPLOAD PARA GITHUB === + +REM Inicializar repositório Git +echo Inicializando repositorio Git... +git init + +REM Adicionar todos os arquivos +echo Adicionando todos os arquivos... +git add . + +REM Fazer commit inicial +echo Realizando commit inicial... +git commit -m "Commit inicial - upload de todos os arquivos da pasta" + +REM Adicionar repositório remoto +echo Conectando ao repositorio remoto... +git remote add origin https://gitea.aplicativopro.com/wander/Google-Ads.git + +REM Definir branch principal +echo Definindo branch principal como 'main'... +git branch -M main + +REM Fazer push para o GitHub +echo Fazendo upload para o GitHub... +git push -u origin main + +echo === UPLOAD CONCLUIDO COM SUCESSO! === + +pause \ No newline at end of file diff --git a/google_ads_assets.xlsx b/google_ads_assets.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..549d2d763930d8cd7cf09f00abce49fa65a48085 GIT binary patch literal 8896 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 GVE_O>m>r4$ literal 0 HcmV?d00001 diff --git a/src/__pycache__/ai_generator.cpython-310.pyc b/src/__pycache__/ai_generator.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9bb40cb129883619577b3cc33cb77b299668bc3 GIT binary patch literal 7759 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 literal 0 HcmV?d00001 diff --git a/src/__pycache__/scraper.cpython-310.pyc b/src/__pycache__/scraper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8163c52ec4050b02ea1b61b23003bdb23235609 GIT binary patch literal 7896 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 tuple[dict, dict]: + """ + Gera todos os ativos de campanha Google Ads a partir do conteúdo da LP. + + Args: + lp_content: Texto completo extraído da LP (full_text do scraper). + provider: Provider de IA ("OpenAI" ou "Gemini"). + model: Modelo a ser usado (deve ser compatível com o provider). + + Returns: + Tupla com: + - assets: Dicionário com keywords, negative_keywords, headlines, etc. + - prompts: Dicionário com system_prompt e user_prompt usados. + """ + system_prompt = _build_system_prompt() + user_prompt = _build_user_prompt(lp_content) + + prompts = { + "system_prompt": system_prompt, + "user_prompt": user_prompt, + } + + if provider == "OpenAI": + raw_response = _call_openai(system_prompt, user_prompt, model) + elif provider == "Gemini": + raw_response = _call_gemini(system_prompt, user_prompt, model) + else: + raise ValueError(f"Provider não suportado: {provider}") + + # Limpar possíveis blocos de código markdown + raw_response = _clean_json_response(raw_response) + + result = json.loads(raw_response) + assets = _validate_and_normalize(result) + + return assets, prompts + + +def _call_openai(system_prompt: str, user_prompt: str, model: str) -> str: + """Chama a API da OpenAI e retorna a resposta bruta.""" + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key: + raise ValueError("OPENAI_API_KEY não encontrada no arquivo .env") + + client = OpenAI(api_key=api_key) + + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + temperature=0.7, + max_tokens=4000, + ) + + return response.choices[0].message.content.strip() + + +def _call_gemini(system_prompt: str, user_prompt: str, model: str, max_retries: int = 2) -> str: + """ + Chama a API do Google Gemini e retorna a resposta bruta. + Faz retry automático em caso de rate limit (429). + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise ValueError("GEMINI_API_KEY não encontrada no arquivo .env") + + client = genai.Client(api_key=api_key) + + for attempt in range(max_retries + 1): + try: + response = client.models.generate_content( + model=model, + contents=user_prompt, + config=genai.types.GenerateContentConfig( + system_instruction=system_prompt, + temperature=0.7, + max_output_tokens=4000, + ), + ) + return response.text.strip() + + except Exception as e: + error_str = str(e) + if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str: + # Extrair tempo de retry sugerido pela API + wait_match = re.search(r"retry.*?(\d+)", error_str, re.IGNORECASE) + wait_seconds = int(wait_match.group(1)) if wait_match else 45 + + if attempt < max_retries: + time.sleep(wait_seconds) + continue + else: + raise ValueError( + f"Gemini API: Limite de requisições excedido (Free Tier). " + f"Aguarde ~{wait_seconds}s e tente novamente, ou mude para um " + f"plano pago em https://ai.google.dev/pricing. " + f"Alternativa: use o provider OpenAI." + ) from e + else: + raise + + +def _clean_json_response(raw: str) -> str: + """Remove blocos de código markdown e extrai apenas o JSON.""" + raw = raw.strip() + if raw.startswith("```"): + raw = raw.split("\n", 1)[1] + if raw.endswith("```"): + raw = raw[:-3] + raw = raw.strip() + return raw + + +def _build_system_prompt() -> str: + """Constrói o prompt de sistema.""" + return ( + "Você é um especialista em Google Ads com mais de 10 anos de experiência. " + "Seu trabalho é gerar ativos de campanha de alta performance a partir do " + "conteúdo de Landing Pages. Responda SEMPRE em formato JSON válido, " + "sem markdown, sem blocos de código. Apenas o JSON puro." + ) + + +def _build_user_prompt(lp_content: str) -> str: + """Constrói o prompt do usuário para geração de ativos.""" + return f"""Com base no conteúdo desta Landing Page, gere ativos completos para uma campanha de Google Ads. + +=== CONTEÚDO DA LANDING PAGE === +{lp_content} +=== FIM DO CONTEÚDO === + +Gere o seguinte em formato JSON: + +{{ + "keywords": [ + {{"keyword": "exemplo de palavra-chave", "match_type": "Exact"}}, + {{"keyword": "outro exemplo", "match_type": "Phrase"}} + ], + "negative_keywords": [ + {{"keyword": "grátis", "reason": "Atrai tráfego que não converte"}}, + {{"keyword": "como fazer", "reason": "Topo de funil, não converte"}} + ], + "headlines": [ + "Título com até 30 caracteres" + ], + "descriptions": [ + "Descrição com até 90 caracteres que destaca benefícios e inclui CTA" + ], + "sitelinks": [ + {{"title": "Título do Sitelink", "description1": "Linha 1", "description2": "Linha 2"}} + ], + "callouts": [ + "Frase de destaque curta" + ] +}} + +REGRAS OTIMIZADAS PARA GOOGLE ADS: + +1. PALAVRAS-CHAVE (Fundo de Funil): Gere 20 termos com alta intenção de contratação. Use [Exata] e "Frase". Limite os termos a no máximo 25 caracteres para viabilizar o uso nos títulos. +2. NEGATIVAS: Gere 20 termos que filtrem estudantes, curiosos e buscas gratuitas (ex: pdf, curso, modelo, jurisprudência, tcc). +3. TÍTULOS (Headlines): Gere 15 títulos (máx. 30 caracteres). Regra de Ouro: A palavra-chave da Regra 1 deve aparecer de forma INTEGRAL e IDENTICA no título. Se a keyword for longa, o título será apenas ela. +4. DESCRIÇÕES: Gere 4 descrições (máx. 90 caracteres). Devem terminar obrigatoriamente com um ponto final ou exclamação. Inclua uma Proposta Única de Valor (UVP). +5. EXTENSÕES (Sitelinks & Callouts): + - 4 Sitelinks (Título 25ch / Desc 35ch). + - 6 Callouts (Frases de destaque, máx 25ch) focadas em autoridade e agilidade. +6. POLÍTICAS EDITORIAIS: + - Proibido: "Grátis" e sinônimos. + - Proibido: CAIXA ALTA em palavras inteiras (exceto siglas como SP, OAB). + - Proibido: Uso excessivo de pontuação (ex: !!!). + +RETORNO: Apenas o JSON estruturado.""" + +def _validate_and_normalize(data: dict) -> dict: + """Valida e normaliza os dados retornados pela IA.""" + result = { + "keywords": [], + "negative_keywords": [], + "headlines": [], + "descriptions": [], + "sitelinks": [], + "callouts": [], + } + + # Keywords + for kw in data.get("keywords", []): + if isinstance(kw, dict) and "keyword" in kw: + result["keywords"].append({ + "keyword": kw["keyword"], + "match_type": kw.get("match_type", "Phrase"), + }) + elif isinstance(kw, str): + result["keywords"].append({"keyword": kw, "match_type": "Phrase"}) + + # Negative Keywords + for nkw in data.get("negative_keywords", []): + if isinstance(nkw, dict) and "keyword" in nkw: + result["negative_keywords"].append({ + "keyword": nkw["keyword"], + "reason": nkw.get("reason", ""), + }) + elif isinstance(nkw, str): + result["negative_keywords"].append({"keyword": nkw, "reason": ""}) + + # Headlines - garantir limite de 30 chars + for h in data.get("headlines", []): + if isinstance(h, str) and len(h) <= 30: + result["headlines"].append(h) + elif isinstance(h, str): + result["headlines"].append(h[:30]) + + # Descriptions - garantir limite de 90 chars + for d in data.get("descriptions", []): + if isinstance(d, str) and len(d) <= 90: + result["descriptions"].append(d) + elif isinstance(d, str): + result["descriptions"].append(d[:90]) + + # Sitelinks + for sl in data.get("sitelinks", []): + if isinstance(sl, dict) and "title" in sl: + result["sitelinks"].append({ + "title": sl.get("title", "")[:25], + "description1": sl.get("description1", "")[:35], + "description2": sl.get("description2", "")[:35], + }) + + # Callouts + for c in data.get("callouts", []): + if isinstance(c, str) and len(c) <= 25: + result["callouts"].append(c) + elif isinstance(c, str): + result["callouts"].append(c[:25]) + + return result diff --git a/src/exporter.py b/src/exporter.py new file mode 100644 index 0000000..c6ddae4 --- /dev/null +++ b/src/exporter.py @@ -0,0 +1,163 @@ +""" +Módulo de Exportação - Gera arquivos CSV compatíveis com Google Ads Editor. + +Recebe os ativos gerados pela IA e os formata em DataFrames do Pandas, +prontos para importação no Google Ads Editor. +""" + +import pandas as pd +from io import BytesIO + + +def create_keywords_df(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> pd.DataFrame: + """Cria DataFrame de palavras-chave no formato Google Ads Editor.""" + rows = [] + for kw in assets.get("keywords", []): + match_type = kw.get("match_type", "Phrase") + keyword = kw.get("keyword", "") + + # Formatar keyword conforme tipo de correspondência + if match_type == "Exact": + formatted_kw = f"[{keyword}]" + elif match_type == "Phrase": + formatted_kw = f'"{keyword}"' + else: + formatted_kw = keyword + + rows.append({ + "Campaign": campaign_name, + "Ad Group": ad_group, + "Keyword": formatted_kw, + "Match Type": match_type, + "Status": "Enabled", + }) + + return pd.DataFrame(rows) + + +def create_negative_keywords_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: + """Cria DataFrame de palavras-chave negativas no formato Google Ads Editor.""" + rows = [] + for nkw in assets.get("negative_keywords", []): + rows.append({ + "Campaign": campaign_name, + "Keyword": nkw.get("keyword", ""), + "Criterion Type": "Negative", + "Reason": nkw.get("reason", ""), + }) + + return pd.DataFrame(rows) + + +def create_ads_df(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> pd.DataFrame: + """Cria DataFrame do anúncio responsivo (RSA) no formato Google Ads Editor.""" + headlines = assets.get("headlines", []) + descriptions = assets.get("descriptions", []) + + row = { + "Campaign": campaign_name, + "Ad Group": ad_group, + "Ad Type": "Responsive Search Ad", + } + + # Preencher até 15 headlines + for i, h in enumerate(headlines[:15], start=1): + row[f"Headline {i}"] = h + + # Preencher até 4 descrições + for i, d in enumerate(descriptions[:4], start=1): + row[f"Description {i}"] = d + + return pd.DataFrame([row]) + + +def create_sitelinks_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: + """Cria DataFrame de sitelinks no formato Google Ads Editor.""" + rows = [] + for sl in assets.get("sitelinks", []): + rows.append({ + "Campaign": campaign_name, + "Sitelink Text": sl.get("title", ""), + "Description Line 1": sl.get("description1", ""), + "Description Line 2": sl.get("description2", ""), + }) + + return pd.DataFrame(rows) + + +def create_callouts_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: + """Cria DataFrame de callouts no formato Google Ads Editor.""" + rows = [] + for c in assets.get("callouts", []): + rows.append({ + "Campaign": campaign_name, + "Callout Text": c, + }) + + return pd.DataFrame(rows) + + +def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> BytesIO: + """ + Exporta todos os ativos em um único arquivo Excel com múltiplas abas. + + Returns: + BytesIO com o conteúdo do arquivo Excel. + """ + output = BytesIO() + + with pd.ExcelWriter(output, engine="openpyxl") as writer: + kw_df = create_keywords_df(assets, campaign_name, ad_group) + if not kw_df.empty: + kw_df.to_excel(writer, sheet_name="Keywords", index=False) + + nkw_df = create_negative_keywords_df(assets, campaign_name) + if not nkw_df.empty: + nkw_df.to_excel(writer, sheet_name="Negative Keywords", index=False) + + ads_df = create_ads_df(assets, campaign_name, ad_group) + if not ads_df.empty: + ads_df.to_excel(writer, sheet_name="Ads RSA", index=False) + + sl_df = create_sitelinks_df(assets, campaign_name) + if not sl_df.empty: + sl_df.to_excel(writer, sheet_name="Sitelinks", index=False) + + co_df = create_callouts_df(assets, campaign_name) + if not co_df.empty: + co_df.to_excel(writer, sheet_name="Callouts", index=False) + + output.seek(0) + return output + + +def export_all_to_csv(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> dict[str, str]: + """ + Exporta todos os ativos como strings CSV separadas. + + Returns: + Dicionário com nome da aba -> conteúdo CSV. + """ + csvs = {} + + kw_df = create_keywords_df(assets, campaign_name, ad_group) + if not kw_df.empty: + csvs["keywords"] = kw_df.to_csv(index=False) + + nkw_df = create_negative_keywords_df(assets, campaign_name) + if not nkw_df.empty: + csvs["negative_keywords"] = nkw_df.to_csv(index=False) + + ads_df = create_ads_df(assets, campaign_name, ad_group) + if not ads_df.empty: + csvs["ads"] = ads_df.to_csv(index=False) + + sl_df = create_sitelinks_df(assets, campaign_name) + if not sl_df.empty: + csvs["sitelinks"] = sl_df.to_csv(index=False) + + co_df = create_callouts_df(assets, campaign_name) + if not co_df.empty: + csvs["callouts"] = co_df.to_csv(index=False) + + return csvs diff --git a/src/scraper.py b/src/scraper.py new file mode 100644 index 0000000..9875b8d --- /dev/null +++ b/src/scraper.py @@ -0,0 +1,265 @@ +""" +Módulo de Scraping - Extração de conteúdo de Landing Pages. + +Utiliza Selenium (Chrome headless) + BeautifulSoup4 para extrair +títulos, textos, CTAs e meta tags de uma Landing Page fornecida via URL. +O Selenium renderiza o JavaScript antes da extração, garantindo que +todo o conteúdo dinâmico seja capturado. +""" + +import time +from bs4 import BeautifulSoup +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from webdriver_manager.chrome import ChromeDriverManager + + +def _create_driver() -> webdriver.Chrome: + """Cria e retorna um driver Chrome headless.""" + options = Options() + options.add_argument("--headless=new") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--disable-gpu") + options.add_argument("--window-size=1920,1080") + options.add_argument("--disable-extensions") + options.add_argument("--disable-infobars") + options.add_argument( + "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36" + ) + # Suprimir logs do Chrome + options.add_argument("--log-level=3") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + + service = Service(ChromeDriverManager().install()) + driver = webdriver.Chrome(service=service, options=options) + return driver + + +def scrape_landing_page(url: str, wait_seconds: int = 5) -> dict: + """ + Faz scraping de uma Landing Page usando Selenium (Chrome headless) + e retorna um dicionário com os elementos relevantes para geração de anúncios. + + Args: + url: URL completa da Landing Page. + wait_seconds: Segundos para aguardar o carregamento do JS. + + Returns: + Dicionário com as chaves: + - url: URL original + - title: Título da página () + - meta_description: Conteúdo da meta description + - h1: Lista de textos dos <h1> + - h2: Lista de textos dos <h2> + - h3: Lista de textos dos <h3> + - paragraphs: Lista dos parágrafos principais + - ctas: Lista de textos de botões e links de ação + - full_text: Texto completo concatenado (para envio à IA) + """ + driver = _create_driver() + + try: + driver.get(url) + + # Aguardar o body estar presente (sinal de que a página carregou) + WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.TAG_NAME, "body")) + ) + + # Aguardar tempo extra para JavaScript renderizar conteúdo dinâmico + time.sleep(wait_seconds) + + # Scroll até o final para disparar lazy-loading + driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") + time.sleep(2) + + # Capturar HTML completo após renderização + page_source = driver.page_source + + finally: + driver.quit() + + # Parsear com BeautifulSoup + soup = BeautifulSoup(page_source, "html.parser") + + # Remover scripts e styles para limpeza do texto + for tag in soup(["script", "style", "noscript", "iframe", "svg"]): + tag.decompose() + + # Título da página + title = soup.title.string.strip() if soup.title and soup.title.string else "" + + # Meta description + meta_desc_tag = soup.find("meta", attrs={"name": "description"}) + meta_description = "" + if meta_desc_tag and meta_desc_tag.get("content"): + meta_description = meta_desc_tag["content"].strip() + + # Headings + h1_tags = [tag.get_text(strip=True) for tag in soup.find_all("h1") if tag.get_text(strip=True)] + h2_tags = [tag.get_text(strip=True) for tag in soup.find_all("h2") if tag.get_text(strip=True)] + h3_tags = [tag.get_text(strip=True) for tag in soup.find_all("h3") if tag.get_text(strip=True)] + + # Parágrafos relevantes (ignora parágrafos muito curtos) + paragraphs = [ + tag.get_text(strip=True) + for tag in soup.find_all("p") + if tag.get_text(strip=True) and len(tag.get_text(strip=True)) > 20 + ] + + # Lista items (muitas LPs usam <li> para benefícios) + list_items = [ + tag.get_text(strip=True) + for tag in soup.find_all("li") + if tag.get_text(strip=True) and 10 < len(tag.get_text(strip=True)) < 200 + ] + + # Spans e divs com texto significativo (para LPs que não usam <p>) + extra_texts = _extract_visible_text_blocks(soup) + + # CTAs - botões e links com texto de ação + ctas = _extract_ctas(soup) + + # Texto completo para enviar à IA + full_text = _build_full_text( + title, meta_description, h1_tags, h2_tags, h3_tags, + paragraphs, list_items, extra_texts, ctas, + ) + + return { + "url": url, + "title": title, + "meta_description": meta_description, + "h1": h1_tags, + "h2": h2_tags, + "h3": h3_tags, + "paragraphs": paragraphs, + "list_items": list_items, + "ctas": ctas, + "full_text": full_text, + } + + +def _extract_visible_text_blocks(soup: BeautifulSoup) -> list[str]: + """ + Extrai blocos de texto visível de divs e spans que não estão + dentro de tags semânticas (p, h1-h6, li, button, a). + Útil para LPs que usam divs customizados para conteúdo. + """ + semantic_tags = {"p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "button", "a", "input", "label"} + texts = [] + seen = set() + + for tag in soup.find_all(["div", "span"]): + # Pular se tem filhos que são tags semânticas + if tag.find(list(semantic_tags)): + continue + + text = tag.get_text(strip=True) + if text and 30 < len(text) < 500 and text not in seen: + seen.add(text) + texts.append(text) + + return texts[:15] # Limitar para não poluir + + +def _extract_ctas(soup: BeautifulSoup) -> list[str]: + """Extrai textos de botões e links que parecem ser CTAs.""" + ctas = [] + + # Botões + for btn in soup.find_all("button"): + text = btn.get_text(strip=True) + if text and len(text) < 80: + ctas.append(text) + + # Inputs do tipo submit + for inp in soup.find_all("input", attrs={"type": "submit"}): + value = inp.get("value", "").strip() + if value: + ctas.append(value) + + # Links com classes comuns de CTA ou texto indicativo + cta_keywords = [ + "btn", "button", "cta", "action", "comprar", "contratar", + "saiba", "agendar", "solicitar", "falar", "whatsapp", + "contato", "orcamento", "orçamento", "consulta", "agende", + ] + for a_tag in soup.find_all("a"): + classes = " ".join(a_tag.get("class", [])).lower() + href = (a_tag.get("href") or "").lower() + text = a_tag.get_text(strip=True) + if text and len(text) < 80: + if any(kw in classes for kw in cta_keywords): + ctas.append(text) + elif any(kw in text.lower() for kw in cta_keywords): + ctas.append(text) + elif "whatsapp" in href or "wa.me" in href: + ctas.append(text) + + # Remover duplicados mantendo ordem + seen = set() + unique_ctas = [] + for cta in ctas: + if cta not in seen: + seen.add(cta) + unique_ctas.append(cta) + + return unique_ctas + + +def _build_full_text( + title: str, + meta_description: str, + h1: list[str], + h2: list[str], + h3: list[str], + paragraphs: list[str], + list_items: list[str], + extra_texts: list[str], + ctas: list[str], +) -> str: + """Monta um texto completo e estruturado da LP para envio à IA.""" + parts = [] + + if title: + parts.append(f"TÍTULO DA PÁGINA: {title}") + if meta_description: + parts.append(f"META DESCRIPTION: {meta_description}") + + if h1: + parts.append("TÍTULOS PRINCIPAIS (H1):") + parts.extend(f" - {h}" for h in h1) + + if h2: + parts.append("SUBTÍTULOS (H2):") + parts.extend(f" - {h}" for h in h2) + + if h3: + parts.append("SUBTÍTULOS (H3):") + parts.extend(f" - {h}" for h in h3) + + if paragraphs: + parts.append("TEXTOS PRINCIPAIS:") + parts.extend(f" {p}" for p in paragraphs[:25]) + + if list_items: + parts.append("ITENS DE LISTA / BENEFÍCIOS:") + parts.extend(f" - {li}" for li in list_items[:15]) + + if extra_texts: + parts.append("OUTROS TEXTOS VISÍVEIS:") + parts.extend(f" {t}" for t in extra_texts[:10]) + + if ctas: + parts.append("CHAMADAS PARA AÇÃO (CTAs):") + parts.extend(f" - {c}" for c in ctas) + + return "\n".join(parts)