Marketplace: O que é, como funciona e como anunciar

Marketplaces Thumb

Marketplace: O que é, como funciona e como anunciar

rotadesk No Comments

Marketplaces

Ter uma loja virtual se transformou em uma tendência nos últimos anos, já que, cada vez mais, os consumidores aderem às compras online. Além de ser prático e seguro, na internet é possível escolher entre uma grande variedade de produtos do conforto do seu lar.

E, o melhor, muitas vezes, os preços praticados pelo e-commerce são mais baixos, o frete é mais em conta e a mercadoria chega na porta do seu endereço. Todas essas vantagens estimulam muitas lojas físicas a venderem na internet também.

Além disso, muitos empreendedores novos estão abrindo o seu primeiro negócio na internet. No entanto, em praticamente todos os segmentos de mercado, a concorrência é grande. Então, como ganhar visibilidade e ser encontrado na internet pelo seu público alvo?

É nesse sentido que surge o conceito de Marketplace, um tipo de shopping virtual que reúne produtos de diferentes outras empresas. Assim, todos saem ganhando, a pequena ou novo loja que encontra uma vitrine online e os consumidores que acham tudo no mesmo lugar.

Quer saber mais sobre o assunto? Então, continue lendo este artigo e confira tudo a respeito de Marketplace!

  • Como funciona o Marketplace?
  • Quais são os Marketplaces disponíveis?
  • Quais são as vantagens de utilizar os Marketplaces?
  • Como integrar minha loja ao Marketplace?

O que é o Marketplace?

O que é Marketplace

Uma forma eficiente de explicar o que é o Marketplace é usando a mesma comparação já citada, a de um shopping center. De modo semelhante ao que acontece nesses lugares, o Marketplace reúne em uma só loja virtual várias empresas.

Podem ser negócios de diferentes segmentos de mercado, assim, disponibilizando diversos tipos de produtos. Para quem tem um empreendimento, as vantagens são inúmeras, afinal, consumidores vão encontrar os seus produtos mesmo que não conheça a sua loja.

Assim como uma pessoa caminha pelo shopping e acaba comprando até mesmo o que não tinha previsto, no Marketplace o internauta também pode adquirir produtos. Afinal, ao se deparar com produtos variados que precisa, aproveita a visita.

Além dessas, existem outras vantagens, porém, também há algumas questões que devem ser levadas em consideração, como as taxas que devem ser pagas para o Marketplace. Assim como nos shoppings centers o aluguel costuma ser alto, as taxas do Marketplace também.

Mesmo assim, pode valer a pena, conforme o seu tipo de negócio, produtos que vende e demais fatores que precisam ser avaliados caso a caso. Hoje em dia, já existe uma ampla variedade de Marketplaces, sendo que cada um possui a sua política.

Muitos dos Marketplaces no país são lojas online já bem conhecidas que abrem espaço para a venda de produtos de outras lojas. Essa é mais uma vantagem, pois é como se fosse um shopping bastante famoso e frequentado por uma significativa parcela da população.

Como funciona o Marketplace?

Como funciona o Marketplace

Para participar de um Marketplace, o primeiro passo é cadastrar o seu negócio e o produtos que são comercializados. Dessa forma, o Marketplace os promove para quem acessa a sua loja virtual, de acordo com as pesquisas de produtos realizadas por cada visitante.

Os Marketplaces também costumam avaliar o perfil e o potencial de uma empresa antes de fechar a parceria. Isso porque, se houver algum problema com a compra executada por um cliente, a sua reputação é a que mais será afetada.

A loja virtual que reúne outras empresas em sua plataforma também se responsabiliza pela entrega da mercadoria e pela cobrança. Isso quer dizer que ela tem responsabilidade em grande parte do processo de compra.

Por isso, além de taxas extras que o Marketplace pode comprar, existe uma comissão sobre cada item vendido, o que pode variar em conformidade com as regras de cada Marketplace. Essa comissão normalmente fica entre 9,5% a 30%.

Ela pode ser definida de acordo com a visibilidade oferecida a cada empresa. Dessa forma, quanto mais visibilidade o seu negócio recebe, maior é a comissão. Mesmo assim, é preciso analisar a viabilidade desse tipo de parceria para a sua marca.

Quais são os Marketplaces disponíveis?

Nos últimos anos, cresceu a oferta de Marketplaces disponíveis para as empresas no Brasil. Hoje, já é possível encontrar uma grande variedade, sendo que algumas delas pertencem aos mesmos grupos. Veja a seguir quais são os Marketplaces disponíveis, atualmente, no mercado:

Quais são as vantagens de utilizar os Marketplaces?

São muitos os benefícios de fazer parte de um Marketplace, confira a seguir quais são os principais:

Maior visibilidade

Como já foi citada, a visibilidade está entre os motivos mais fortes para fazer parceria com um Marketplace, uma vez que, todos os meses, essas plataformas recebem milhões de visitas. Ou seja, não tem como isso parecer ruim. Pessoas de todo o país conferindo os seus artigos.

Redução de gastos

A partir do momento que a sua empresa integra um Marketplace, pode ser desnecessário divulgar em outros canais os seus produtos. Isso quer dizer que os custos com a parceria podem valer a pena.

É importante lembrar que os custos com marketing digital não envolvem só o anúncio ou ação em si, mas também manter uma equipe capacitada e experiente em criar estratégias eficientes. Você pode investir em marketing ainda, mas certamente, não vai precisar tanto.

Já quem tem uma loja física e fizer a parceria com o Marketplace não precisa gastar com as despesas comuns que envolvem a criação de um e-commerce. A sua equipe pode ser mais enxuta também, porém, deve estar capacitada para gerenciar com sucesso o seu negócio.

Credibilidade junto ao público

Mesmo que as compras online tenham grande aceitação do público, muitos consumidores preferem adquirir mercadorias de marcas que já conhecem. Ou que são reconhecidas e, por isso, sentem-se mais seguros em realizar uma compra.

E ao estar na vitrine de um Marketplace, a sua marca ganha automaticamente a confiança que o consumidor dá à plataforma onde está comprando. Isso quer dizer que um consumidor poderia não comprar diretamente da sua loja virtual se não a conhecesse bem.

Mas se você estiver em um Marketplace, o cliente não pensa duas vezes. Isso acontece porque a sua compra é garantida pela marca com a qual já teve uma experiência ou que é amplamente usada por outros consumidores.

Dessa maneira, vale a pena fazer a parceria com um Marketplace reconhecido no seu nicho de mercado.

Novas oportunidades de negócio

Com a maior visibilidade da sua empresa em um Marketplace, você pode ampliar o seu mix de produtos, a fim de atingir um público mais amplo. Isso pode dar a guinada necessária no seu empreendimento para se expandir e crescer ainda mais.

Aumento na conversão de vendas

Além de ser mais uma das vantagens de utilizar os Marketplaces, o maior número de vendas é também uma consequência dos demais benefícios que essa parceria oferece. O mesmo acontece com o faturamento e com o lucro do seu negócio.

Com mais visibilidade, credibilidade, redução de gastos e novas oportunidades, o crescimento no volume de vendas é apenas uma consequência. Além disso, você consegue fidelizar os seus clientes online com muito mais sucesso.

E desvantagens, existem?

Mesmo com tantas vantagens, não se pode ignorar as desvantagens dessa parceria. Uma delas é a dependência que a sua empesa pode ter em relação ao Marketplace, o que pode se tornar um grande problema se por alguma razão a plataforma encerrar as suas atividades.

Por isso, não se pode esquecer de manter o bom funcionamento da sua loja própria online ou física. Assim, caso o Marketplace feche ou por outro motivo a parceria acabe, você consegue manter as suas vendas sem grandes perdas.

Os custos dessa parceria podem ser altos ou mesmo aumentarem com o passar do tempo. Afinal, em meio às crises econômicas do país, todos os preços sobem. Dessa forma, você deve ter a independência necessária para terminar a parceria quando ela não for mais vantajosa.

Com isso, você não corre o risco de se tornar refém do Marketplace. Outro cuidado que você deve ter é o de não perder a personalidade da sua marca, uma vez que o mais comum é que nessas plataformas o nome da sua empresa não ganha muito destaque.

Portanto, ao pensar em expor os seus produtos em um Marketplace, é preciso considerar todos os pontos positivos e negativos para saber qual a melhor decisão a tomar. Além disso, nada impede que você faça essa parceria quando estiver iniciando com as vendas online.

E depois que já estiver com a experiência necessária pode montar o seu próprio e-commerce. Porém, confira todas as informações do contrato que fez com o Marketplace para saber em que momento você pode encerrar essa parceria sem causar nenhum prejuízo ao seu negócio.

Como integrar minha loja ao Marketplace?

Como integrar minha loja ao Marketplace

Os Marketplaces são ótimas oportunidades para empresas conquistarem mais clientes e até fidelizá-los. No entanto, antes de saber como integrar a sua loja ao Marketplace é preciso não descuidar da sua própria loja virtual e/ou física.

Dessa forma, é preciso ter uma organização e planejamento eficientes para que possa gerenciar tanto o seu empreendimento quanto a sua parceria com a plataforma. A atenção principal deve estar na logística para que nunca falte produtos em nenhum dos dois.

Caso você deixe na mão algum consumidor conquistado por meio do Marketplace é possível até mesmo que perca a sua parceria. Além disso, é preciso colocar na ponta do lápis a viabilidade de aparecer no site de terceiros.

Você vai ter que pagar taxas e comissões para o Marketplace, mas também vai ter uma maior conversão de vendas e menor custos com publicidade. Compensa? É preciso considerar que o prazo para receber os valores obtidos através da parceria pode ser mais do que um mês.

Por isso, é indispensável conferir os diferenciais e peculiaridades de cada Marketplace, ponderar o mais vantajoso conforme o seu segmento de mercado e conferir todos os pormenores das políticas e termos da parceria.

Dicas para vender através do Marketplace

Depois de ser aceito pelo Marketplace e poder usar a sua vitrine para promover as suas mercadorias é essencial conhecer o seu funcionamento. Para não cometer nenhum erro, é preciso verificar como os produtos podem ser cadastrados, se exige um padrão e há regras.

Outro ponto importante é investir em boas descrições de produtos que tenham imagens em alta resolução. Da mesma forma que você faz em sua própria loja virtual, caso tenha, ou faria se a tivesse, no Marketplace tudo deve ser muito bem constituído.

Além disso, o conteúdo deve ser atrativo e completo. Afinal, você pode ter milhares de consumidores todos os dias acessando os seus artigos, mas só vão comprar se encontrarem todas as informações que buscam.

Ao integrar a sua loja ao Marketplace e, depois de tudo pronto, não se esqueça de manter uma equipe qualificada e comprometida no gerenciamento tanto da sua vitrine no site parceiro quanto no seu próprio negócio. Nesse caso, a gestão é ainda mais crucial.

Mais uma dica é ampliar a pesquisa que você deve fazer regularmente da concorrência, englobando ainda os concorrentes dentro do Marketplace. É possível que existam muitos e, além de diferenciais, você deve manter preços competitivos.

O mesmo serve para o valor do frete, pois consumidores online levam isso em consideração na hora de escolher entre uma empresa e outra. E se o seu valor estiver alto pode perder a venda para outra empresa.

Carol Volodka: Minhas impressões sobre o evento Inbound 2018…

rotadesk No Comments

Carol Volodka: Analista de Inbound Marketing da MindLab.

“Participar do Inbound18 foi um sonho que se realizou. Como profissional de Marketing Digital com ênfase em Inbound Marketing, posso dizer que os assuntos abordados nas sessões foram de extrema relevância e, por ser um evento internacional, me surpreendi com a quantidade de coisas que consegui extrair e aplicar no meu dia a dia.

Além disso, um ponto marcante foi o networking que o evento propiciou. É incrível encontrar com pessoas que possuem os mesmos desafios profissionais que o seu e entender boas práticas do mercado na visão do outro.

Tudo isso não seria possível sem a organização do evento, que foi mais do que impecável.”

Grupo de Brasileiros presentes no evento Inbound 2018.

 

INBOUND é uma comunidade de pessoas que são apaixonadas por marketing, vendas e satisfação de clientes. O evento em Boston é uma plataforma de mídia que durante todo o ano inspira e educa centenas de milhares de pessoas, para que elas – e seus negócios – possam crescer melhor.

O evento coloca as pessoas em primeiro lugar, criando conexões e propagando sua filosofia com muita humanidade, afinal, esses são fatores que estão no cerne de fazer negócios. Oradores apaixonados não apenas por melhorar o resultado final dos clientes, mas também por construir um futuro mais brilhante, mais gentil e mais inclusivo fazem parte do grupo de palestrantes.

O INBOUND é hospedado pela HubSpot que se orgulha de reunir uma comunidade de profissionais do setor para inspirarem-se e crescerem juntos.

Inbound em números:
24.000 participantes
3 milhões de visualizações por mês
400 mil seguidores

//www.inbound.com/

Keyword Planner thumb

Keyword Planner: Descubra palavras-chave com essa ferramenta!

rotadesk No Comments

Keyword Planner

Quando a ideia é fazer com que o site ou blog do seu negócio esteja bem posicionado nos resultados de pesquisa dos mecanismos de busca, não se pode esquecer da importância das palavras-chave. Afinal, elas permitem que os internautas visitem o seu endereço na web.

Ou seja, depois que um usuário do Google, por exemplo, digita na barra de pesquisa uma determinada palavra, aparecem os sites e blogs que possuem como foco essa mesma palavra. Então, você deve escolher boas palavras-chave para a sua marca ser acessada além de oferecer um conteúdo que compartilhe com as intenções de pesquisas dos usuários.

Claro que esses termos devem ser coerentes com o seu segmento de mercado, mas de acordo com ele, é possível dar preferência a uma palavra ao invés de outra. Nessa hora, como fazer as escolhas mais assertivas?

Continue lendo este artigo e saiba mais sobre Keyword Planner: descubra palavras-chave com essa ferramenta do Google!

O que é o Keyword Planner?

Na hora de investir em marketing digital, a sua empresa pode usar o marketing de conteúdo para atrair e conquistar o seu público alvo. Ele consiste em criar sites e blogs com conteúdo interessante e relevante para os consumidores em potencial do seu produto ou serviço.

Dessa forma, a marca se aproxima do público, cria autoridade ao oferecer conteúdo informativo de qualidade e ganha a confiança do consumidor. Mas para que os internautas acessem o seu site, primeiro, é preciso que eles o encontrem. E como fazer isso?

É nesse ponto que entra em cena o poder das palavras-chave, sendo que as mais frequentemente usadas pelo seu público nos buscadores devem ser as mesmas que você tem em seu blog ou site. Com isso, são maiores as chances da sua mídia aparecer nos resultados.

Existem outras técnicas de SEO que contribuem com o melhor rankeamento da sua marca na internet, mas as palavras-chave estão entre as principais. Vale lembrar que SEO quer dizer otimização para mecanismos de busca.

Ele consiste em várias orientações estipuladas pelo Google para que o seu endereço virtual seja bem posicionado na busca orgânica.

No entanto, ao pagar para aparecer bem ranqueado, também é preciso utilizar de forma adequada as palavras-chave. Nesse caso, o Google disponibiliza a ferramenta Google Ads, que são os links pagos que aparecem no topo da pesquisa.

E o Google Keyword Planner?

Antes de entender o que é o Google Keyword Planner, é preciso saber o que são as palavras-chave, SEO e Google Ads. Dessa forma, fica bem simples compreender o uso dessa ferramenta. Então, o Keyword Planner consiste em um planejador de palavra-chave. (Você pode acessar a ferramenta nesse link)

Essa, aliás, é a tradução do termo em inglês. E, aqui, planejar se refere a ajudar você a encontrar as palavras-chave mais apropriadas para usar em seu site ou blog, e dá uma estimativa do custo para ficar bem posicionado no Google e, assim, garantir um número maior de visitas.

Essa ferramenta substituiu o antigo Keyword Tool, também disponibilizado pelo Google. A principal diferença, agora, é que o Keyword Planner está integrado ao Google Ads. Com isso, quem faz campanhas com o Google Ads se beneficia pela facilidade de criar novos anúncios.

De qualquer forma, todos os usuários interessados em planejar palavras-chave para as campanhas de marketing digital podem acessar e usar essa ferramenta.

Como funciona o Planejador de palavras-chave do Google?

Como funciona o planejador de palavras-chave

O Planejador de palavras-chave é simples e intuitivo de usar, de forma semelhante ao que acontece com as demais ferramentas do Google. No entanto, se você não pretende iniciar uma nova campanha ou mesmo a primeira no Google Ads é preciso pular alguns passos.

Desse modo, primeiro, você deve acessar o Google Ads com a sua conta ou criar uma nova, lembrando que é possível acessar a ferramenta com o seu Gmail.

Depois, no menu superior da página do Google Ads, é preciso selecionar a área de ferramentas e clicar em Planejador de palavras-chave. Se não tiver cadastro ainda, deve informar os dados pessoais.

Já ao selecionar o tipo de conta, opte pela alternativa “gerenciar contas de outras pessoas”. Isso vai levar você para o dashboard da conta gerenciada, sendo necessário ainda clicar em “adicionar nova conta”, onde você deve inserir um nome.

Nessa hora, não se esqueça de informar corretamente os dados de fuso horário e valores, o que vai contribuir para que, posteriormente, o uso da ferramenta seja mais eficiente. Além disso, pule o tutorial, caso contrário, você vai iniciar uma campanha de Google Ads.

Usando o Google Keyword Planner

Se o seu intuito é o de apenas usar o Planejador de palavras-chave do Google para pesquisar as melhores para o seu negócio, digite na ferramenta a palavra-chave que você pretende destacar no seu blog ou site. Assim, vai saber como é a sua aceitação na internet.

Nesse caso, observe qual é a média de pesquisas mensais relacionadas à palavra-chave digitada. Se você está em dúvida sobre uma ou outra, o planejador pode ajudar você a identificar quais das duas é a mais procurada.

Além disso, ao fazer a pesquisa usando uma palavra-chave, aparecem sugestões de palavras relacionadas, chamadas de secundárias ou de apoio.

Dicas para usar o Google Keyword Planner

O Planejador de palavras-chave do Google também mostra informações da concorrência. Ou seja, existe uma opção chamada Concorrência, onde é exibido o número de anunciantes do Google Ads que usam em seus anúncios esse mesmo termo.

Exemplo do planejador de palavras-chave

Assim, a concorrência pode ser baixa, média e alta. Nessa hora, vale a pena fazer algumas ponderações. Você pode achar melhor escolher uma palavra com mais procura, no entanto, ela também pode ter uma maior concorrência o que pode fazer seu custo por clique seja mais elevado. Palavras-chave da cauda longa podem ser uma boa ideia pois são palavras de baixa concorrência e é possível conseguir conversões genuínas.

No Google Keyword Planner, você consegue ainda conferir o Lance Sugerido, que se refere ao valor indicado para investir em um anúncio de links patrocinados, o Google Ads. Vale lembrar que o valor pago é subtraído apenas quando um internauta clica no anúncio.

Já para uma pesquisa ainda mais detalhada, outros recursos são segmentação, período da pesquisa e personalização. A segmentação pode ser por localidade, idioma, pesquisas no Google e seus parceiros.

Também é possível definir palavras-chave negativas, para que não apareçam na sua pesquisa. Quanto à personalização, pode-se usar os filtros de palavra e determinar quais termos precisam sempre ser incluídos ou excluídos do seu levantamento.

Quais as funcionalidades dessa plataforma?

Além de ajudar você a analisar quais as tendências em relação às palavras-chave, a fim de escolher as melhores para o seu negócio, o Google Keyword Planner contribui com a definição de novas palavras. Pode ainda combinar palavras-chave para obter outras.

Isso quer dizer que a ferramenta é útil para o seu negócio nas diferentes etapas da estratégia de marketing. Tanto no início do planejamento, quanto na hora de dar seguimento a sua estratégia já existente e até quando as palavras-chave não são novidades e você quer avançar.

Saiba mais sobre cada uma das funcionalidades dessa plataforma:

Tendências do volume de pesquisas

Com essa funcionalidade do Planejador de Palavras-chave do Google, é possível verificar as tendências do volume de pesquisas. Observe como o termo “Black Friday” tem um pico de buscas nos meses de Outubro e  Novembro.

Pesquisas Black Friday

Busca de novos termos

Essa funcionalidade do Keyword Planner permite que você verifique quais são as palavras-chave mais associadas ao termo que deseja usar nas suas mídias. Para tanto, é possível pesquisar de três maneiras:

  • A partir de uma frase: use frases que você acredita serem usadas pelo seu público alvo;
  • A partir de um site: insira um site na pesquisa para saber quais as palavras-chave usadas. Essa opção é perfeita para pesquisar a concorrência ou antes de comprar um domínio.
  • A partir de uma categoria: selecione o nicho do seu negócio e confira quais são as palavras-chave mais procuradas nesse segmento.

Combinação de listas de palavras-chave

Com essa funcionalidade, você consegue combinar palavras-chave para obter novas palavras-chave. Isso contribui para a sua estratégia quando ela está bem avançada e é preciso ampliá-la.

Como escolher as melhores palavras-chave na ferramenta?

Como você já entendeu, é muito importante saber como escolher as melhores palavras-chave na ferramenta do Google. Afinal, identificá-las é o primeiro passo para iniciar a sua estratégia de marketing digital. Mas como fazer isso?

Mesmo que o Keyword Planner seja muito eficiente, existem outros aspectos a considerar e que complementam o uso dessa ferramenta. Quem está começando agora um negócio e deseja apostar no marketing online pode não saber por onde começar.

O primeiro passo é identificar quais as palavras-chave que mais se relacionam com o seu empreendimento. Quem abriu uma floricultura virtual e criou um blog para gerar conteúdo relevante para o seu público alvo deve pensar nos termos relacionados ao seu negócio.

Nesse exemplo, é possível pensar em floricultura, flores, venda online, rosas e outros. Use essas palavras-chave no Planejador para ter uma ideia de como as pessoas que compram flores online pesquisam por elas no Google.

Já que a internet é muito usada também para aprender a como fazer algo ou onde encontrar, você pode pesquisar por frases como: Onde encontrar flores ou Como escolher flores. Essas são apenas algumas dicas para iniciar a sua pesquisa.

Confira outras dicas:

Conheça o seu público alvo

Público-alvo

Identificar quem são os consumidores em potencial do seu negócio, bem como quais são as suas necessidades é fundamental para qualquer tipo de empreendimento, inclusive, para investir no marketing digital. E para usar essa ferramenta acontece o mesmo.

Isso porque as buscas são determinadas pelo objetivo do consumidor, sendo que ao saber quais são os objetivos do seu público fica mais fácil de prever as suas necessidades. E, assim, as palavras-chave que mais vão ao encontro do que precisam e procuram.

Considere o volume de pesquisas

Como já foi dito, o Keyword Planner mostra o quanto cada palavra é pesquisada por mês. Se um termo é bastante pesquisado, a concorrência tende a ser grande, sendo necessário avaliar os benefícios para o seu negócio.

Além disso, existem palavras-chave mais e menos específicas, que podem ser combinadas na sua estratégia de marketing online. Nesse caso, há três grupos:

  • Head Tail: palavras-chave generalistas que apresentam um maior volume de pesquisas e muita concorrência. Exemplo: flores.
  • Middle Tail: termos mais específicos, mas ainda generalistas. Exemplo: Flores São Paulo.
  • Long Tail: são bastante específicas, por isso, possuem baixa concorrência, contribuindo com o tráfego mais qualificado, ou seja, possivelmente seus clientes em potencial. Exemplo: Flores casamento São Paulo.

Palavras-chave secundárias

Também chamadas palavras-chave de apoio, são essenciais para o marketing de conteúdo, pois como já foi dito, funcionam como sinônimo ou complemento para que o texto não fique repetitivo. Podem ainda ajudar a direcionar melhor o seu público alvo.

A dica é escolher até quatro palavras-chave secundárias para enriquecer o seu conteúdo, pois mais do que isso pode ser exagero. No mesmo exemplo de Flores casamento São Paulo, você pode usar termos como rosas para casamento, flores para noivas e buquê de noiva.

Com o uso adequado do Google Keyword Planner, você consegue não só ter ideias de palavras-chave para o conteúdo de suas mídias, como também conhece melhor a relevância de cada uma e até mesmo se inspira em temas e subtemas que pode usar para os seus textos.

O que é a gtag.js e como você pode estar perdendo conversões thumb

O que é a gtag.js e como você pode estar perdendo conversões!

rotadesk No Comments

O que é a gtag e como você pode estar perdendo conversõesA Apple em 2017 havia implementado em todos os dispositivos com iOS o “Intelligence Tracking Prevention”, uma iniciativa importante para anunciantes do Google Ads pois reduz o tempo de conversão para apenas 1 dia. Isso porque utiliza um cookie secundário.

Seguindo a iniciativa da Apple, o Mozilla Firefox também implementou uma forma semelhante de bloquear os cookies secundários chamado “Tracking Protection”. A atualização do Firefox 57 acrescentará uma opção para bloquear cookies secundários.

Esses recursos impediriam por exemplo, que suas campanhas tivessem impacto menor pois, seus anúncios teriam apenas 1 dia de janela de conversão, ao invés do tempo determinado em suas campanhas. Para contornar essa situação o Google alterou os códigos de tagueamento do Google Ads e do Google Analytics para que os cookies passem a ser considerados como primários. Esse código se chama gtag.js.

O que são cookies primários e secundários?

Os cookies, primeiramente, são arquivos de textos simples que são enviados de um site para seu navegador na primeira visita de um site. Esses arquivos armazenam informações sobre o que o usuário faz na internet, como por exemplo, informações de login e senha, endereços de IP, idiomas etc.

Existem dois tipos de cookies os primários e secundários:

  • Cookies primários são aqueles em que o navegador interpreta que estão sendo gravado pelo site requisitante, não por uma fonte terceira, como o Google.
  • Cookies secundários são aqueles em que o navegador interpreta que estão sendo gravados por um site externo, de terceiro, fora do site que está fazendo a requisição primária.

Medidas de resposta do Google

O Google atualmente oferece 2 opções para contornar o bloqueio de cookies secundários da Apple e do Firefox, o Conversion Linker e a atualização das tags de conversões gtag.js.

O Conversion Linker atuou como uma forma paliativa ao problema da janela de conversão de 1 dia dos anunciantes do Google Ads. Apresentado como uma opção do Google Tag Manager, o conversion linker permite que seus anúncios de remarketing sejam ativados em múltiplas plataformas para o mesmo usuário. Além de ampliar a janela de conversão.

A solução definitiva ao problema dos cookies secundários, foi alterar como a forma das tags do Google funcionam. A fim de estar conforme às diretrizes da Apple e do Firefox, ao invés de tratar como um cookie secundário, as tags passam gravar como um cookie primário pelos navegadores e não são chamadas por um servidor de terceiros.

Os códigos de rastreamento do Google Analytics passará a incluir também o código de conversão do Google Ads. O novo código do Google Analytics utilizará o cookie para gravar, também, informações de suas campanhas no Google Ads. O Analytics grava informações no cookie quando um usuário abre uma página no website por uma URL que utiliza o recurso de auto-tagging do Google Ads.

Rastreamento de Conversões e Remarketing

O código de rastreamento de conversões do Google Ads será instalado de uma maneira diferente. Além da mudança no próprio script, que passará escrever cookies primários nos websites, ele vai ser dividido em duas partes.

A primeira se trata da tag global do site, responsável pela configuração de novos cookies no domínio. Já a segunda parte é um snippet de evento, para acompanhar ações que devem ser contabilizadas como conversões.

Outra novidade é que a tag global do site será responsável também pelo sucesso de suas campanhas de remarketing, já que agora, essa tag adiciona às listas os visitantes às listas de remarketing do Google Ads. Assim, não é preciso mais instalar duas tags diferentes para configurar suas contas do Google Ads.

O snippet de evento permite selecionar dois tipos de ações para serem consideradas como conversões. A ação de carregamento de uma página, que pode ser instalado em uma “página de obrigado”, onde o usuário é direcionado após preencher um formulário ou realizar uma compra em seu site. Já o clique é uma opção do snippet que é instalado em uma página com um botão ou link cujo os cliques são você deseja acompanhar.

Como identificar os códigos estão atualizados?

Tag Conversão

Caso não saiba se o seus códigos estão atualizados com a nova versão do Google, estamos disponibilizando os scripts de códigos, basta compará-los e verificar se são antigos ou os novos.

Remarketing como era:


<!– Código do Google para tag de remarketing –>
<!————————————————–
As tags de remarketing não podem ser associadas a informações pessoais de identificação nem inseridas em páginas relacionadas a categorias de confidencialidade. Veja mais informações e instruções sobre como configurar a tag em: //google.com/ads/remarketingsetup
—————————————————>
<script type=”text/javascript”>
/* <![CDATA[ */
var google_conversion_id = xxxxxx;
var google_custom_params = window.google_tag_params;
var google_remarketing_only = true;
/* ]]> */
</script>
<script type=”text/javascript” src=”//www.googleadservices.com/pagead/conversion.js”>
</script>
<noscript>
<div style=”display:inline;”>
<img height=”1″ width=”1″ style=”border-style:none;” alt=”” src=”//googleads.g.doubleclick.net/pagead/viewthroughconversion/980603142/?guid=ON&amp;script=0″/>
</div>
</noscript>

Conversão – como era:
<!– Google Code for Prospect Conversion Page –>
<script type=”text/javascript”>
/* <![CDATA[ */
var google_conversion_id = xxxxxx;
var google_conversion_label = “xxxxxxx”;
var google_conversion_value = x.00;
var google_remarketing_only = false;
/* ]]> */
</script>
<script type=”text/javascript” src=”//www.googleadservices.com/pagead/conversion.js”>
</script>
<noscript>
<div style=”display:inline;”>
<img height=”1″ width=”1″ style=”border-style:none;” alt=”” src=”//www.googleadservices.com/pagead/conversion/980603142/?value=1.00&amp;label=az8aCNLanAcQhqLL0wM&amp;guid=ON&amp;script=0″/>
</div>
</noscript>

Google Analytics antigo:

<script type=”text/javascript”>
var _gaq = _gaq || [];
_gaq.push([‘_setAccount’, ‘UA-24227783-1’]);
_gaq.push([‘_trackPageview’]);

(function() {
var ga = document.createElement(‘script’); ga.type = ‘text/javascript’; ga.async = true;
ga.src = (‘https:’ == document.location.protocol ? ‘//ssl’ : ‘//www’) + ‘.google-analytics.com/ga.js’;
var s = document.getElementsByTagName(‘script’)[0]; s.parentNode.insertBefore(ga, s);
})();
</script>

Novo código Analytics 

<!– Global site tag (gtag.js) – Google Analytics –>
<script async src=”//www.googletagmanager.com/gtag/js?id=UA-74631491-1″></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag(‘js’, new Date());

gtag(‘config’, ‘UA-74631491-1’);
</script>

Novo do adwords (remarketing).

<!– Global site tag (gtag.js) – Google Ads: 980603142 –>
<script async src=”//www.googletagmanager.com/gtag/js?id=AW-980603142″></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag(‘js’, new Date());

gtag(‘config’, ‘AW-980603142’);
</script>

Evento de load (conversion)
<!– Event snippet for Prospect conversion page –>
<script>
gtag(‘event’, ‘conversion’, {
‘send_to’: ‘AW-980603142/az8aCNLanAcQhqLL0wM’,
‘value’: 1.0,
‘currency’: ‘BRL’
});
</script>

A nova versão do Gtag.js simplificou bastante as coisas.  Agora, basta instalar apenas uma tag para que a conversão e a lista de re-marketing já esteja funcionando.  Além disso, a nova versão promete contornar os problemas de captura gerados pelo Safari e Firefox.

Inbound marketing: aumente os seus leads e vendas!

rotadesk No Comments

Ao invés de forçar o consumidor a ver um anúncio na televisão ou nas publicações impressas, como acontece com o marketing tradicional, é o cliente em potencial de uma empresa quem busca pela marca com o Inbound marketing. Esse é o seu grande diferencial.

Também chamado de marketing de atração e tendo como pilar o marketing de conteúdo, o Inbound marketing oferece muitas vantagens para negócios de diversos segmentos de mercado. Outras denominações são marketing de entrada e novo marketing. Quer saber mais?

Continue lendo este artigo e descubra tudo sobre Inbound marketing: aumente os seus leads e vendas! Para tanto, confira as informações contidas nos tópicos a seguir.

  • O que é Inbound marketing?
  • Como funciona o Inbound marketing?
  • Como fazer o Inbound marketing?
  • Quais são as etapas do Inbound marketing?
  • Como medir os resultados do Inbound marketing?

O que é Inbound marketing?

Inbound marketing é um tipo de marketing bastante utilizado pelas empresas dos mais variados segmentos de mercado e de todos os tamanhos. Tem o objetivo de atrair e conquistar novos clientes, bem como fidelizar consumidores que já conhecem seus produtos e serviços.

Com esse tipo de marketing é possível ainda ter outras vantagens, entre elas:

  • Tornar o seu negócio mais visível;
  • Reduzir despesas para angariar novos clientes;
  • Gerar conteúdos que destaquem a sua empresa;
  • Otimizar o processo de vendas.

Com a internet, essa forma de divulgar marcas se tornou muito mais eficiente, uma vez que é no mundo digital onde as suas ações têm maior viabilidade. Também pode ser colocada em prática de maneiras distintas, todas com a finalidade de aproximar empresas do seu público.

Para tanto, diferente das formas tradicionais de divulgação, como TV, rádio, jornal, revistas e outras mídias, conhecidas como Outbound marketing, o Inbound marketing não se resume aos anúncios publicitários. Com o Inbound marketing é o consumidor que chega até a marca.

Porém, a aproximação do cliente em potencial com uma empresa não é direta, o que faz com que os resultados sejam ainda mais satisfatórios. Afinal, não é preciso que a marca convença o cliente a realizar uma compra, é ele quem toma essa decisão.

Para tanto, quem tem um negócio deve usar o Inbound marketing para fazer com que o consumidor se interesse pelos seus produtos e serviços. Isso é realizado através da criação de autoridade, credibilidade e confiança perante o público alvo. Mas como funciona isso?

Como funciona o Inbound marketing?

Como já foi dito, é a internet a principal aliada do Inbound marketing, pois o mundo virtual conta com redes sociais e outros canais de comunicação que possibilitam uma maior interação entre empresas e o seu público alvo. Isso é de fundamental importância.

Para entender melhor como funciona o Inbound marketing podemos dar como exemplo uma empresa que vende roupas para o público feminino. Se essa marca fosse usar os canais tradicionais de marketing, poderia produzir um comercial para a televisão.

Esse tipo de publicidade é bastante tradicional e ainda possui a sua relevância, inclusive, para alguns segmentos de mercado.

No entanto, peca por possuir alguns pontos negativos, como o fato de não ter um direcionamento eficiente. Ou seja, um grande número de pessoas está assistindo o mesmo comercial, embora nem todo mundo tenha interesse no seu conteúdo.

E mesmo quem tem interesse no produto que é divulgado naquele momento pode não estar interessado em adquirir a mercadoria. Isso quer dizer que esse anúncio pode interessar ou não, já que tem pouco foco.

O mesmo costuma acontecer com o rádio, jornal e demais meios de comunicação. Além disso, se o comercial aparecer no meio de um filme, há quem vai achá-lo inoportuno, reduzindo ainda mais as chances de ser atrativo para o consumidor.

Como o Inbound marketing atrai o consumidor?

Por outro lado, o Inbound marketing faz uso das redes sociais, sites, blogs e demais mídias virtuais, porém, não publicando anúncios, e sim, conteúdo relevante e interessante para o seu público alvo. Mesmo assim, está divulgando a sua marca, mas indiretamente e com discrição.

Um exemplo é se a mesma empresa que vende roupas femininas resolver apostar no Inbound marketing. Ela vai criar um blog e publicar periodicamente dicas de moda, convidando o seu público alvo a acessá-lo, através dos mecanismos de buscas e de outros canais.

Assim, o consumidor entra no seu blog, lê as dicas e, se for de qualidade, possivelmente vai voltar a visitá-lo para conferir mais informações. E na hora de comprar uma roupa, são maiores as chances desse consumidor considerar a marca que promove o blog para a sua compra.

Essa é a estratégia básica do Inbound marketing: usar as mídias sociais para se tornar uma referência no seu segmento de mercado, mostrando que entende do assunto. Isso faz com que o seu público busque pelos seus produtos, mesmo sem anunciá-los diretamente.

Como fazer o Inbound marketing?

O Inbound marketing se baseia, principalmente, na produção de conteúdo, podendo ser vídeos, textos, imagens e outros. Hoje em dia, redes sociais e blogs são os canais mais usados, uma vez que é onde a postagem periódica de conteúdo pode ser feita com total praticidade.

No entanto, é preciso se organizar e ter um calendário de atividades para marcar presença na internet por meio do marketing de conteúdo. Não adianta postar nas redes sociais uma vez por semana e achar que isso é o suficiente para criar a autoridade necessária.

É preciso haver uma determinada frequência, o que vai depender do seu público alvo. Se ele for jovem, por exemplo, é possível fazer algumas publicações ao longo do dia. Afinal, ele costuma acessar com frequência as redes sociais.

Já se o seu público alvo são homens casados com filhos, é provável que eles acessem a internet menos vezes por dia, sendo indicado reduzir o número de posts diários. Se isso não for feito, você pode incomodar o consumidor ao invés de atrai-lo e conquistá-lo.

Dicas para fazer Inbound marketing

Há diferentes redes sociais, algumas mais vantajosas para o seu negócio do que outras. Por isso, não vale a pena usar todas, o melhor é focar no que realmente pode trazer resultados. Então, é preciso eleger duas ou três redes sociais.

Verifique junto ao seu público alvo quais são as que ele mais utiliza e aposte nas suas postagens. Além de criar um perfil, é necessário manter a periodicidade das publicações, sempre postando conteúdo relevante e interessante para a sua audiência.

O blog, que pode estar associado ao seu site, caso tenha um, é outro canal eficiente para apostar no marketing de conteúdo. Ele deve conter vídeos ou artigos que supram a necessidade dos seus clientes em potencial.

Podem ser dicas, tutorias, passo a passo e mesmo reflexões sobre o seu segmento de mercado. Você pode ainda usar as redes sociais para divulgar o blog, convidando os seus seguidores a ler ou ver o seu conteúdo.

Da mesma forma, pode colocar no site ou blog o link para as redes sociais. É importante que elas se comuniquem entre si e que você promova interação com o seu público, podendo ser através de sorteios e brindes ou mesmo enquetes.

Quais são as etapas do Inbound marketing?

Por mais que o seu desejo seja o de agora mesmo fazer uma conta em todas as redes sociais e criar um blog, quem deseja investir no Inbound marketing deve avaliar primeiro quais são as mídias digitais mais relevantes para usar o marketing de conteúdo.

Isto é, deve criar uma estratégia. Além disso, o Inbound marketing deve entrar na estratégia de marketing da sua empresa, não podendo ser promovido de maneira separada. Assim como todos os outros tipos de marketing, eles precisam se complementar.

Com isso, você não corre o risco de perder dinheiro e tempo com ações repetidas ou ineficientes para alavancar os resultados do seu negócio. Para ajudar no processo, o Inbound marketing conta com quatro etapas para efetivar as vendas, formando o funil de vendas.

Resumidamente, são as seguintes:

  • Atrair;
  • Converter;
  • Fechar;

Agora, entenda melhor cada uma delas:

Atrair o tráfego de visitantes

É o momento em que se cria o conteúdo para o público alvo, levando em conta as suas características, interesses e preferência. Por isso, é fundamental conhecer os consumidores que você deseja atingir.

Converter visitantes em leads

Nessa segunda etapa, o objetivo é transformar os visitantes atraídos com o seu conteúdo em leads, para tanto, é preciso obter suas informações, como o seu e-mail.

Lead é o termo usado para indicar o consumidor que ainda não comprou o seu produto ou serviço, mas já fez um primeiro contato, tornando-se um cliente em potencial. Ou seja, quanto mais leads, melhor, contribuindo com a prospecção de vendas.

Mas pode ser preciso oferecer algo para conseguir os seus dados de contato, como conteúdo exclusivo, um e-book ou outro brinde. Assim, é possível, por exemplo, começar uma campanha de e-mail marketing com conteúdo informativo que leve o lead a se efetivar como cliente.

Fechar leads em clientes

Nessa etapa do Inbound marketing, o objetivo é vender o seu produto ao consumidor que você atraiu e depois transformou em lead obtendo o seu contato.

Com o cliente em potencial na palma da mão, basta colocar em ação as técnicas de vendas. Para tanto, é importante convencê-lo que o seu produto ou serviço atende as suas necessidades.

Encantar os clientes para promoverem sua marca

Depois de efetivar a venda, a sua empresa deve fidelizar o cliente. Para isso, ele deve ter ficado satisfeito com a sua aquisição e você pode fechar com chave de ouro as etapas do Inbound marketing com o pós-vendas.

É indispensável ainda manter a comunicação com esse cliente, enviando mensagens a respeito de suas promoções. Em dadas especiais pode ainda oferecer um cupom de desconto ou outro incentivo para que realize uma nova compra.

Como medir os resultados do Inbound marketing?

Outro ponto importante de qualquer estratégia de marketing é o monitoramento dos resultados. Afinal, se eles não forem como o esperado, pode ser necessário fazer ajustes. No entanto, pode levar algum tempo para os resultados aparecerem.

Eles podem demorar mais ou menos, conforme o seu segmento de mercado, perfil do público alvo e canais de comunicação utilizados. Não deixe de considerar todas as variáveis.

Além disso, o objetivo final do Inbound marketing costuma ser a conversão de vendas, pois é apenas vendendo mais que se aumenta a lucratividade do seu negócio. Assim, torna-se viável expandir e fazer investimentos necessários. Mas como medir os resultados?

Para tanto, existem as chamadas métricas de Inbound Marketing, que devem ser acompanhadas. No entanto, é necessário avaliar quais delas são mais coerentes com o seu segmento de mercado.

ROI — retorno sobre investimento

Essa métrica ajuda a empresa a identificar se o investimento em Inbound Marketing está dando lucro ou prejuízo. Afinal, se der prejuízo, significa que a estratégia não está certa.

O cálculo é simples, sendo preciso somar os investimentos feitos e comparar com os ganhos obtidos em um mesmo período de tempo.

Taxa de conversão

Outra forma de medir os resultados é conferindo o número de leads obtidos e a sua conversão em clientes. Por isso, todo esse processo de venda deve ser registrado para posterior análise. Relatórios e profissionais dedicados são essenciais para fazer os devidos diagnósticos.

Conversão do funil

Já que o Inbound marketing possui várias etapas, o funil de vendas, é preciso analisar a taxa de conversão de cada uma. A estratégia pode ter que mudar no início do funil, na hora de apostar no marketing de conteúdo, por exemplo.

Proporção entre CLV e CAC

CLV se refere ao Valor Vitalício do Cliente, ou seja, a média de tempo que um consumidor é seu cliente. O CAC é o Custo de Aquisição por Cliente, indicando quanto se gasta com cada um. Basicamente, o CLV deve ser muito maior que o CAC para a sua empresa não perder dinheiro.

Além disso, de tempos em tempos, vale a pena mudar a estratégia de Inbound marketing. Desse modo, a sua marca acompanha as tendências e sempre se mantém em destaque entre os concorrentes.

Como escolher a melhor agência de marketing digital?

rotadesk No Comments

Empresas que desejam apostar no marketing digital para alavancar os seus resultados encontram uma ampla variedade de mídias sociais para usar. Em meio a tantas opções é normal ficar na dúvida sobre quais são as mais eficientes.

Afinal, geralmente, não adianta “atirar para todos os lados”, sendo o melhor focar nos canais de comunicação digitais realmente vantajosos para o seu negócio. Com isso, economiza-se dinheiro e tempo.

Mas em quais mídias sociais a sua empresa deve investir para atingir os seus objetivos? Uma forma bastante eficiente de promover a sua marca na internet é com a ajuda de uma agência de marketing digital, que pode indicar os caminhos mais apropriados para seguir.

Com uma equipe de profissionais experientes e capacitados, a agência, a partir do estudo do perfil da sua empresa, identifica o que é mais benéfico em termos de marketing digital. Então, como escolher a melhor agência de marketing digital?

Para saber mais sobre o assunto e esclarecer as suas dúvidas, continue lendo este artigo e confira as informações de cada uma das perguntas dos tópicos a seguir:

  • O que uma agência de marketing digital faz para o seu negócio?
  • Quais serviços podem ser contratados?
  • Quais estratégias são elaboradas pelas agências de marketing digital?
  • Quais são os objetivos de sua empresa?
  • Como saber se uma agência de marketing digital vai alavancar seus resultados?

O que uma agência de marketing digital faz para o seu negócio?

Uma agência de marketing digital pode averiguar quais são as melhores estratégias para promover a sua marca na internet. E, a partir disso, se responsabilizar pelo gerenciamento das mesmas.

Diferente do que se pode imaginar, usar a internet de modo profissional é bastante distinto do que utilizá-la para fins pessoais. Ou seja, não adianta responsabilizar um membro da sua equipe pelo marketing digital se esse profissional não tem o conhecimento necessário.

Nesse sentido, uma agência de marketing digital pode cuidar de todo o trabalho que envolve a visibilidade do seu negócio no mundo virtual. Além de contar com profissionais qualificados, outra vantagem dessa contratação é não precisar de uma equipe própria na sua empresa.

Quando se quer apostar nas mídias sociais é necessário ter profissionais dedicados durante todo o expediente. A presença virtual exige dedicação e tempo, pois de nada adianta, por exemplo, fazer publicações nas redes sociais algumas vezes por semana.

Se isso for feito, será apenas desperdício de recursos, uma vez que para obter resultados satisfatórios com a internet é preciso ações diárias.

Vantagens de contratar uma agência de marketing digital

Ao fazer um perfil em uma rede social, por exemplo, a sua empresa está abrindo um novo canal de comunicação com o público.

Isso quer dizer que a sua equipe deve estar preparada para atender essa nova demanda, o que pode ser feito em conjunto com a agência. E mais importante do que a prática diária do marketing digital, uma agência pode pensar quais as melhores estratégias.

Afinal, não existe uma receita única para marcar presença na internet que todas as empresas possam usar. É preciso analisar o seu público alvo, bem como os produtos ou serviços oferecidos para analisar as ações mais assertivas.

Com isso, é possível aumentar a conversão de vendas, através da fidelização dos clientes e da conquista de novos consumidores. Portanto, mais do que apenas colocar a sua empresa na internet, uma agência de marketing digital contribui com a sua lucratividade.

E mais, de acordo com o seu nicho de mercado, a agência pode fazer com que o seu empreendimento esteja sempre inovando, ao seguir as tendências de comunicação virtual. Assim, não corre o risco de ficar para trás, mas sim, sempre à frente da concorrência.

Quais serviços podem ser contratados?

Hoje em dia, é possível se deparar com uma grande variedade de agências de marketing digital, as quais podem disponibilizar uma gama maior ou menor de serviços. O seu envolvimento com as ações também pode variar.

Dessa forma, além de desenvolver as ações de marketing digital, algumas podem colocá-las em prática, participando do dia a dia da empresa. Por isso, ao escolher uma agência para contratar é fundamental saber quais são os serviços que oferece.

E, com isso, avaliar se vai ao encontro das suas necessidades. Em geral, podem ser três as principais situações. A primeira é quando a agência é contratada para fazer todo o trabalho, pois o contratante não possui nenhum profissional responsável pelo marketing.

Em uma segunda situação, a empresa tem um profissional que realiza o básico em termos de marketing e a agência é contratada para fazer a maior parte do trabalho. Na terceira situação, a empresa possui uma equipe dedicada ao marketing, mas a demanda é grande.

Dessa maneira, contrata a agência para ações e atividades específicas. Portanto, na hora de escolher uma agência de marketing digital é necessário ainda averiguar a situação do seu negócio para contratar uma empresa que possa suprir a demanda existente.

Outra dica é conferir a formação profissional do time da agência, se ela possui certificações e quais são as histórias de sucesso dos seus clientes.

Quanto aos serviços normalmente oferecidos pelas agências estão os seguintes:

  • Concepção da estratégia de marketing digital para a sua empresa
  • Desenvolvimento da presença nas redes sociais e o seu gerenciamento
  • Envio de e-mail marketing ou newsletter com periodicidade
  • Criação e manutenção de blogs e sites com desenvolvimento de conteúdo
  • Anúncios nas redes sociais, sites parceiros e nos mecanismos de busca
  • Acompanhamento e análise dos resultados
  • Atualização da estratégia de marketing periodicamente

Quais estratégias são elaboradas pelas agências de marketing digital?

Para definir o melhor planejamento de marketing digital para uma empresa, a agência precisa conhecer muito bem o seu cliente. Assim, deve reunir informações a respeito de mix de produtos ou serviços, público alvo, área de atuação, história, missão, valores, objetivos, etc.

Todas as informações são relevantes, para que a agência de marketing digital trace o perfil da empresa e, a partir daí, possa escolher as mais adequadas estratégias para alavancar os resultados. A agência também deve levar em consideração as tendências.

Atualmente, por exemplo, algumas redes sociais são utilizadas de maneira massiva pelos consumidores. Dessa forma, podem fazer parte da estratégia de marketing digital adotada. Contar com um site é mais uma ação normalmente presente no planejamento de marketing.

Afinal, o site costuma ser a principal vitrine virtual de um negócio que deseja marcar presença de internet. Isso porque, além de ser apenas seu, ao contrário das redes sociais que reúnem diversas marcas, nele é possível exibir todos os demais canais, como links para as redes sociais.

Importância do SEO para o marketing digital

Além dos canais de comunicação já citados, como redes sociais, blogs, sites, e-mail marketing e outros, existe mais uma estratégia que perpassa todas as demais ações. Por isso, atualmente, é sempre muito citada quando o assunto é presença virtual.

Trata-se do SEO que, abrindo a sua sigla, pode ser traduzido como otimização para mecanismos de busca. Ele consiste em fazer com que as publicações, principalmente, de blogs e sites, ganhem maior visibilidade dos internautas.

Para tanto, as técnicas de SEO baseiam-se em regras definidas pelos mecanismos de busca, como o Google, que as cria como forma de qualificar o conteúdo visto pelos seus usuários. Afinal, esse e outros buscadores querem garantir a melhor experiência para o internauta.

Dessa maneira, quanto mais o conteúdo digital de uma empresa seguir essas regras, mais bem posicionado ele fica nos resultados de busca orgânicos. Ou seja, quando o usuário digita uma palavra-chave para encontrar o assunto que deseja.

O uso adequado da palavra chave, aliás, é uma das principais formas de usar o SEO a seu favor. Além disso, é importante postar conteúdo de qualidade e relevante para o seu público alvo, bem como imagens otimizadas e com legendas.

Da mesma forma, o conteúdo de blogs e sites deve ser esteticamente agradável, com parágrafos curtos, listas e subtítulos para facilitar a leitura. Também deve ser coerente, coeso e primar pela correta orografia dos textos

Quais são os objetivos de sua empresa?

Na hora de fazer a concepção da estratégia de marketing digital da sua empresa, a agência deve conhecer quais são os objetivos de sua empresa. Esse é um ponto essencial para o sucesso da sua aposta na internet como um aliado poderoso do seu negócio.

Nem sempre uma empresa que contrata uma agência de marketing digital não possui nenhuma experiência com a presença virtual. Pode se tratar de um negócio que já tem uma estratégia e deseja mantê-la ou mesmo mudá-la para que tenha o retorno desejado.

Pode ainda se tratar de uma empresa que apenas investe em marketing tradicional e, agora, quer ampliar a sua estratégia com a internet. Ou seja, há negócios que estão em diferentes patamares e a agência deve conhecer a sua experiência no mundo virtual.

A partir disso, é preciso traçar os objetivos da empresa, o que a agência pode ajudar a fazer caso os seus gestores não saibam com exatidão o que desejam alcançar com a sua contratação. Por mais que se queira estar na web, deve haver um objetivo por trás.

Importância de ter um objetivo para o marketing digital

O gestor de um negócio que nunca teve presença na internet e, agora, deseja ter um site e perfil nas redes sociais pode querer modernizar a sua marca e ganhar mais visibilidade. Outro pode desejar uma atualização do seu antigo site e blog para serem mais atrativos.

A ideia pode ser a de migrar a empresa para o comércio eletrônico e trabalhar apenas ou ter também uma loja virtual. Empreendimentos podem perceber que poderiam conquistar mais clientes se usassem os canais digitais de maneira apropriada.

Enfim, são muitas as razões para iniciar ou somente fazer a manutenção da sua presença no mundo virtual. E é indispensável a agência identificar esses objetivos para desenvolver uma estratégia realmente condizente com as necessidades do seu cliente.

Além disso, empresa e agência juntas podem criar estratégias para o curto, médio e longo prazo, a fim de avaliar os resultados obtidos e fazer os ajustes necessários com o passar do tempo. Vale lembrar que para tudo isso funcionar é essencial a comunicação.

A agência e a empresa devem estar em constante comunicação, podendo o seu negócio ser representado por um profissional da área da comunicação, pela sua equipe de marketing ou mesmo pelo próprio gestor.

Como saber se uma agência de marketing digital vai alavancar seus resultados?

Mesmo depois de realizar uma contratação ou enquanto estiver fazendo a pesquisa, você pode querer saber como a agência de marketing digital vai alavancar seus resultados. Isto é, quais parâmetros você deve levar em consideração para averiguar se fez uma boa escolha.

A questão é que a resposta para isso depende de diferentes aspectos. De qualquer maneira, é importante ter em mente que os resultados não são obtidos de uma hora para a outra. Ainda mais se a sua empresa ainda não tinha presença na internet pode levar tempo.

Além disso, as agências de marketing digital também costumam oferecer o serviço de acompanhamento dos resultados, o que pode ser feito de várias formas. Existem recursos próprios da internet que ajudam a avaliar os efeitos das ações de marketing.

Com eles, é possível verificar o número de visitantes que o seu site ou blog teve por dia, em que páginas permaneceram mais tempo, etc. Nas redes sociais, os resultados podem ser verificados com um número maior de seguidores e curtinhas nas postagens.

Quais resultados esperar do marketing digital

No médio e longo prazo, de acordo com o seu segmento de mercado, os resultados devem aparecer por meio do aumento da conversão de vendas. Afinal, independente dos seus objetivos ao contratar a agência, no fim das contas, o que sempre se quer é vender mais.

Isso acontece porque a sua marca se torna mais conhecida, já que a internet tem justamente esse poder. Isso é ainda mais vantajoso para negócios que podem atender consumidores de outras regiões do país, além da onde têm a sua sede.

Assim, uma agência de marketing digital vai alavancar seus resultados ao contribuir com a expansão da sua marca. Se você levar em consideração todos esses fatores, são maiores as chances de escolher a melhor agência de marketing digital para o seu empreendimento.

O que é o Black Hat SEO Como isso afeta o site thumb

O que é o Black Hat SEO? Como isso afeta o site?

rotadesk No Comments

O que é o Black Hat SEO Como isso afeta o site

Para que o Google possa oferecer aos seus usuários resultados que combinem com suas pesquisas, as páginas de seu site devem seguir orientações para que o os robôs que rastreiam os sites consigam entendê-los e indexá-los ao Google. Sendo assim, é fundamental que seu site esteja de acordo com as diretrizes para webmasters do Google.

O principal pilar dessas diretrizes é criar um conteúdo único e original voltado para os usuários. Isso quer dizer que, para que o Google considere que sua página seja de qualidade, o usuário deve estar encantado com o conteúdo que ela oferece e não, os robôs que fazem o rastreamento.

O Google não aprecia conteúdos voltados unicamente para os mecanismos de buscas. Ainda menos, conteúdos com o propósito de enganar os mecanismos.

O que é o Black Hat em SEO?

O termo Black Hat em seu fundamento significa ações que visam explorar brechas nas seguranças para conseguir um benefício próprio ou apenas por um ato malicioso.

Técnicas conhecidas como Black Hat em SEO são recursos utilizado como uma maneira de “burlar” os algoritmos dos mecanismos de buscas para conseguir melhorar os resultados orgânicos. Não é preciso explicar porque obviamente essas técnicas são condenadas pelos mecanismos de buscas.

Black Hat

Webmasters que são pegos utilizando essas técnicas de Black Hat têm seus sites penalizados pelo Google e, dependendo da situação, os sites podem ter seu conteúdo removido do Google. Segundo a pesquisa do Bright Edge, 51% de todo tráfego de um site tem origem de buscas orgânicas. Portanto, não seria uma boa ideia ter seu site bloqueado pelo Google.

Com o passar dos anos o Google tem alterado a forma de categorizar os sites nos resultados das buscas e amenizando a possibilidade de que práticas de Black Hats obtenham sucesso. Como por exemplo, dando menor importância as meta keyword e meta descriptions nos fatores de rankeamento, prevenindo práticas de spam de palavras-chave nos textos.

Exemplos de Black Hat

O Google sugere que a principal questão que deve ser perguntada para geração de conteúdo voltado para os mecanismos de buscas é “Isso ajudará meus usuários? Eu faria isso se os mecanismos de pesquisa não existissem?”. Ainda que hajam muitos site que buscam genuinamente responder essas questões, muitos profissionais buscam dar um “jeitinho” de trapacear gerando conteúdos unicamente para conseguir enganar os rastreadores do Google.

Alguns exemplos de Black Hat em SEO são os seguintes:

  • Geração de conteúdo automatizado
  • Esquemas de aquisição de links
  • Páginas sem conteúdo único
  • Cloaking
  • Textos/links ocultos
  • Plágios
  • Páginas com conteúdos maliciosos
  • SPAM de palavras-chave
  • Fazenda de Links
  • etc.

Caso identifique algum site que possam estar violando alguma dessas políticas, você pode enviar ao Google uma denúncia.

Como saber se estou fazendo Black Hat?

Ainda que a intenção de seu site não seja esta, é possível que seu site esteja sendo punido pelo Google sem sua conscientização por violar as diretrizes de Webmasters. Uma queda abrupta no tráfego orgânico de seu site, sem que haja nenhuma justificativa plausível, pode indicar que seu site tenha recebido uma ação manual. O próprio Google manda uma notificação por e-mail caso detecte alguma violação em seu site.

Para verificar se o seu site está sendo punido, na ferramenta de Webmasters do Google há uma seção de “Ações Manuais” em que é possível identificar as penalizações que o Google aplicou em seu site. Assim, sendo identificada uma ação do Google é possível corrigi-la revertendo os passos sugeridos pelo Google e fazendo um pedido de reconsideração para que a equipe revise o seu site novamente.

Ações Manuais

White Hat SEO

Práticas de White Hat, ao contrário do Black Hat, são ações que são altamente recomendadas pelos mecanismos de buscas. Esta é a forma mais segura de se conseguir gerar tráfego orgânico para seu site sem que ele possa sofrer alguma penalidade.

Estar atento as atualizações que o Google publica sobre as mudanças em seu algoritmo é uma boa maneira de conseguir destacar seu site nas pesquisas orgânicas. Recentemente o Google passou a considerar a velocidade de carregamento do site em dispositivos móveis como um fator de rankeamento, como discutimos no Google Speed Update.

A segurança de seu site também pode ser um fator a se considerar para conseguir subir algumas posições no Google. Certificar-se de que seu site oferece segurança aos dados e do computador do usuário é fundamental para que seu site não seja penalizado. Por isso, o Google tem incentivado a utilização de páginas com segurança em SSL, como o “S” em HTTPS, dando um pequeno empurrãozinho nas posições de páginas com esse certificado de segurança.

A usabilidade é outro fator que pode contribuir drasticamente no aumento de visibilidade orgânica de seu site. Um site de fácil navegação permite que os usuários encontrem rapidamente o que estava procurando, sendo assim, o conteúdo que o usuário está procurando devem estar apenas a alguns cliques de sua página principal. Isso não só beneficia os usuários como os rastreadores do Google conseguem navegar rapidamente em seu site.

 

Qual a diferença entre Inbound vs Outbound Marketing ?

rotadesk No Comments

Inbound x Outbound

 

Você já ouviu o mantra Marketing é a alma do negócio? Seu site pode ser excelente e independentemente de quão espetacular seu programa de negócios é, eles não são serão reconhecidos se ninguém ouve falar deles. Marketing é o coração dos negócios. Mas você sabia que existem dois tipos de publicidade? Chamamos esses dois métodos de marketing de Inbound e Outbound. Seu método é definido de acordo com as necessidades de cada empresa.

No vídeo abaixo falamos um pouco mais sobre as diferenças do Inbound e Outbound, diretamente do maior evento do mundo sobre como criar ímãs pela internet.

Enquanto no Inbound os clientes já estão procurando ativamente por um produto ou serviço. No Outbound são as empresas quem sobem em montanhas e gritam: Hey gente, temos uma solução especial para você aqui. Veja como este produto é bom. Quem gostaria de comprá-lo? A maioria das pessoas que ouve esse grito não vai ficar interessada com o produto. Na verdade, alguns podem ficar tão aborrecidos que intencionalmente decidem manter a empresa fora de que cogitação. A esperança da publicidade online Outbound, no entanto, é que uma das milhares de pessoas que ouvem esteja interessada o suficiente para comprá-lo.

Desde a diferenciação entre um megafone e um ímã, considere o marketing de entrada e de saída como uma metáfora. Existem várias maneiras de chamar a atenção dos clientes. Blogar e Blogar é uma boa maneira de direcionar o tráfego para o site da empresa, mesmo quando as postagens não estão diretamente relacionadas ao seu produto. Pesquisas demonstram que a maioria dos clientes realiza 70% de sua pesquisa de itens na internet, antes de formar a decisão de compra.

Um blog permite que você ofereça recursos valiosos enquanto faz essa pesquisa. Isso cria consciência de marca e pode até mudar a trajetória das empresas. Quanto tempo você gasta percorrendo GIFs, vídeos e placas do Pinterest ou Facebook? Meu palpite é de que é muito. Mais do que gostaria, talvez. Por outro lado, não é ótimo para as empresas poder de alguma forma usar o dilúvio de vídeos e memes virais para chamar a atenção de seus clientes? Essa é a ideia por trás da publicidade do conteúdo (Inbound). Em vez de tentar ativamente vender seu item, você cria conteúdo que as pessoas querem compartilhar. O desafio é criar algo tão bom algo que se espalha por si só.

 

Inbound vs Outbound Marketing Infografico

 

Google altera (novamente) palavras-chave de correspondência exata thumb

Google altera palavras-chave de correspondência exata

rotadesk No Comments

Google altera (novamente) palavras-chave de correspondência exataA partir de Outubro de 2018, palavras-chave de correspondência exata no Google Ads não terão mais o sentido literal. Recentemente o Google publicou em seus comunicados que está alterando a forma de que palavras de correspondência exata ativarão os anúncios adicionando ainda mais variações de palavras. Tentando se aproximar ainda mais das intenções dos usuários ao realizar uma pesquisa, a empresa tem procurado se aproximar mais da semântica das palavras do que o sentido literal.

Esse trajeto começou desde 2012 quando o Google implementou uma função opcional de adicionar variações aproximadas (plurais, acentuações, acrônimos, etc.) em palavras-chave de correspondência exata e de frase. Em 2014 isso já não era mais uma opção, essas variações já eram adicionadas por padrão. No ano passado o Google deu mais uma afrouxada em seus algorítimos e a ordem das palavras, conjunções, preposições passaram a ter menor importância na forma que os anúncios seriam ativados.

O que o Google está mudando nas correspondências exatas?

Em 2017 o Google percebeu que pessoas utilizam mais de 150.000 maneiras para pesquisar desodorantes. Para os anunciantes de links patrocinados não seria muito prático alcançar esse público. A inteligência artificial do Google agora buscará entender a intenção da busca dos usuários e decidir se a palavra-chave de correspondência exata combina com essa pesquisa. Isso significa que palavras implícitas, paráfrases e outros termos com significados idênticos passarão a ativar anúncios nesses tipos de palavras-chave.

Utilizamos como exemplo a [Catedral da Sé] como uma palavra de correspondência exata, caso a pesquisa seja feita por “Catedral da Sé em SP”, o anúncio poderá ser ativado por palavras implícitas.  O anúncio também poderá ser ativado caso seja feita uma pesquisa por “Igreja da Sé”, pois indicaria uma paráfrase. Caso o algoritmo entenda que a intenção da pesquisa é a mesma da palavra-chave, como por exemplo “Igreja da praça da Sé”, o anúncio também poderá ser ativado.

Pesquisa exata explicação

Vale destacar que as demais correspondências de palavras-chave não sofrerão essas mudanças, como as de frase, amplas e amplas modificadas. O Google estima que essas mudanças afetem o alcance das palavras de correspondência exata em cerca de 3%.

Como essa mudança irá afetar as minhas campanhas?

A cada segundo são feitas aproximadamente 63.000 pesquisas somente no Google, você pode conferir no site da Internet Live Stats. E cada uma dessas pesquisas são compostas por palavras-chave que transpõe a intenção do pesquisador. Assim, palavras-chave fazem parte de uma base importante para os profissionais que investem em marketing de mecanismos de busca. São elas que determinam o volume de buscas de um determinado produto na internet e quantos passos a procura está relacionado à uma venda fechada.

Segundo a estimativa do Google, a adição de mais termos de variações às palavras-chave de correspondência exata dessa atualização afetaria cerca de 3% na abrangência. Anunciantes que não monitoram constantemente seus termos de pesquisa e adicionando frequentemente suas palavras-chave podem ver um aumento da performance de suas campanhas, já que o Google cuidará de exibir seus anúncios em pesquisas com semânticas semelhantes as suas palavras-chave. Além disso, é possível que haja um aumento de resultados em campanhas que não se apoiam na precisão exata de suas palavras-chave.

Agora, caso você tenha uma restrição de orçamento muito grande e prefere focar em pesquisas que realmente vão trazer resultados ou, trabalha com um nicho de mercado que precise utilizar palavras de cauda longa para alcançar seu público essa atualização não vai ser uma boa notícia. Seus anúncios vão estar sendo ativado por mais pesquisas em que o algoritmo do Google estará considerando como relevante, assim gastando seu orçamento.

O que fazer para se preparar?

Revise suas palavras-chave

Você pode revisar suas palavras começando com as de correspondência exata e verificar sua performance. Tente identificar dentro do contexto das alterações que o Google estará mudando se a relevância delas irão continuar ou estarão ativando um novo significado. Utilizando o exemplo da catedral da sé, caso o usuário esteja no Brasil, a referência é válida, porém, em Portugal pode ativar a pesquisa para “Catedral da Sé de Lisboa” por palavras implícitas.

Depois verifique se você possui alguma outra palavra-chave que possa estar duplicada por meio de palavras implícitas, paráfrase ou por intenção de pesquisa.

Remova pesquisas desnecessárias com palavras negativas

No caso da Catedral da Sé de Lisboa você pode contornar a situação negativando o termo “Lisboa”, assim você afunila as pesquisas sobre Catedral da Sé. O mesmo é válido para novas pesquisas que surgirem a partir de suas palavras-chave de pesquisa exata, busque por palavras irrelevantes ao seu negócio e negative-as.

Cuidado com SKAGs

Grupos de anúncios com apenas uma única palavra-chave ou SKAGs ativarão novas pesquisas a partir da nova atualização. O propósito dos SKAGs é focar em buscas de termos que correspondem exatamente às palavras-chave, assim tendo uma maior chance de conversão. Verifique se eles poderão estar competindo com outras palavras-chave do grupo de anúncios e faça ajustes em suas palavras-chave.

ClickCease - Ferramenta de Bloqueio de Cliques Inválidos thumb

ClickCease – Ferramenta de Bloqueio de Cliques Inválidos

rotadesk No Comments

ClickCease - Ferramenta de Bloqueio de Cliques InválidosSegundo o diretor de anti-fraude do Bing, mecanismo de busca da Microsoft, os clique fraudulentos estão crescendo a uma taxa de 50% ao ano. Isso significa que robôs podem estar sendo programados para clicar em seus anúncios para gastar o orçamento de suas campanhas.

É possível identificar de diversas maneiras as fraudes de cliques. Se você acompanha diariamente sua conta de anúncios, um aumento anormal do custo em um determinado período pode indicar fraude de cliques. Também, caso você tenha acesso aos IPs acessados no site, é possível identificar pela quantidade de acessos.

Outra maneira mais efetiva de proteger seu dinheiro de robôs ou concorrentes que gastem o orçamento de sua conta são ferramentas de bloqueio de cliques fraudulentos. O ClickCease é uma ferramenta que busca cumprir esse propósito, impedindo que seus anúncios continuem sendo ativados para esses usuários.

Quais são os tipos de cliques fraudulentos mais comuns?

Fraudes de cliques

  • Múltiplos cliques humanos – Esses são feitos por concorrentes ou por pessoas que procuram esgotar o orçamento do anunciante. A pessoa clica repetidamente nos seus anúncios entrando e saindo da página para seu saldo se esgotar e os anúncios deles ocuparem a sua posição.
  • Cliques de robôs – São programações ou script feitos para clicar em seus anúncios automaticamente. Eles simulam navegações de browser, clicando em anúncios sem intenções de compra.
  • Mobile – Fraudes mobiles estão se tornando mais comum, isso porque são mais difíceis de serem detectadas, pois estão “maquiadas” em forma forma de aplicativos. O usuário comum baixa um jogo em seu celular, por exemplo, e cada clique que o usuário faz ao jogar, estaria clicando em um anúncio escondido sem saber.
  • Click Farms ou Fazenda de Cliques – São operações em larga escala especialmente feitas para clicar em anúncios e gastar orçamento de publicitários. Uma grande quantidade de pessoas são contratadas com o objetivo de clicar em anúncios para o orçamento de campanhas.

Como funciona a ferramenta de proteção de cliques fraudulentos?

Proteção de Cliques

O ClickCease funciona através da instalação de um script em todas as páginas de seu site e vinculando o código de acompanhamento em ferramentas de anúncios, como o Google Ads e o Bing, por exemplo. Assim, o ClickCease rastreia a ação dos usuários em seu website coletando dados e informações de atividades. Cada ação que os usuários fizerem é analisada pela ferramenta e classificada em ações válidas ou fraudulentas.

Ao ser identificado uma ação fraudulenta, a ferramenta grava o IP do usuário e registra automaticamente na lista negra de IPs no Google Ads. Assim, seus anúncios deixam de ser exibidos para aquela pessoa. Caso seja um robô que esteja alternando de IPs para gastar o orçamento de suas campanhas, o ClickCease detecta a faixa de IPs e envia para o Google fazer o bloqueio automaticamente.

O Google Ads tem uma detecção automática de cliques fraudulentos quando seus anúncios estão sendo clicados por robôs e retorna o dinheiro na forma de créditos, porém, a ferramenta não é cem porcento eficiente e permite que você envie uma solicitação de análise de cliques fraudulentos. Além de bloquear IPs e dispositivos fraudulentos, o ClickCease envia automaticamente solicitações quando cliques fraudulentos são identificados em sua campanha.

Ferramenta do ClickCease

ClickCease Layout

No dashboard do ClickCease há um cenário geral de suas contas de links patrocinados. O gráfico mostra a quantidade de cliques em seus anúncios, visitantes e IPs foram bloqueados. É possível ter uma visibilidade da economia de custos que a ferramenta gerou colocando o CPC médio de sua conta.

A ferramenta também grava de maneira simplificada as ações que os usuários fizeram em seu site ao clicar em seus anúncios. A gravação é feita como se tivesse uma câmera em frente ao computador e as ações dos visitantes são gravadas a medida em que navega em seu site.

Além disso a ferramenta gera uma análise das fraudes que foram identificadas em sua campanha. Dispositivos fraudulentos, redes VPN, bloqueios manuais são mostrados em forma de gráfico com a quantidade de bloqueios.

Quanto custa o ClickCease?

ClickCease preços mensais

O clickcease tem uma versão de teste por 7 dias ao fazer o cadastro na plataforma. Porém, a versão básica custa 15 dólares/mês por domínio e a versão padrão tem o valor de 50 dólares/mês por domínio. Os planos anuais custam 12 doláres/mês no plano básico e 50 dólares/mês para o padrão.

Vale a pena investir no ClickCease?

Caso sua conta tenha um grande investimento em anúncios do Google Ads ou Bing, o ClickCease pode ajudar a economizar custos bloqueando cliques fraudulentos. Anunciantes que possuem uma boa parcela de seu tráfego online vindo de campanhas pagas podem evitar perder clientes por conta de cliques fraudulentos que esgotaram o orçamento de suas campanhas.

A Rotamáxima utiliza este tipo de ferramenta, combinada com outras tecnologias para ajudar os clientes em altos investimentos a obterem um tráfego mais qualificado.

Google Display, como otimizar o design para uma melhor conversão thumb

Google Display, como otimizar o design para uma melhor conversão

rotadesk No Comments

Google Display, como otimizar o design para uma melhor conversão

Campanhas são criadas e colocadas em práticas frequentemente, mas nem todas atingem o resultado esperado. No entanto, durante esse ciclo de tentativa e erro, existe a adaptação e melhoria.

Apenas configurar a campanha e executar não traz tanto resultado, pois a otimização é algo essencial para ter uma visibilidade mais relevante e o maior número de conversões possível.

Por volta de 50% das empresas reportam que o design escolhido de um anúncio influencia diretamente em quão bem-sucedido o mesmo é, até porque o design influencia a primeira impressão que uma pessoa tem como anúncio, ou seja: influencia o modo que o consumidor percebe a sua marca.

Então, vamos dar uma olhada em como você pode melhorar seu jogo de design aplicando cinco princípios-chave do design visual para criar anúncios gráficos de alto desempenho

Estrutura do Anúncio

Um bom anúncio tem como base a sua estrutura e há práticas já bem difundidas sobre como você deveria mapear seu anúncio.

De acordo com a empresa do ramo de Marketing Digital, Interactive Advertising Bureau, os anúncios devem ser diferenciáveis do conteúdo normal do site e a área do anúncio deve ter uma borda bem definida para não ser confundido com o restante do site.

Eles inclusive falam sobre como o tamanho do anúncio deve ser flexível, pois as pessoas podem estar visualizando o anúncio em telas de diferentes tamanhos.

Os tamanhos de ads oferecidos pelo Google são vários, desde anúncios de meia página a grades banners para dispositivos móveis.

Mas há três principais tamanhos quando se fala em termos de desempenho. São eles: 300 x 250 (retângulo médio), 336 x 280 (retângulo grande) e 728 x 90 (cabeçalho).

Você pode ver no gráfico como eles são e os outros dois tipos de anúncio que, junto com os anteriores, formam os 5 tipos de anúncio com maior desempenho.

Display

Levando isso em consideração, você deve possuir uma estrutura forte e flexível para se adequar a cada um dos formatos, atentando-se especialmente aos tamanhos de melhor desempenho.

Para fazer isso, pense nos elementos fundamentais de um anúncio gráfico. Cada anúncio deve conter quatro componentes principais:

  • Seu logotipo ou nome da empresa
  • Uma proposta de valor
  • Uma imagem ou representação visual do seu serviço
  • Um botão de CTA

A estrutura dos elementos do anúncio cabe a você fazer, desde que o CTA e a proposta de valor estejam em destaque e os elementos possam ser reorganizados para que se adequem a diferentes tamanhos de anúncio.

Cores

No design, a atenção a cores é de fato imprescindível, pois a cor é usada para atrair a atenção de pessoas e evocar emoções.

É importante lembrar a comum associação que muitos fazem de cores às marcas.

É completamente normal, e bastante comum, inclusive, que você associe a cor vermelha à Coca-Cola, por exemplo.

A psicologia por trás da cor é interessantíssima e algo que você precisa prestar atenção ao criar anúncios.

Homens e mulheres, por exemplo, costumam ter preferências de cores bem diferentes, de acordo com um estudo na UX Planet.

Eles chegaram à conclusão de que a cores mais populares entre os homens são azul (57%) e verde (14%); enquanto as mulheres estão em azul (35%) e roxo (23%).

O resto das cores e quão favoritadas elas são de acordo com o gênero podem ser vistos no gráfico abaixo:

Cores por gênero

Fonte: UX Planet

Tendo em vista isso, você pode decidir usar uma paleta de cores um pouco diferente, considerando o público alvo de sua campanha e possivelmente aumentando o nível de atenção que se voltará para a mesma.

Tipografia

Tipografia refere-se a como se organiza a escrita, ou as fontes no caso de hoje em dia; no entanto, é mais do que uma questão de estética.

A tipografia estratégica pode facilitar o processo de leitura e criar interesse em seu produto ou serviço anunciado.

O mais importante é ter uma hierarquia tipográfica clara e legível. Não importa o quão incríveis sejam seus recursos visuais se o público não conseguir ler ou entender sua mensagem.

Uma imagem com estilo pode chamar a atenção sim dos visitantes, mas se não há uma mensagem por trás da mesma, dificilmente atrairá um clique.

Hierarquia Tipográfica

Note como seus olhos foram a princípio para o grande nome no topo antes de qualquer outra coisa.

Esse é o exemplo mais simples e notável da importância da Hierarquia Tipográfica. Você pode usar, inclusive, um enorme texto que passa a mensagem e deixar ao lado a marca.

Lembre-se que o que é ensinado aqui não é excludente, então além disso, também pode-se usar uma paleta de cores referente ao seu público alvo no anúncio.

Menos é mais

Esta não é uma ideia original e se aplica em uma enorme gama de diferentes contextos.

Estes anúncios não vão carregar tudo sobre sua marca e produto ou serviço e, portanto, você terá que ser o mais sucinto possível.

Sua mensagem terá que ser direta e clara, de forma que seja entendida na primeira leitura.

Design minimalista

Fonte: Imagine por www.jvm.com

Ou seja, ao passar rapidamente os olhos sobre o seu anúncio, a pessoa tem que sentir curiosa sobre sua ideia, criar um senso de valor sobre ela e ainda se sentir inclinado a clicar nele.

Isso é melhor atingido com um anúncio que diga muita coisa com uma menor quantidade de caracteres e sem poluição visual.

Originalidade e Funcionalidade

Não faça nada sem propósito algum, pois isso remete a amadorismo e é percebido pelos internautas.

Imagens, por exemplo, nunca deveriam ser colocadas em um anúncio pelo único propósito de se ter uma imagem ali.

Obviamente, imagens são de extrema importância, principalmente quando usadas corretamente, mas como tudo, podem causar o efeito contrário quando mal utilizadas.

Uma imagem deve ser a menos genérica possível, sendo algo produzido por você, como fotos do seu escritório ou produto ou algum representante direto do seu serviço, como fotos de um destino de turismo, por exemplo.

Banner Coca-cola

A Coca-cola coloca aqui uma imagem das latas predominantemente vermelhas de seus novos produtos, assim associando a qualidade deles ao produto carro-chefe.

Além disso, a imagem é um demonstrativo da novidade, que é o produto da própria marca, junto com a logo. Nada que está aí é mais que o necessário.

Pessoas costumam se lembrar mais costumeiramente do que elas veem em relação ao que elas ouvem, reforçando que uma boa imagem vai ser ótimo para sua marca e uma imagem ruim vai ser extremamente desvantajoso.

Imagens também devem ter um propósito claro que se conecte com a mensagem, do contrário, falharão em transmitir realidade na proposta.

Como planejar o design da sua Landing Page de acordo com o conteudo thumb

Como planejar o design da sua Landing Page de acordo com o conteúdo

rotadesk No Comments

Como planejar o design da sua Landing Page de acordo com o conteudo

Com a presença de várias marcas é cada vez mais comum na internet, a competição por alguns segundos a mais de atenção.

As empresas acabam tendo que lutar e se esforçar frequentemente para evoluir de forma que consigam sempre passar de forma clara sua ideia e obter os melhores resultados.

E este é o grande objetivo das técnicas de otimização das Landing Pages.

O que é uma Landing Page?

O termo Landing Page costumava ser usado para definir a página por onde um internauta passava para acessar o site.

No caso, a página onde ele “cai primeiro” depois de clicar em um link, daí o nome, que em inglês, seria traduzido como “página de aterrissagem”.

Não é à toa que ao checar o Google Analytics em inglês, você verá que Landing Pages é apresentada como uma das dimensões de comportamento.

Em Português, no entanto, o nome que o Google usa para defini-las é outro, um pouco diferente do termo em inglês: “Páginas de destino”.

Este conceito acaba ficando um pouco vago, já que basicamente pode ser usado para qualquer página de um site onde você caia após clicar em um link em outro blog ou em um mecanismo de busca, por exemplo.

No entanto, o termo acabou caindo em desuso para este propósito, hoje em dia sendo usado para algo diferente.

Em Marketing Digital, quando criamos uma página cujo único objetivo é a conversão, esta página é o que nós chamamos de Landing Page.

O design da Landing Page é feito pensando na comunicação da sua oferta aos visitantes da página.

Este processo de comunicação é para que você influencie as pessoas a perceberem o que você deseja que elas percebam.

Utilizar-se de uma área em branco para organizar a página com um botão Call-to-Action em contraste é uma das maneiras fazer isso.

No final, no entanto, o apelo visual é apenas uma das facetas do design da página.

Devemos prestar atenção ao jeito que os elementos da Landing Page interagem e como eles fluem juntos para determinar se a página vai ser bem-sucedida em engajar os visitantes.

É aí que entra a Hierarquia Visual.

Hierarquia Visual

Hierarquia visual é a ordem na qual um usuário processa informações em uma página.

A sua função no design da interface do usuário (UI) é permitir que os usuários entendam as informações facilmente.

Isso é feito ao atribuir-se diferentes características visuais a seções de informações, como as fontes maiores em um cabeçalho, por exemplo.

Por meio do emprego da hierarquia visual, um designer pode influenciar o que os usuários perceberão como estando mais acima na hierarquia.

Influenciando a percepção dos usuários

As características visuais que um designer pode usar para influenciar a percepção dos usuários sobre as informações são:

  • Tamanho: quanto maior o elemento, mais atenção ele atrairá
  • Cor: as pessoas são atraídas por cores ousadas e contrastantes.
  • Contraste: mudanças de cor chamam atenção
  • Alinhamento: chame atenção alinhando ou quebrando o alinhamento
  • Repetição: repetir estilos pode dar a impressão de que o conteúdo está relacionado
  • Proximidade: elementos próximos também aparecerão relacionados
  • Espaço em branco: mais espaço ao redor dos elementos atrairá o olhar para eles
  • Textura e estilo: texturas mais ricas atraem mais atenção do que as planas

Todos os elementos de design acima são importantes, mas obviamente, como tudo, o sucesso depende do bom emprego do conhecimento.

Aliando este conhecimento aos estudos de rastreamento ocular, nós podemos criar poderosos designs que sempre guiarão os olhos dos visitantes para onde nós queremos.

Estilos de Leitura

Acompanhando os estudos de rastreamento ocular, muito se descobriu sobre como os olhos se movem em determinados contextos.

No caso da leitura, foram encontrados dois padrões mais dominantes.

O padrão F

O Nielsen Norman Group compilou vários relatórios e ainda continuam a ser citados quando o assunto é rastreamento ocular.

A verdade é que foram os estudos deles que trouxeram uma série de descobertas que acabaram por ser úteis em vários ramos.

Uma das mais importantes descobertas do grupo foi o chamado “F-Pattern” ou “Padrão F”.

Os usuários costumam começar lendo em um movimento horizontal que costuma se originar na parte superior da área do conteúdo, que seria o movimento que dá origem à barra inicial do “F” que se desenhará pelo movimento dos olhos.

Em seguida, os usuários movem a página um pouco para baixo e leem em um segundo movimento horizontal que geralmente cobre uma área menor que o movimento anterior.

Esta segunda leitura de menor extensão é o que formaria a barra inferior do “F”.

Por fim, os usuários verificam o lado esquerdo do conteúdo em um movimento vertical, fazendo assim o último elemento do “F”, seu tronco.

Essa imagem é referente à pesquisa citada anteriormente e demonstra o movimento dos olhos uma página de um site.

As cores demonstram o seguinte:

  • Vermelho: Área mais visualizada, onde o olho se manteve por mais tempo.
  • Amarelo: Área visualizada, mas com menor fixação.
  • Azul: Área pouco visualizada e com pouca fixação.
  • Cinza: Área pouco visualizada e com nenhuma fixação.

Comecemos da premissa de que o movimento dos olhos, em geral, começa pelo canto superior esquerdo e, a partir daí, desliza pela página.

Ao posicionar os elementos que você considera mais importantes em uma página no padrão F acaba por garantir que tais elementos sejam vistos.

O Padrão Z

Páginas menos pesadas em texto ou de organização mais solta tendem a provocar um movimento menor nos olhos.

Este movimento, também mais solto e mais direto, é o que chamamos de “Z Pattern”, ou Padrão Z.

Este padrão ocorre quando a simplicidade é uma característica notável na página, que geralmente tem o Call-to-Action como foco principal.

Esse foco na simplicidade torna o padrão Z especialmente adequado ao design da página de destino, no qual você deseja que um foco singular atraia as pessoas e incentive-as a agir.

O Padrão Z pode ser igualmente aproveitado, contanto que você coloque sua chamada em ação ao longo do caminho Z para garantir que o visitante o veja; levando-o assim a conseguir mais conversões.

Padrão Z

Assim como o Padrão F, o formato do padrão Z não precisa ser um exato Z.

Mas prestando atenção na imagem acima, podemos ver, a partir dos números, por onde os olhos dos visitantes vão passar em uma página mais simplificada.

O que significa que você pode distribuir as informações ao longo deste Z, considerando a ordem dos pontos onde os olhos se fixam.

Ou seja, um texto mais longo, como um título, pode ficar ao longo do círculo 1 chegando até onde se encontra o círculo 2. Uma imagem ilustrativa pode ficar no 3 e o botão CTA ao longo da Seta até a área do círculo 4.

Em geral, siga a seguinte linha:

  • A linha horizontal superior inclui os principais componentes nos quais você deseja que os visitantes se concentrem primeiro.
  • A linha diagonal deve apresentar qualquer informação que leve ao seu botão de CTA, então ela vai servir como um conversor da informação na linha anterior que transforma a ideia em ação.
  • A linha horizontal inferior deve destacar o CTA em qualquer ponto ao longo desta linha, com o visitante já informado ou trabalhado nas linhas anteriores.

 

O que analisar quando o tráfego orgânico despenca thumb

O que analisar quando o tráfego orgânico despenca?

rotadesk No Comments

Não é algo incomum lidar com queda de fluxo. Sendo um profissional de Marketing Digital, você deve ter visto isso pelo menos algumas vezes. Se é a sua primeira vez, não se desespere.

O que analisar quando o tráfego orgânico despenca

Alguns profissionais da área são contratados especialmente para reverter quadros de queda de tráfego.

Muitas vezes, a queda pode acontecer de tal maneira que fica praticamente impossível determinar a causa.

Os motivos que provocam a queda do fluxo podem ser muitos, desde a queda de interesse do público a mudanças de algoritmos em sistemas de busca.

Portanto, existe muito a se estudar e analisar antes de chegar a alguma conclusão sobre o que causou a diminuição das visitações no seu site.

Se você está passando por alguma situação de perda de tráfego, entenda agora o que deve ser analisado ao perceber a queda de tráfego orgânico no seu site.

Possivelmente, com estas novas informações, não só você terá mais chances de entender a causa do problema, como também terá chance de revertê-lo.

O que checar primeiro?

Existem algumas diretrizes que você pode tomar assim que você percebe a queda de tráfego.

Estas podem ser o caminho mais rápido para a identificação do problema.

Google Search Console

Google Search Console

Essa é a primeira coisa que qualquer profissional da área vai te falar quando você comentar sobre o problema que está tendo.

E isso não é à toa, já que esta ferramenta tem informações sobre tudo que envolve o tráfego no seu site.

Não só isso, como também informa coisas que envolvem seu site indiretamente e acabam por afetar o seu tráfego orgânico.

Não só é recomendado que você olhe o Google Search Console assim que você notar uma queda no tráfego, como também é aconselhável que você frequentemente dê uma checada nas informações disponíveis, fazendo assim um trabalho de prevenção.

Informação nunca é demais, principalmente quando é uma informação tão beneficial para o seu negócio.

No Google Search Console, você vai encontrar coisas como:

  • Mensagens do Google (sobre ações manuais, por exemplo)
  • A lista de páginas internas e externas vinculadas ao seu site
  • Erros do site
  • A taxa de rastreamento e quando o Googlebot acessa seu site
  • As pesquisas de palavras-chave que ajudam seu site a aparecer nas SERPs

Isso deve ser mais que o suficiente para que você possa deduzir as coisas mais básicas que possam justificar o ocorrido.

Você pode, por exemplo, checar o ranking das palavras chave e ver se houve mudança na frequência de seus usos.

Ou por exemplo, ver se há algum problema no rastreamento dos bots, acusando talvez um erro de código em sua plataforma, no caso de a queda coincidir com uma atualização.

Existem uma série de coisas que podem ser deduzidas diretamente dos dados mais básicos.

Tente estudá-los da forma mais minuciosa possível antes de qualquer outra coisa. Dificilmente eles terão nada para informar.

Mudanças no algoritmo do Google

Uma coisa que você tem que entender é que a mudança no algoritmo não é necessariamente um evento incomum ou notável.

Na verdade, mudanças de algoritmo, sim, seria um evento grande, mas mudanças em um algoritmo em funcionamento são atualizações que mudam alguns detalhes sobre como o algoritmo funciona.

Estas mudanças, no entanto, podem sim causar diferenças significativas na forma em que funcionam certos mecanismos básicos que previamente contribuíam para o tráfego no seu site.

Ou seja, outro ponto rápido e simples a ser investigado é se houve qualquer forma de alteração do algoritmo e uma vez descoberta a mudança, é necessário entendê-la e vê se ela poderá afetá-lo.

Se você estiver sempre a par das mudanças, não terá que trabalhar com controle de prejuízo quando as suas visitações diminuírem.

De qualquer forma, se for o caso do seu tráfego cair na época da mudança no algoritmo do Google, você já tem um caminho para remediar a situação.

Há uma série de sites e organizações que frequentemente acompanham o funcionamento dos mecanismos de busca, como o SEM Rush ou o MOZ.

Concorrência

Concorrência

Esta é uma explicação bem simples, mas não deixa de ser bem possível e real.

Um novo site de conteúdo relevante e similar vai competir diretamente com o seu e portanto, pode acabar dividindo o número de visitações com você.

A concorrência é uma parte natural do mercado e ela pode vir desde novos competidores a antigos que resolveram, mais recentemente, investir com força.

Esse efeito da concorrência pode ser mais sentido principalmente quando seu tráfego orgânico depende de um pequeno conjunto de palavras chave.

Uma vez que você concentre seu conteúdo inteiro em uma única coisa, fica maior a chance de uma concorrência especializada chamar tanta atenção quanto você. Às vezes até mais.

Se o caso for mesmo o de concorrência, a solução é conhecida. Melhore. Se desenvolva assim como o seu concorrente se desenvolveu.

Existem páginas que não deveriam mais estar ativas. Lembre-se que o Google prioriza conteúdos de valor.

Esforce-se para criar conteúdo de mais valor, aumentando o tamanho e a qualidade do seu conteúdo de uma forma chamativa.

Estude seu competidor e veja quais são os gaps que os concorrentes estão abordando que o seu site não está aproveitando e melhore seu conteúdo com base neles.

Migração ou mudanças no site

Mudanças no site vão causar sempre uma queda no tráfego. Isso é algo completamente esperado.

Os motivos da alteração do site são de pouca importância, mas é necessário entender o que a mudança causa.

Principalmente, se o site foi mudado sem atenção ao SEO, é quase impossível não haver alteração no fluxo.

É importante também checar se alguma coisa passou despercebido que acabou diminuindo a otimização do funcionamento do site.

Mudanças na velocidade, boa visualização para dispositivos móveis, uso correto de indexação entre outros são apenas algumas das coisas que você tem que checar uma vez que a mudança ocorra.

Então sempre que houver migração ou mudança de design, tente checar todos os aspectos do seu site, incluindo o funcionamento de seus serviços, links, disponibilidade de conteúdo e etc.

Redirects 301

Fique atendo a estrutura dos URL do seu site antigo e o novo.  Se você utiliza WordPress, vale a pena instalar plugins como Redirection  que lhe mostrará todas as páginas acessadas que não tiveram correspondência.   Através deste plugin, mesmo sem conhecimento em programação, você poderá adicionar redirects 301, ou seja, transferindo a autoridade de um url antigo e desatualizado para a nova página, sem grandes prejuízos em termos de SEO.

Concluindo

Não se desespere, afinal de contas, isso não irá resolver seus problemas.

Problemas com fluxo são algo que ocorrem com certa frequência e essa não será a primeira nem a última vez que você possivelmente lidará com este tipo de coisa.

 

Como criar uma estratégia de link building para SEO thumb

Como criar uma estratégia de link building para SEO

rotadesk No Comments

O que é Link Building?

Este é definido como “o processo de aquisição de hiperlinks de outros sites para o seu próprio site”. Um hiperlink (geralmente chamado de link) é uma forma de os usuários navegarem entre as páginas da Internet.

As técnicas de Link Building são várias e, apesar de haver uma grande variação quanto à facilidade (ou no caso, dificuldade) de aplicação de cada uma delas, profissionais de SEO tendem a concordar que o link building é uma das partes mais difíceis do trabalho.

A verdade é que muitos SEOs passam a maioria do seu tempo investindo em Link Building, aprendendo e aprimorando como fazê-los da melhor maneira possível.

Por essa razão, se você pode dominar a arte de construir links de alta qualidade, pode realmente colocá-lo à frente de outros concorrentes.

A importância dos links

Os links é um dos fatores responsáveis pela quantificação da popularidade e relevância de uma página. Eles funcionam como se fossem indicações, dizendo ao Google, por exemplo, que a página que o link está direcionando é uma página com um conteúdo bom e de confiança.

Link confiança

Uma página sem links não vai conseguir se posicionar tão bem nos mecanismos de busca, o que acaba por ter fluxo de usuários menor vindo de tráfego orgânico.

Fazendo o seu site ter valor

Se você quiser pessoas linkando o seu site, você tem que fazer com que valha a pena fazê-lo.

Comece devagar e construa as coisas no seu tempo. A maioria dos sites não surge do nada, eles crescem.

No fim das contas, tudo se resume a conversão, engajamento e tráfego.

Então, não tem muito segredo aqui. A primeira parte é fazer o seu site ter valor.

Trabalhe em seu conteúdo; use código limpo, sem atalhos; não exagere no CTA, um botão deveria bastar; verifique se o trabalho de SEO no seu site está bom; faça o seu site o mais rápido e otimizado possível; e obviamente, tenha bom conteúdo.

Dê atenção especial para a sua Home Page

Sua home page é a página que tem que ter a maior classificação ou, pelo menos, é a página cuja classificação vai importar primeiro.

Como este é o lugar onde a maioria das pessoas vão parar ao procurar seu site, esta é a página que deveria ter sua atenção e foco ao começar suas estratégias de Link Building.

HOME

Além disso, a Home Page é a página em que todas as outras páginas estarão à poucos cliques de serem alcançadas. Portanto, é uma ótima página para distribuir o link interno de seu site.

Lembre-se que primeiras impressões causam um impacto forte e duradouro.

Uma vez que a Home Page seja muito boa, o usuário poderá se sentir motivado a explorar o resto do site.

Com um site de qualidade, a reputação da marca será alavancada e isso aumentará o tráfego eventualmente.

Por meio de divulgação orgânica, o seu site chamará mais atenção, geralmente a longo prazo, o que eventualmente pode resultar em ter pessoas digitando o nome da sua marca nos mecanismos de busca.

Rankeando suas páginas internas

As páginas internas são onde a conversão costuma ocorrer, então, a medida que o seu site toma forma e popularidade, você vai querer outras páginas melhor rankeadas.

Aqui vai uma ideias de como rankear suas páginas internas:

  • Procure classificar suas páginas que geram receita usando palavras-chave de cauda longa

Com o uso do Google para webmasters, você pode saber quais são as páginas e quais keywords estão sendo mais relevantes para você.

Análise de Busca

Você pode se surpreender com o quanto você aprende em uma análise como esta.

Se você já tem tráfego em uma determinada direção, capitalize-o. Uma vez que uma página tenha destaque, use-a.

Crie o hábito de vincular  suas páginas, mas não crie um loop fechado. Vincule com um direcionamento único, em direção às suas melhores páginas.

Uso de Keywords em Long Tail

De acordo com Sam Kusinitz do Hubspot, Long Tail Keyword (que em Português, seria algo como: “palavra-chave de cauda longa”) é “uma frase de keywords que contém pelo menos três palavras (embora alguns digam que dois ou mais são considerados de cauda longa)”.

Ainda de acordo com o Sam, “As palavras-chave de cauda longa são usadas para segmentar dados demográficos de nicho, em vez de públicos de massa. Em outras palavras, eles são mais específicos e geralmente menos competitivos do que termos genéricos de palavras-chave”.

Elas causarão resultados mais rápido para um novo site, segmentando e classificando palavras-chave de cauda longa.

As frases, por serem mais extensas, acabam tendo menos buscas e menos concorrência. Por serem palavras-chave bastante específicas, elas permitem que você se classifique altamente nos resultados de pesquisa para tópicos populares com relativa rapidez, enquanto ainda ganha terreno nos termos principais.

É um jeito de crescer devagar a longo prazo, tendo um impulso mais forte para criar valor em suas páginas ao tratar de assuntos bem mais específicos.

Além disso, páginas com ranking alto mais específicas como as de cauda longa tendem a atrair tráfego altamente qualificado, o que significa em uma possibilidade de conversão bem maior, graças à especificidade do assunto e pelo fato que são poucos concorrentes que estão abordando.

Consiga Citações

Encontre um jeito de contribuir com o trabalho dos outros e ter uma menção direta a você, a uma frase que você disse ou algum trabalho seu.

Isto é algo que você pode conseguir basicamente por ser relevante e apesar de ter muito trabalho na questão do conteúdo que tem como consequência este tipo de ação, o link building em si acaba não exigindo trabalho seu.

Alguns sites em específico podem te ajudar nessa missão, como o Haro ou o Quora, onde você pode ajudar outras pessoas com seu conhecimento especializado e acabar recebendo citações em troca disso.

Guest Posts

Os guest posts são posts em que o dono de outro site concede um espaço em seu blog ou site para você fazer um conteúdo. Assim, conseguindo gerar links para seu site.

Guest posts são boas oportunidades para que sites que possuem certa relevância em seu nicho colocarem um link direcionando para seu site. Além de conseguir que sua página ganhe autoridade, você pode utilizar essa estratégia para ganhar presença de sua marca.

Você pode utilizar algumas ferramentas para encontrar sites com conteúdo semelhantes ao seu e entrar em contato com o dono do site para fazer o primeiro contato. Ferramentas como o SEMRush possuem uma aba de linkbuilding na aba de projetos com sites que abordam o conteúdo de seu site. Outras formas de buscar contatos é utilizar o próprio Google e pesquisar por sites que estão falando sobre seu assunto.

Assim, é possível encontrar parcerias duradouras, principalmente se sua empresa está fornecendo artigos com conteúdo diferenciado de qualidade para seus parceiros.

 

Marketing Digital 2018 Thumb

O que está acontecendo com o marketing digital em 2018

rotadesk No Comments

Marketing Digital 2018

O setor de Marketing nunca fica estagnado. A mudança é inevitável e aquele que não se adapta, sofre.

Aprender constantemente e frequentemente sobre novas tendências e a melhor forma de gastar seu dinheiro com anúncios é um desafio que nunca acaba.

No entanto, por ele nunca acabar, ele nunca para de dar frutos enquanto o trabalho em cima disso for produtivo, ou seja, contanto que se entenda as novas tendências.

Para isso, no entanto, só existe um método: manter-se informado e a par de tudo sobre o mercado e suas tendências.

Levando isso em consideração, aqui estão algumas dicas e dados importantes sobre Marketing Digital em 2018.

Empresas ganham, em média, o dobro do que foi gasto com anúncios no Google

Google Ads dobro

É normal vê-se argumentações de que a margem já foi maior e de que o Google tem cada vez extraído mais dinheiro dos anúncios, no entanto, ainda é uma margem muito boa.

Então se você tem dúvida sobre ser ou não uma boa ideia usar o Google Ads para aumentar as visitações e conversões em seu site, comece do princípio em em sua grande maioria, as empresas estão tendo um excelente retorno.

O Google fez uma pesquisa nos Estados Unidos sobre o retorno que seus anúncios têm gerado na economia, e, o resultado foi que a cada dólar investido na plataforma, o retorno era dobrado.

Mídias Sociais cada vez mais relevantes

O tempo que um internauta gasta em sites e aplicativos como Twitter e Facebook é de um terço de seu tempo online, em média.

Tempo em mídias sociais

Essa informação por si só já deveria ser mais que o suficiente para que você se motivasse a usar parte de seu investimento nesse meio, senão a maior parte.

Não existe outra atividade que usuários da internet gastam mais tempo que em redes sociais.

Descobrir se seu público usa mais uma rede social que outra também seria interessante, mas independentemente, é uma área de forte interesse, de fato.

Mídias Sociais e suas influências sobre consumidores

Redes Sociais

Não há como escapar disso na época em que vivemos.

É praticamente impossível conhecer alguém que não tenha pelo menos uma conta em uma rede social.

Cerca de 40% dos internautas seguem suas marcas favoritas em mídias sociais

Redes sociais acabam sendo uma escolha consequencial para fidelização da clientela, considerando os dados.

O uso de mídias sociais se torna a maneira mais fácil de manter informado o cliente engajado.

Além disso, 25% dos usuários de internet também seguem as marcas que os interessa quando estão pensando em fazer alguma compra.

Ter uma mídia social de boa apresentação, com bastante informações, incluindo texto, áudio e vídeo, só ajuda você a causar uma melhor impressão.

Usuários muitas vezes são inspirados a fazer compras por causa das redes sociais

Comprar Redes Sociais

A influência não acaba por aí, no entanto. Já que as redes sociais são muito do que motiva os usuários a realizarem compras online.

Verdade seja dita, não é incomum que um usuário se interesse por um produto porque viu alguém falando sobre ou porque um post sobre tal produto viralizou de forma orgânica.

Exatamente por isso o ideal é a produção de conteúdo sobre o seu produto ou serviço, fazendo assim que haja mais possibilidades deste tipo de evento ocorrer.

Outras redes sociais

Como foi dito, o perfil do usuário é extremamente importante. Embora o Facebook tenha mesmo uma quantidade gigantesca de usuários, existem outras redes.

Muitos usuários do Facebook passam a maior parte do seu tempo no Instagram, por exemplo. Felizmente, este tem anúncios que funcionam de forma similar ao Facebook.

Uma rede social, se é que se pode chamá-lo assim, que é constantemente ignorada é o Reddit.

O Reddit tem um tempo de visitação médio ainda maior que o próprio Facebook.

Considerando que o usuário passe mais tempo visitando a plataforma, você tem mais tempo para chamar a atenção  para o seu anúncio.

O Reddit também é especialmente interessante para encontrar um público alvo de gostos ou intenções bem específicas já que ele funciona como um fórum de tópicos.

A maior parte do tempo online das pessoas é no celular

A grande maioria do tempo que um usuário passa verificando mídias digitais é gasta enquanto a pessoa está no celular.

Lembre-se de projetar seus anúncios para funcionarem também quando visualizados por celular.

Divulgue sua marca, especialmente para usuários de celular

Segundo a Think With Google, só o fato de sua marca aparecer em uma pesquisa que um usuário fez em um celular já coloca sua marca no mapa. O resultado na busca do Google aumenta sua presença de marca em até 46% segundo a pesquisa.

Uma vez que o seu nome seja conhecido, as chances de conversão aumentam de forma significativa.

A familiaridade com um nome dá uma maior impressão de segurança e conforto na hora da compra.

Portanto, lembre-se disso na hora de dividir seus anúncios e faça sua marca ser vista.

Pessoas preferem assistir do que ler

Preferência de assistir

A Animoto publicou uma pesquisa e sua visão na influência que o Facebook tem na hora das pessoas fazerem uma compra.

E parece que 64% dos consumidores dizem que já foram influenciados a fazer uma compra por causa de um vídeo do Facebook.

Embora vídeos no Facebook fossem a princípio usados para divulgação e engajamento, percebe-se aqui o potencial de usá-los para conversão.

 

 

Como lidar com depoimentos negativos no Google thumb

Como lidar com depoimentos negativos no Google

rotadesk No Comments

Como lidar com depoimentos negativos no Google

É impossível não termos reviews negativas e esta é a primeira coisa que você tem que aceitar.

O máximo que você pode fazer é se propor a agradar o máximo possível de pessoas da forma mais consistente possível.

O seu negócio não vai ser bem visto por todo mundo, mas enquanto você agradar a grande maioria dos frequentadores ou contratantes, você sempre terá clientela.

Minorias que reclamam acabam sendo ignoradas no mar positivo de boas reviews e são percebidas por outros clientes como casos isolados e seu objetivo é chegar a esse ponto.

Mesmo se você chegar a ter um mar de reviews positivas, você deve sim manter o padrão e esforço para sempre melhorar o serviço.

As pessoas podem mudar de opinião sobre o seu estabelecimento, principalmente se perceberem uma diminuição na qualidade do serviço ou no seu empenho.

Mas e quando as reviews ruins começam a acontecer, o que devemos fazer?

Bom, existem algumas diretrizes que podem ser tomadas.

Identifique se as reviews são genuínas ou falsas.

Este é o primeiro passo. Uma vez que os comentários negativos comecem a surgir, é importante checar a validade dos mesmos.

Investigue e cheque se os comentários são reais, mesmo que a motivação seja barata ou inválida no seu ponto de vista.

Aqui, o importante não é quem tem razão, mas sim descobrir se é uma reclamação real ou apenas pessoas tentando te prejudicar.

No caso de Reviews genuínas

Depoimentos

Se você se perguntou o que fazer quando recebe um review negativo, você deveria pensar na parte mais óbvia.

Se um cliente ficou insatisfeito, a primeira coisa é procurar saber como isso aconteceu.

É algo costumeiro do seu negócio? É algo que pode ser melhorado? Foi um acontecimento isolado?

Identificar o problema e suas causas é o primeiro passo para evitar que o mesmo acabe por se repetir.

Se você, no entanto, está se perguntando sobre se deve ou não responder uma review negativa, a resposta é: Sim, definitivamente!

Responder um review negativo pode ser a sua chance de fidelizar um cliente, mesmo que ele tenha ficado insatisfeito com o serviço ou produto. Além disso, a resposta do cliente insatisfeito pode ajudar a entender melhor o problema, caso tenha sido o serviço, qualidade do produto, etc.

Mantenha a calma e seja bem educado.

É, eu sei, parece besteira. No entanto, quando alguém fala mal de algo que você suou bastante para construir e fazer funcionar, pode ser bem fácil se descontrolar um pouco.

Sem calma

Manter a calma e a educação mesmo no caso do cliente mais mal educado e sem razão que você encontrar só vai engrandecer você nos olhos dos outros clientes e ainda diminui seu risco de causar um problema ainda maior com o reclamante.

Deixe claro quem você é e que você se preocupa com o ocorrido

O ideal aqui é a transparência quanto ao ocorrido e quem lida com este tipo de situação.

Clientes não costumam responder bem a respostas automatizadas escondidas atrás da marca de uma empresa.

Se apresentar como encarregado do problema, por exemplo, é um grande passo para criar percepção de confiabilidade para com a clientela.

Lembre-se de lidar diretamente com o problema sem criar pouco caso do mesmo, explicar-se da melhor maneira e, na medida do possível, acalmar o cliente com o que seja viável para você, principalmente se o erro foi mesmo seu.

Sendo prestativo, vocês juntos podem conversar sobre o assunto e encontrar uma situação que não só seja benéfica para ambas as partes, como também o fará ser percebido por outros clientes como alguém que se importa com a satisfação do consumidor.

Não empurre com a barriga

Quanto mais você ignora um review negativo ou deixa a coisa passar de qualquer jeito, mais o cliente sente ter razão em dar a nota baixa e mais reafirma para si mesmo a pouca consideração que ele sentiu do estabelecimento para com ele.

Identifique as reações negativas e ataque-as, no bom sentido, o mais rápido possível. Não deixe a situação se transformar em algum escândalo.

O tempo ideal para responder a review depende do tipo de incidente e isso é algo que se considera com o tempo.

Quem está irritado naquele momento pode estar menos disposto para uma conversa mais tranquila e portanto, pode ser melhor esperar um pouco até responder.

De uma forma ou de outra, nunca espere demais.

No caso de reviews falsas

Estas são especialmente prejudiciais para donos de pequenos negócios.

Protegidos pelo anonimato da internet, não é muito difícil criar novas contas e deixar reviews positivas ou negativas para empresas de forma que a identidade do sujeito fique protegida.

Um dos maiores problemas do mundo digital atual é que estas reviews com contas falsas são facilmente feitas sem mesmo que o usuário tenha sequer tentado contratar o serviço ou comprar o produto. A política do Google, por exemplo, permite que seja feita uma review levando em conta apenas o contato feito com a empresa.

O motivo pouco importa, mas a verdade é que ainda é uma situação com que donos de empresa lidam frequentemente e que prejudica especialmente os donos de negócios locais.

Os maiores problemas do anonimato nas reviews

Anonimato

Considerando que o Google não sabe quem são cada um dos seus clientes, nem adianta tentar explicar para ele que aquela review não era de um deles.

O que nos leva ao outro problema: uma pessoa não tem que ser um cliente para poder deixar uma review, no sentido de que ela pode ter tentado contratar o serviço e não gostou de como foi tratada, ou achou o site terrível e etc.

Uma possível solução, tornando público o problema

Joy Hawkings, dona da pequena empresa Sterling Sky, disse que a solução para os comentários negativos que a acusavam de dar um golpe nos clientes foi transformar em público o seu dilema.

Ao postar no Facebook e no Twitter que fora vítima de reviews falsas negativas, ela acabou tendo rápido apoio de uma série de indivíduos que usaram de seu serviço ou patrocinaram sua empresa em algum ponto, conseguindo assim, reverter o aspecto negativo deixado pelas reviews falsas.

E quanto à remoção dos comentários negativos?

No caso da Joy, ela conseguiu ter boa parte dos comentários falsos removidos, pois nos reviews negativos que haviam textos de comentários, ela baseou-se nas políticas do Google. Porém, para os reviews sem comentários o Google não pôde remover pois é mais difícil identificar a violação de política.

Não é incomum o Google se recusar a remover as reviews para as pessoas, pelos motivos citados anteriormente e outros.

A solução que Joy encontrou foi responder os “falsos” clientes, oferecendo um reembolso de 100% do serviço, “afinal de contas, 100% de zero ainda é zero“, disse Joy.

Isso é uma solução que, além de ter risco zero, mostra pra outras pessoas que você está disposto a resolver quaisquer supostos problemas causados durante a prestação de seu serviço.

 

Google Ads novidades thumb

Google ADS – Quais as novidades que vão fazer a diferença

rotadesk No Comments

Google Ads novidades

O Google não para. Logo depois da vinda do Google Ads, já existem várias mudanças projetadas.

Uma vez que estas mudanças possam ter um efeito sério na efetividade de um anúncio, é normal ser um pouco conservador com seu dinheiro antes de saber como as coisas vão mudar.

De qualquer forma, já sabemos de uma série de coisas que serão diferentes como Google Ads.

Será mais fácil desativar personalização de anúncios

O Google está trabalhando sob os temas de valor, confiança e transparência e, portanto, o usuário agora será capaz de desativar a personalização de anúncios mais facilmente.

Personalização de anúncios

Este não é um recurso novo, mas é um recurso que se tornará mais prático e mais difundido.

Os usuários conseguirão desativar anúncios personalizados por dados demográficos, tópicos, visitas em sites de anunciantes. Os anúncios passarão a ser a exibidos então, de acordo com o conteúdo do site acessado e a localização geral.

A ideia é evitar qualquer tipo de problema ou polêmica de direcionamento comportamental ou uso indevido dos dados dos usuários da plataforma. Como ocorreu no caso do Facebook resultando na mudanças da política de regulamentação online como a GDPR.

AdWords tornou-se Google Ads

Isso era de fato inevitável e muitos estavam apenas esperando este anúncio ser feito.

Google Ads Animado

O Google com isso pode separar uma faceta da companhia para todo e qualquer tipo de anúncio nos vários braços da sua plataforma sem evitar toda a confusão que causava anteriormente.

E ainda por cima, pode usar este novo logo que causou várias reações positivas.

Google Ad Manager: elevando a publicidade programática por meio da colaboração

Da forma mais simples possível de se explicar, uma série de ferramentas trabalharão de forma integrada.

Serão mais de 100 integrações com trocas e soluções de mediação, tudo em uma única interface e algumas outras coisas.

Vamos detalhar um pouco o que acontece:

Google Ad Manager

A nova plataforma do Google é a combinação do DoubleClick Ad Exchange e o DoubleClick for Publishers.

Se você não tem familiaridade com as ferramentas, pelo menos terá que lidar com uma ferramenta unificada e menos complicada.

A plataforma tem como objetivo auxiliar os anunciantes a conseguir alcance para atingir pessoas em várias plataformas diferentes, incluindo Live Streams, Jogos, Aplicativos e uma série de outros.

Plataforma de marketing do Google

Esta também será uma junção, mas da rede de anunciantes da DoubleClick e o Google Analytics 360.

Tais mudanças com certeza serão bem vindas pelos profissionais de marketing, já que ela agora se torna significativamente mais prática.

Se você gosta de relatórios e análises, ficará feliz com a melhora na plataforma que facilita a compreensão de indicadores e geração de relatórios.

Com as mudanças, será possível fazer o planejamento, compra e otimização da mídia digital e da experiência dos usuários em um único lugar.

O Google entra com força nos anúncios de vídeo no YouTube e suas novas funcionalidades

Em geral, a maioria absoluta das pessoas que viram um anúncio no youtube planejaram comprar ou realizaram uma compra de algo que viram em um anúncio.

O crescimento em relação ao ano anterior de conversões por anúncios na plataforma foi de 150%.

Não é à toa que o Google está criando uma gama enorme de funcionalidades para o Youtube, não isento de certa confusão por causa disso.

Um novo recurso do YouTube, anunciado recentemente pelo Google, é uma estratégia de lances inteligentes que faz anunciantes alcançarem clientes em potencial que pesquisarão a marca após assistirem os vídeos.

Além disso, os anúncios do YouTube vão poder acionar um formulário na página com os anúncios de geração de leads. Assim ao clicar no anúncio o usuário consegue preencher os seus dados sem atrapalhar sua experiência de usuário.

A ideia é criar uma abordagem abrangente que seja centrada em um único vídeo, mas que leve a conversões reais.

Velocidade Móvel ganha destaque quanto à sua relevância

Bom, não é nenhuma novidade que a velocidade de uma página é bastante importante, mesmo quando você a checa de um celular. Nas últimas atualizações o Google tem dado cada vez mais ênfase nesse aspecto, e não é por menos, pois, segundo sua pesquisa feita no “speed matters”, uma redução mínima de 400ms no carregamento da página pode gerar uma redução de até 0,76% nas buscas em 6 semanas.

Assim, de que adiantam os anúncios de pesquisa responsivos, se as suas páginas de destino para dispositivos móveis não estiverem prontas para ser reproduzidas? Não muito, de acordo com o diretor de gerenciamento de projetos do Google Ads, Anthony Chavez: 50% dos usuários passarão de uma compra em potencial se a página de destino demorar para ser carregada.

O Google anunciou o lançamento de uma coluna de pontuação de velocidade de página de destino para dispositivos móveis.

Coluna velocidade mobile

Esta ferramenta classificaria a velocidade da Landing Page com uma nota que vai até 10, de muito lenta a extremamente rápida.

Além disso, a ferramenta também apresentará outros dados que informam a interferência que a sua velocidade traz ao seu site, incluindo à sua taxa de conversão.

Não só você não tem mais que sair do Google Ads para acessar a área de medição de sua velocidade, porque a página da Coluna de Velocidade da Página de Destino Móvel está no Google Ads, como ela é atualizada diariamente.

O relatório em vários dispositivos ganha uma atenção especial

Não tem nada de novo em relatórios entre dispositivos, na verdade. Eles não só já existiam como eram amplamente utilizados no ramo. A novidade está no fato que o Google Analytics agora vai disponibilizar relatórios entre diversos dispositivos.

Esses relatórios serão divididos em três subcategorias:

Sendo assim, você poderá visualizar as etapas de uma conversão associadas aos dispositivos que foram acessados.

Páginas de destino otimizadas do Google

Esta é uma novidade particularmente interessante porque boa parte dos pequenos negócios não tem site próprio.

Google Ads Landing Page

O Google se antecipou de forma que já crie mercado para si resolvendo o problema dos outros.

Há uma beleza especial neste tipo de solução.

O Google está testando Páginas de Destino que se otimizam automaticamente. Essa funcionalidade estará disponível nas campanhas inteligentes do Google, que permitirão anunciantes criar campanhas de anúncios mesmo que não tenha um site no momento.

Estas páginas seriam capazes de aprender, eventualmente, como buscar informações sobre você, seu negócio e seus produtos a partir de um anúncio e criar uma Página de Destino, com capacidade de conversão, rastreamento, criação de relatórios e etc.

Tudo isso parece fantástico? É porque é!

O Google parece estar focado em melhorar a experiência do usuário.

Facilidade e praticidade parece ser algo bastante recorrente no que foi anunciado.

Cada vez mais, o Google se supera aumentando a funcionalidade de suas ferramentas e diminuindo a complicação do uso das mesmas.

 

Automação Google Ads: Gerencie múltiplas contas com o Google Drive.

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads.

 

1. Adicione scripts para múltiplas contas com o Google Drive – Por Russell Savage. Utilizando uma planilha do Google é possível você automatizar o trabalho de atualizar todos os scripts de suas contas.

Uma das desvantagens de usar o AdWords Scripts é que você precisa fazer login em cada conta e configurar o script. Para a maioria das pessoas, isso não é um problema nas primeiras vezes. Mas quando você começa a ver o valor de alguns desses scripts, pode haver um conjunto deles que você deseja colocar em todas as suas contas. Configurá-los é bom, até encontrar um bug no seu código e ter que entrar e atualizar todas as 20 cópias do script em cada conta.

Bem, neste post, criei uma maneira simples de manter uma única cópia do seu script e carregá-lo em qualquer número de contas do Google AdWords. Então, se você tiver alguma alteração, poderá atualizar uma única versão do script e todas as contas começarão a usar o novo código instantaneamente.

O código para isso consiste em duas partes. O primeiro snippet de código é o código genérico que você precisa colocar em cada uma das suas contas. Esse código faz referência a uma única planilha do Google (aqui está uma amostra para você copiar: //goo.gl/y6hPfy ) que é usada para saber quais scripts ela deve executar. A planilha tem apenas três colunas: uma descrição que é usada apenas para registro, o local do script no Google Drive e o nome do objeto no script. Não se preocupe com isso agora, vou descrevê-lo melhor na próxima seção. Finalmente, carrega o arquivo de script e executa a função principal.

/************************************
* Generic Script Runner
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
***********************************/
function main() {
//See //goo.gl/KvINmD for an example spreadsheet.
var scriptConfigId = ‘Your Spreadsheet Id Goes Here’;
var sheet = SpreadsheetApp.openById(scriptConfigId).getActiveSheet();
var data = sheet.getRange(‘A:C’).getValues();
for(var i in data) {
if(i == 0) { continue; }
var [description, location, classname] = data[i];
if(!location) { continue; }
Logger.log(‘Running “‘+description+'” from location: ‘+location);
var scriptFile = getFile(location);
var scriptText = scriptFile.getBlob().getDataAsString();
eval(scriptText);
var script = eval(‘new ‘+classname+'();’);
script.main();
}
}

//This function gets the file from GDrive
function getFile(loc) {
var locArray = loc.split(‘/’);
var folder = getFolder(loc);
if(folder.getFilesByName(locArray[locArray.length-1]).hasNext()) {
return folder.getFilesByName(locArray[locArray.length-1]).next();
} else {
return null;
}
}

//This function finds the folder for the file and creates folders if needed
function getFolder(folderPath) {
var folder = DriveApp.getRootFolder();
if(folderPath) {
var pathArray = folderPath.split(‘/’);
for(var i in pathArray) {
if(i == pathArray.length – 1) { break; }
var folderName = pathArray[i];
if(folder.getFoldersByName(folderName).hasNext()) {
folder = folder.getFoldersByName(folderName).next();
}
}
}
return folder;
}

Agora que temos um código genérico que lê a descrição, o local e o nome do objeto de uma planilha e executa o código, precisamos fazer algumas pequenas modificações em alguns dos nossos scripts existentes para que ele funcione.

Um dos meus scripts favoritos é sobre como encontrar anomalias na sua conta . Para que o script seja executado a partir de outro script, precisamos convertê-lo em um objeto com uma única função pública. Essa mesma técnica deve funcionar em quase todos os scripts do FreeAdWordsScripts.com .

Primeiro, coloque todo o script em uma chamada de função e dê a ele qualquer nome que desejar.

function Anomalies() {
// Copy and Paste the code from:
// //goo.gl/IT1UcV
};

Em seguida, você precisa atualizar a função principal para ser um método público, para que possamos chamá-la de nosso script genérico.

this.main = function() {
// Don’t make any changes to the body of the main method
}

Uma versão completa do código atualizado pode ser encontrada aqui: Encontre anomalias na versão do objeto da sua conta .

Agora você pode salvar este novo script em algum lugar em seu GDrive e atualizar o local e o nome do objeto (anomalias neste caso) em sua planilha de configuração.

Agora você deveria estar bem para ir. Você pode adicionar quantos scripts desejar à planilha de configuração, mas lembre-se de que o limite de 30 minutos ainda se aplica.

2. Criando “deep links” para sua conta – Por Russell Savage. Este script cria links diretamente para a entidade que está com problemas, assim, você consegue acessar rapidamente a raiz do problema sem ter que acessar todas as páginas.

Acontece que existem dois números mágicos que você precisa para que isso funcione. Quando você acessar sua conta, no URL, você verá __u = e __c =. De acordo com esta postagem do blog , esses valores são ‘effectiveUserId’ e ‘customerId’ respectivamente. Infelizmente, não há como acessar esses valores ao usar scripts, portanto, você precisará copiá-los manualmente no script abaixo.

Depois disso, você pode incluir a função em todos os seus scripts e fazer um link direto para o conteúdo do seu coração. Não é a coisa mais bonita do mundo, mas é auto-contida, por isso deve ser fácil de copiar para o fundo de seus scripts.

// Link to the Keyword Tab of the AdGroup
Logger.log(getUrl(someAdGroupEntity,’Keywords’));
// Link to the Ads Tab of the AdGroup
Logger.log(getUrl(someAdGroupEntity,’Ads’));
// Link to Location Settings Tab of the Campaign
Logger.log(getUrl(comeCampaignEntity,’Settings:Locations’));

 

/***********************************
* Build Deep Link Urls for Entities
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
***********************************/
function getUrl(entity,tab) {
var customerId = ‘__c from the url’;
var effectiveUserId = ‘__u from the url’;
var decodedTab = getTab(tab);

var base = ‘//adwords.google.com/cm/CampaignMgmt?’;
var url = base+’__c=’+customerId+’&__u=’+effectiveUserId+’#’;

if(typeof entity[‘getBudget’] !== ‘undefined’) {
//A Campaign
return url+’c.’+entity.getId()+’.’+decodedTab+’&app=cm’;
}
if(typeof entity[‘createKeyword’] !== ‘undefined’) {
//An AdGroup
return url+’a.’+entity.getId()+’_’+entity.getCampaign().getId()+’.’+decodedTab+’&app=cm’;
}
if(typeof entity[‘getMatchType’] !== ‘undefined’) {
//A Keyword
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.key&app=cm’;
}
if(typeof entity[‘getHeadline’] !== ‘undefined’) {
//An Ad
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.create&app=cm’;
}
return url+’r.ONLINE.di&app=cm’;

function getTab(tab) {
var mapping = {
‘Ad groups’:’ag’,’Settings:All settings’:’st_sum’,
‘Settings:Locations’:’st_loc’,’Settings:Ad schedule’:’st_as’,
‘Settings:Devices’:’st_p’,’Ads’:’create’,
‘Keywords’:’key’,’Audiences’:’au’,’Ad extensions’:’ae’,
‘Auto targets’:’at’,’Dimensions’ : ‘di’
};
if(mapping[tab]) { return mapping[tab]; }
return ‘key’; //default to keyword tab
}
}

 

3. Converta feeds RSS em JSON – Por Russel Savage. Utilize o Feed API do Google em seu script para converter feed em RSS para JSON. Assim você consegue puxar informações e trabalhá-las com mais facilidade.

/******************************************
* Use Google Feed API to convert RSS to json
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
// Usage: var jsonData = convertRssToJson(‘//www.cpsc.gov/en/Newsroom/CPSC-RSS-Feed/Recalls-RSS/’);
function convertRssToJson(rssUrl) {
var FEED_API_URL = “//ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=”
var url = FEED_API_URL+encodeURIComponent(rssUrl);
var resp = UrlFetchApp.fetch(url);
if(resp.getResponseCode() == 200) {
return JSON.parse(resp.getContentText());
} else {
throw “An error occured while trying to parse: “+rssUrl;
}
}

4. Scripts no fuso horário correto – Por Nathan Byloff. Os scripts do Google são executados no horário da região em que o servidor se encontra, portanto, caso o servidor esteja localizado em outra região de fuso horário, ele será executado em um horário diferente da sua conta. Esse script corrige este problema ajustando ao horário de sua conta.

A seguir, dois métodos de exemplo que usei para garantir que todas as datas foram definidas no mesmo fuso horário. Este primeiro apenas retorna o tempo atual correto da conta (não o tempo do servidor no Google).

/**
* Make sure when getting a date object, we’re basing it off the account time, not the data center server time
*/
function getAccountCurrentDateTime() {
return new Date(Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “MMM dd,yyyy HH:mm:ss”));
}

Ou use um método como esse para garantir que um objeto de data com o qual você trabalha corresponda ao horário correto da conta.

/**
* The time will convert to whatever server/data center the script is running on. Make sure the time is set to the AW account time
* @param {Date} date
*/
function setProperTimeZone(date) {
return new Date(Utilities.formatDate(date, AdWordsApp.currentAccount().getTimeZone(), “MMM dd,yyyy HH:mm:ss”));
}

5. Utilize o recurso de autocompletar do Google – Por Russell Savage. Utilize esse script para aumentar ou negativar as palavras-chave de sua conta utilizando as sugestões de autocompletar do próprio Google.

/**********************************************************************************************************************
* Brand Keyword Analysis Google
* Leverages the Google Autocomplete feature to find potential keyword opportunities and negative keywowrds
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com or MixedMarketingArtist.com
**********************************************************************************************************************/

var hashMapResults = {};
var numOfKeywords = 0;
var doWork = false;
var keywordsToQuery = new Array();
var keywordsToQueryIndex = 0;
var queryflag = false;

var brandKeywordList = [];
var targetKeyword = “serious mass”; // this is the keyword that you want to know about
var emailAddress =”[email protected]”; // this is where the final report will be sent

function main() {
// Get the brand name from the account
var accountName = AdWordsApp.currentAccount().getName().split(‘-‘);

var clientName = accountName[0];

info(‘Now starting Google Search Autocomplete Analysis..’);

buildKeywordList(targetKeyword);

// Sort array
brandKeywordList.sort();

/* // remove any keywords that don’t include the brand term
for (i = 0; i < brandKeywordList.length; i++) {
if ((brandKeywordList[i].indexOf(accountName)) == -1) {

brandKeywordList.splice(i,1);

}
} */

// let user know that search has completed
info(‘Google Autocomplete Search has completed, expect an email with search term results momentarily’);
info(brandKeywordList.length + ‘ searches were found. ‘);

var fileUrl = createSpreadsheet(brandKeywordList);

info(‘ Or you can find it here:’ + fileUrl);
sendAnEmail(clientName, brandKeywordList.toString(), fileUrl);
}

function buildKeywordList(keyword) {

// get the first set of keywords related to the term and add to list
brandKeywordList = queryKeyword(keyword);

// iterate through alphabet and build keyword list for initial keyword
info(‘now checking for the variation [‘ + keyword + ‘] + [a-z][0-9] …’);
info(‘variation: ‘ + keyword);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keyword + ‘ ‘+ chr;

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keyword + ‘ ‘+ n;

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

// Split keyword up if possible and look for different variations
var keywordPieces = _.str.words(keyword);

if (keywordPieces.length > 1) {
info(keywordPieces);
// iterate through alphabet and build keyword list for the variation: [keywordPiece1] + [a-z][0-9] + [keywordPiece2]

warn(‘now checking for the variation [keywordPiece1] + [a-z][0-9] + [keywordPiece2]…’);
warn(‘variation: ‘ + keywordPieces[0] + ‘ ‘+ keywordPieces[1] + keywordPieces[2]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keywordPieces[0] + ‘ ‘+ chr + ‘ ‘ + keywordPieces[1]+ ‘ ‘ + keywordPieces[2];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keywordPieces[0] + ‘ ‘+ n + ‘ ‘ + keywordPieces[1] +’ ‘+ keywordPieces[2];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

Utilities.sleep(2000);

/* CHECK FOR THE VARIATION [keywordPiece1] + [a-z][0-9] + [keywordPiece0] */
warn(‘now checking for the variation [keywordPiece2] + [a-z][0-9] + [keywordPiece1]…’);
warn(‘variation: ‘ + keywordPieces[1] + ‘ ‘+ keywordPieces[0]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keywordPieces[1] + ‘ ‘ + keywordPieces[2] + ‘ ‘+ chr + ‘ ‘ + keywordPieces[0];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keywordPieces[1] + ‘ ‘ + keywordPieces[2] + ‘ ‘+ n + ‘ ‘ + keywordPieces[0];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

Utilities.sleep(2000);
/* last variation: [a-z][0-9] [keyword1] [keyword2] */
/* CHECK FOR THE VARIATION [keywordPiece1] + [a-z][0-9] + [keywordPiece0] */
info(‘now checking for the variation [a-z][0-9] + [keywordPiece1] + [keywordPiece2]…’);
// warn(‘variation: ‘ + keywordPieces[0] + ‘ ‘+ keywordPieces[1]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = chr + ‘ ‘ + keywordPieces[0] + ‘ ‘ + keywordPieces[1] + keywordPieces[2];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = n + ‘ ‘ + keywordPieces[0] + ‘ ‘ + keywordPieces[1] + keywordPieces[2];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

}
}

function createSpreadsheet(results) {
var newSS = SpreadsheetApp.create(‘searchtermreport’, results.length, 26);

var sheet = newSS.getActiveSheet();

var columnNames = [“Campaign Name”, “AdGroup”, “Keyword”, “Match Type”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

for (i = 0; i < results.length; i++) {

headersRange.setValues([columnNames]);

var resultKw;
resultKw = results[i].toString();

sheet.appendRow([“Your Campaign”, “Your AdGroup”, resultKw,’Phrase’]);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);

}

return newSS.getUrl();

}

function sendAnEmail (results, fileUrl) {

var data = Utilities.parseCsv(results, ‘\t’);
var today = new Date();

var filename = ‘search-results’ + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, ‘text/html’, ”);

MailApp.sendEmail(emailAddress, ‘Google Autocomplete Results ‘, ‘You can find the results at the following URL:’ + fileUrl, {
name: ‘Google Autocomplete Search Results’
});

}
/* Utility Functions */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

function queryKeyword(keyword)
{
var querykeyword = encodeURIComponent(keyword);

var queryresult = ”;
queryflag = true;

Utilities.sleep(1000);
var response = UrlFetchApp.fetch(“//www.google.com/s?gs_rn=18&gs_ri=psy-ab&cp=7&gs_id=d7&xhr=t&q=” + querykeyword);

var retval = response.getContentText();

var test = _.str.stripTags(retval);

var retList = ScrapePage(retval, ‘[“‘, ‘”,’);

queryflag = false;

return retList;
}
// });
// }

function ScrapePage(page, left, right)
{
var i = 0;
var retVal = new Array();
var firstIndex = page.indexOf(left);
while (firstIndex != -1)
{
firstIndex += left.length;
var secondIndex = page.indexOf(right, firstIndex);
if (secondIndex != -1)
{
var val = page.substring(firstIndex, secondIndex);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u0026amp;”, “&”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u0026”, “”);
val = val.replace(“\\u0026#39;”, “‘”);
val = val.replace(“#39;”, “‘”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u2013”, “2013”);
retVal[i] = val;
i++;
firstIndex = page.indexOf(left, secondIndex);
}
else
{
return retVal;
}
}
return retVal;
}

!function(e,t){“use strict”;var n=t.prototype.trim;var r=t.prototype.trimRight;var i=t.prototype.trimLeft;var s=function(e){return e*1||0};var o=function(e,t){if(t<1)return””;var n=””;while(t>0){if(t&1)n+=e;t>>=1,e+=e}return n};var u=[].slice;var a=function(e){if(e==null)return”\\s”;else if(e.source)return e.source;else return”[“+p.escapeRegExp(e)+”]”};var f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”};var l={};for(var c in f){l[f[c]]=c}var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o;var r=function(){if(!r.cache.hasOwnProperty(arguments[0])){r.cache[arguments[0]]=r.parse(arguments[0])}return r.format.call(null,r.cache[arguments[0]],arguments)};r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”){f.push(r[l])}else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c])){throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]))}a=a[p[2][c]]}}else if(p[1]){a=i[p[1]]}else{a=i[s++]}if(/[^s]/.test(p[8])&&e(a)!=”number”){throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)))}switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase();break}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a;v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “;m=p[6]-t(a).length;d=p[6]?n(v,m):””;f.push(p[5]?a+d:d+a)}}return f.join(“”)};r.cache={};r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null){r.push(n[0])}else if((n=/^\x25{2}/.exec(t))!==null){r.push(“%”)}else if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))!==null){if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))!==null){s.push(u[1]);while((o=o.substring(u[0].length))!==””){if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null){s.push(u[1])}else if((u=/^\[(\d+)\]/.exec(o))!==null){s.push(u[1])}else{throw new Error(“[_.sprintf] huh?”)}}}else{throw new Error(“[_.sprintf] huh?”)}n[2]=s}else{i|=2}if(i===3){throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”)}r.push(n)}else{throw new Error(“[_.sprintf] huh?”)}t=t.substring(n[0].length)}return r};return r}();var p={VERSION:”2.3.0″,isBlank:function(e){if(e==null)e=””;return/^\s*$/.test(e)},stripTags:function(e){if(e==null)return””;return t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){e=e==null?””:t(e);return e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){if(e==null)return[];e=t(e);n=~~n;return n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e]},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){if(e==null||n==null)return 0;return t(e).split(n).length-1},chars:function(e){if(e==null)return[];return t(e).split(“”)},swapCase:function(e){if(e==null)return””;return t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){if(e==null)return””;return t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){if(e==null)return””;return t(e).replace(/\&([^;]+);/g,function(e,n){var r;if(n in f){return f[n]}else if(r=n.match(/^#x([\da-fA-F]+)$/)){return t.fromCharCode(parseInt(r[1],16))}else if(r=n.match(/^#(\d+)$/)){return t.fromCharCode(~~r[1])}else{return e}})},escapeRegExp:function(e){if(e==null)return””;return t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);i.splice(~~t,~~n,r);return i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){if(n===””)return true;if(e==null)return false;return t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();if(t==null)t=””;return e.join(t)},lines:function(e){if(e==null)return[];return t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){if(n===””)return true;if(e==null||n==null)return false;e=t(e);n=t(n);return e.length>=n.length&&e.slice(0,n.length)===n},endsWith:function(e,n){if(n===””)return true;if(e==null||n==null)return false;e=t(e);n=t(n);return e.length>=n.length&&e.slice(e.length-n.length)===n},succ:function(e){if(e==null)return””;e=t(e);return e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1)},titleize:function(e){if(e==null)return””;return t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){if(e==null)return””;if(!r&&n)return n.call(e);r=a(r);return t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””)},ltrim:function(e,n){if(e==null)return””;if(!n&&i)return i.call(e);n=a(n);return t(e).replace(new RegExp(“^”+n+”+”),””)},rtrim:function(e,n){if(e==null)return””;if(!n&&r)return r.call(e);n=a(n);return t(e).replace(new RegExp(n+”+$”),””)},truncate:function(e,n,r){if(e==null)return””;e=t(e);r=r||”…”;n=~~n;return e.length>n?e.slice(0,n)+r:e},prune:function(e,n,r){if(e==null)return””;e=t(e);n=~~n;r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);if(s.slice(s.length-2).match(/\w\w/))s=s.replace(/\s*\S+$/,””);else s=p.rtrim(s.slice(0,s.length-1));return(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){if(p.isBlank(e))return[];return p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e);n=~~n;var s=0;if(!r)r=” “;else if(r.length>1)r=r.charAt(0);switch(i){case”right”:s=n-e.length;return e+o(r,s);case”both”:s=n-e.length;return o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:s=n-e.length;return o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){t.unshift(e);return h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t);r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.indexOf(n);return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.lastIndexOf(n);return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.indexOf(n);return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””;t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “;n=n||” and “;var i=e.slice(),s=i.pop();if(e.length>2&&r)n=p.rtrim(t)+n;return i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);e[3]=true;return p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“});return p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e){}return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e);n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++){if(o&&u)if(e.charAt(u-1)===n.charAt(o-1))s=i;else s=Math.min(r[u],r[u-1],i)+1;else s=o+u;i=r[u];r[u]=s}return r.pop()}};p.strip=p.trim;p.lstrip=p.ltrim;p.rstrip=p.rtrim;p.center=p.lrpad;p.rjust=p.lpad;p.ljust=p.rpad;p.contains=p.include;p.q=p.quote;if(typeof exports!==”undefined”){if(typeof module!==”undefined”&&module.exports){module.exports=p}exports._s=p}else if(typeof define===”function”&&define.amd){define(“underscore.string”,[],function(){return p})}else{e._=e._||{};e._.string=e._.str=p}}(this,String)

 

6. Junte múltiplas campanhas em apenas uma – Por Russell Savage. Caso você utilize diversas campanhas com segmentações diferentes e queira juntar todas em uma esse script pode auxiliá-lo. Ele pausa as campanhas e copia-as para uma única campanha escolhida.

//———————————–
// Merge Multiple Campaigns Together
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var DESTINATION_CAMPAIGN_NAME = “dest_camp_name”;
var ORIGIN_CAMPAIGN_NAMES = [“to_merge_camp_name_1”,”to_merge_camp_name_2″/*,…*/];
var DEFAULT_KW_BID = 0.01; //used in case we can’t get the origin kw bid

//build a list of adgroups in the original
var dest_adgroups = [];
var ag_iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();

while(ag_iter.hasNext()) {
dest_adgroups.push(ag_iter.next());
}

var dest_camp;
if(dest_adgroups.length > 0) {
dest_camp = dest_adgroups[0].getCampaign();
}

for(var i in ORIGIN_CAMPAIGN_NAMES) {
var camp_name = ORIGIN_CAMPAIGN_NAMES[i];
var kw_iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+camp_name+”‘”)
.get();
while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var dest_adgroup = _find_adgroup(dest_adgroups,kw.getAdGroup());
if(!dest_adgroup) {
dest_adgroup = dest_camp.newAdGroupBuilder()
.withName(kw.getAdGroup().getName())
.withStatus((kw.getAdGroup().isPaused()) ? “PAUSED” : “ENABLED”)
.withKeywordMaxCpc(kw.getAdGroup().getKeywordMaxCpc())
.create();
dest_adgroups.push(dest_adgroup);
//now we move all the ads over
var ad_iter = kw.getAdGroup().ads().get();
while(ad_iter.hasNext()) {
var ad = ad_iter.next();
dest_adgroup.createTextAd(
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
{ isMobilePreferred : ad.isMobilePreferred() }
);
ad.pause();
}
}
var max_cpc = kw.getMaxCpc() || DEFAULT_KW_BID;
var dest_url = kw.getDestinationUrl() || “”;
var kw_text = kw.getText();
dest_adgroup.createKeyword(kw_text,max_cpc,dest_url);

kw.pause();
}
}

function _find_adgroup(ag_list,ag) {
for(var i in ag_list) {
if(ag_list[i].getName() == ag.getName()) {
return ag_list[i];
}
}
return null;
}
}

Automação Google Ads: Campanhas de Display e Shopping

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Campanhas de Display e Shopping”.

 

1. Campanhas de Shopping – Por Google Ads. Gerencie suas campanhas do Google Shopping sem a necessidade de utilizar a plataforma do Google Ads. Esse grupo de scripts permitem criar anúncios de produtos, atualizar lances, etc.

Recuperar todas as campanhas de compras

function getAllShoppingCampaigns() {
var retval = [];
var campaignIterator = AdWordsApp.shoppingCampaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();

// Optional: Comment out if you don’t need to print details.
Logger.log(‘Campaign Name: %s’, campaign.getName());

retval.push(campaign);
}
return retval;
}

 

Recuperar uma campanha de compras pelo nome dela

function getShoppingCampaignByName(campaignName) {
  var campaignIterator = AdWordsApp.shoppingCampaigns()
      .withCondition("CampaignName = '" + campaignName + "'")
      .get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    Logger.log('Campaign Name: %s', campaign.getName());
  }
}


Recuperar um grupo de anúncios de compras pelo nome dele

function getShoppingAdGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var adGroupIterator = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get();
while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
Logger.log(
‘AdGroup Name: %s, CPC = %s, Mobile Bid ‘ + ‘Modifier = %s’,
adGroup.getName(),
adGroup.bidding().getCpc(),
adGroup.devices().getMobileBidModifier()
);
}
}

 

Crie um grupo de anúncios do Shopping

function createShoppingAdGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;

var shoppingCampaign = AdWordsApp.shoppingCampaigns()
.withCondition(“CampaignName = ‘” + campaignName + “‘”)
.get()
.next();
var adGroupOperation = shoppingCampaign.newAdGroupBuilder().build();
var adGroup = adGroupOperation.getResult();
Logger.log(adGroup);
}

 

Criar uma hierarquia de grupo de produtos de compras

function createTree() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var root = shoppingAdGroup.rootProductGroup();

// The structure created is
// – root
// – cardcow brand
// – New
// – Refurbished
// – Other conditions
// – Other brands

// Add a brand product group for “cardcow” under root product group.
var brandNode = root.newChild()
.brandBuilder()
.withName(‘cardcow’)
.withBid(1.2)
.build()
.getResult();

// Add new conditions for New and Refurbished cardcow brand items.
var newItems = brandNode.newChild()
.conditionBuilder()
.withCondition(‘NEW’)
.build()
.getResult();

var refurbishedItems = brandNode.newChild()
.conditionBuilder()
.withCondition(‘REFURBISHED’)
.withBid(0.9)
.build()
.getResult();
}

 

Atravessa a hierarquia do grupo de produtos

function walkProductPartitionTree() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();
var root = shoppingAdGroup.rootProductGroup();
walkHierarchy(root, 0);
}

function walkHierarchy(productGroup, level) {
var description = ”;

if (productGroup.isOtherCase()) {
description = ‘Other’;
} else if (productGroup.getDimension() == ‘CATEGORY’) {
// Shows how to process a product group differently based on its type.
description = productGroup.asCategory().getName();
} else {
description = productGroup.getValue();
}

var padding = new Array(level + 1).join(‘-‘);

// Note: Child product groups may not have a max cpc if it has been excluded.
Logger.log(
‘%s %s, %s, %s, %s, %s’,
padding,
description,
productGroup.getDimension(),
productGroup.getMaxCpc(),
productGroup.isOtherCase(),
productGroup.getId().toFixed()
);
var childProductGroups = productGroup.children().get();
while (childProductGroups.hasNext()) {
var childProductGroup = childProductGroups.next();
walkHierarchy(childProductGroup, level + 1);
}
}

 

Obtém o grupo de produtos “Tudo mais”

function getEverythingElseProductGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var rootProductGroup = shoppingAdGroup.rootProductGroup();
var childProductGroups = rootProductGroup.children().get();
while (childProductGroups.hasNext()) {
var childProductGroup = childProductGroups.next();
if (childProductGroup.isOtherCase()) {
// Note: Child product groups may not have a max cpc if it has been
// excluded.
Logger.log(
‘”Everything else” product group found. Type of the product ‘ +
‘group is %s and bid is %s.’,
childProductGroup.getDimension(),
childProductGroup.getMaxCpc());
return;
}
}
Logger.log(‘”Everything else” product group not found under root ‘ +
‘product group.’);
}

 

Atualizar lances para grupos de produtos

function updateProductGroupBid() {
var productGroups = AdWordsApp.productGroups()
.withCondition(‘Clicks > 5’)
.withCondition(‘Ctr > 0.01’)
.forDateRange(‘LAST_MONTH’)
.get();
while (productGroups.hasNext()) {
var productGroup = productGroups.next();
productGroup.setMaxCpc(productGroup.getMaxCpc() + 0.01);
}
}

 

Recuperar anúncios de produtos

function getProductAds() {
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var productAds = shoppingAdGroup.ads().get();
while (productAds.hasNext()) {
var productAd = productAds.next();
Logger.log(
“Ad with ID = %s was found.”,
productAd.getId().toFixed(0));
}
}

 

Crie anúncios de produtos

function createProductAd() {
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var adOperation = shoppingAdGroup.newAdBuilder()
.withMobilePreferred(true)
.build();
var productAd = adOperation.getResult();
Logger.log(
“Ad with ID = %s was created.”,
productAd.getId().toFixed(0));

}

 

2. Conteúdo do Shopping – Por Google Ads. Assim como o script de Campanhas de Shopping, é possível inserir produtos, extrair informações da conta do merchant, listar todos os produtos e mais, sem a necessidade da plataforma do Google Ads.

Inserir um produto

function insertProduct() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// Create a product resource. See
// //developers.google.com/shopping-content/v2/reference/v2/products
// for the full list of fields supported by product resource.
var productResource = {
‘offerId’: ‘book123’,
‘title’: ‘A Tale of Two Cities’,
‘description’: ‘A classic novel about the French Revolution’,
‘link’: ‘//my-book-shop.com/tale-of-two-cities.html’,
‘imageLink’: ‘//my-book-shop.com/tale-of-two-cities.jpg’,
‘contentLanguage’: ‘en’,
‘targetCountry’: ‘US’,
‘channel’: ‘online’,
‘availability’: ‘in stock’,
‘condition’: ‘new’,
‘googleProductCategory’: ‘Media > Books’,
‘productType’: ‘Media > Books’,
‘gtin’: ‘9780007350896’,
‘price’: {
‘value’: ‘2.50’,
‘currency’: ‘USD’
},
‘shipping’: [{
‘country’: ‘US’,
‘service’: ‘Standard shipping’,
‘price’: {
‘value’: ‘0.99’,
‘currency’: ‘USD’
}
}],
‘shippingWeight’: {
‘value’: ‘2’,
‘unit’: ‘pounds’
}
};

ShoppingContent.Products.insert(productResource, merchantId);
}

 

Listar todos os produtos

function listProducts() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// List all the products for a given merchant.
var products = ShoppingContent.Products.list(merchantId);
if (products.resources) {
for (var i = 0; i < products.resources.length; i++) {
Logger.log(products.resources[i]);
}
}
}

 

Inserir produtos usando a API custombatch

function custombatch() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// Create your product resources. See
// //developers.google.com/shopping-content/v2/reference/v2/products
// for the full list of fields supported by product resource. See the
// insertProduct() snippet for a code example that shows how to construct
// a product resource.
var productResource1 = {
// FILL THIS OUT.
};

var productResource2 = {
// FILL THIS OUT.
};

var productResource3 = {
// FILL THIS OUT.
};

var custombatchResource = {
‘entries’: [
{
‘batchId’: 1,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book124’,
‘product’: productResource1
},
{
‘batchId’: 2,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book125’,
‘product’: productResource2
},
{
‘batchId’: 3,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book126’,
‘product’: productResource3
},

]
};
var response = ShoppingContent.Products.custombatch(custombatchResource);
Logger.log(response);
}

 

Obter informações da conta do comerciante

function getAccountInfo() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;
var accountId = ‘INSERT_ACCOUNT_ID_HERE’;

// See //developers.google.com/shopping-content/v2/reference/v2/accounts
// for the list of fields supported by Account type.
var accounts = ShoppingContent.Accounts.get(merchantId, accountId);
Logger.log(accounts);

// See //developers.google.com/shopping-content/v2/reference/v2/accountstatuses
// for the list of account status fields supported by Shopping Content API.
var accountstatuses = ShoppingContent.Accountstatuses.get(merchantId,
accountId);
Logger.log(accountstatuses);

// See //developers.google.com/shopping-content/v2/reference/v2/accountshipping
// for various Account shipping settings fields supported by Shopping
// Content API..
var accountshipping = ShoppingContent.Accountshipping.get(merchantId,
accountId);
Logger.log(accountshipping);

// See //developers.google.com/shopping-content/v2/reference/v2/accounttax
// for various Account tax fields supported by Shopping Content API.
var accounttax = ShoppingContent.Accounttax.get(merchantId, accountId);
Logger.log(accounttax);
}

3. Rede de Display – Por Google Ads. Esse script permite gerenciar alguns recursos da rede de display sem que você utilize a plataforma.

Adicionar um canal a um grupo de anúncios existente

function addPlacementToAdGroup() {
var adGroup = AdWordsApp.adGroups()
.withCondition(“Name = ‘INSERT_ADGROUP_NAME_HERE'”)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be built in a similar manner using the
// corresponding builder method in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var placementOperation = adGroup.display()
.newPlacementBuilder()
.withUrl(‘//www.site.com’) // required
.withCpc(0.50) // optional
.build();
var placement = placementOperation.getResult();
Logger.log(‘Placement with id = %s and url = %s was created.’,
placement.getId(), placement.getUrl());
}

 

Recuperar todos os tópicos em um grupo de anúncios existente

function getAllTopics() {
var adGroup = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be retrieved in a similar manner using
// the corresponding selector methods in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var topicIterator = AdWordsApp.display()
.topics()
.withCondition(‘Impressions > 100’)
.forDateRange(‘LAST_MONTH’)
.orderBy(‘Clicks DESC’)
.get();

while (topicIterator.hasNext()) {
var topic = topicIterator.next();

// The list of all topic IDs can be found on
// //developers.google.com/adwords/api/docs/appendix/verticals
Logger.log(‘Topic with criterion id = %s and topic id = %s was ‘ +
‘found.’, topic.getId().toFixed(0),
topic.getTopicId().toFixed(0));
}
}

 

Obter estatísticas de todos os públicos-alvo de um grupo de anúncios existente

function getAudienceStats() {
var adGroup = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be retrieved in a similar manner using
// the corresponding selector methods in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var audienceIterator = adGroup.display()
.audiences()
.get();

Logger.log(‘ID, Audience ID, Clicks, Impressions, Cost’);

while (audienceIterator.hasNext()) {
var audience = audienceIterator.next();
var stats = audience.getStatsFor(‘LAST_MONTH’);

// User List IDs (List IDs) are available on the details page of
// a User List (found under the Audiences section of the Shared
// Library)
Logger.log(‘%s, %s, %s, %s, %s’, audience.getId().toFixed(0),
audience.getAudienceId(), stats.getClicks(),
stats.getImpressions(), stats.getCost());
}
}

4. Encontre oportunidades nos canais na Rede de Display – Por Derek Martin. O script destaca canais que estão com a performance baixa com base em algumas condições. A rede de display gera uma grande quantidade de canais, com esse script é possível economizar tempo já que ele faz essa análise automaticamente.

// This script reviews your GDN placements for the following conditions:
// 1) Placements that are converting at less than $40
// 2) Placements that have cost more than $50 but haven’t converted
// 3) Placements that have more than 5K impressions and less than .10 CTR

function main() {

var body = “<h2>Google Display Network Alert</h2>”;
body += “<h3>Placements that are converting at less than $40:</h3> ” ;
body += “<ul>”;

var list = runLowCostAndConvertingReport();

for (i=0; i < list.length; i++) {
body += “<li><strong>” + list[i].placement + “</strong> – ” + list[i].adgroup + ‘ – $’ + list2[i].cost + “</li>”;

}
body += “</ul>”;

body += “<h3>Placements that have cost more than $50 but haven’t converted:</h3> ” ;
body += “<ul>”;

var list2 = runHighCostNoConversionsReport();

for (i=0; i < list2.length; i++) {
body += “<li><strong>” + list2[i].placement + “</strong> – ” + list2[i].adgroup + ‘ – $’ + list2[i].cost + “</li>”;

}
body += “</ul>”;

body += “<h3>Placements that have more than 5K impressions and less than .10 CTR:</h3> ” ;
body += “<ul>”;

var list3 = runLowCtrAndHighImpressionsReport();

for (i=0; i < list3.length; i++) {
body += “<li><strong>” + list3[i].placement + “</strong> – ” + list3[i].adgroup +” – ” + parseFloat(list3[i].clicks/list3[i].impressions).toFixed(4) + “% – ” + list3[i].clicks + ” clicks – ” + list3[i].impressions + ‘ impressions ‘ + “</li>”;

}
body += “</ul>”;

MailApp.sendEmail(‘[email protected]’,’Display Network Alerts ‘, body,{htmlBody: body});
}

function runLowCostAndConvertingReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Cost < 40000000 ‘ +
‘AND Conversions >= 2 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}
function runLowCtrAndHighImpressionsReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Impressions > 5000 ‘ +
‘AND Ctr < 0.1 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}

function runHighCostNoConversionsReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Cost > 50000000 ‘ +
‘AND Conversions = 0 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}

function placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost) {
this.placement = placement;
this.campaign = campaign;
this.adgroup = adgroup;
this.clicks = clicks;
this.impressions = impressions;
this.conversions = conversions;
this.cost = cost;

}

// Helpers
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

5. Identifique mudanças bruscas nos produtos do Google Shopping – Por Derek Martin. Receba um e-mail com um alerta de mudanças nas suas campanhas do Google Shopping. Ele analisa o custo e o CPC médio de seus produtos.

/***************************************************************************************
* AdWords Account Management — Review Google Shopping Products for sharp changes in
* Product level Cost and Product level Avg CPC (200%+ or -200%)
* Will e-mail any offending products for review.
* Version 1.1
* Created By: Derek Martin
* DerekMartinLA.com
****************************************************************************************/
var EMAIL_ADDRESS = “[email protected]

function main() {
var clientName = AdWordsApp.currentAccount().getName().split(“-“)[0];
var offendingProducts = []; // will hold list of product items that need review
var products = [];

products = runShoppingReport();
offendingProducts = analyzeShoppingResults(products);

if (offendingProducts.length > 0) {
var file = createSpreadsheet(clientName,offendingProducts);
sendAnEmail(clientName[0], offendingProducts.toString(), file);
} // end of if statement

} // end of main function

function runShoppingReport() {

// var reportFormat = _.str.quote(camp.getName());
var listOfProducts = [];

var report = AdWordsApp.report(
‘SELECT Date, Brand, OfferId, AverageCpc, Cost ‘ +
‘FROM SHOPPING_PERFORMANCE_REPORT ‘ +
‘DURING LAST_7_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {
var row = rows.next();

var brand = row[‘Brand’];
var offerId= row[‘OfferId’];
var date = row[‘Date’];
var averageCpc = row[‘AverageCpc’];
var cost = row[‘Cost’];

var productResult = new productData(brand, offerId, date, averageCpc, cost);

listOfProducts.push(productResult);

} // end of report run

return listOfProducts;

}

function productData(brand, offerId, date, averageCpc, totalCost) {
this.brand = brand;
this.offerId = offerId;
this.date = date;
this.averageCpc = averageCpc;
this.totalCost = totalCost;
} // end of productData

function analyzeShoppingResults (products) {
var listOfProducts = products;
var listOfResults = [];

listOfProducts = _.uniq(listOfProducts);
listOfProducts.sort(); // sort list to keep things clean

var i = 0;
for each (offerId in listOfProducts) {

var currentOffer = _.where(listOfProducts, {offerId: listOfProducts[i].offerId});
if (currentOffer.length > 1) { // check if there are multiple dates at play, this way we can calculate min and max-

var oldestAvgCpc = parseFloat(currentOffer[0].averageCpc);
var oldestCost = parseFloat(currentOffer[0].totalCost);
var newestAvgCpc = parseFloat(currentOffer[currentOffer.length – 1].averageCpc);
var newestCost = parseFloat(currentOffer[currentOffer.length – 1].totalCost);

var cpcChange = parseFloat((newestAvgCpc – oldestAvgCpc) / oldestAvgCpc).toFixed(2);
var costChange = parseFloat((newestCost – oldestCost) / oldestCost).toFixed(2);

var offerResult = new productResult(listOfProducts[i].brand, listOfProducts[i].offerId, oldestAvgCpc, newestAvgCpc, cpcChange, oldestCost, newestCost, costChange);

listOfResults.push(offerResult);
} // end of length if statement
i++;
} // end of for each

listOfResults = _.uniq(listOfResults);

listOfResults.sort();

var uniqueList = _.uniq(listOfResults, function(item, key, productId) {
return item.productId;
} );

uniqueList.sort();

var offendingProducts = _.filter (uniqueList, function(product) {
return product.cpcDelta <= -2 || product.cpcDelta >=2 || product.costDelta >= 2 || product.costDelta <= -2 ;
});

return offendingProducts;

} // end of analyzeShoppingResults

function productResult (brand, id, oldCpc, newCpc, cpcDelta, oldCost, newCost, costDelta) {
this.brand = brand;
this.productId = id;
this.oldCpc = oldCpc;
this.newCpc = newCpc
this.cpcDelta = cpcDelta;
this.oldCost = oldCost;
this.newCost = newCost;
this.costDelta = costDelta;

} // end of productResult

function createSpreadsheet(client,results) {

var productResults = results;
var clientName = client;
var spreadsheetName = clientName + ‘-shoppingreport’;

var newSS = SpreadsheetApp.create(spreadsheetName, results.length, 26);

var sheet = newSS.getActiveSheet();

var columnNames = [“ProductId”, “Brand”, “Old AvgCpc”, “New AvgCpc”, “AvgCpc Delta”, “Old Daily Cost”, “New Daily Cost”, “Cost Delta”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

for (i = 0; i < productResults.length; i++) {

headersRange.setValues([columnNames]);

var product;
product = productResults[i].productId;
var productBrand = productResults[i].brand;
var oldAvgCpc = productResults[i].oldCpc;
var newAvgCpc = productResults[i].newCpc;
var cpcDelta = productResults[i].cpcDelta;
var oldCost = productResults[i].oldCost;
var newCost = productResults[i].newCost
var costDelta = productResults[i].costDelta;

sheet.appendRow([product, productBrand, oldAvgCpc, newAvgCpc, cpcDelta, oldCost, newCost, costDelta]);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);

var range = sheet.getRange(sheet.getMaxColumns(), sheet.getMaxRows());

range.setFontFamily(“Helvetica”);
range.setFontSize(30);

}

return newSS.getUrl();

}

function sendAnEmail (clientName, results, fileUrl) {

var data = Utilities.parseCsv(results, ‘\t’);
var today = new Date();
today = today.getMonth() + today.getDate() + today.getFullYear();

var filename = clientName + ‘search-results’ + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, ‘text/html’, ”);

MailApp.sendEmail(EMAIL_ADDRESS, clientName + ‘ – Google Shopping Alert Results ‘, ‘There are Google Shopping Products that need your attention. You can find the results at the following URL:\n\n’ + fileUrl, {
name: ‘Google Shopping Alert’
});

} // end of sendAnEmail function
/* HELPER FUNCTIONS */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

/* UNDERSCORE LIBRARIES */

// Underscore.js 1.6.0
// //underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};”undefined”!=typeof exports?(“undefined”!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION=”1.6.0″;var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O=”Reduce of empty array with no initial value”;j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[–i]:–i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),”value”)};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,”length”).concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,””+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if(“number”!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u–;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error(“bindAll must be passed function names”);return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:j.now(),a=null,i=n.apply(e,u),e=u=null};return function(){var l=j.now();o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r–)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return–n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case”[object String]”:return n==String(t);case”[object Number]”:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case”[object Date]”:case”[object Boolean]”:return+n==+t;case”[object RegExp]”:return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if(“object”!=typeof n||”object”!=typeof t)return!1;for(var i=r.length;i–;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&”constructor”in n&&”constructor”in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if(“[object Array]”==u){if(c=n.length,f=c==t.length)for(;c–&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c–)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return”[object Array]”==l.call(n)},j.isObject=function(n){return n===Object(n)},A([“Arguments”,”Function”,”String”,”Number”,”Date”,”RegExp”],function(n){j[“is”+n]=function(t){return l.call(t)==”[object “+n+”]”}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,”callee”))}),”function”!=typeof/./&&(j.isFunction=function(n){return”function”==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||”[object Boolean]”==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{“&”:”&amp;”,”<“:”&lt;”,”>”:”&gt;”,'”‘:”&quot;”,”‘”:”&#x27;”}};T.unescape=j.invert(T.escape);var I={escape:new RegExp(“[“+j.keys(T.escape).join(“”)+”]”,”g”),unescape:new RegExp(“(“+j.keys(T.unescape).join(“|”)+”)”,”g”)};j.each([“escape”,”unescape”],function(n){j[n]=function(t){return null==t?””:(“”+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+””;return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={“‘”:”‘”,”\\”:”\\”,”\r”:”r”,”\n”:”n”,” “:”t”,”\u2028″:”u2028″,”\u2029″:”u2029″},D=/\\|’|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join(“|”)+”|$”,”g”),i=0,a=”__p+='”;n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return”\\”+B[n]}),r&&(a+=”‘+\n((__t=(“+r+”))==null?”:_.escape(__t))+\n'”),e&&(a+=”‘+\n((__t=(“+e+”))==null?”:__t)+\n'”),u&&(a+=”‘;\n”+u+”\n__p+='”),i=o+t.length,t}),a+=”‘;\n”,r.variable||(a=”with(obj||{}){\n”+a+”}\n”),a=”var __t,__p=”,__j=Array.prototype.join,”+”print=function(){__p+=__j.call(arguments,”);};\n”+a+”return __p;\n”;try{e=new Function(r.variable||”obj”,”_”,a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source=”function(“+(r.variable||”obj”)+”){\n”+a+”}”,c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A([“pop”,”push”,”reverse”,”shift”,”sort”,”splice”,”unshift”],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),”shift”!=n&&”splice”!=n||0!==r.length||delete r[0],z.call(this,r)}}),A([“concat”,”join”,”slice”],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),”function”==typeof define&&define.amd&&define(“underscore”,[],function(){return j})}).call(this);
//# sourceMappingURL=underscore-min.map

// Underscore.string
!function(e,t){“use strict”;var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return””;var n=””;while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?”\\s”:e.source?e.source:”[“+p.escapeRegExp(e)+”]”},f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”)f.push(r[l]);else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!=”number”)throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)));switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a,v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “,m=p[6]-t(a).length,d=p[6]?n(v,m):””,f.push(p[5]?a+d:d+a)}}return f.join(“”)},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push(“%”);else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error(“[_.sprintf] huh?”);if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1]);while((o=o.substring(u[0].length))!==””)if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”);r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:”2.3.0″,isBlank:function(e){return e==null&&(e=””),/^\s*$/.test(e)},stripTags:function(e){return e==null?””:t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){return e=e==null?””:t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e])},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split(“”)},swapCase:function(e){return e==null?””:t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?””:t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){return e==null?””:t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?””:t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===””?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=””),e.join(t)},lines:function(e){return e==null?[]:t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?””:(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?””:t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){return e==null?””:!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””))},ltrim:function(e,n){return e==null?””:!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp(“^”+n+”+”),””))},rtrim:function(e,n){return e==null?””:!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+”+$”),””))},truncate:function(e,n,r){return e==null?””:(e=t(e),r=r||”…”,n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return””;e=t(e),n=~~n,r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,””):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=” “;switch(i){case”right”:return s=n-e.length,e+o(r,s);case”both”:return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t),r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””,t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “,n=n||” and “;var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“}),p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!=”undefined”?(typeof module!=”undefined”&&module.exports&&(module.exports=p),exports._s=p):typeof define==”function”&&define.amd?define(“underscore.string”,[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);

 

6. Criador de grupos de anúncios do Shopping em massa – Por Google Ads. Crie grupos de anúncios e produtos em massa com esse script do Google. Para utilizá-lo é necessário que você utilize uma planilha do Google.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Bulk Shopping AdGroup Creator
*
* @overview The Bulk Shopping AdGroup Creator provides a way to bulk create ad
* groups and product groups in existing Shopping Campaigns. See
* //developers.google.com/adwords/scripts/docs/solutions/bulk-shopping-ad-group-creator
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Removed use of ProductAdBuilder.withPromotionLine as it is deprecated.
* – version 1.0.1
* – Added validation for external spreadsheet setup.
* – version 1.0
* – Released initial version.
*/

/**
* SPREADSHEET_URL: URL for spreadsheet to read
* SHEET_NAME: Name of sheet in spreadsheet to read
*/
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;
var SHEET_NAME = ‘YOUR_SHEET_NAME’;

/**
* Column header specification
* These are the actual text the script looks for in the spreadsheet header.
*/
var CAMPAIGN_COLUMN = ‘Campaign’;
var AD_GROUP_COLUMN = ‘Ad Group’;
var MAX_CPC_COLUMN = ‘Max CPC’;

var PRODUCT_GROUP_COLUMN = ‘Product Group’;
var AD_GROUP_STATUS_COLUMN = ‘AdGroup Status’;

var REQUIRED_COLUMN_NAMES = {};
REQUIRED_COLUMN_NAMES[CAMPAIGN_COLUMN] = true;
REQUIRED_COLUMN_NAMES[AD_GROUP_COLUMN] = true;
REQUIRED_COLUMN_NAMES[MAX_CPC_COLUMN] = true;
REQUIRED_COLUMN_NAMES[PRODUCT_GROUP_COLUMN] = true;
REQUIRED_COLUMN_NAMES[AD_GROUP_STATUS_COLUMN] = true;
/** End of column header specification */

/**
* Reads campaign and bid data from spreadsheet and writes it to AdWords.
*/
function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var sheet = validateAndGetSpreadsheet(SPREADSHEET_URL, SHEET_NAME);
parseSheetAndConstructProductGroups(sheet);

Logger.log(‘Parsed spreadsheet and completed shopping campaign ‘ +
‘construction.’);
}

//Stores results of each row, along with formatting details
var resultsByRow = [‘Result’, ‘Notes’];
var resultColors = [‘Black’, ‘Black’];

/**
* Validates header of sheet to make sure that header matches expected format.
* Throws exception if problems are found. Saves the column number of each
* expected column to global variable.
*
* @param {!Array.<string>} headerRow A list of column header names.
* @return {!Object.<number>} A mapping from column name to column index.
* @throws If required column is missing.
*/
function validateHeader(headerRow) {
var result = {};

var missingColumns = Object.keys(REQUIRED_COLUMN_NAMES);

// Grab the column # for each expected input column.
for (var columnIndex = 0; columnIndex < headerRow.length; columnIndex++) {
var columnName = headerRow[columnIndex];
if (columnName in REQUIRED_COLUMN_NAMES) {
result[columnName] = columnIndex;
var index = missingColumns.indexOf(columnName);
if (index >= 0) {
missingColumns.splice(index, 1);
}
}
}

if (missingColumns.length > 0) {
throw ‘Bid sheet data format doesn\’t match expected format. ‘ +
‘Missing columns: ‘ + missingColumns.join();
}
return result;
}

/**
* Converts a spreadsheet row into map representation.
*
* @param {!Array.<!Object>} row The spreadsheet row.
* @param {!Object.<number>} headers Mapping from column name to column index.
* @return {?Object} The row in object form, or null for a parsing error.
*/
function parseRow(row, headers) {
var parsedRow = {};

for (var header in headers) {
var colNum = headers[header];
var val = row[colNum].toString().trim();
if (!val) {
continue;
}
// AWEditor will add double quotes (“) around string if it contains
// commas or double quotes, so we need to strip those out.
if (val.charAt(0) == ‘”‘ && val.charAt(val.length – 1) == ‘”‘) {
val = val.substring(1, val.length – 1);
}
// AWEditor escapes double quotes (“) with another double quote (“”).
val = val.replace(/””/g, ‘”‘);
if (header == PRODUCT_GROUP_COLUMN) {
var productGroups = [];
var parsedProductGroups =
parseAWEditorFormat(val);
if (parsedProductGroups.error) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = parsedProductGroups.error;
resultColors[0] = ‘Red’;
return null;
}

for (var x = 0; x < parsedProductGroups.length; x++) {
// Product group type and value indices are level-1
// (e.g. for L1, x=0).
productGroups[x] = {
type: parsedProductGroups[x][0],
value: parsedProductGroups[x][1]
};
}
parsedRow[header] = productGroups;
} else {
parsedRow[header] = val;
}
}

// Ignore rows that don’t have any useful information.
var testRow = [];
for (var k in parsedRow) {
// Remove campaign, ad group columns and test if the rest is empty.
if (k == CAMPAIGN_COLUMN || k == AD_GROUP_COLUMN) {
continue;
}
testRow.push(parsedRow[k]);
}
if (testRow.toString().replace(/[,]+/g, ”) == ”) {
resultsByRow[0] = ‘SKIPPED’;
resultsByRow[1] = ‘Superfluous row’;
return null;
}
return parsedRow;
}

/**
* Parses spreadsheet and constructs ad groups and product groups in AdWords.
*
* @param {!Sheet} sheet The sheet to parse.
*/
function parseSheetAndConstructProductGroups(sheet) {
var headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];

var headers = validateHeader(headerRow);

var values = sheet.getRange(2, 1, sheet.getLastRow() – 1,
sheet.getLastColumn()).getValues();

var campaigns = {};

var outputColumn = sheet.getLastColumn() + 1;
var resultHeaderRange = sheet.getRange(1, outputColumn, 1, 2);
resultHeaderRange.setValues([resultsByRow]);
resultHeaderRange.setFontColors([resultColors]);
resultHeaderRange.setFontWeights([[‘Bold’, ‘Bold’]]);

// Iterate through rows.
for (var r = 0; r < values.length; r++) {
resultsByRow = [”, ”];
resultColors = [‘Black’, ‘Black’];
var row = values[r];

var parsedRow = parseRow(row, headers);

if (parsedRow) {
var bid = parsedRow[MAX_CPC_COLUMN];
if (bid) {
// For European locale, decimal points are commas.
bid = bid.toString().toLowerCase().replace(/,/g, ‘.’);
if (bid != ‘excluded’ && (isNaN(parseFloat(bid)) || !isFinite(bid))) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Invalid bid’;
}
}
var campaignName = parsedRow[CAMPAIGN_COLUMN];
campaigns[campaignName] =
fetchCampaignIfNecessary(campaigns, campaignName);
if (campaigns[campaignName]) {
var adGroupName = parsedRow[AD_GROUP_COLUMN];
campaigns[campaignName].createdAdGroups[adGroupName] =
buildAdGroupIfNecessary(parsedRow, campaigns[campaignName],
adGroupName, bid);
if (campaigns[campaignName].createdAdGroups[adGroupName]) {
if (!campaigns[campaignName].createdAdGroups[adGroupName].skip) {
buildProductGroups(parsedRow,
campaigns[campaignName].createdAdGroups[adGroupName], bid);
} else {
resultsByRow[0] = ‘SKIPPED’;
resultsByRow[1] =
‘Ad group already exists with product groups’;
}
}
}

if (!resultsByRow[0]) {
resultsByRow[0] = ‘SUCCESS’;
}
}
switch (resultsByRow[0]) {
case ‘ERROR’:
resultColors[0] = ‘Red’;
break;
case ‘SUCCESS’:
resultColors[0] = ‘Green’;
break;
case ‘WARNING’:
resultColors[0] = ‘Yellow’;
}
var resultRange = sheet.getRange(r + 2, outputColumn, 1, 2);
resultRange.setValues([resultsByRow]);
resultRange.setFontColors([resultColors]);
resultRange.setFontWeights([[‘Bold’, ‘Normal’]]);
}
}

/**
* Fetches campaign from AdWords if it hasn’t already been done.
*
* @param {!Object.<ShoppingCampaign>} campaigns A cache of campaigns.
* @param {string} campaignName The name of the campaign to fetch.
* @return {?ShoppingCampaign} The campaign, or null if not found.
*/
function fetchCampaignIfNecessary(campaigns, campaignName) {
//Find the campaign
if (!campaignName) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Missing campaign name’;
return null;
}
var campaign = campaigns[campaignName];
if (!campaign) {
campaign = findCampaignByName(campaignName);
campaign.createdAdGroups = {};
if (!campaign) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Could not find campaign’;
}
}
return campaign;
}

/**
* Builds ad group if necessary, otherwise returns existing ad group.
*
* @param {!Array.<Object>} row A spreadsheet row.
* @param {!ShoppingCampaign} campaign
* @param {string} adGroupName The ad group to build or fetch.
* @param {number} bid
* @return {?ShoppingAdGroup} The ad group of null if there is an error.
*/
function buildAdGroupIfNecessary(row, campaign, adGroupName, bid) {
if (!adGroupName) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Missing ad group name’;
return null;
}
// See if we already fetched/created the ad group.
var adGroup = campaign.createdAdGroups[adGroupName];
if (!adGroup) {
// Only use the bid on this line for the ad group default bid if there are
// no product groups specified for it. Ad group default bid must be
// specified.
if (row[PRODUCT_GROUP_COLUMN]) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Ad Group is missing a default bid.’;
return null;
}
// Set default status to enabled.
var status = ‘ENABLED’;
// If ad group status is specified, make sure it’s “active”, “enabled”, or
// “paused”, and set status. Ad group status must be set on the first row
// that the ad group appears in.
if (row[AD_GROUP_STATUS_COLUMN]) {
status = row[AD_GROUP_STATUS_COLUMN].toUpperCase();
if (status == ‘ACTIVE’) {
status = ‘ENABLED’;
}
}
adGroup = createAdGroup(adGroupName, status, bid, campaign);
if (adGroup) {
adGroup.rootProductGroup = adGroup.rootProductGroup();
adGroup.rootProductGroup.childMap = {};
}
}
return adGroup;
}

/**
* Builds product groups from row.
*
* @param {!Array.<Object>} row A spreadsheet row.
* @param {!ShoppingAdGroup} adGroup The ad group to operate on.
* @param {number} bid The product group bid.
*/
function buildProductGroups(row, adGroup, bid) {
if (!row[PRODUCT_GROUP_COLUMN]) {
return;
}
// Iterate through product groups in row.
var maxLevel = -1;
var productGroupsToAdd = row[PRODUCT_GROUP_COLUMN];
var productGroup = adGroup.rootProductGroup;
for (var i = 0; i < productGroupsToAdd.length; i++) {
var type =
productGroupsToAdd[i].type.toString().toLowerCase()
.replace(/[ ]+/g, ”);
var val = productGroupsToAdd[i].value.toString().trim();
if (type) {
//For each Group level n, row must contain values for 1…n-1
if (i – maxLevel > 1) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Every level of the product ‘ +
‘group type must have all higher ‘ +
‘level values. L’ + i + ‘ is filled but missing L’ +
(maxLevel + 1);
return;
}
maxLevel = i;

// Each row must have matching # of bidding attribute type and value.
if (!val) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Every product group type must have an associated value. L’ +
i + ‘ has a type but no value’;
return;
}

// Build product groups.
if (!productGroup.childMap[val.toLowerCase()]) {
if (val == ‘*’) {
if (Object.keys(productGroup.childMap).length == 0) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘”Everything else” product group must come after all others’;
return;
} else {
var child = productGroup.childMap[val];
if (!child) {
var children = productGroup.children().get();
while (children.hasNext()) {
child = children.next();
if (child.isOtherCase()) {
break;
}
}
child.childMap = {};
}
productGroup.childMap[val] = child;
productGroup = child;
productGroup.setMaxCpc(adGroup.getMaxCpc());
//Only assign the bid to the lowest level product group
//on that row
if (i + 1 == productGroupsToAdd.length) {
if (bid != ‘excluded’) {
productGroup.setMaxCpc(bid);
} else {
productGroup.exclude();
}
}
}
} else {
var productGroupBuilder = productGroup.newChild();
// Verify that bidding attribute type is valid, construct
// productGroupBuilder.
switch (type) {
case ‘producttype’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.productTypeBuilder()
.withValue(val);
break;
case ‘brand’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.brandBuilder()
.withName(val);
break;
case ‘category’:
productGroupBuilder = productGroupBuilder.categoryBuilder()
.withName(val);
break;
case ‘condition’:
val = val.toUpperCase();
productGroupBuilder = productGroupBuilder.conditionBuilder()
.withCondition(val);
break;
case ‘itemid’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.itemIdBuilder()
.withValue(val);
break;
default:
if (type.match(/^custom((\\s)?(label|attribute))?/)) {
val = val.toLowerCase();
//make sure there’s a number at the end that’s between 0-4
if (type.match(/[0-4]$/)) {
productGroupBuilder =
productGroupBuilder.customLabelBuilder()
.withType(‘CUSTOM_LABEL_’ +
type.substring(type.length – 1))
.withValue(val);
} else {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Invalid custom attribute type: ‘ +
productGroupsToAdd[i].type;
return;
}
} else {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Invalid bidding attribute type: ‘ +
productGroupsToAdd[i].type;
return;
}
}

var productGroupOp = productGroupBuilder.build();

if (!productGroupOp.isSuccessful()) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Error creating product group ‘ +
‘level ‘ + (i + 1) + ‘: ‘ + productGroupOp.getErrors();
return;
}

var result = productGroupOp.getResult();
// Only assign the bid to the lowest level product group on that row.
if (i + 1 == productGroupsToAdd.length) {
if (bid == ‘excluded’) {
result.exclude();
} else if (bid) {
result.setMaxCpc(bid);
}
}

result.childMap = {};
productGroup.childMap[val.toLowerCase()] = result;

// Set current product group to the newly created product group.
productGroup = result;
}
} else {
// Set current product group to the last read product group.
productGroup = productGroup.childMap[val.toLowerCase()];
}
}
}
}

/**
* Parses AdWords Editor product group format
* (e.g. * / Condition=’New’ / Custom label 2=’furniture’ /
* Product type=’bar carts’).
*
* @param {string} productGroupPath The product group path.
* @return {!Array.<!Array.<string>>} A list of product group component name/
* value pairs.
*/
function parseAWEditorFormat(productGroupPath) {
// Ignore * / case which is the root product group.
if (productGroupPath == ‘* /’ || !productGroupPath) {
return [];
}

var regexVals = productGroupPath.match(new RegExp(/'(.*?)’/g));

if (regexVals) {
for (var i = 0; i < regexVals.length; i++) {
productGroupPath = productGroupPath.replace(regexVals[i], ‘$’ + i);
}
}

var result = [];
var productGroup = productGroupPath.split(‘/’);

// AW Editor format starts with ‘* /’ so we ignore first one.
for (var x = 1; x < productGroup.length; x++) {
if (!productGroup[x]) {
continue;
}
// AW Editor format looks like: Brand=’nike’.
var pair = productGroup[x].trim().split(‘=’);
if (pair.length != 2) {
return {error: ‘Product group string malformed. Should have 1 “=”, ‘ +
‘but has ‘ + (pair.length – 1)};
}
var val = pair[1];
if (val.charAt(0) != ‘$’ && val.charAt(0) != ‘*’) {
return {error: ‘Product group string malformed. Please ensure you are ‘ +
‘using AdWords Editor format’};
}
// ‘*’ value doesn’t have single quotes around it.
if (val != ‘*’) {
var values = pair[1].split(‘$’);
// Skip 0 because it’s always blank. String always starts with $.
for (var i = 1; i < values.length; i++) {
val = val.replace(‘$’ + values[i], regexVals[values[i]]);
}
val = val.substring(1, val.length – 1).replace(/”/g, ‘\”);
}

result.push([pair[0], val]);
}
return result;
}

/**
* Fetches campaign from AdWords by name.
*
* @param {string} name The campaign name.
* @return {?ShoppingCampaign} The found campaign, or null if not found.
*/
function findCampaignByName(name) {
var campaignName = name.replace(/’/g, ‘\\\”);
var shoppingCampaignSelector = AdWordsApp
.shoppingCampaigns()
.withCondition(‘Name = \” + campaignName + ‘\”);

var campaign = null;

var shoppingCampaignIterator = shoppingCampaignSelector.get();
if (shoppingCampaignIterator.hasNext()) {
campaign = shoppingCampaignIterator.next();
}

return campaign;
}

/**
* Fetches ad group from AdWords given ad group name and campaign.
*
* @param {string} agName The name of the ad group.
* @param {!ShoppingCampaign} campaign The campaign within which to search.
* @return {?ShoppingAdGroup} The ad group or null if not found.
*/
function findAdGroupByName(agName, campaign) {
var adGroupName = agName.replace(/’/g, ‘\\\”);
var adGroupSelector = campaign
.adGroups()
.withCondition(‘Name = \” + adGroupName + ‘\”);

var adGroup = null;

var adGroupIterator = adGroupSelector.get();
if (adGroupIterator.hasNext()) {
adGroup = adGroupIterator.next();
}

return adGroup;
}

/**
* Creates ad group in AdWords if it doesn’t already exist, along with ad group
* ad.
*
* @param {string} name The name of the ad group.
* @param {string} status The desired status of the ad group.
* @param {number} defaultBid The max CPC for the ad group.
* @param {!ShoppingCampaign} campaign The campaign to create the ad group for.
* @return {?ShoppingAdGroup} The created ad group or null if there is an error.
*/
function createAdGroup(name, status, defaultBid, campaign) {
// See if ad group exists. If so, fetch it.
var adGroup = findAdGroupByName(name, campaign);
if (adGroup != null) {
if (adGroup.rootProductGroup()) {
// If root product group exists and not delete, then skip ad group.
adGroup.skip = true;
} else {
adGroup.createRootProductGroup();
}
return adGroup;
}

// Build ad group.
var adGroupOp = campaign.newAdGroupBuilder()
.withName(name)
.withStatus(status)
.withMaxCpc(defaultBid)
.build();

// Check for errors.
if (!adGroupOp.isSuccessful()) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Error creating ad group: ‘ +
adGroupOp.getErrors();
return null;
}

var adGroupResult = adGroupOp.getResult();

adGroupResult.createRootProductGroup();

Logger.log(‘Successfully created ad group [‘ + adGroupResult.getName() + ‘]’);

// Build ad group ad.
var adGroupAdOp = adGroupResult.newAdBuilder().build();

// Check for errors.
if (!adGroupAdOp.isSuccessful()) {
resultsByRow[0] = ‘WARNING’;
resultsByRow[1] = ‘Error creating ad group ad: ‘ +
adGroupAdOp.getErrors();
}

return adGroupResult;
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and email addresses at the top of the file
* only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @param {string} sheetname The name of the sheet within the spreadsheet that
* should be fetched.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl, sheetname) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
if (sheetname == ‘YOUR_SHEET_NAME’) {
throw new Error(‘Please specify the name of the sheet you want to use on’ +
‘ your spreadsheet.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
var sheet = spreadsheet.getSheetByName(sheetname);
return sheet;
}

 

Como Configurar

Google Ads

Google Ads – Quais as novidades para as quais você deve estar preparado

rotadesk No Comments

Google Adwords agora passa a ser Google Ads

Google Ads – Quais as novidades para as quais você deve estar preparado

Se você trabalha com marketing digital e utiliza o Google Ads para seus clientes ou para seu negócio, provavelmente já sabe que atualmente a plataforma está passando por algumas mudanças.

O Google Adwords será agora “Anúncios do Google” ou simplesmente, Google Ads e, além da mudança óbvia do logotipo, há algumas alterações importantes. Saiba para quais as novidades para as quais você deve estar preparado.

Campanha Inteligente para Shopping

Google Shopping

Nesses últimos anos o Google vem trazendo novidades em tecnologia de aprendizado de máquinas para automatizar tarefas, como por exemplo os lances automatizados. E nessa última atualização o Google anunciou que vai começar a utilizar esse recurso nas campanhas de Shopping.

As campanhas inteligentes para o Google Shopping vão funcionar da seguinte maneira, o usuário tem a opção de selecionar qual é o objetivo da campanha, seja novos clientes ou visitas na loja física. Após isso, a própria ferramenta do Google vai ajustar seus lances, otimizações de locais, produtos a serem disponibilizados etc.

Em conjunto as novidades do Google Shopping, será lançado também um novo recurso de feed automatizado, em que será possível de extrair feeds de produtos do website na interface de usuário.

Anúncios Responsivos

Prepare-se para os anúncios gráficos responsivos. Em breve, você só precisará fornecer ao Google um URL, título, descrição e imagem, e o Google criará anúncios gráficos responsivos para você.

Você poderá exibir anúncios que se adaptam ao conteúdo dos websites em que eles estão e aplicativos.  Isso é muito legal porque é difícil criar anúncios para todos os formatos diferentes (arranha-céu, tabela de classificação, quadrado, retângulo, etc).

Além disso, o novo anúncio adaptável do Google Ads permite que você crie até 15 títulos e 4 descrições. E ainda, essa alteração vai permitir que os anúncios tenham 3 títulos e 2 descrições em um anúncio. O que pode ser fantástico para testar a performance das variações de criativos sem precisar criar vários anúncios.

3 títulos e 2 descrições

A ideia é que a plataforma aprenda a mostrar quais combinações fazem mais sentido para cada pesquisa, tornando os resultados responsivos. O Google diz que quem deixa sua tecnologia de aprendizagem de máquina escolher a cópia de seus anúncios tem uma quantidade mais considerável de cliques do que quem decide manualmente.

Assim, quanto mais títulos e descrições você fornecer, maior é o número de combinações que o Google Ads pode testar. Dessa forma, mais pessoas serão atingidas por diferentes formatos de anúncios que levam ao seu site.

Anúncios de leads no YouTube

O alcance do público do YouTube é enorme. Combine isso com a capacidade de permitir que o usuário nunca saia do site e você tem um tipo de anúncio com muito potencial. A novidade tornou possível usar informações específicas para apresentar seus produtos ou serviços a novos clientes em potencial.

Youtube lead Ads

Os principais anúncios do Facebook Ads provaram ser uma ferramenta estratégica valiosa, se executados corretamente. O grande número de anunciantes que usam anúncios de leads no Facebook provavelmente fez com que a ideia do Google se materializasse. Agora, será possível fazer o mesmo no YouTube, o que permite anunciar para audiências que estão navegando na plataforma de vídeos online.

A questão é como os leads vão ser exportados e se eles vão disponibilizar alguma integração com ferramentas de automação. Assim, as agências não precisariam exportar manualmente todos os cadastros e enviá-los para seus respectivos clientes.

Compras inteligentes para comércio eletrônico

Se você é como a grande maioria das agências de marketing digital, existe uma grande probabilidade de gerenciar clientes de comércio eletrônico. Nos últimos dois meses, o Google anunciou que sua tecnologia de aprendizado de máquina agora será transferida para sua plataforma de compras.

Comércio Eletrônico

Quando você se inscreve em uma campanha inteligente, escreve um anúncio que descreve sua empresa. Você também descreverá o produto ou serviço da sua empresa e definirá um orçamento.

Depois disso, o aprendizado automático de máquinas cuida do restante: ajustes de lance, otimização do posicionamento do anúncio e quais produtos são exibidos com base em vários fatores.

Seu anúncio é o que os clientes em potencial verão quando pesquisarem empresas como a sua no Google ou no Google Maps. Seu anúncio inteligente pode aparecer quando clientes em potencial em sua área geográfica segmentada pesquisam frases relacionadas à sua empresa no Google ou no Google Maps.

Seu anúncio também pode ser exibido para pessoas fora da sua vizinhança, mas que incluem termos relacionados à sua empresa e à localização da sua empresa em suas pesquisas.

E quais são as implicações e vantagens de tudo isso para uma agência com clientes de comércio eletrônico?

  • Permite criar um anúncio on-line de maneira rápida e fácil.
  • Possibilidade de atrair mais clientes para seu website ou para a listagem do Google Maps.
  • É necessário menor gerenciamento contínuo. O Google Ads exibe seus anúncios para você.
  • Possibilidade de alcançar clientes em computadores desktop e dispositivos móveis (como telefones celulares e tablets).
  • Analise a eficácia dos seus anúncios no seu painel.
  • Valor agregado para clientes existentes.

As campanhas inteligentes podem ser configuradas em apenas 15 minutos e, em seguida, funcionam para melhorar constantemente seu anúncio em torno de sua meta, medir seu desempenho e mostrar resultados claros e compreensíveis.

Dessa forma, você pode se sentir confiante de que sua publicidade está gerando resultados reais em seu investimento, enquanto você gasta tempo fazendo o que mais gosta: administrar sua empresa.

A partir desta mudança do Google, podemos entender que mais do que nunca devemos preparar nossa empresa para o mundo dos dispositivos móveis e ainda com a vantagem de ter o maior mecanismo de busca nos acompanhando nessa incrível jornada.

Não existem estratégias certeiras, algo que determine o sucesso, mas as mudanças recentes no Google Ads ajudam bastante, principalmente os novos empreendedores, que ainda não tem conhecimento suficiente sobre a ferramenta.

Disputar a atenção dos usuários em meio a tanta competitividade de mercado pode ser uma tarefa difícil, mas se você se concentrar em entregar informações relevantes e priorizar o que seu público-alvo procura, certamente não terá trabalho algum em ser encontrado facilmente nas primeiras páginas do Google.

 

Automação Google Ads: Palavras-chave

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Palavras-chave”.

 

1. Palavras-chave “exatas” – Por Brainlabs. O script faz com que suas palavras-chave sejam exatamente o que estão sendo digitadas, eliminando as variações aproximadas, como erros de digitação, plural, etc.

/**
*
* Adds as campaign or AdGroup negatives search queries which have triggered exact keywords
* Version: 1.1
* Updated: 2015-10-26
* Authors: Visar Shabi & Daniel Gilbert
* brainlabsdigital.com
*
**/
function main() {

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Options

//Choose whether to add your negative exact keywords at campaign or AdGroup level.
//Set variable as “true” to add or as “false” to not add.
var AddAdGroupNegative = true; // true or false
var AddCampaignNegative = true; // true of false

//Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
//Leave blank, i.e. “”, if you want this script to run over all campaigns and AdGroups.
var campaignNameContains = “”;
var adGroupNameContains = “”;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

var campaigns = {};
var adGroups = {};

var exactKeywords = [];

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Pull a list of all exact match keywords in the account

var report = AdWordsApp.report(
“SELECT AdGroupId, Id ” +
“FROM KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE Impressions > 0 AND KeywordMatchType = EXACT ” +
“DURING LAST_7_DAYS”);

var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var keywordId = row[‘Id’];
var adGroupId = row[‘AdGroupId’];
exactKeywords.push(adGroupId + “#” + keywordId);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Pull a list of all exact (close variant) search queries

var report = AdWordsApp.report(
“SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, QueryMatchTypeWithVariant ” +
“FROM SEARCH_QUERY_PERFORMANCE_REPORT ” +
“WHERE CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“AND AdGroupName CONTAINS_IGNORE_CASE ‘” + adGroupNameContains + “‘ ” +
“DURING LAST_7_DAYS”);

var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var adGroupId = parseInt(row[‘AdGroupId’]);
var campaignId = parseInt(row[‘CampaignId’]);
var keywordId = parseInt(row[‘KeywordId’]);
var searchQuery = row[‘Query’].toLowerCase();
var keyword = row[‘KeywordTextMatchingQuery’].toLowerCase();
var matchType = row[‘QueryMatchTypeWithVariant’].toLowerCase();
if(keyword !== searchQuery && matchType.indexOf(“exact (close variant)”) !== -1){

if(!campaigns.hasOwnProperty(campaignId)){
campaigns[campaignId] = [[], []];
}

campaigns[campaignId][0].push(searchQuery);
campaigns[campaignId][1].push(adGroupId + “#” + keywordId);

if(!adGroups.hasOwnProperty(adGroupId)){
adGroups[adGroupId] = [[], []];
}

adGroups[adGroupId][0].push(searchQuery);
adGroups[adGroupId][1].push(adGroupId + “#” + keywordId);
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Parse data correctly

var adGroupIds = [];
var campaignIds = [];
var adGroupNegatives = [];
var campaignNegatives = [];

for(var x in campaigns){
campaignIds.push(parseInt(x));
campaignNegatives.push([]);
for(var y = 0; y < campaigns[x][0].length; y++){
var keywordId = campaigns[x][1][y];
var keywordText = campaigns[x][0][y];
if(exactKeywords.indexOf(keywordId) !== -1){
campaignNegatives[campaignIds.indexOf(parseInt(x))].push(keywordText);
}
}
}

for(var x in adGroups){
adGroupIds.push(parseInt(x));
adGroupNegatives.push([]);
for(var y = 0; y < adGroups[x][0].length; y++){
var keywordId = adGroups[x][1][y];
var keywordText = adGroups[x][0][y];
if(exactKeywords.indexOf(keywordId) !== -1){
adGroupNegatives[adGroupIds.indexOf(parseInt(x))].push(keywordText);
}
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Create the new negative exact keywords

var campaignResults = {};
var adGroupResults = {};

if(AddCampaignNegative){
var campaignIterator = AdWordsApp.campaigns()
.withIds(campaignIds)
.get();
while(campaignIterator.hasNext()){
var campaign = campaignIterator.next();
var campaignId = campaign.getId();
var campaignName = campaign.getName();
var campaignIndex = campaignIds.indexOf(campaignId);
for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
campaign.createNegativeKeyword(“[” + campaignNegatives[campaignIndex][i] + “]”)
if(!campaignResults.hasOwnProperty(campaignName)){
campaignResults[campaignName] = [];
}
campaignResults[campaignName].push(campaignNegatives[campaignIndex][i]);
}
}
}

if(AddAdGroupNegative){
var adGroupIterator = AdWordsApp.adGroups()
.withIds(adGroupIds)
.get();
while(adGroupIterator.hasNext()){
var adGroup = adGroupIterator.next();
var adGroupId = adGroup.getId();
var adGroupName = adGroup.getName();
var adGroupIndex = adGroupIds.indexOf(adGroupId);
for(var i = 0; i < adGroupNegatives[adGroupIndex].length; i++){
adGroup.createNegativeKeyword(“[” + adGroupNegatives[adGroupIndex][i] + “]”);
if(!adGroupResults.hasOwnProperty(adGroupName)){
adGroupResults[adGroupName] = [];
}
adGroupResults[adGroupName].push(adGroupNegatives[adGroupIndex][i]);
}
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Format the results

var resultsString = “The following negative keywords have been added to the following campaigns:”;

for(var x in campaignResults){
resultsString += “\n\n” + x + “:\n” + campaignResults[x].join(“\n”);
}

resultsString += “\n\n\n\nThe following negative keywords have been added to the following AdGroups:”;

for(var x in adGroupResults){
resultsString += “\n\n” + x + “:\n” + adGroupResults[x].join(“\n”);
}

Logger.log(resultsString);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

}

 

2. Oportunidades de Termos de Pesquisa – Por Daniel Gilbert. Utilize esse scripts para gerar insights dos termos que são buscados e que geraram boas conversões ou apenas custos. Ele quebra as palavras nos termos de pesquisa e organiza por custo e conversão, assim você poderá ter uma ideia de que tipo palavras estão gerando conversão ou apenas custos. Excelente script para buscar termos de cauda longa e analisar seu desempenho.

/**
*
* Search Query Mining Tool
*
* This script calculates the contribution of each word found in the search query report
* and outputs a report into a Google Doc spreadsheet.
*
* Version: 1.0
* Google Apps Script maintained on brainlabsdigital.com
*
**/

function main() {

//////////////////////////////////////////////////////////////////////////////
// Options

var startDate = “2015-04-01”;
var endDate = “2015-04-30”;
// The start and end date of the date range for your search query data
// Format is yyyy-mm-dd

var currencySymbol = “£”;
// The currency symbol used for formatting. For example “£”, “$” or “€”.

var campaignNameContains = “”;
// Use this if you only want to look at some campaigns
// such as campaigns with names containing ‘Brand’ or ‘Shopping’.
// Leave as “” if not wanted.

var spreadsheetUrl = “//docs.google.com/YOUR-SPREADSHEET-URL-HERE”;
// The URL of the Google Doc the results will be put into.

//////////////////////////////////////////////////////////////////////////////

// Thresholds

var impressionThreshold = 10;
var clickThreshold = 0;
var costThreshold = 0;
var conversionThreshold = 0;
// Words will be ignored if their statistics are lower than any of these thresholds

//////////////////////////////////////////////////////////////////////////////
// Find the negative keywords

var negativesByGroup = [];
var negativesByCampaign = [];
var sharedSetData = [];
var sharedSetNames = [];
var sharedSetCampaigns = [];
var dateRange = startDate.replace(/-/g, “”) + “,” + endDate.replace(/-/g, “”);
var activeCampaignIds = [];

// Gather ad group level negative keywords

var keywordReport = AdWordsApp.report(
“SELECT CampaignId, AdGroupId, KeywordText, KeywordMatchType ” +
“FROM KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE CampaignStatus = ENABLED AND AdGroupStatus = ENABLED AND Status = ENABLED AND IsNegative = TRUE ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“DURING ” + dateRange);

var keywordRows = keywordReport.rows();
while (keywordRows.hasNext()) {
var keywordRow = keywordRows.next();

if (negativesByGroup[keywordRow[“AdGroupId”]] == undefined) {
negativesByGroup[keywordRow[“AdGroupId”]] =
[[keywordRow[“KeywordText”].toLowerCase(),keywordRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByGroup[keywordRow[“AdGroupId”]].push([keywordRow[“KeywordText”].toLowerCase(),keywordRow[“KeywordMatchType”].toLowerCase()]);
}

if (activeCampaignIds.indexOf(keywordRow[“CampaignId”]) < 0) {
activeCampaignIds.push(keywordRow[“CampaignId”]);
}
}//end while

// Gather campaign level negative keywords

var campaignNegReport = AdWordsApp.report(
“SELECT CampaignId, KeywordText, KeywordMatchType ” +
“FROM CAMPAIGN_NEGATIVE_KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE IsNegative = TRUE ” +
“AND CampaignId IN [” + activeCampaignIds.join(“,”) + “]”
);
var campaignNegativeRows = campaignNegReport.rows();
while (campaignNegativeRows.hasNext()) {
var campaignNegativeRow = campaignNegativeRows.next();

if (negativesByCampaign[campaignNegativeRow[“CampaignId”]] == undefined) {
negativesByCampaign[campaignNegativeRow[“CampaignId”]] = [[campaignNegativeRow[“KeywordText”].toLowerCase(),campaignNegativeRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByCampaign[campaignNegativeRow[“CampaignId”]].push([campaignNegativeRow[“KeywordText”].toLowerCase(),campaignNegativeRow[“KeywordMatchType”].toLowerCase()]);
}
}//end while

// Find which campaigns use shared negative keyword sets

var campaignSharedReport = AdWordsApp.report(
“SELECT CampaignName, CampaignId, SharedSetName, SharedSetType, Status ” +
“FROM CAMPAIGN_SHARED_SET_REPORT ” +
“WHERE SharedSetType = NEGATIVE_KEYWORDS ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘”);
var campaignSharedRows = campaignSharedReport.rows();
while (campaignSharedRows.hasNext()) {
var campaignSharedRow = campaignSharedRows.next();

if (sharedSetCampaigns[campaignSharedRow[“SharedSetName”]] == undefined) {
sharedSetCampaigns[campaignSharedRow[“SharedSetName”]] = [campaignSharedRow[“CampaignId”]];
} else {

sharedSetCampaigns[campaignSharedRow[“SharedSetName”]].push(campaignSharedRow[“CampaignId”]);
}
}//end while

// Map the shared sets’ IDs (used in the criteria report below)
// to their names (used in the campaign report above)

var sharedSetReport = AdWordsApp.report(
“SELECT Name, SharedSetId, MemberCount, ReferenceCount, Type ” +
“FROM SHARED_SET_REPORT ” +
“WHERE ReferenceCount > 0 AND Type = NEGATIVE_KEYWORDS “);
var sharedSetRows = sharedSetReport.rows();
while (sharedSetRows.hasNext()) {
var sharedSetRow = sharedSetRows.next();
sharedSetNames[sharedSetRow[“SharedSetId”]] = sharedSetRow[“Name”];
}//end while

// Collect the negative keyword text from the sets,
// and record it as a campaign level negative in the campaigns that use the set

var sharedSetReport = AdWordsApp.report(
“SELECT SharedSetId, KeywordMatchType, KeywordText ” +
“FROM SHARED_SET_CRITERIA_REPORT “);
var sharedSetRows = sharedSetReport.rows();
while (sharedSetRows.hasNext()) {
var sharedSetRow = sharedSetRows.next();
var setName = sharedSetNames[sharedSetRow[“SharedSetId”]];
if (sharedSetCampaigns[setName] !== undefined) {
for (var i=0; i<sharedSetCampaigns[setName].length; i++) {
var campaignId = sharedSetCampaigns[setName][i];
if (negativesByCampaign[campaignId] == undefined) {
negativesByCampaign[campaignId] =
[[sharedSetRow[“KeywordText”].toLowerCase(),sharedSetRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByCampaign[campaignId].push([sharedSetRow[“KeywordText”].toLowerCase(),sharedSetRow[“KeywordMatchType”].toLowerCase()]);
}
}
}
}//end while

Logger.log(“Finished negative keyword lists.”);

//////////////////////////////////////////////////////////////////////////////
// Defines the statistics to download or calculate, and their formatting

var statColumns = [“Clicks”, “Impressions”, “Cost”, “ConvertedClicks”, “ConversionValue”];
var calculatedStats = [[“CTR”,”Clicks”,”Impressions”],
[“CPC”,”Cost”,”Clicks”],
[“Conv. Rate”,”ConvertedClicks”,”Clicks”],
[“Cost / conv.”,”Cost”,”ConvertedClicks”],
[“Conv. value/cost”,”ConversionValue”,”Cost”]]
var currencyFormat = currencySymbol + “#,##0.00”;
var formatting = [“#,##0”, “#,##0”, currencyFormat, “#,##0″, currencyFormat,”0.00%”,currencyFormat,”0.00%”,currencyFormat,”0.00%”];

//////////////////////////////////////////////////////////////////////////////
// Go through the search query report, remove searches already excluded by negatives
// record the performance of each word in each remaining query

var queryReport = AdWordsApp.report(
“SELECT CampaignName, CampaignId, AdGroupId, AdGroupName, Query, ” + statColumns.join(“, “) + ” ” +
“FROM SEARCH_QUERY_PERFORMANCE_REPORT ” +
“WHERE CampaignStatus = ENABLED AND AdGroupStatus = ENABLED ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“DURING ” + dateRange);

var campaignSearchWords = [];
var totalSearchWords = [];
var totalSearchWordsKeys = [];
var numberOfWords = [];

var queryRows = queryReport.rows();
while (queryRows.hasNext()) {
var queryRow = queryRows.next();
var searchIsExcluded = false;

// Checks if the query is excluded by an ad group level negative

if (negativesByGroup[queryRow[“AdGroupId”]] !== undefined) {
for (var i=0; i<negativesByGroup[queryRow[“AdGroupId”]].length; i++) {
if ( (negativesByGroup[queryRow[“AdGroupId”]][i][1] == “exact” &&
queryRow[“Query”] == negativesByGroup[queryRow[“AdGroupId”]][i][0]) ||
(negativesByGroup[queryRow[“AdGroupId”]][i][1] != “exact” &&
(” “+queryRow[“Query”]+” “).indexOf(” “+negativesByGroup[queryRow[“AdGroupId”]][i][0]+” “) > -1 )){
searchIsExcluded = true;
break;
}
}
}

// Checks if the query is excluded by a campaign level negative

if (!searchIsExcluded && negativesByCampaign[queryRow[“CampaignId”]] !== undefined) {
for (var i=0; i<negativesByCampaign[queryRow[“CampaignId”]].length; i++) {
if ( (negativesByCampaign[queryRow[“CampaignId”]][i][1] == “exact” &&
queryRow[“Query”] == negativesByCampaign[queryRow[“CampaignId”]][i][0]) ||
(negativesByCampaign[queryRow[“CampaignId”]][i][1]!= “exact” &&
(” “+queryRow[“Query”]+” “).indexOf(” “+negativesByCampaign[queryRow[“CampaignId”]][i][0]+” “) > -1 )){
searchIsExcluded = true;
break;
}
}
}

if (searchIsExcluded) {continue;}
// if the search is already excluded by the current negatives,
// we ignore it and go on to the next query

var currentWords = queryRow[“Query”].split(” “);
var doneWords = [];

if (campaignSearchWords[queryRow[“CampaignName”]] == undefined) {
campaignSearchWords[queryRow[“CampaignName”]] = [];
}

var wordLength = currentWords.length;
if (wordLength > 6) {
wordLength = “7+”;
}
if (numberOfWords[wordLength] == undefined) {
numberOfWords[wordLength] = [];
}
for (var i=0; i<statColumns.length; i++) {
if (numberOfWords[wordLength][statColumns[i]] > 0) {
numberOfWords[wordLength][statColumns[i]] += parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
} else {
numberOfWords[wordLength][statColumns[i]] = parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
}
}

// Splits the query into words and records the stats for each

for (var w=0;w<currentWords.length;w++) {
if (doneWords.indexOf(currentWords[w]) < 0) { //if this word hasn’t been in the query yet

if (campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]] == undefined) {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]] = [];
}
if (totalSearchWords[currentWords[w]] == undefined) {
totalSearchWords[currentWords[w]] = [];
totalSearchWordsKeys.push(currentWords[w]);
}

for (var i=0; i<statColumns.length; i++) {
var stat = parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
if (campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] > 0) {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] += stat;
} else {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] = stat;
}
if (totalSearchWords[currentWords[w]][statColumns[i]] > 0) {
totalSearchWords[currentWords[w]][statColumns[i]] += stat;
} else {
totalSearchWords[currentWords[w]][statColumns[i]] = stat;
}
}

doneWords.push(currentWords[w]);
}//end if
}//end for
}//end while

Logger.log(“Finished analysing queries.”);

//////////////////////////////////////////////////////////////////////////////
// Output the data into the spreadsheet

var campaignSearchWordsOutput = [];
var campaignSearchWordsFormat = [];
var totalSearchWordsOutput = [];
var totalSearchWordsFormat = [];
var wordLengthOutput = [];
var wordLengthFormat = [];

// Add headers

var calcStatNames = [];
for (var s=0; s<calculatedStats.length; s++) {
calcStatNames.push(calculatedStats[s][0]);
}
var statNames = statColumns.concat(calcStatNames);
campaignSearchWordsOutput.push([“Campaign”,”Word”].concat(statNames));
totalSearchWordsOutput.push([“Word”].concat(statNames));
wordLengthOutput.push([“Word count”].concat(statNames));

// Output the campaign level stats

for (var campaign in campaignSearchWords) {
for (var word in campaignSearchWords[campaign]) {

if (campaignSearchWords[campaign][word][“Impressions”] < impressionThreshold) {continue;}
if (campaignSearchWords[campaign][word][“Clicks”] < clickThreshold) {continue;}
if (campaignSearchWords[campaign][word][“Cost”] < costThreshold) {continue;}
if (campaignSearchWords[campaign][word][“ConvertedClicks”] < conversionThreshold) {continue;}

// skips words under the thresholds

var printline = [campaign, word];

for (var s=0; s<statColumns.length; s++) {
printline.push(campaignSearchWords[campaign][word][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (campaignSearchWords[campaign][word][divisor] > 0) {
printline.push(campaignSearchWords[campaign][word][multiplier] / campaignSearchWords[campaign][word][divisor]);
} else {
printline.push(“-“);
}
}

campaignSearchWordsOutput.push(printline);
campaignSearchWordsFormat.push(formatting);
}
} // end for

totalSearchWordsKeys.sort(function(a,b) {return totalSearchWords[b][“Cost”] – totalSearchWords[a][“Cost”];});

for (var i = 0; i<totalSearchWordsKeys.length; i++) {
var word = totalSearchWordsKeys[i];

if (totalSearchWords[word][“Impressions”] < impressionThreshold) {continue;}
if (totalSearchWords[word][“Clicks”] < clickThreshold) {continue;}
if (totalSearchWords[word][“Cost”] < costThreshold) {continue;}
if (totalSearchWords[word][“ConvertedClicks”] < conversionThreshold) {continue;}

// skips words under the thresholds

var printline = [word];

for (var s=0; s<statColumns.length; s++) {
printline.push(totalSearchWords[word][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (totalSearchWords[word][divisor] > 0) {
printline.push(totalSearchWords[word][multiplier] / totalSearchWords[word][divisor]);
} else {
printline.push(“-“);
}
}

totalSearchWordsOutput.push(printline);
totalSearchWordsFormat.push(formatting);
} // end for

for (var i = 1; i<8; i++) {
if (i < 7) {
var wordLength = i;
} else {
var wordLength = “7+”;
}

var printline = [wordLength];

if (numberOfWords[wordLength] == undefined) {
printline.push([0,0,0,0,”-“,”-“,”-“,”-“]);
} else {
for (var s=0; s<statColumns.length; s++) {
printline.push(numberOfWords[wordLength][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (numberOfWords[wordLength][divisor] > 0) {
printline.push(numberOfWords[wordLength][multiplier] / numberOfWords[wordLength][divisor]);
} else {
printline.push(“-“);
}
}
}

wordLengthOutput.push(printline);
wordLengthFormat.push(formatting);
} // end for

// Finds available names for the new sheets

var campaignWordName = “Campaign Word Analysis”;
var totalWordName = “Total Word Analysis”;
var wordCountName = “Word Count Analysis”;
var campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(campaignWordName);
var totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(totalWordName);
var wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(wordCountName);
var i = 1;
while (campaignWordSheet != null || wordCountSheet != null || totalWordSheet != null) {
campaignWordName = “Campaign Word Analysis ” + i;
totalWordName = “Total Word Analysis ” + i;
wordCountName = “Word Count Analysis ” + i;
campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(campaignWordName);
totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(totalWordName);
wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(wordCountName);
i++;
}
campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(campaignWordName);
totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(totalWordName);
wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(wordCountName);

campaignWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, By Campaign”);
wordCountSheet.getRange(“R1C1”).setValue(“Analysis of Search Query Performance by Words Count”);

if (campaignNameContains == “”) {
totalWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, By Account”);
} else {
totalWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, Over All Campaigns Containing ‘” + campaignNameContains + “‘”);
}

campaignWordSheet.getRange(“R2C1:R” + (campaignSearchWordsOutput.length+1) + “C” + campaignSearchWordsOutput[0].length).setValues(campaignSearchWordsOutput);
campaignWordSheet.getRange(“R3C3:R” + (campaignSearchWordsOutput.length+1) + “C” + (formatting.length+2)).setNumberFormats(campaignSearchWordsFormat);
totalWordSheet.getRange(“R2C1:R” + (totalSearchWordsOutput.length+1) + “C” + totalSearchWordsOutput[0].length).setValues(totalSearchWordsOutput);
totalWordSheet.getRange(“R3C2:R” + (totalSearchWordsOutput.length+1) + “C” + (formatting.length+1)).setNumberFormats(totalSearchWordsFormat);
wordCountSheet.getRange(“R2C1:R” + (wordLengthOutput.length+1) + “C” + wordLengthOutput[0].length).setValues(wordLengthOutput);
wordCountSheet.getRange(“R3C2:R” + (wordLengthOutput.length+1) + “C” + (formatting.length+1)).setNumberFormats(wordLengthFormat);

Logger.log(“Finished writing to spreadsheet.”);
}

 

3. Analise seus Termos de Pesquisa – Por Derek Martin. Revise seus termos de pesquisa e faça ajustes pela planilha do Google.

/**********************************************************************************************
* AdWords Account Management — Review Search Queries & Post Adjustments via Google Docs.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com & MixedMarketingArtist.com
*********************************************************************************************/

var GOOGLE_DOC_URL = “put your url here”;
var START_DATE = ‘20150401’;
var END_DATE = ‘20150415’;

function main() {
var results = runQueryReport();
modifySpreadSheet(results);
}

// check a query for whether the keyword exists in the account
// returns true or false

function keywordExists(keyword) {
var kw = keyword;

if (kw != null) {
kwIter = AdWordsApp.keywords().withCondition(“Text = \'”+kw+”\'”).withCondition(“Status = ENABLED”).get();

var exists = kwIter.totalNumEntities() > 0 ? true : false;
return exists;
}
}

function runQueryReport() {

var listOfQueries = [];

var report = AdWordsApp.report(
‘SELECT Query, CampaignName, AdGroupName, ConversionsManyPerClick, ConversionValue, Cost, AverageCpc, Clicks, Impressions, Ctr, ConversionRateManyPerClick ‘ +
‘FROM SEARCH_QUERY_PERFORMANCE_REPORT ‘ +
‘WHERE CampaignName does_not_contain Shopping ‘ +
‘DURING ‘ + START_DATE + ‘,’ + END_DATE +’ ‘);

var rows = report.rows();

while (rows.hasNext()) {
var row = rows.next();

var query = row[‘Query’];
var campaign= row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var conversions = row[‘ConversionsManyPerClick’];
var conversionValue = parseFloat(row[‘ConversionValue’]).toPrecision(2);
var cost = parseFloat(row[‘Cost’]).toPrecision(2);
var roas = conversionValue / cost;
var averageCpc = row[‘AverageCpc’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var ctr = row[‘Ctr’];
var conversionRate = row[‘ConversionRateManyPerClick’];

if (query.length < 20) {
var keyword_exists = keywordExists(query);
} else {
var keyword_exists = false;
}

var queryResult = new queryData(query, campaign, adgroup, conversions, conversionValue,cost, roas,averageCpc, clicks, impressions, ctr, conversionRate, keyword_exists);

listOfQueries.push(queryResult);

} // end of report run

return listOfQueries;

}

function queryData(query, campaign, adgroup, conversions, conversionValue, cost, roas, averageCpc, clicks, impressions, ctr, conversionRate, exists ) {
this.query = query;
this.campaign = campaign;
this.adgroup = adgroup;
this.conversions = conversions;
this.conversionValue = conversionValue;
this.cost = cost;
this.roas = roas;
this.averageCpc = averageCpc;
this.clicks = clicks;
this.impressions = impressions;
this.ctr = ctr;
this.conversionRate = conversionRate;
this.exists = exists;
} // end of productData

function modifySpreadSheet(results) {

var queryResults = results;

var querySS = SpreadsheetApp.openByUrl(GOOGLE_DOC_URL);

var sheet = querySS.getActiveSheet();

var columnNames = [“Query”, “In Account”, “Campaign”, “Ad Group”, “Conversions”, “Conversion Value”, “Cost”, “ROAS”, “Average CPC”,”Clicks”, “Impressions”, “Ctr”, “ConversionRate”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

headersRange.setFontWeight(“bold”);
headersRange.setFontSize(12);
headersRange.setBorder(false, false, true, false, false, false);

for (i = 0; i < queryResults.length; i++) {
headersRange.setValues([columnNames]);

var query = queryResults[i].query;
var exists = (queryResults[i].exists == true) ? “Added” : “Not Added”;
var campaign = queryResults[i].campaign;
var adgroup = queryResults[i].adgroup;
var conversions = queryResults[i].conversions;
var conversionValue = queryResults[i].conversionValue;
var cost = queryResults[i].cost;
var roas = (isNaN(queryResults[i].roas)) ? 0.00 : queryResults[i].roas;
var averageCpc = queryResults[i].averageCpc;
var clicks = queryResults[i].clicks;
var impressions = queryResults[i].impressions;
var ctr = queryResults[i].ctr;
var conversionRate = queryResults[i].conversionRate;

sheet.appendRow([query, exists, campaign, adgroup, conversions, conversionValue, cost, roas, averageCpc, clicks, impressions, ctr, conversionRate]);
}

sheet.getRange(“A2:M”).setFontSize(12);
sheet.getRange(“E:E”).setNumberFormat(“0”);
sheet.getRange(“F:G”).setNumberFormat(“$0.00”);
sheet.getRange(“H:H”).setNumberFormat(“0.00”);
sheet.getRange(“I:I”).setNumberFormat(“$0.00”);

sheet.getRange(“J:K”).setNumberFormat(“0.00”);
sheet.getRange(“A2:M”)
.sort({column: 5, ascending: false});
sheet.getRange(“A:D”).setVerticalAlignment(“middle”);
sheet.getRange(“A:D”).setHorizontalAlignment(“center”);
}

// Helper functions
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

4. Lista de palavras-chave negativas – Por Google Ads. Com esses 2 scripts você pode criar uma nova lista de palavras-chave negativa e apagar os termos que estão sendo compartilhados das outras listas de palavras-chave.

Construa uma nova lista de palavras-chave negativas e adicione-a a uma campanha

function addNegativeKeywordListToCampaign() {
var NEGATIVE_KEYWORD_LIST_NAME = ‘INSERT_LIST_NAME_HERE’;
var CAMPAIGN_NAME = ‘INSERT_CAMPAIGN_NAME_HERE’;

var negativeKeywordListOperator =
AdWordsApp.newNegativeKeywordListBuilder()
.withName(NEGATIVE_KEYWORD_LIST_NAME)
.build();

if (negativeKeywordListOperator.isSuccessful()) {
var negativeKeywordList = negativeKeywordListOperation.getResult();
negativeKeywordList.addKeywords(
‘broad match keyword’,
‘”phrase match keyword”‘,
‘[exact match keyword]’
);

var campaign = AdWordsApp.campaigns()
.withCondition(‘Name = “‘ + CAMPAIGN_NAME + ‘”‘)
.get();
campaign.addNegativeKeywordList(negativeKeywordList);
} else {
Logger.log(‘Could not add Negative Keyword List.’);
}
}

Remover todas as palavras-chave negativas compartilhadas em uma lista de palavras-chave negativas

function removeAllNegativeKeywordsFromList() {
var NEGATIVE_KEYWORD_LIST_NAME = ‘INSERT_LIST_NAME_HERE’;

var negativeKeywordListIterator =
AdWordsApp.negativeKeywordLists()
.withCondition(‘Name = “‘ + NEGATIVE_KEYWORD_LIST_NAME + ‘”‘)
.get();

if (negativeKeywordListIterator.totalNumEntities() == 1) {
var negativeKeywordList = negativeKeywordListIterator.next();
var sharedNegativeKeywordIterator =
negativeKeywordList.negativeKeywords().get();

var sharedNegativeKeywords = [];

while (sharedNegativeKeywordIterator.hasNext()) {
sharedNegativeKeywords.push(sharedNegativeKeywordIterator.next());
}

for (var i = 0; i < sharedNegativeKeywords.length; i++) {
sharedNegativeKeywords[i].remove();
}
}
}

 

5. Palavras-chave negativas – Por Google Ads. Adicione e exporte palavras-chave de suas campanhas e grupos de anúncios com esse script do Google Ads. Utilize-o caso não queira utilizar a plataforma do Google Ads.

Adicione palavras-chave negativas a uma campanha

function addNegativeKeywordToCampaign() {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘Name = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.createNegativeKeyword(‘[Budget hotels]’);
}
}

 

Obter palavras-chave negativas em uma campanha

function getNegativeKeywordForCampaign() {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘Name = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var negativeKeywordIterator = campaign.negativeKeywords().get();
while (negativeKeywordIterator.hasNext()) {
var negativeKeyword = negativeKeywordIterator.next();
Logger.log(‘Text: ‘ + negativeKeyword.getText() + ‘, MatchType: ‘ +
negativeKeyword.getMatchType());
}
}
}

 

Adicione uma palavra-chave negativa a um grupo de anúncios

function addNegativeKeywordToAdGroup() {
// If you have multiple ad groups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
adGroup.createNegativeKeyword(‘[Budget hotels]’);
}
}

 

Obter palavras-chave negativas em um grupo de anúncios

function getNegativeKeywordForAdGroup() {
// If you have multiple ad groups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var negativeKeywordIterator = adGroup.negativeKeywords().get();
while (negativeKeywordIterator.hasNext()) {
var negativeKeyword = negativeKeywordIterator.next();
Logger.log(‘Text: ‘ + negativeKeyword.getText() + ‘, MatchType: ‘ +
negativeKeyword.getMatchType());
}
}
}

 

6. Lista mestre negativa – Por Google Ads. Esse script é ideal caso você tenha uma conta de administrador e tenha várias outras contas sob sua gerência, assim, com você consegue expandir termos comuns que não são interessantes para buscas.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Master Negative List Script for AdWords manager accounts
*
* @overview The Master Negative List script for AdWords manager accounts
* applies negative keywords and placements from a spreadsheet to multiple
* campaigns in your account using shared keyword and placement lists. The
* script can process multiple AdWords accounts in parallel. See
* //developers.google.com/adwords/scripts/docs/solutions/mccapp-master-negative-list
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Added validation for external spreadsheet setup.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

/**
* The URL of the tracking spreadsheet. This should be a copy of
* //goo.gl/i4q728
*/
var SPREADSHEET_URL = ‘INSERT_SPREADSHEET_URL_HERE’;

/**
* Keep track of the spreadsheet names for various criteria types, as well as
* the criteria type being processed.
*/
var CriteriaType = {
KEYWORDS: ‘Keywords’,
PLACEMENTS: ‘Placements’
};

/**
* The code to execute when running the script.
*/
function main() {
var config = readConfig();

var accountSelector = MccApp.accounts();
if (config.customerids.length > 0) {
accountSelector.withIds(config.customerids);
}
accountSelector.executeInParallel(‘processAccounts’, ‘postProcess’);
}

/**
* Process an account when processing multiple accounts under an AdWords manager
* account in parallel.
*
* @return {string} A JSON string that summarizes the number of keywords and
* placements synced, and the number of campaigns processed.
*/
function processAccounts() {
return JSON.stringify(syncMasterLists());
}

/**
* Callback method after processing accounts, when processing multiple accounts
* under an AdWords manager account in parallel.
*
* @param {Array.<MccApp.ExecutionResult>} results The execution results from
* the accounts that were processed by this script.
*/
function postProcess(results) {
var config = readConfig();
var emailParams = {
// Number of placements that were synced.
PlacementCount: 0,
// Number of keywords that were synced.
KeywordCount: 0,
// Summary of customers who were synced.
Customers: {
// How many customers were synced?
Success: 0,
// How many customers failed to sync?
Failure: 0,
// Details of each account processed. Contains 3 properties:
// CustomerId, CampaignCount, Status.
Details: []
}
};

for (var i = 0; i < results.length; i++) {
var customerResult = {
// The customer ID that was processed.
CustomerId: results[i].getCustomerId(),
// Number of campaigns that were synced.
CampaignCount: 0,
// Status of processing this account – OK / ERROR / TIMEOUT
Status: results[i].getStatus()
};

if (results[i].getStatus() == ‘OK’) {
var retval = JSON.parse(results[i].getReturnValue());
customerResult.CampaignCount = retval.CampaignCount;
if (emailParams.Customers.Success == 0) {
emailParams.KeywordCount = retval.KeywordCount;
emailParams.PlacementCount = retval.PlacementCount;
}
emailParams.Customers.Success++;
} else {
emailParams.Customers.Failure++;
}
emailParams.Customers.Details.push(customerResult);
}

var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
spreadsheet.getRangeByName(‘LastRun’).setValue(new Date());
spreadsheet.getRangeByName(‘CustomerId’).setValue(
AdWordsApp.currentAccount().getCustomerId());

sendEmail(config, emailParams);
}

/**
* Sends a summary email about the changes that this script made.
*
* @param {Object} config The configuration object.
* @param {Object} emailParams Contains details required to create the email
* body.
*/
function sendEmail(config, emailParams) {
var html = [];

html.push(‘<html>’,
‘<head></head>’,
‘<body>’,
“<table style=’font-family:Arial,Helvetica; ” +
‘border-collapse:collapse;font-size:10pt; ‘ +
“color:#444444; border: solid 1px #dddddd;’ ” +
“width=’600′ cellpadding=20>”,
‘<tr>’,
‘<td>’,
‘<p>Hello,</p>’,
‘<p>The Master Negative List script synced a total ‘ +
‘of <b>’ + emailParams.KeywordCount + ‘</b> ‘ +
‘keywords and <b>’ + emailParams.PlacementCount +
‘</b> placements. <b>’ +
(emailParams.Customers.Success +
emailParams.Customers.Failure) +
‘</b> accounts were processed, of which <b>’ +
emailParams.Customers.Success + ‘</b> ‘ +
‘succeeded, and <b>’ +
emailParams.Customers.Failure + ‘</b> failed. ‘ +
‘See the table below’ +
‘ for details.</p>’,
“<table border=’1′ width=’100%’ ” +
“style=’border-collapse: collapse; ” +
“border: solid 1px #dddddd;font-size:10pt;’>”,
‘<tr>’,
‘<th>CustomerId</th>’,
‘<th>Synced Campaigns</th>’,
‘<th>Status</th>’,
‘</tr>’
);

for (var i = 0; i < emailParams.Customers.Details.length; i++) {
var detail = emailParams.Customers.Details[i];
html.push(‘<tr>’,
‘<td>’ + detail.CustomerId + ‘</td>’,
‘<td>’ + detail.CampaignCount + ‘</td>’,
‘<td>’ + detail.Status + ‘</td>’,
‘</tr>’
);
}

html.push(‘</table>’,
‘<p>Cheers<br />AdWords Scripts Team</p>’,
‘</td>’,
‘</tr>’,
‘</table>’,
‘</body>’,
‘</html>’
);

if (config.email != ”) {
MailApp.sendEmail({
to: config.email,
subject: ‘Master Negative List Script’,
htmlBody: html.join(‘\n’)
});
}
}

/**
* Synchronizes the negative criteria list in an account with the master list
* in the user spreadsheet.
*
* @return {Object} A summary of the number of keywords and placements synced,
* and the number of campaigns to which these lists apply.
*/
function syncMasterLists() {
var config = readConfig();
var syncedCampaignCount = 0;

var keywordListDetails = syncCriteriaInNegativeList(config,
CriteriaType.KEYWORDS);
syncedCampaignCount = syncCampaignList(config, keywordListDetails.SharedList,
CriteriaType.KEYWORDS);

var placementListDetails = syncCriteriaInNegativeList(config,
CriteriaType.PLACEMENTS);
syncedCampaignCount = syncCampaignList(config,
placementListDetails.SharedList, CriteriaType.PLACEMENTS);

return {
‘CampaignCount’: syncedCampaignCount,
‘PlacementCount’: placementListDetails.CriteriaCount,
‘KeywordCount’: keywordListDetails.CriteriaCount
};
}

/**
* Synchronizes the list of campaigns covered by a negative list against the
* desired list of campaigns to be covered by the master list.
*
* @param {Object} config The configuration object.
* @param {AdWordsApp.NegativeKeywordList|AdWordsApp.ExcludedPlacementList}
* sharedList The shared negative criterion list to be synced against the
* master list.
* @param {String} criteriaType The criteria type for the shared negative list.
*
* @return {Number} The number of campaigns synced.
*/
function syncCampaignList(config, sharedList, criteriaType) {
var campaignIds = getLabelledCampaigns(config.label);
var totalCampaigns = Object.keys(campaignIds).length;

var listedCampaigns = sharedList.campaigns().get();

var campaignsToRemove = [];

while (listedCampaigns.hasNext()) {
var listedCampaign = listedCampaigns.next();
if (listedCampaign.getId() in campaignIds) {
delete campaignIds[listedCampaign.getId()];
} else {
campaignsToRemove.push(listedCampaign);
}
}

// Anything left over in campaignIds starts a new list.
var campaignsToAdd = AdWordsApp.campaigns().withIds(
Object.keys(campaignIds)).get();
while (campaignsToAdd.hasNext()) {
var campaignToAdd = campaignsToAdd.next();

if (criteriaType == CriteriaType.KEYWORDS) {
campaignToAdd.addNegativeKeywordList(sharedList);
} else if (criteriaType == CriteriaType.PLACEMENTS) {
campaignToAdd.addExcludedPlacementList(sharedList);
}
}

for (var i = 0; i < campaignsToRemove.length; i++) {
if (criteriaType == CriteriaType.KEYWORDS) {
campaignsToRemove[i].removeNegativeKeywordList(sharedList);
} else if (criteriaType == CriteriaType.PLACEMENTS) {
campaignsToRemove[i].removeExcludedPlacementList(sharedList);
}
}

return totalCampaigns;
}

/**
* Gets a list of campaigns having a particular label.
*
* @param {String} labelText The label text.
*
* @return {Array.<Number>} An array of campaign IDs having the specified
* label.
*/
function getLabelledCampaigns(labelText) {
var campaignIds = {};

if (labelText != ”) {
var label = getLabel(labelText);
var campaigns = label.campaigns().withCondition(
‘Status in [ENABLED, PAUSED]’).get();
} else {
var campaigns = AdWordsApp.campaigns().withCondition(
‘Status in [ENABLED, PAUSED]’).get();
}

while (campaigns.hasNext()) {
var campaign = campaigns.next();
campaignIds[campaign.getId()] = 1;
}
return campaignIds;
}

/**
* Gets a label with the specified label text.
*
* @param {String} labelText The label text.
*
* @return {AdWordsApp.Label} The label text.
*/
function getLabel(labelText) {
var labels = AdWordsApp.labels().withCondition(
“Name='” + labelText + “‘”).get();
if (labels.totalNumEntities() == 0) {
var message = Utilities.formatString(‘Label named %s is missing in your ‘ +
‘account. Make sure the label exists in the account, and is applied ‘ +
‘to campaigns and adgroups you wish to process.’, labelText);
throw (message);
}

return labels.next();
}

/**
* Synchronizes the criteria in a shared negative criteria list with the user
* spreadsheet.
*
* @param {Object} config The configuration object.
* @param {String} criteriaType The criteria type for the shared negative list.
*
* @return {Object} A summary of the synced negative list, and the number of
* criteria that were synced.
*/
function syncCriteriaInNegativeList(config, criteriaType) {
var criteriaFromSheet = loadCriteria(criteriaType);
var totalCriteriaCount = Object.keys(criteriaFromSheet).length;

var sharedList = null;
var listName = config.listname[criteriaType];

sharedList = createNegativeListIfRequired(listName, criteriaType);

var negativeCriteria = null;

try {
if (criteriaType == CriteriaType.KEYWORDS) {
negativeCriteria = sharedList.negativeKeywords().get();
} else if (criteriaType == CriteriaType.PLACEMENTS) {
negativeCriteria = sharedList.excludedPlacements().get();
}
} catch (e) {
Logger.log(‘Failed to retrieve shared list. Error says ‘ + e);
if (AdWordsApp.getExecutionInfo().isPreview()) {
var message = Utilities.formatString(‘The script cannot create the ‘ +
‘negative %s list in preview mode. Either run the script without ‘ +
‘preview, or create a negative %s list with name “%s” manually ‘ +
‘before previewing the script.’, criteriaType, criteriaType,
listName);
Logger.log(message);
}
throw e;
}

var criteriaToDelete = [];

while (negativeCriteria.hasNext()) {
var negativeCriterion = negativeCriteria.next();
var key = null;

if (criteriaType == CriteriaType.KEYWORDS) {
key = negativeCriterion.getText();
} else if (criteriaType == CriteriaType.PLACEMENTS) {
key = negativeCriterion.getUrl();
}

if (key in criteriaFromSheet) {
// Nothing to do with this criteria. Remove it from loaded list.
delete criteriaFromSheet[key];
} else {
// This criterion is not in the sync list. Mark for deletion.
criteriaToDelete.push(negativeCriterion);
}
}

// Whatever left in the sync list are new items.
if (criteriaType == CriteriaType.KEYWORDS) {
sharedList.addNegativeKeywords(Object.keys(criteriaFromSheet));
} else if (criteriaType == CriteriaType.PLACEMENTS) {
sharedList.addExcludedPlacements(Object.keys(criteriaFromSheet));
}

for (var i = 0; i < criteriaToDelete.length; i++) {
criteriaToDelete[i].remove();
}

return {
‘SharedList’: sharedList,
‘CriteriaCount’: totalCriteriaCount,
‘Type’: criteriaType
};
}

/**
* Creates a shared negative criteria list if required.
*
* @param {string} listName The name of shared negative criteria list.
* @param {String} listType The criteria type for the shared negative list.
*
* @return {AdWordsApp.NegativeKeywordList|AdWordsApp.ExcludedPlacementList} An
* existing shared negative criterion list if it already exists in the
* account, or the newly created list if one didn’t exist earlier.
*/
function createNegativeListIfRequired(listName, listType) {
var negativeListSelector = null;
if (listType == CriteriaType.KEYWORDS) {
negativeListSelector = AdWordsApp.negativeKeywordLists();
} else if (listType == CriteriaType.PLACEMENTS) {
negativeListSelector = AdWordsApp.excludedPlacementLists();
}
var negativeListIterator = negativeListSelector.withCondition(
“Name = ‘” + listName + “‘”).get();

if (negativeListIterator.totalNumEntities() == 0) {
var builder = null;

if (listType == CriteriaType.KEYWORDS) {
builder = AdWordsApp.newNegativeKeywordListBuilder();
} else if (listType == CriteriaType.PLACEMENTS) {

builder = AdWordsApp.newExcludedPlacementListBuilder();
}

var negativeListOperation = builder.withName(listName).build();
return negativeListOperation.getResult();
} else {
return negativeListIterator.next();
}
}

/**
* Loads a list of criteria from the user spreadsheet.
*
* @param {string} sheetName The name of shared negative criteria list.
*
* @return {Object} A map of the list of criteria loaded from the spreadsheet.
*/
function loadCriteria(sheetName) {
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName(sheetName);
var values = sheet.getRange(‘B4:B’).getValues();

var retval = {};
for (var i = 0; i < values.length; i++) {
var keyword = values[i][0].toString().trim();
if (keyword != ”) {
retval[keyword] = 1;
}
}
return retval;
}

/**
* Loads a configuration object from the spreadsheet.
*
* @return {Object} A configuration object.
*/
function readConfig() {
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
var values = spreadsheet.getRangeByName(‘ConfigurationValues’).getValues();

var config = {
‘label’: values[0][0],
‘listname’: {
},
‘email’: values[3][0],
‘customerids’: extractCustomerIds(values[4][0])
};
config.listname[CriteriaType.KEYWORDS] = values[1][0];
config.listname[CriteriaType.PLACEMENTS] = values[2][0];
return config;
}

/**
* Extracts customerIds from a comma separated list.
*
* @param {string} data the input.
* @return {Array.<number>} A list of customer IDs.
*/
function extractCustomerIds(data) {
var retval = [];

var splits = data.split(‘,’);

for (var i = 0; i < splits.length; i++) {
var split = splits[i];
split = split.trim().replace(/-/g, ”).replace(/^\s+|\s+$/g, ”);
if (split) {
if (isNaN(split)) {
Logger.log(‘Invalid customer ID found in spreadsheet: ‘ + split);
} else {
var customerId = parseInt(split).toFixed(0);
retval.push(customerId);
}
}
}
return retval;
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL at the top of the file only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘INSERT_SPREADSHEET_URL_HERE’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
return spreadsheet;
}

 

Como Configurar

 

7. Palavras-chave – Por Google Ads. Os scripts de palavras-chave do Google Ads permitem que você adicione/pause palavras-chave à um grupo de anúncios. Além disso, é possível que você extraia todas as palavras-chave de um grupo de anúncios e seus dados também.

Adicione uma palavra-chave a um grupo de anúncios existente

function addKeyword() {
// If you have multiple adGroups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();

adGroup.newKeywordBuilder()
.withText(‘Hello world’)
.withCpc(1.25) // Optional
.withFinalUrl(‘//www.example.com’) // Optional
.build();

// KeywordBuilder has a number of other options. For more details see
// //developers.google.com/adwords/scripts/docs/reference/adwordsapp/adwordsapp_keywordbuilder
}
}

 

Pausar uma palavra-chave existente em um grupo de anúncios

function pauseKeywordInAdGroup() {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var keywordIterator = adGroup.keywords()
.withCondition(‘Text=”INSERT_KEYWORDS_HERE”‘).get();
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
keyword.pause();
}
}
}

 

Obter todas as palavras-chave em um grupo de anúncios

function getKeywordsInAdGroup() {
var keywordIterator = AdWordsApp.keywords()
.withCondition(‘AdGroupName = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (keywordIterator.hasNext()) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
Logger.log(formatKeyword(keyword));
}
}
}

function formatKeyword(keyword) {
return ‘Text : ‘ + keyword.getText() + ‘\n’ +
‘Match type : ‘ + keyword.getMatchType() + ‘\n’ +
‘CPC : ‘ + keyword.bidding().getCpc() + ‘\n’ +
‘Final URL : ‘ + keyword.urls().getFinalUrl() + ‘\n’ +
‘Approval Status : ‘ + keyword.getApprovalStatus() + ‘\n’ +
‘Enabled : ‘ + keyword.isEnabled() + ‘\n’;
}

 

Obter estatísticas de todas as palavras-chave em um grupo de anúncios

function getKeywordStats() {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var keywordIterator = adGroup.keywords().get();
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
// You can also request reports for pre-defined date ranges. See
// //developers.google.com/adwords/api/docs/guides/awql,
// DateRangeLiteral section for possible values.
var stats = keyword.getStatsFor(‘LAST_MONTH’);
Logger.log(adGroup.getName() + ‘, ‘ + keyword.getText() + ‘, ‘ +
stats.getClicks() + ‘, ‘ + stats.getImpressions());
}
}
}

Automação Google Ads: Texto do Anúncio

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Textos dos anúncios”.

 

1. Corrija os erros de letras maiúsculas em seus anúncios – por Russell Savage. Caso seus anúncios contenham muitas letras maiúsculas, o Google pode não aprová-los. Esse script substitui quaisquer sequências de 3 letras maiúsculas, criando um anúncio novo e pausando o antigo.

//———————————–
// Fix Ads with EXCESSIVE CAPITALIZATION
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var find_caps = /[A-Z]{3,}/g;
var SEP = ‘[email protected]~~’; // this needs to be something you would never put in your ads.
var ad_iter = AdWordsApp.ads().withCondition(“ApprovalStatus = ‘DISAPPROVED'”).get();

while(ad_iter.hasNext()) {
var ad = ad_iter.next();
var old_ad_cnt = get_ad_count(ad.getAdGroup());
var old_ad_str = [ad.getHeadline(),ad.getDescription1(),ad.getDescription2(),ad.getDisplayUrl()].join(SEP);
var new_ad_str = old_ad_str;
Logger.log(“Before:”+old_ad_str);
var m = “”;
while((m = find_caps.exec(new_ad_str)) != null) {
new_ad_str = replace_all(new_ad_str,m[0],init_cap(m[0]),false);
}
Logger.log(“After:”+new_ad_str);
if(old_ad_str != new_ad_str) {
var [new_headline,new_desc1,new_desc2,new_disp_url] = new_ad_str.split(SEP);
ad.getAdGroup().createTextAd(new_headline, new_desc1, new_desc2, new_disp_url, ad.getDestinationUrl());
var new_ad_cnt = get_ad_count(ad.getAdGroup());
if(new_ad_cnt == (old_ad_cnt+1)) {
ad.remove();
}
} else {
Logger.log(“Skipping because no changes were made.”);
}
}

function init_cap(s) {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}

// This function was adapted from: //dumpsite.com/forum/index.php?topic=4.msg8#msg8
function replace_all(original,str1, str2, ignore) {
return original.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,”\\$&”),(ignore?”gi”:”g”)),(typeof(str2)==”string”)?str2.replace(/\$/g,”$$$$”):str2);
}

function get_ad_count(ad_group) {
var ad_iter = ad_group.ads().get();
var new_ad_cnt = 0;
while(ad_iter.hasNext()) {
ad_iter.next();
new_ad_cnt++;
}
return new_ad_cnt;
}
}

 

2. Automação de testes de anúncios – Por Russell Savage. O Google costuma analisar em 1 dia anúncios para aprovação, porém nem sempre este é o caso. Esse script detecta quando há uma alteração no anúncio e grava em uma planilha do Google com a data em que ele começou a rodar.

/************************************
* Ad Creative Test Automation Script
* Version: 1.3
* Changelog v1.3 – Data is written to the spreadsheet a little faster
* Changelog v1.2 – Added additional threshold options
* Changelog v1.1 – Fixed issue with dates in email
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
// You can use any of the same values here as you can for METRIC below
var THRESHOLD = { metric : ‘Clicks’, value : 100 };
var TO = [‘[email protected]’];

//Try any of these values for METRIC:
//AverageCpc, AverageCpm, AveragePageviews, AveragePosition,
//AverageTimeOnSite, BounceRate, Clicks, ConversionRate,
//Conversions, Cost, Ctr, Impressions
var METRIC = ‘Ctr’;
var ASC_OR_DESC = ‘ASC’; //ASC – pause ad with lowest value, DESC – pause ad with highest value

function main() {
//Start by finding what has changed in the account
var ad_map = buildCurrentAdMap();
var prev_ad_map = readMapFromSpreadsheet();
prev_ad_map = updatePreviousAdMap(prev_ad_map,ad_map);
writeMapToSpreadsheet(prev_ad_map);

//Now run through the adgroups to find creative tests
var ag_iter = AdWordsApp.adGroups().get();
var paused_ads = [];
while(ag_iter.hasNext()) {
var ag = ag_iter.next();
if(!prev_ad_map[ag.getId()]) { continue; }

//Here is the date range for the test metrics
var last_touched_str = _getDateString(prev_ad_map[ag.getId()].last_touched,’yyyyMMdd’);
var get_today_str = _getDateString(new Date(),’yyyyMMdd’);

var ag_stats = ag.getStatsFor(last_touched_str, get_today_str);
if(ag_stats[‘get’+THRESHOLD.metric]() >= THRESHOLD.value) {
var ad_iter = ag.ads().withCondition(‘Status = ENABLED’)
.forDateRange(last_touched_str, get_today_str)
.orderBy(METRIC+” “+ASC_OR_DESC).get();
var ad = ad_iter.next();
var metric = ad.getStatsFor(last_touched_str, get_today_str)[‘get’+METRIC]();
ad.pause();
paused_ads.push({a : ad, m : metric});
}
}
sendEmailForPausedAds(paused_ads);
}

// A function to send an email with an attached report of ads it has paused
function sendEmailForPausedAds(ads) {
if(ads.length == 0) { return; } //No ads paused, no email
var email_body = ‘”‘ + [‘CampaignName’,’AdGroupName’,’Headline’,’Desc1′,’Desc2′,’DisplayUrl’,METRIC].join(‘”,”‘) + ‘”\n’;
for(var i in ads) {
var ad = ads[i].a;
var metric = ads[i].m;
email_body += ‘”‘ + [ad.getCampaign().getName(),
ad.getAdGroup().getName(),
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
metric
].join(‘”,”‘) +
‘”\n’;
}
var date_str = _getDateString(new Date(),’yyyy-MM-dd’);
var options = { attachments: [Utilities.newBlob(email_body, ‘text/csv’, “FinishedTests_”+date_str+’.csv’)] };
var subject = ‘Finished Tests – ‘ + date_str;
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, ‘See attached.’, options);
}
}

//Given two lists of ads, this checks to make sure they are the same.
function sameAds(ads1,ads2) {
for(var i in ads1) {
if(ads1[i] != ads2[i]) { return false; }
}
return true;
}

//This reads the stored data from the spreadsheet
function readMapFromSpreadsheet() {
var ad_map = {};
var sheet = SpreadsheetApp.openById(findSpreadsheetId()).getActiveSheet();
var data = sheet.getRange(‘A:C’).getValues();
for(var i in data) {
if(data[i][0] == ”) { break; }
var [ag_id,last_touched,ad_ids] = data[i];
ad_map[ag_id] = { ad_ids : (”+ad_ids).split(‘,’), last_touched : new Date(last_touched) };
}
return ad_map;
}

//This will search for a label containing the spreadsheet id
//If one isn’t found, it will create a new one and the label as well
function findSpreadsheetId() {
var spreadsheet_id = “”;
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘history_script:'”).get();
if(label_iter.hasNext()) {
var label = label_iter.next();
return label.getName().split(‘:’)[1];
} else {
var sheet = SpreadsheetApp.create(‘AdGroups History’);
var sheet_id = sheet.getId();
AdWordsApp.createLabel(‘history_script:’+sheet_id, ‘stores sheet id for adgroup changes script.’);
return sheet_id;
}
}

//This will store the data from the account into a spreadsheet
function writeMapToSpreadsheet(ad_map) {
var toWrite = [];
for(var ag_id in ad_map) {
var ad_ids = ad_map[ag_id].ad_ids;
var last_touched = ad_map[ag_id].last_touched;
toWrite.push([ag_id,last_touched,ad_ids.join(‘,’)]);
}
writeToSpreadsheet(toWrite);
}

// Write the keyword data to the spreadsheet
function writeToSpreadsheet(toWrite) {
var sheet = SpreadsheetApp.openById(findSpreadsheetId()).getActiveSheet();
sheet.clear();

var numRows = sheet.getMaxRows();
if(numRows < toWrite.length) {
sheet.insertRows(1,toWrite.length-numRows);
}
var range = sheet.getRange(1,1,toWrite.length,toWrite[0].length);
range.setValues(toWrite);
}

//This builds a map of the ads in the account so that it is easy to compare
function buildCurrentAdMap() {
var ad_map = {}; // { ag_id : { ad_ids : [ ad_id, … ], last_touched : date } }
var ad_iter = AdWordsApp.ads().withCondition(‘Status = ENABLED’).get();
while(ad_iter.hasNext()) {
var ad = ad_iter.next();
var ag_id = ad.getAdGroup().getId();
if(ad_map[ag_id]) {
ad_map[ag_id].ad_ids.push(ad.getId());
ad_map[ag_id].ad_ids.sort();
} else {
ad_map[ag_id] = { ad_ids : [ad.getId()], last_touched : new Date() };
}
}
return ad_map;
}

//This takes the old ad map and the current ad map and returns an
//updated map with all changes.
function updatePreviousAdMap(prev_ad_map,ad_map) {
for(var ag_id in ad_map) {
var current_ads = ad_map[ag_id].ad_ids;
var previous_ads = (prev_ad_map[ag_id]) ? prev_ad_map[ag_id].ad_ids : [];
if(!sameAds(current_ads,previous_ads)) {
prev_ad_map[ag_id] = ad_map[ag_id];
}
}
return prev_ad_map;
}

//Helper function to format the date
function _getDateString(date,format) {
return Utilities.formatDate(date,AdWordsApp.currentAccount().getTimeZone(),format);
}

 

3. Gerencie seus criativos com Excel – Por Russell Savage. Integre o Google Ads com o Drive e baixe todas as métricas automaticamente no seu computador. Não só isso, mas também adicione palavras-chave, gerencie anúncios, grupo de anúncios, extensões etc. Para utilizar esse recurso é necessário ter o Google Drive em seu computador.

/******************************************
* Manage AdWords Ads Using Excel
* Version 1.0
* Author: Russ Savage
* FreeAdWordsScripts.com
****************************************/
var FOLDER_PATH = ‘AdWordsData’; //The path where the file will be stored on GDrive
var FILE_NAME = ‘creatives.csv’; //The name of the file on GDrive

var INCLUDE_STATS = true; // Set to false to remove stats from the file
var DATE_RANGE = ‘LAST_30_DAYS’; //The date range for the stats

var INCLUDE_PAUSED_ADS = true; //Set to false to only report on active ads
var DELETE_ORIGINAL_ADS = true; //When set to false, the original ads will be paused instead of deleted

function main() {
var file = getFile(FILE_NAME,FOLDER_PATH);
if(!file) {
file = createNewFile(FILE_NAME,FOLDER_PATH,buildCreativesFile());
return;
}

var fileContent = file.getBlob().getDataAsString();
var creatives = {};
if(fileContent) {
creatives = parseFileContent(fileContent);
}

var creativeIter = getAdIterator();
while(creativeIter.hasNext()) {
var creative = creativeIter.next();
var adId = creative.getId();
if(creatives[adId]) {
//ok we found the ad.
//Checking status
var isEnabledFile = (creatives[adId][‘Status’] === ‘Enabled’);
if(creative.isEnabled() != isEnabledFile) {
if(isEnabledFile) {
creative.enable();
} else {
creative.pause();
}
}

if(hadAdChanged(creative,creatives[adId])) {
if(DELETE_ORIGINAL_ADS) {
creative.remove();
} else {
creative.pause();
}
createNewAd(creative.getAdGroup(),creatives[creative.getId()]);
}
}
}
file.setContent(buildCreativesFile());
}

//Helper function to create a new ad
function createNewAd(ag,newAd) {
var optArgs = {
isMobilePreferred: (newAd[‘Device’] === ‘Mobile’) ? true : false
};
ag.createTextAd(newAd[‘Headline’],newAd[‘Desc1’],newAd[‘Desc2’],newAd[‘DisplayUrl’],newAd[‘DestinationUrl’],optArgs);
}

//This checks to see if the ad has been changed
function hadAdChanged(ad,oldAd) {
var newAdText = [ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
(ad.isMobilePreferred()) ? ‘Mobile’ : ‘Desktop’].join(‘~~!~~’);
var oldAdText = [oldAd[‘Headline’],
oldAd[‘Desc1’],
oldAd[‘Desc2’],
oldAd[‘DisplayUrl’],
oldAd[‘DestinationUrl’],
oldAd[‘Device’]].join(‘~~!~~’);
Logger.log(newAdText);
Logger.log(oldAdText);
return (newAdText !== oldAdText);
}

//This builds the creatives file from all the ads in the account.
function buildCreativesFile() {
var report = getReportColumns();
var creativeIter = getAdIterator();
while(creativeIter.hasNext()) {
var creative = creativeIter.next();
report += getReportRow(creative);
}
return report;
}

//This returns the ad iterator based on options.
function getAdIterator() {
var adSelector = AdWordsApp.ads().withCondition(“Type = ‘TEXT_AD'”);
if(!INCLUDE_PAUSED_ADS) {
adSelector = adSelector.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘Status = ENABLED’);
}
return adSelector.get();
}

//This returns a CSV fow for the report.
function getReportRow(ad) {
var retVal = [
ad.getId(),
ad.getCampaign().getName(),(ad.getCampaign().isPaused()) ? ‘Paused’ : ‘Enabled’,
ad.getAdGroup().getName(),(ad.getAdGroup().isPaused()) ? ‘Paused’ : ‘Enabled’,
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
(ad.isPaused()) ? ‘Paused’ : ‘Enabled’,
(ad.isMobilePreferred()) ? ‘Mobile’ : ‘Desktop’,
];
if(INCLUDE_STATS) {
var stats = ad.getStatsFor(DATE_RANGE);
retVal = retVal.concat([
stats.getImpressions(),
stats.getClicks(),
stats.getCtr(),
stats.getCost(),
stats.getAverageCpc(),
stats.getConversions(),
stats.getConversionRate(),
stats.getAveragePageviews(),
stats.getAveragePosition(),
stats.getAverageTimeOnSite(),
stats.getBounceRate()
]);
}
return ‘”‘ + retVal.join(‘”,”‘) + ‘”\n’;
}

//This returns the column headings used for the report.
function getReportColumns() {
var columnHeadings = [
‘AdId’,
‘CampaignName’,’CampaignStatus’,
‘AdGroupName’,’AdGroupStatus’,
‘Headline’,
‘Desc1’,
‘Desc2’,
‘DisplayUrl’,
‘DestinationUrl’,
‘Status’,
‘Device’];
if(INCLUDE_STATS) {
columnHeadings = columnHeadings.concat([
‘Impressions’,
‘Clicks’,
‘Ctr’,
‘Cost’,
‘Cpc’,
‘Conversions’,
‘ConversionRate’,
‘AveragePageviews’,
‘AvgPosition’,
‘AvgTimeOnSite’,
‘BounceRate’
]);
}
return ‘”‘ + columnHeadings.join(‘”,”‘) + ‘”\n’;
}

//This function parses the creatives file into an object for processing
function parseFileContent(fileContent) {
var headers = [];
var idHash = {};
var data = Utilities.parseCsv(fileContent);
for(var i in data) {
var cells = data[i]
if(cells.length == 1) { continue; } //skip any empty rows
if(i == 0) {
headers = cells;
} else {
var rowMap = {};
for(var x in headers) {
headers[x] = headers[x];
cells[x] = cells[x];
rowMap[headers[x]] = cells[x];
}
idHash[rowMap[‘AdId’]] = rowMap;
}
}
return idHash;
}

//This function gets the file from GDrive
function getFile(fileName,folderPath) {
var folder = getFolder(folderPath);
if(folder.getFilesByName(fileName).hasNext()) {
return folder.getFilesByName(fileName).next();
} else {
return null;
}
}

//This function creates a new file on GDrive
function createNewFile(fileName,folderPath,content) {
if(!fileName) { throw ‘createNewFile: Missing filename.’; }
var folder = getFolder(folderPath);

return folder.createFile(fileName, content);
}

//This function finds the folder for the file and creates folders if needed
function getFolder(folderPath) {
var folder = DriveApp.getRootFolder();
if(folderPath) {
var pathArray = folderPath.split(‘/’);
for(var i in pathArray) {
var folderName = pathArray[i];
if(folder.getFoldersByName(folderName).hasNext()) {
folder = folder.getFoldersByName(folderName).next();
} else {
folder = folder.createFolder(folderName);
}
}
}
return folder;
}

 

4. Copie anúncios existentes com uma nova URL – Por Derek Martin. Economize tempo ao realizar testes para uma nova página de destino, o script irá copiar os anúncios da sua conta com a URL desejada.

function main() {
// change the CampaignName condition to whatever suits you
var adIter = AdWordsApp.ads().withCondition(“CampaignName contains WP”).withCondition(“Status = ENABLED”).get();

while(adIter.hasNext()) {
var ad = adIter.next();

var headline = ad.getHeadline();
var d1 = ad.getDescription1()
var d2 = ad.getDescription2();
var displayUrl = ad.getDisplayUrl();
var dest = “//your-url-here.com/”;

var camp = ad.getCampaign();
var adgroup = ad.getAdGroup();

var newAd = adgroup.newTextAdBuilder()
.withHeadline(headline)
.withDescription1(d1)
.withDescription2(d2)
.withDisplayUrl(displayUrl)
.withDestinationUrl(dest)
.build();

}
}

 

 

5. Sincronize seu estoque com anúncios do Google Ads – Por Nathan Byloff. Esse script é exclusivo para sites que utilizam o WooCommerce. Caso o item de seu e-commerce esteja esgotado, o script pausa os anúncios impedindo que seu orçamento seja gastado.

var CAMPAIGN_NAME = “Your Campaign Name”;
var userKey = “Your API Key”;
var userSecret = “Your API Secret”;
function main() {
var campaign = getCampaign();
var url = ‘//www.yourstore.com/wc-api/v2/products?consumer_key=’ + userKey + ‘&consumer_secret=’ + userSecret;
var response = UrlFetchApp.fetch(url);
response = response.getContentText();
var data = JSON.parse(response);
for(var i=0; i < data.products.length; i++) {
var product = data.products[i];
if (product.in_stock == true) {
Logger.log(‘enable ad: ‘ + product.sku);
toggleAd(campaign, product.sku, true);
} else {
Logger.log(‘disable ad: ‘ + product.sku);
toggleAd(campaign, product.sku, false);
}
}
}

function toggleAd(campaign, sku, status) {
var adsIterator = campaign.ads().withCondition(‘LabelNames CONTAINS_ANY [“‘ + sku + ‘”]’).get();
if (adsIterator.hasNext()) {
//Find all ad groups and store them in an array
while (adsIterator.hasNext()) {
var ad = adsIterator.next();
if (status) {
if (!ad.isEnabled()) {
ad.enable();
}
} else if (!status) {
//disable ad
if (ad.isEnabled()) {
ad.pause();
}
}
}
}
}

/**
* Get a specified campaign based on a global var we’ve set
*/
function getCampaign() {
return AdWordsApp.campaigns().withCondition(“Name = ‘” + CAMPAIGN_NAME + “‘”).get().next();
}

 

6. Atualizador de contagens regressivas – Por Derek Martin. O script procura por anúncios que estejam com a contagem regressiva expirada e substitui por novos anúncios com datas atualizadas.

/***************************************************************************************
* AdWords Countdown Ad Updater — Find stale countdown ads and replace them with
* Ads that are updated with new dates.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com or MixedMarketingArtist.com
****************************************************************************************/

var DESCRIPTION2_TEXT = “Sale Ends In”

function main() {
// Set your campaign criteria here

var campIter = AdWordsApp.campaigns().withCondition(“Status=ENABLED”).withCondition(“Name does_not_contain Remarketing”).get();

while (campIter.hasNext()) {
var camp = campIter.next();

var agIter = camp.adGroups().withCondition(“Status = ENABLED”).get();

while (agIter.hasNext()) {
var ag = agIter.next();

var adsIter = ag.ads().withCondition(“Status = ENABLED”).get();

while (adsIter.hasNext()) {
ad = adsIter.next();

var headline,d1,d2,displayUrl, destUrl;
headline = ad.getHeadline();
d1 = ad.getDescription1();
d2 = ad.getDescription2();
displayUrl = ad.getDisplayUrl();
destUrl = ad.getDestinationUrl();

var regex = /\{=COUNTDOWN\(\”(\d{4})\/(\d{1,2})\/(\d{1,2})/

if (d2.match(regex)) {
match = d2.match(regex);
info(match);
var the_date = new Date(match[1], match[2]-1,match[3])
var today = new Date;
info(today);
if (daysBetween(today, the_date) < 0) {
ad.pause();
var newDate = new Date();

newDate.setDate(newDate.getDate() + 5); // add 5 days
var formattedDate = Utilities.formatDate(newDate, “PST”, “yyyy/MM/dd”);
var newDest2 = DESCRIPTION2_TEXT + ” {\=COUNTDOWN(\”” + formattedDate +” 23:59:00\”)}.”

status = ag.newTextAdBuilder().withHeadline(headline).withDescription1(d1).withDescription2(newDest2).withDisplayUrl(displayUrl).withDestinationUrl(destUrl).build();
}
}
}

}
}
}

function daysBetween( date1, date2 ) {
//Get 1 day in milliseconds
var one_day=1000*60*60*24;

// Convert both dates to milliseconds
var date1_ms = date1.getTime();
var date2_ms = date2.getTime();

// Calculate the difference in milliseconds
var difference_ms = date2_ms – date1_ms;

// Convert back to days and return
return Math.round(difference_ms/one_day);
}

/* HELPER FUNCTIONS */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

7. Automatizador de criativos com estatísticas – Por Russell Savage. O script notifica por e-mail quando seus anúncios de teste adquirem certa significância estatística para você tomar uma ação. É um ótimo script caso não queira tomar decisões apenas pelas métricas do Google Ads.

/*********************************************
* Automated Creative Testing With Statistical Significance
* Version 2.1
* Changelog v2.1
* – Fixed INVALID_PREDICATE_ENUM_VALUE
* Changelog v2.0
* – Fixed bug in setting the correct date
* – Script now uses a minimum visitors threshold
* per Ad instead of AdGroup
* – Added the ability to add the start date as a label to AdGroups
* – Added ability to check mobile and desktop ads separately
* Changelog v1.1.1 – Fixed bug with getDisplayUrl
* Changelog v1.1
* – Added ability to only run on some campaigns
* – Fixed bug in info logging
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
var EXTRA_LOGS = true;
var TO = [‘[email protected]’];
var CONFIDENCE_LEVEL = 95; // 90%, 95%, or 99% are most common

//If you only want to run on some campaigns, apply a label to them
//and put the name of the label here. Leave blank to run on all campaigns.
var CAMPAIGN_LABEL = ”;

//These two metrics are the components that make up the metric you
//want to compare. For example, this measures CTR = Clicks/Impressions
//Other examples might be:
// Cost Per Conv = Cost/Conversions
// Conversion Rate = Conversions/Clicks
// Cost Per Click = Cost/Clicks
var VISITORS_METRIC = ‘Impressions’;
var CONVERSIONS_METRIC = ‘Clicks’;
//This is the number of impressions the Ad needs to have in order
//to start measuring the results of a test.
var VISITORS_THRESHOLD = 100;

//Setting this to true to enable the script to check mobile ads
//against other mobile ads only. Enabling this will start new tests
//in all your AdGroups so only enable this after you have completed
//a testing cycle.
var ENABLE_MOBILE_AD_TESTING = false;

//Set this on the first run which should be the approximate last time
//you started a new creative test. After the first run, this setting
//will be ignored.
var OVERRIDE_LAST_TOUCHED_DATE = ‘Jan 1, 2014’;

var LOSER_LABEL = ‘Loser ‘+CONFIDENCE_LEVEL+’% Confidence’;
var CHAMPION_LABEL = ‘Current Champion’;

// Set this to true and the script will apply a label to
// each AdGroup to let you know the date the test started
// This helps you validate the results of the script.
var APPLY_TEST_START_DATE_LABELS = true;

//These come from the url when you are logged into AdWords
//Set these if you want your emails to link directly to the AdGroup
var __c = ”;
var __u = ”;

function main() {
createLabelIfNeeded(LOSER_LABEL,”#FF00FF”); //Set the colors of the labels here
createLabelIfNeeded(CHAMPION_LABEL,”#0000FF”); //Set the colors of the labels here

//Let’s find all the AdGroups that have new tests starting
var currentAdMap = getCurrentAdsSnapshot();
var previousAdMap = getPreviousAdsSnapshot();
if(previousAdMap) {
currentAdMap = updateCurrentAdMap(currentAdMap,previousAdMap);
}
storeAdsSnapshot(currentAdMap);
previousAdMap = null;

//Now run through the AdGroups to find tests
var agSelector = AdWordsApp.adGroups()
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘Status = ENABLED’);
if(CAMPAIGN_LABEL !== ”) {
var campNames = getCampaignNames();
agSelector = agSelector.withCondition(“CampaignName IN [‘”+campNames.join(“‘,'”)+”‘]”);
}
var agIter = agSelector.get();
var todayDate = getDateString(new Date(),’yyyyMMdd’);
var touchedAdGroups = [];
var finishedEarly = false;
while(agIter.hasNext()) {
var ag = agIter.next();

var numLoops = (ENABLE_MOBILE_AD_TESTING) ? 2 : 1;
for(var loopNum = 0; loopNum < numLoops; loopNum++) {
var isMobile = (loopNum == 1);
var rowKey;
if(isMobile) {
info(‘Checking Mobile Ads in AdGroup: “‘+ag.getName()+'”‘);
rowKey = [ag.getCampaign().getId(),ag.getId(),’Mobile’].join(‘-‘);
} else {
info(‘Checking Ads in AdGroup: “‘+ag.getName()+'”‘);
rowKey = [ag.getCampaign().getId(),ag.getId()].join(‘-‘);
}

if(!currentAdMap[rowKey]) { //This shouldn’t happen
warn(‘Could not find AdGroup: ‘+ag.getName()+’ in current ad map.’);
continue;
}

if(APPLY_TEST_START_DATE_LABELS) {
var dateLabel;
if(isMobile) {
dateLabel = ‘Mobile Tests Started: ‘+getDateString(currentAdMap[rowKey].lastTouched,’yyyy-MM-dd’);
} else {
dateLabel = ‘Tests Started: ‘+getDateString(currentAdMap[rowKey].lastTouched,’yyyy-MM-dd’);
}

createLabelIfNeeded(dateLabel,”#8A2BE2”);
//remove old start date
var labelIter = ag.labels().withCondition(“Name STARTS_WITH ‘”+dateLabel.split(‘:’)[0]+”‘”)
.withCondition(“Name != ‘”+dateLabel+”‘”).get();
while(labelIter.hasNext()) {
var label = labelIter.next();
ag.removeLabel(label.getName());
if(!label.adGroups().get().hasNext()) {
//if there are no more entities with that label, delete it.
label.remove();
}
}
applyLabel(ag,dateLabel);
}

//Here is the date range for the test metrics
var lastTouchedDate = getDateString(currentAdMap[rowKey].lastTouched,’yyyyMMdd’);
info(‘Last Touched Date: ‘+lastTouchedDate+’ Todays Date: ‘+ todayDate);
if(lastTouchedDate === todayDate) {
//Special case where the AdGroup was updated today which means a new test has started.
//Remove the old labels, but keep the champion as the control for the next test
info(‘New test is starting in AdGroup: ‘+ag.getName());
removeLoserLabelsFromAds(ag,isMobile);
continue;
}

//Is there a previous winner? if so we should use it as the control.
var controlAd = checkForPreviousWinner(ag,isMobile);

//Here we order by the Visitors metric and use that as a control if we don’t have one
var adSelector = ag.ads().withCondition(‘Status = ENABLED’).withCondition(‘AdType = TEXT_AD’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_NONE [‘”+[LOSER_LABEL,CHAMPION_LABEL].join(“‘,'”)+”‘]”);
}
var adIter = adSelector.forDateRange(lastTouchedDate, todayDate)
.orderBy(VISITORS_METRIC+” DESC”)
.get();
if( (controlAd == null && adIter.totalNumEntities() < 2) ||
(controlAd != null && adIter.totalNumEntities() < 1) )
{
info(‘AdGroup did not have enough eligible Ads. Had: ‘+adIter.totalNumEntities()+’, Needed at least 2′);
continue;
}

if(!controlAd) {
info(‘No control set for AdGroup. Setting one.’);
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
controlAd = ad;
break;
}
if(!controlAd) {
continue;
}
applyLabel(controlAd,CHAMPION_LABEL);
}

while(adIter.hasNext()) {
var testAd = adIter.next();
if(shouldSkip(isMobile,testAd)) { continue; }
//The Test object does all the heavy lifting for us.
var test = new Test(controlAd,testAd,
CONFIDENCE_LEVEL,
lastTouchedDate,todayDate,
VISITORS_METRIC,CONVERSIONS_METRIC);
info(‘Control – Visitors: ‘+test.getControlVisitors()+’ Conversions: ‘+test.getControlConversions());
info(‘Test – Visitors: ‘+test.getTestVisitors()+’ Conversions: ‘+test.getTestConversions());
info(‘P-Value: ‘+test.getPValue());

if(test.getControlVisitors() < VISITORS_THRESHOLD ||
test.getTestVisitors() < VISITORS_THRESHOLD)
{
info(‘Not enough visitors in the control or test ad. Skipping.’);
continue;
}

//Check for significance
if(test.isSignificant()) {
var loser = test.getLoser();
removeLabel(loser,CHAMPION_LABEL); //Champion has been dethroned
applyLabel(loser,LOSER_LABEL);

//The winner is the new control. Could be the same as the old one.
controlAd = test.getWinner();
applyLabel(controlAd,CHAMPION_LABEL);

//We store some metrics for a nice email later
if(!ag[‘touchCount’]) {
ag[‘touchCount’] = 0;
touchedAdGroups.push(ag);
}
ag[‘touchCount’]++;
}
}

//Let’s bail if we run out of time so we can send the emails.
if((!AdWordsApp.getExecutionInfo().isPreview() && AdWordsApp.getExecutionInfo().getRemainingTime() < 60) ||
( AdWordsApp.getExecutionInfo().isPreview() && AdWordsApp.getExecutionInfo().getRemainingTime() < 10) )
{
finishedEarly = true;
break;
}
}
}
if(touchedAdGroups.length > 0) {
sendMailForTouchedAdGroups(touchedAdGroups,finishedEarly);
}
beacon();
}

// A helper function to return the list of campaign ids with a label for filtering
function getCampaignNames() {
var campNames = [];
var labelIter = AdWordsApp.labels().withCondition(“Name = ‘”+CAMPAIGN_LABEL+”‘”).get();
if(labelIter.hasNext()) {
var label = labelIter.next();
var campIter = label.campaigns().get();
while(campIter.hasNext()) {
campNames.push(campIter.next().getName());
}
}
return campNames;
}

function applyLabel(entity,label) {
if(!AdWordsApp.getExecutionInfo().isPreview()) {
entity.applyLabel(label);
} else {
var adText = (entity.getEntityType() === ‘Ad’) ? [entity.getHeadline(),entity.getDescription1(),
entity.getDescription2(),entity.getDisplayUrl()].join(‘ ‘)
: entity.getName();
Logger.log(‘PREVIEW: Would have applied label: ‘+label+’ to Entity: ‘+ adText);
}
}

function removeLabel(ad,label) {
if(!AdWordsApp.getExecutionInfo().isPreview()) {
ad.removeLabel(label);
} else {
var adText = [ad.getHeadline(),ad.getDescription1(),ad.getDescription2(),ad.getDisplayUrl()].join(‘ ‘);
Logger.log(‘PREVIEW: Would have removed label: ‘+label+’ from Ad: ‘+ adText);
}
}

// This function checks if the AdGroup has an Ad with a Champion Label
// If so, the new test should use that as the control.
function checkForPreviousWinner(ag,isMobile) {
var adSelector = ag.ads().withCondition(‘Status = ENABLED’)
.withCondition(‘AdType = TEXT_AD’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_ANY [‘”+CHAMPION_LABEL+”‘]”);
}
var adIter = adSelector.get();
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
info(‘Found a previous winner. Using it as the control.’);
return ad;
}
return null;
}

function shouldSkip(isMobile,ad) {
if(isMobile) {
if(!ad.isMobilePreferred()) {
return true;
}
} else {
if(ad.isMobilePreferred()) {
return true;
}
}
return false;
}

// This function sends the email to the people in the TO array.
// If the script finishes early, it adds a notice to the email.
function sendMailForTouchedAdGroups(ags,finishedEarly) {
var htmlBody = ‘<html><head></head><body>’;
if(finishedEarly) {
htmlBody += ‘The script was not able to check all AdGroups. ‘ +
‘It will check additional AdGroups on the next run.<br / >’ ;
}
htmlBody += ‘The following AdGroups have one or more creative tests that have finished.’ ;
htmlBody += buildHtmlTable(ags);
htmlBody += ‘<p><small>Generated by <a href=”//www.freeadwordsscripts.com”>FreeAdWordsScripts.com</a></small></p>’ ;
htmlBody += ‘</body></html>’;
var options = {
htmlBody : htmlBody,
};
var subject = ags.length + ‘ Creative Test(s) Completed – ‘ +
Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘yyyy-MM-dd’);
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, ags.length+’ AdGroup(s) have creative tests that have finished.’, options);
}
}

// This function uses my HTMLTable object to build the styled html table for the email.
function buildHtmlTable(ags) {
var table = new HTMLTable();
//CSS from: //coding.smashingmagazine.com/2008/08/13/top-10-css-table-designs/
//Inlined using: //inlinestyler.torchboxapps.com/
table.setTableStyle([‘font-family: “Lucida Sans Unicode”,”Lucida Grande”,Sans-Serif;’,
‘font-size: 12px;’,
‘background: #fff;’,
‘margin: 45px;’,
‘width: 480px;’,
‘border-collapse: collapse;’,
‘text-align: left’].join(”));
table.setHeaderStyle([‘font-size: 14px;’,
‘font-weight: normal;’,
‘color: #039;’,
‘padding: 10px 8px;’,
‘border-bottom: 2px solid #6678b1’].join(”));
table.setCellStyle([‘border-bottom: 1px solid #ccc;’,
‘padding: 4px 6px’].join(”));
table.addHeaderColumn(‘#’);
table.addHeaderColumn(‘Campaign Name’);
table.addHeaderColumn(‘AdGroup Name’);
table.addHeaderColumn(‘Tests Completed’);
for(var i in ags) {
table.newRow();
table.addCell(table.getRowCount());
var campName = ags[i].getCampaign().getName();
var name = ags[i].getName();
var touchCount = ags[i][‘touchCount’];
var campLink, agLink;
if(__c !== ” && __u !== ”) { // You should really set these.
campLink = getUrl(ags[i].getCampaign(),’Ad groups’);
agLink = getUrl(ags[i],’Ads’);
table.addCell(a(campLink,campName));
table.addCell(a(agLink,name));
} else {
table.addCell(campName);
table.addCell(name);
}
table.addCell(touchCount,’text-align: right’);
}
return table.toString();
}

// Just a helper to build the html for a link.
function a(link,val) {
return ‘<a href=”‘+link+'”>’+val+'</a>’;
}

// This function finds all the previous losers and removes their label.
// It is used when the script detects a change in the AdGroup and needs to
// start a new test.
function removeLoserLabelsFromAds(ag,isMobile) {
var adSelector = ag.ads().withCondition(‘Status = ENABLED’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_ANY [‘”+LOSER_LABEL+”‘]”);
}
var adIter = adSelector.get();
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
removeLabel(ad,LOSER_LABEL);
}
}

// A helper function to create a new label if it doesn’t exist in the account.
function createLabelIfNeeded(name,color) {
if(!AdWordsApp.labels().withCondition(“Name = ‘”+name+”‘”).get().hasNext()) {
info(‘Creating label: “‘+name+'”‘);
AdWordsApp.createLabel(name,””,color);
} else {
info(‘Label: “‘+name+'” already exists.’);
}
}

// This function compares the previous and current Ad maps and
// updates the current map with the date that the AdGroup was last touched.
// If OVERRIDE_LAST_TOUCHED_DATE is set and there is no previous data for the
// AdGroup, it uses that as the last touched date.
function updateCurrentAdMap(current,previous) {
info(‘Updating the current Ads map using historical snapshot.’);
for(var rowKey in current) {
var currentAds = current[rowKey].adIds;
var previousAds = (previous[rowKey]) ? previous[rowKey].adIds : [];
if(currentAds.join(‘-‘) === previousAds.join(‘-‘)) {
current[rowKey].lastTouched = previous[rowKey].lastTouched;
}
if(previousAds.length === 0 && OVERRIDE_LAST_TOUCHED_DATE !== ”) {
current[rowKey].lastTouched = new Date(OVERRIDE_LAST_TOUCHED_DATE);
}
//if we make it here without going into the above if statements
//then the adgroup has changed and we should keep the new date
}
info(‘Finished updating the current Ad map.’);
return current;
}

// This stores the Ad map snapshot to a file so it can be used for the next run.
// The data is stored as a JSON string for easy reading later.
function storeAdsSnapshot(data) {
info(‘Storing the Ads snapshot to Google Drive.’);
var fileName = getSnapshotFilename();
var file = DriveApp.getFilesByName(fileName).next();
file.setContent(Utilities.jsonStringify(data));
info(‘Finished.’);
}

// This reads the JSON formatted previous snapshot from a file on GDrive
// If the file doesn’t exist, it creates a new one and returns an empty map.
function getPreviousAdsSnapshot() {
info(‘Loading the previous Ads snapshot from Google Drive.’);
var fileName = getSnapshotFilename();
var fileIter = DriveApp.getFilesByName(fileName);
if(fileIter.hasNext()) {
return Utilities.jsonParse(fileIter.next().getBlob().getDataAsString());
} else {
DriveApp.createFile(fileName, ”);
return {};
}
}

// A helper function to build the filename for the snapshot.
function getSnapshotFilename() {
var accountId = AdWordsApp.currentAccount().getCustomerId();
return (accountId + ‘ Ad Testing Script Snapshot.json’);
}

// This function pulls the Ad Performance Report which is the fastest
// way to build a snapshot of the current ads in the account.
// This only pulls in active text ads.
function getCurrentAdsSnapshot() {
info(‘Running Ad Performance Report to get current Ads snapshot.’);
var OPTIONS = { includeZeroImpressions : true };
var cols = [‘CampaignId’,’AdGroupId’,’Id’,’DevicePreference’,’Impressions’];
var report = ‘AD_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,
‘where AdType = TEXT_AD’,
‘and AdNetworkType1 = SEARCH’,
‘and CampaignStatus = ENABLED’,
‘and AdGroupStatus = ENABLED’,
‘and Status = ENABLED’,
‘during’,’TODAY’].join(‘ ‘);
var results = {}; // { campId-agId : row, … }
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
var rowKey = [row.CampaignId,row.AdGroupId].join(‘-‘);
if(ENABLE_MOBILE_AD_TESTING && row.DevicePreference == 30001) {
rowKey += ‘-Mobile’;
}
if(!results[rowKey]) {
results[rowKey] = { adIds : [], lastTouched : new Date() };
}
results[rowKey].adIds.push(row.Id);
}
for(var i in results) {
results[i].adIds.sort();
}
info(‘Finished building the current Ad map.’);
return results;
}

//Helper function to format the date
function getDateString(date,format) {
return Utilities.formatDate(new Date(date),AdWordsApp.currentAccount().getTimeZone(),format);
}

// Function to build out the urls for deeplinking into the AdWords account.
// For this to work, you need to have __c and __u filled in.
// Taken from: //www.freeadwordsscripts.com/2013/11/building-entity-deep-links-with-adwords.html
function getUrl(entity,tab) {
var customerId = __c;
var effectiveUserId = __u;
var decodedTab = getTab(tab);

var base = ‘//adwords.google.com/cm/CampaignMgmt?’;
var url = base+’__c=’+customerId+’&__u=’+effectiveUserId+’#’;

if(typeof entity[‘getEntityType’] === ‘undefined’) {
return url+’r.ONLINE.di&app=cm’;
}

var type = entity.getEntityType()
if(type === ‘Campaign’) {
return url+’c.’+entity.getId()+’.’+decodedTab+’&app=cm’;
}
if(type === ‘AdGroup’) {
return url+’a.’+entity.getId()+’_’+entity.getCampaign().getId()+’.’+decodedTab+’&app=cm’;
}
if(type === ‘Keyword’) {
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.key&app=cm’;
}
if(type === ‘Ad’) {
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.create&app=cm’;
}
return url+’r.ONLINE.di&app=cm’;

function getTab(tab) {
var mapping = {
‘Ad groups’:’ag’,’Settings:All settings’:’st_sum’,
‘Settings:Locations’:’st_loc’,’Settings:Ad schedule’:’st_as’,
‘Settings:Devices’:’st_p’,’Ads’:’create’,
‘Keywords’:’key’,’Audiences’:’au’,’Ad extensions’:’ae’,
‘Auto targets’:’at’,’Dimensions’ : ‘di’
};
if(mapping[tab]) { return mapping[tab]; }
return ‘key’; //default to keyword tab
}
}

// Helper function to print info logs
function info(msg) {
if(EXTRA_LOGS) {
Logger.log(‘INFO: ‘+msg);
}
}

// Helper function to print more serious warnings
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

/********************************
* Track Script Runs in Google Analytics
* Created By: Russ Savage
* FreeAdWordsScripts.com
********************************/
function beacon() {
var TAG_ID = ‘UA-40187672-2’;
var CAMPAIGN_SOURCE = ‘adwords’;
var CAMPAIGN_MEDIUM = ‘scripts’;
var CAMPAIGN_NAME = ‘AdTestingScriptV2_1’;
var HOSTNAME = ‘www.freeadwordsscripts.com’;
var PAGE = ‘/Ad_Testing_Script_v2_1’;
if(AdWordsApp.getExecutionInfo().isPreview()) {
PAGE += ‘/preview’;
}
var DOMAIN_LINK = ‘//’+HOSTNAME+PAGE;

//Pulled from: //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var uuid = ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g,
function(c) {var r = Math.random()*16|0,v=c==’x’?r:r&0x3|0x8;return v.toString(16);});

var url = ‘//www.google-analytics.com/collect?’;
var payload = {
‘v’:1,’tid’:TAG_ID,’cid’:uuid,
‘t’:’pageview’,’cs’:CAMPAIGN_SOURCE,’cm’:CAMPAIGN_MEDIUM,’cn’:CAMPAIGN_NAME,
‘dl’:DOMAIN_LINK
};
var qs = ”;
for(var key in payload) {
qs += key + ‘=’ + encodeURIComponent(payload[key]) + ‘&’;
}
url += qs.substring(0,qs.length-1);
UrlFetchApp.fetch(url);
}

/*********************************************
* Test: A class for runnning A/B Tests for Ads
* Version 1.0
* Based on VisualWebsiteOptimizer logic: //goo.gl/jiImn
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
// A description of the parameters:
// control – the control Ad, test – the test Ad
// startDate, endDate – the start and end dates for the test
// visitorMetric, conversionMetric – the components of the metric to use for the test
function Test(control,test,desiredConf,startDate,endDate,visitorMetric,conversionMetric) {
this.desiredConfidence = desiredConf/100;
this.verMetric = visitorMetric;
this.conMetric = conversionMetric;
this.startDate = startDate;
this.endDate = endDate;
this.winner;

this.controlAd = control;
this.controlStats = (this.controlAd[‘stats’]) ? this.controlAd[‘stats’] : this.controlAd.getStatsFor(this.startDate, this.endDate);
this.controlAd[‘stats’] = this.controlStats;
this.controlVisitors = this.controlStats[‘get’+this.verMetric]();
this.controlConversions = this.controlStats[‘get’+this.conMetric]();
this.controlCR = getConversionRate(this.controlVisitors,this.controlConversions);

this.testAd = test;
this.testStats = (this.testAd[‘stats’]) ? this.testAd[‘stats’] : this.testAd.getStatsFor(this.startDate, this.endDate);
this.testAd[‘stats’] = this.testStats;
this.testVisitors = this.testStats[‘get’+this.verMetric]();
this.testConversions = this.testStats[‘get’+this.conMetric]();
this.testCR = getConversionRate(this.testVisitors,this.testConversions);

this.pValue;

this.getControlVisitors = function() { return this.controlVisitors; }
this.getControlConversions = function() { return this.controlConversions; }
this.getTestVisitors = function() { return this.testVisitors; }
this.getTestConversions = function() { return this.testConversions; }

// Returns the P-Value for the two Ads
this.getPValue = function() {
if(!this.pValue) {
this.pValue = calculatePValue(this);
}
return this.pValue;
};

// Determines if the test has hit significance
this.isSignificant = function() {
var pValue = this.getPValue();
if(pValue && pValue !== ‘N/A’ && (pValue >= this.desiredConfidence || pValue <= (1 – this.desiredConfidence))) {
return true;
}
return false;
}

// Returns the winning Ad
this.getWinner = function() {
if(this.decideWinner() === ‘control’) {
return this.controlAd;
}
if(this.decideWinner() === ‘challenger’) {
return this.testAd;
}
return null;
};

// Returns the losing Ad
this.getLoser = function() {
if(this.decideWinner() === ‘control’) {
return this.testAd;
}
if(this.decideWinner() === ‘challenger’) {
return this.controlAd;
}
return null;
};

// Determines if the control or the challenger won
this.decideWinner = function () {
if(this.winner) {
return this.winner;
}
if(this.isSignificant()) {
if(this.controlCR >= this.testCR) {
this.winner = ‘control’;
} else {
this.winner = ‘challenger’;
}
} else {
this.winner = ‘no winner’;
}
return this.winner;
}

// This function returns the confidence level for the test
function calculatePValue(instance) {
var control = {
visitors: instance.controlVisitors,
conversions: instance.controlConversions,
cr: instance.controlCR
};
var challenger = {
visitors: instance.testVisitors,
conversions: instance.testConversions,
cr: instance.testCR
};
var z = getZScore(control,challenger);
if(z == -1) { return ‘N/A’; }
var norm = normSDist(z);
return norm;
}

// A helper function to make rounding a little easier
function round(value) {
var decimals = Math.pow(10,5);
return Math.round(value*decimals)/decimals;
}

// Return the conversion rate for the test
function getConversionRate(visitors,conversions) {
if(visitors == 0) {
return -1;
}
return conversions/visitors;
}

function getStandardError(cr,visitors) {
if(visitors == 0) {
throw ‘Visitors cannot be 0.’;
}
return Math.sqrt((cr*(1-cr)/visitors));
}

function getZScore(c,t) {
try {
if(!c[‘se’]) { c[‘se’] = getStandardError(c.cr,c.visitors); }
if(!t[‘se’]) { t[‘se’] = getStandardError(t.cr,t.visitors); }
} catch(e) {
Logger.log(e);
return -1;
}

if((Math.sqrt(Math.pow(c.se,2)+Math.pow(t.se,2))) == 0) {
Logger.log(‘WARNING: Somehow the denominator in the Z-Score calulator was 0.’);
return -1;
}
return ((c.cr-t.cr)/Math.sqrt(Math.pow(c.se,2)+Math.pow(t.se,2)));
}

//From: //www.codeproject.com/Articles/408214/Excel-Function-NORMSDIST-z
function normSDist(z) {
var sign = 1.0;
if (z < 0) { sign = -1; }
return round(0.5 * (1.0 + sign * erf(Math.abs(z)/Math.sqrt(2))));
}

// From: //picomath.org/javascript/erf.js.html
function erf(x) {
// constants
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;

// Save the sign of x
var sign = 1;
if (x < 0) {
sign = -1;
}
x = Math.abs(x);

// A&S formula 7.1.26
var t = 1.0/(1.0 + p*x);
var y = 1.0 – (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);

return sign*y;
}
}

/*********************************************
* HTMLTable: A class for building HTML Tables
* Version 1.0
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
function HTMLTable() {
this.headers = [];
this.columnStyle = {};
this.body = [];
this.currentRow = 0;
this.tableStyle;
this.headerStyle;
this.cellStyle;

this.addHeaderColumn = function(text) {
this.headers.push(text);
};

this.addCell = function(text,style) {
if(!this.body[this.currentRow]) {
this.body[this.currentRow] = [];
}
this.body[this.currentRow].push({ val:text, style:(style) ? style : ” });
};

this.newRow = function() {
if(this.body != []) {
this.currentRow++;
}
};

this.getRowCount = function() {
return this.currentRow;
};

this.setTableStyle = function(css) {
this.tableStyle = css;
};

this.setHeaderStyle = function(css) {
this.headerStyle = css;
};

this.setCellStyle = function(css) {
this.cellStyle = css;
if(css[css.length-1] !== ‘;’) {
this.cellStyle += ‘;’;
}
};

this.toString = function() {
var retVal = ‘<table ‘;
if(this.tableStyle) {
retVal += ‘style=”‘+this.tableStyle+'”‘;
}
retVal += ‘>’+_getTableHead(this)+_getTableBody(this)+'</table>’;
return retVal;
};

function _getTableHead(instance) {
var headerRow = ”;
for(var i in instance.headers) {
headerRow += _th(instance,instance.headers[i]);
}
return ‘<thead><tr>’+headerRow+'</tr></thead>’;
};

function _getTableBody(instance) {
var retVal = ‘<tbody>’;
for(var r in instance.body) {
var rowHtml = ‘<tr>’;
for(var c in instance.body[r]) {
rowHtml += _td(instance,instance.body[r][c]);
}
rowHtml += ‘</tr>’;
retVal += rowHtml;
}
retVal += ‘</tbody>’;
return retVal;
};

function _th(instance,val) {
var retVal = ‘<th scope=”col” ‘;
if(instance.headerStyle) {
retVal += ‘style=”‘+instance.headerStyle+'”‘;
}
retVal += ‘>’+val+'</th>’;
return retVal;
};

function _td(instance,cell) {
var retVal = ‘<td ‘;
if(instance.cellStyle || cell.style) {
retVal += ‘style=”‘;
if(instance.cellStyle) {
retVal += instance.cellStyle;
}
if(cell.style) {
retVal += cell.style;
}
retVal += ‘”‘;
}
retVal += ‘>’+cell.val+'</td>’;
return retVal;
};
}

Automação Google Ads: Acompanhamento

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com acompanhamento de anúncios”.

 

1. Acompanhamento do Índice de Qualidade – Por Martin Roettgerding. Acompanhe o Índice de Qualidade da sua conta com esse script. Além disso, o script oferece um dashboard com a média do Índice de Qualidade histórica de sua conta. Excelente para acompanhar o progresso de suas alterações nos anúncios, páginas de destino, por exemplo.

Apresentando o Quality Score Tracker v3.0
postado em 11 de março de 2016
É maior, é melhor e oficial: o novo Rastreador de Índice de Qualidade está finalmente disponível!

O Quality Score Tracker é um script gratuito do Google Ads que controla os Índices de qualidade de uma conta. A versão anterior ainda é incrivelmente popular, mas já tem três anos. É por isso que criamos um novo que é muito mais rápido e tem novos recursos extravagantes.

 

 

A nova versão é diferente das antigas em vários aspectos. Um dos principais objetivos era escrever um roteiro que fosse o mais fácil de usar possível – ainda mais fácil do que os antigos. O novo Rastreador de Índice de qualidade pode ser usado imediatamente, sem qualquer configuração adicional.

Apesar de ser fácil de usar o novo script é muito complexo e lida com muitas coisas sem incomodar o usuário.

Veja o que faz:

Acompanhamento do Índice de qualidade

O Quality Score Tracker salva os Índices de qualidade de suas palavras-chave em uma planilha. Ao contrário das versões anteriores, você não precisa escolher quais – ele rastreia todas elas. Isso permite que você volte e pesquise o histórico de qualquer uma de suas palavras-chave posteriormente.

Painel de controle

Embora o rastreamento de milhares de Índices de qualidade individuais possa ser útil posteriormente, o script também fornece um painel para mostrar onde você está atualmente.

 

 

O painel é fornecido em uma planilha do Google também. Essa planilha também fornece dados resumidos sobre seus Índices de qualidade.

Google Drive

Todos os arquivos (pontuações de qualidade do painel e da palavra-chave rastreada) são armazenados em uma pasta no seu Google Drive. Você pode acessá-los por meio do navegador ou usar todos os recursos do Google Drive, como sincronizar a pasta com o dispositivo ou compartilhar pastas com outras pessoas.

Há um limite de quantas células uma planilha do Google pode conter (dois milhões). Se você atingir esse limite durante o rastreamento de Índices de qualidade, o script criará planilhas adicionais. Os arquivos da planilha são nomeados e numerados para que você possa juntar tudo mais tarde, se necessário.

 

 

Como tudo está nos arquivos da planilha do Google, o espaço ocupado não conta para o limite de armazenamento do Google Drive.

Customizável

O script fornece várias configurações que você pode alterar (mas não é necessário). Por exemplo, você pode adicionar outros gráficos ao painel, como um Índice de qualidade ponderado por clique.

FAQ integrado

O painel vem com um FAQ que é atualizado toda vez que o script é executado.

Mais para vir!

O script já é bastante complexo, mas tenho algumas ideias para novos recursos que desejo adicionar no futuro (provavelmente uma versão da MCC será a próxima). Quando uma nova versão está disponível, um lembrete sutil é adicionado ao painel.

 

 

Configuração rápida

Como o script funciona imediatamente, é fácil configurá-lo.

  1. Obtenha o código (veja abaixo) e cole-o na sua conta.
  2. Agende o script para ser executado diariamente. Recomendação: 01:00
  3. Execute o script pela primeira vez. Verifique os registros para encontrar o URL do seu painel ou encontre tudo em seu Google Drive .

Configuração Detalhada

No Google AdWords, navegue até ” Operações em massa” e depois ” Scripts” . Clique em + Script . Cole o script no final deste post. Nomeie-o como “Quality Score Tracker v3.0”. Clique em Salvar e, em seguida, em Autorizar agora .

Volte para os Scripts e encontre o Rastreador de Índice de Qualidade no topo da lista. Clique em + Criar agendamento . Como frequência, selecione “Diariamente”, juntamente com um horário de sua conveniência. Recomendação: 01:00 Clique em Salvar .

Clique em Executar e , em seguida, role para baixo para ver o script sendo executado em Logs . Com grandes contas, isso pode demorar um pouco, caso contrário, isso pode ser feito em 15 segundos. Clique em Logs no lado direito. Na próxima tela, clique no botão Logs no topo. Agora, os URLs do painel e da pasta do Google Drive são exibidos. Como alternativa, você pode encontrar tudo em seu Google Drive .

 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Quality Score Tracker v3.0.1
* Written by Martin Roettgerding.
* © 2016 Martin Roettgerding, Bloofusion Germany GmbH.
* www.ppc-epiphany.com/qstracker/latest
*/
function main(){
/*
* The following preferences can be changed to customize this script.
* Most of options can be set by using 1 for yes or 0 for no.
* You don’t have to change anything here. The script will do fine with the defaults.
*/
var config = {
/*
* Which of the following charts should be displayed on the dashboard?
* The “per QS” charts are column charts. They show the current state compared to a previous one (see next option).
* “Average” and “weighted” charts are line charts, showing changes over time.
*/
“chartsToDisplay” : {
“Keywords per QS” : 0,
“Average QS” : 0,
“Keywords with Impressions per QS” : 1,
“Average QS for Keywords with Impressions” : 0,
“Impressions per QS” : 0,
“Impression weighted QS” : 1,
“Clicks per QS” : 0,
“Click weighted QS” : 0,
“Conversions per QS” : 0,
“Conversion weighted QS” : 0,
“Conversion value per QS” : 0,
“Conversion value weighted QS” : 0,
},
/*
* Column charts can show a former date for comparison. Set the number of steps you want to go back for this.
* Note that the date you’re comparing this to will depend on how often you’ve run the script in the past.
* Example: If the setting is 30 and you ran the script daily, your comparison will be with the values from 30 days before. If you ran it hourly, it will be with values from 30 hours before.
* If you haven’t run the script often enough, the comparison will go as far back as possible.
* Put 0 to disable the comparison.
*/
“chartsCompareStepsBack” : 30,
/*
* When stats are taken into account (like impressions per QS, or impression weighted QS), this timeframe is used.
* Note that this affects the values to be tracked and stored. Past values that are already stored won’t be affected.
* Use one of the following: TODAY, YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH
*/
“statsTimeframe” : “LAST_30_DAYS”,
/*
* Whether to only look at stats from Google (e.g. for impression weighted QS).
* Recommended. Quality Score itself only reflects data from Google, so weighting should only take Google into account and leave out search partners.
* Note that this affects the values to be tracked and stored. Past values that are already stored won’t be affected.
*/
“googleOnly” : 1,
/*
* Whether to only track active keywords. This means that the keyword, the adgroup, and the campaign have to be enabled.
* Recommended. Otherwise inactive keywords with meaningless Quality Scores might skew your data.
*/
“activeKeywordsOnly” : 1,
/*
* Set to 1 if you want your dates (in charts, table headers, and file names) to contain hours and minutes as well.
* Do this if you want to run the script hourly.
*/
“useHours” : 0,
/*
* Use this option to not keep track of individual keywords’ Quality Scores and only save data to the dashboard file.
* This makes sense if you have more than 400,000 keywords. Note that you don’t have to change this: The script will notice on its own and log a message otherwise.
*/
“skipIndividualKeywords” : 0,
/*
* The name of the file where dashboard and summarized data are stored.
*/
“summaryFileName” : “Dashboard + Summary”,
/*
* The base folder for all Quality Score Tracker files.
*/
“baseFolder” : “Quality Score Tracker/”,
/*
* Whether to add a client folder in the base folder (resulting in a folder like “Quality Score Tracker/CLIENT_NAME (123-456-7890)/”)
* The folder’s name is not important, as long as the Adwords client id remains in it.
* This is useful if you want to track multiple accounts with this script.
*/
“useClientFolder” : 1,
}

trackQS(config);
}

function trackQS(config){
var version = “3.0”;

if(config[‘useHours’]) var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd HH:mm”);
else var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd”);

var folder = getOrCreateFolder(config[‘baseFolder’]);
if(config[‘useClientFolder’]) folder = getOrCreateClientFolder(folder);

// Find the latest report file in the folder.
var maxFileNumber = 0;
var reportFile;
var summaryFile;
var fileIterator = folder.getFiles();
while(fileIterator.hasNext()){
var file = fileIterator.next();
var matches = new RegExp(‘ #([0-9]+) ‘).exec(file.getName());
if(matches && parseInt(matches[1]) > maxFileNumber){
maxFileNumber = parseInt(matches[1]);
reportFile = file;
}else if(file.getName() == config[‘summaryFileName’]) summaryFile = file;
}

// No report file found? Add a new one.
if(maxFileNumber == 0){
reportFile = addReportFile(folder, “QS Report #1 (” + dateString + “)”);
maxFileNumber = 1;
}
// No summary file found? Add a new one.
if(!summaryFile) summaryFile = addSummaryFile(folder, config[‘summaryFileName’]);

Logger.log(“All files are stored at”);
Logger.log(folder.getUrl());
Logger.log(“The dashboard is here:”);
Logger.log(summaryFile.getUrl());

var spreadsheet = SpreadsheetApp.open(reportFile);
var sheet = spreadsheet.getActiveSheet();
var lastRowNumber = sheet.getLastRow();
var sheetLastColumn = sheet.getLastColumn();
var idColumnValues = sheet.getRange(1, 4, lastRowNumber, 1).getValues();
var summarySpreadsheet = SpreadsheetApp.open(summaryFile);
updateInfo(summarySpreadsheet, version);
var sheetCharts = summarySpreadsheet.getSheetByName(“Dashboard”);

summarySpreadsheet.setActiveSheet(sheetCharts);
summarySpreadsheet.moveActiveSheet(1);

// Track an event in Google Analytics.
trackInAnalytics(version);

// Remember the line number for every keyword.
var lineNumbers = {};
for(var i = 1; i < lastRowNumber; i++){
lineNumbers[idColumnValues[i][0]] = i;
}

// qsValues represents the new column that will go right next to the others.
var qsValues = new Array(lastRowNumber);
qsValues[0] = [ dateString ];
// Initialize everything with an empty string.
var qsValuesLength = qsValues.length;
for(var i = 1; i < qsValuesLength; i++) qsValues[i] = [“”];

// In case new keywords are found, they’ll be added as new rows below the rest (campaign, adgroup, keyword, id string).
var newRows = [];

// All aggregated data goes in this variable.
var qsStats = {
“Keywords” : {},
“Keywords with impressions” : {},
“Impressions” : {},
“Clicks” : {},
“Conversions” : {},
“Conversion value” : {}
};
// Initialize the arrays so that everything can be added up later. Index 0 is for totals, 1-10 for Quality Scores.
for(var key in qsStats){
for(var i = 0; i <= 10; i++){
qsStats[key][i] = 0;
}
}

// Get the data from AdWords.
var awql = “SELECT Id, Criteria, KeywordMatchType, CampaignId, CampaignName, AdGroupId, AdGroupName, QualityScore, Impressions, Clicks, Conversions, ConversionValue FROM KEYWORDS_PERFORMANCE_REPORT WHERE Id NOT_IN [3000000, 3000006] AND Status = ‘ENABLED’ AND AdGroupStatus = ‘ENABLED’ AND CampaignStatus = ‘ENABLED'”;
if(config[‘googleOnly’]) awql += ” AND AdNetworkType2 = ‘SEARCH'”;
if(config[‘activeKeywordsOnly’]) awql += ” AND CampaignStatus = ‘ENABLED’ AND AdGroupStatus = ‘ENABLED’ AND Status = ‘ENABLED'”;
awql += ” DURING ” + config[‘statsTimeframe’];
var report = AdWordsApp.report(awql);
var reportRows = report.rows();

// Go through the report and count Quality Scores.
while(reportRows.hasNext()){
var row = reportRows.next();
// Save the aggregated data.
qsStats[‘Keywords’][row[‘QualityScore’]]++;
if(row[‘Impressions’] > 0) qsStats[‘Keywords with impressions’][row[‘QualityScore’]]++;
qsStats[‘Impressions’][row[‘QualityScore’]] += parseInt(row[‘Impressions’]);
qsStats[‘Clicks’][row[‘QualityScore’]] += parseInt(row[‘Clicks’]);
qsStats[‘Conversions’][row[‘QualityScore’]] += parseInt(row[‘Conversions’]);
qsStats[‘Conversion value’][row[‘QualityScore’]] += parseInt(row[‘ConversionValue’]);

// Save the individual keyword’s Quality Score.
if(!config[‘skipIndividualKeywords’]){
var id = row[‘CampaignId’]+”_”+row[‘AdGroupId’]+”_”+row[‘Id’];
// Check if there is already a line for this keyword
if(lineNumbers[id]) var line_number = lineNumbers[id];
else{
// There is no line for this keyword yet. Create a new one and add the line headers.
line_number = qsValues.length;
if(row[‘KeywordMatchType’] == “Exact”) var keyword = ‘[‘ + row[‘Criteria’] + ‘]’;
else if(row[‘KeywordMatchType’] == “Phrase”) var keyword = ‘”‘ + row[‘Criteria’] + ‘”‘;
else var keyword = row[‘Criteria’];

newRows.push([row[‘CampaignName’], row[‘AdGroupName’], keyword, id]);
}

qsValues[line_number] = [row[‘QualityScore’]];
}
}

// Check if everything fits.
if(!config[‘skipIndividualKeywords’]){
// A spreadsheet can hold up to 2 million cells. Calculate if the new data will fit in with the rest.
// With four rows needed for every keyword, plus one for every tracking run, this won’t fit if there are more than 400,000 rows (header + 399,999 keywords).
if(qsValues.length >= 400000){
Logger.log(“There are too many keywords to be tracked (” + qsValues.length + “). This tool can only track up to 399,999 keywords.”);
Logger.log(“A summary will be logged, but individual keyword quality scores cannot be stored.”);
skipIndividualKeywords = true;
}else if((qsValues.length + 1) * (sheet.getLastColumn() + 1) > 2000000){
// This spreadsheet is full, a new one is needed.
// Add new file.
maxFileNumber++;
reportFile = addReportFile(folder, “QS Report #” + maxFileNumber + ” (” + dateString + “)”);
var newSpreadsheet = SpreadsheetApp.open(reportFile);
var newSheet = newSpreadsheet.getActiveSheet();
// Copy the first columns from the old sheet to the new one.
newSheet.getRange(1, 1, lastRowNumber, 4).setValues(sheet.getRange(1, 1, lastRowNumber, 4).getValues());
// From now on, work with the new sheet and spreadsheet.
spreadsheet = newSpreadsheet;
sheet = newSheet;
sheetLastColumn = 4;
}
}

// Store the keyword data in the spreadsheet.
if(!config[‘skipIndividualKeywords’]){
// If there are new rows, add their line headers beneath the others.
if(newRows.length > 0){
sheet.insertRowsAfter(lastRowNumber, newRows.length).getRange(lastRowNumber + 1, 1, newRows.length, 4).setValues(newRows);
sheet.autoResizeColumn(1).autoResizeColumn(2).autoResizeColumn(3);
}
// Add a new column with the tracked data.
sheet.insertColumnAfter(sheetLastColumn);
sheet.getRange(1, sheetLastColumn + 1, qsValues.length, 1).setValues(qsValues);
sheet.autoResizeColumn(sheetLastColumn + 1);

// Change file name to reflect the new date.
// Find out which dates are currently noted in the file’s name.
var matches = /\((.*?)( – (.*))?\)/.exec(reportFile.getName());
if(matches && matches[1]){
if(matches[2]){
// There’s a start date and an end date.
var startDate = matches[1];
var endDate = matches[3];
if(endDate != dateString){
var newFileName = reportFile.getName().replace(endDate, dateString);
reportFile.setName(newFileName);
}
}else{
// There’s just a start date.
var startDate = matches[1];
if(startDate != dateString){
var newFileName = reportFile.getName().replace(startDate, startDate + ” – ” + dateString);
reportFile.setName(newFileName);
}
}
}else{
Logger.log(“Could not recognize dates in file name ” + reportFile.getName() +”. File name remains unchanged.”);
}
}

// Now take care of the summary file.

// Get the total numbers.
for(var key in qsStats){
for(var i = 1; i <= 10; i++){
qsStats[key][0] += qsStats[key][i];
}
}

// Prepare a new column for the Percentages data sheet.
var newValues = [];
var newValuesNumberFormats = [];

for(var key in qsStats){
newValues.push([dateString]);
newValuesNumberFormats.push([“@[email protected]”]);
for(var i = 1; i <= 10; i++){
if(qsStats[key][0]) newValues.push([qsStats[key][i] / qsStats[key][0]]); else newValues.push([0]);
newValuesNumberFormats.push([“0.00%”]);
}
newValues.push([qsStats[key][0]]);
newValuesNumberFormats.push([“0.##”]);
}

var sheetPercentages = summarySpreadsheet.getSheetByName(“Percentages”);
var sheetAverages = summarySpreadsheet.getSheetByName(“Averages”);

var lastCol = sheetPercentages.getLastColumn() + 1;
var lastRow = sheetAverages.getLastRow() + 1;

// Add the data to the Percentages sheet.
sheetPercentages.insertColumnAfter(lastCol – 1);
sheetPercentages.getRange(1, lastCol, 72, 1).setNumberFormats(newValuesNumberFormats).setValues(newValues);
sheetPercentages.autoResizeColumn(lastCol);

// Add a new row with formulas to the Averages sheet.
sheetAverages.appendRow([“”]);
sheetAverages.getRange(lastRow, 1, 1, 1).setValue(dateString);
sheetAverages.getRange(lastRow, 2, 1, 6).setFormulasR1C1([[
“=SUMPRODUCT(Percentages!R2C1:R11C1; Percentages!R2C” + lastCol + “:R11C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R14C1:R23C1; Percentages!R14C” + lastCol + “:R23C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R26C1:R35C1; Percentages!R26C” + lastCol + “:R35C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R38C1:R47C1; Percentages!R38C” + lastCol + “:R47C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R50C1:R59C1; Percentages!R50C” + lastCol + “:R59C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R62C1:R71C1; Percentages!R62C” + lastCol + “:R71C” + lastCol + “)”
]]);

// The properties for the charts. This is not meant to be reconfigured.
var chartsProperties = {
“Keywords per QS” : {
“type” : “column”,
“vCol” : 2,
},
“Average QS” : {
“type” : “line”,
“vCol” : 2,
},
“Keywords with Impressions per QS” : {
“type” : “column”,
“vCol” : 3,
},
“Average QS for Keywords with Impressions” : {
“type” : “line”,
“vCol” : 3,
},
“Impressions per QS” : {
“type” : “column”,
“vCol” : 4,
},
“Impression weighted QS” : {
“type” : “line”,
“vCol” : 4,
},
“Clicks per QS” : {
“type” : “column”,
“vCol” : 5,
},
“Click weighted QS” : {
“type” : “line”,
“vCol” : 5,
},
“Conversions per QS” : {
“type” : “column”,
“vCol” : 6,
},
“Conversion weighted QS” : {
“type” : “line”,
“vCol” : 6,
},
“Conversion value per QS” : {
“type” : “column”,
“vCol” : 7,
},
“Conversion value weighted QS” : {
“type” : “line”,
“vCol” : 7,
},
};

var row = 1;
var col = 1;
var summarySheets = {
“dataH”: sheetPercentages,
“dataV”: sheetAverages,
“charts”: sheetCharts,
}

// Add charts to the dashboard.
for(var chartName in config[‘chartsToDisplay’]){
// Skip all charts that are not set to be displayed.
if(!config[‘chartsToDisplay’][chartName]) continue;

addChartToDashboard(chartName, chartsProperties[chartName][‘type’], summarySheets, row, col, lastRow, lastCol, chartsProperties[chartName][‘vCol’], config[‘chartsCompareStepsBack’]);

// Add the “Average QS” cells.
sheetCharts.setRowHeight(row, 60).setRowHeight(row + 1, 20).setRowHeight(row + 2, 270);
sheetCharts.getRange(row, 2).setValue(“Average QS”).setFontWeight(“bold”).setFontSize(24).setBorder(true, true, false, true, null, null);
sheetCharts.getRange(row + 2, 2).setFontWeight(“bold”).setFontSize(24).setNumberFormat(“0.00”).setBorder(false, true, false, true, null, null);
sheetCharts.getRange(row + 1, 2, 2, 1).setFormulasR1C1(
[
[“=LOWER(Averages!R1C” + chartsProperties[chartName][‘vCol’] + “)”], [“=Averages!R” + lastRow + “C” + chartsProperties[chartName][‘vCol’]]
]).setBorder(false, true, true, true, null, null);
sheetCharts.autoResizeColumn(2);
row += 3;
}
}

/*
* Checks if there is a folder with the given name in the Google Drive root folder. If not, the folder is created.
* The folderName can be in the form of a complete path with subfolders, like “QS Reports/123/whatever”.
* Returns the folder.
*/
function getOrCreateFolder(folderName){
return getOrCreateFolderFromArray(folderName.toString().split(“/”), DriveApp.getRootFolder());
}

/*
* Does the actual work for getOrCreateFolder. Recursive function, based on an array of folder names (to handle paths with subfolders).
*/
function getOrCreateFolderFromArray(folderNameArray, currentFolder){
var folderName = “”;
// Skip empty folders (multiple slashes or a slash at the end).
do folderName = folderNameArray.shift(); while(folderName == “” && folderNameArray.length > 0);

if(folderName == “”) return currentFolder;

// See if the folder is already there.
var folderIterator = currentFolder.getFoldersByName(folderName);
if(folderIterator.hasNext()){
var folder = folderIterator.next();
}else{
// Create folder.
Logger.log(“Creating folder ‘” + folderName + “‘”);
var folder = currentFolder.createFolder(folderName);
}

if(folderNameArray.length > 0) return getOrCreateFolderFromArray(folderNameArray, folder);
return folder;
}

/*
* Checks if there is a folder for the current client account in the base folder. If not, the folder is created.
* Existing client folders are recognized by the client id in parentheses. This way, folders can be found again, even if an account has been renamed.
*/
function getOrCreateClientFolder(baseFolder){
var folderIterator = baseFolder.getFolders();
var regExp = new RegExp(AdWordsApp.currentAccount().getCustomerId());
while(folderIterator.hasNext()){
var folder = folderIterator.next();
if(folder.getName().match(regExp)) return folder;
}
// Since no folder has been found: Create one.
var newFolderName = AdWordsApp.currentAccount().getName() + ” (” + AdWordsApp.currentAccount().getCustomerId() + “)”;
Logger.log(“Creating folder ‘” + newFolderName + “‘”);
return baseFolder.createFolder(newFolderName);
}

/*
* Creates a spreadsheet for QS tracking.
* Adds headers to the spreadsheet.
* Returns the file.
*/
function addReportFile(folder, name){
var spreadsheet = SpreadsheetApp.create(name, 1, 4);
var sheet = spreadsheet.getActiveSheet();
sheet.setName(“QS history”);
// Put in the table headings
sheet.getRange(1, 1, 1, 4).setValues([[“Campaign”, “AdGroup”, “Keyword”, “ID string”]]);
//sheet.getRange(1, 1, 1, 4).setFontWeight(“bold”);
sheet.setColumnWidth(4, 1);
var file = DriveApp.getFileById(spreadsheet.getId());
folder.addFile(file);
var parentFolder = file.getParents().next();
parentFolder.removeFile(file);
return folder.getFilesByName(name).next();
}

/*
* Creates a spreadsheet for the summary and stores it in the folder.
* Creates sheets for the Percentages and Averages.
* Populates header rows and columns.
*/
function addSummaryFile(folder, name){
var spreadsheet = SpreadsheetApp.create(name);
var sheetH = spreadsheet.getActiveSheet();
sheetH.setName(“Percentages”);

// Add the first column for the horizontal data table.
sheetH.getRange(1, 1, 72, 1).setValues(
[[“All keywords”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Keywords with impressions”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Impression weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Click weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Conversion weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Conversion value weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’]
]
);
sheetH.getRange(“A:A”).setNumberFormat(‘@[email protected]’);
sheetH.autoResizeColumn(1);

var sheetV = spreadsheet.insertSheet(“Averages”);
// Add the first rows for the vertical data table.
sheetV.getRange(1, 1, 4, 7).setValues([
[“Date”, “Average”, “Average for keywords with impressions”, “Impression weighted”, “Click weighted”, “Conversion weighted”, “Value weighted”],
[“Highest”, “”, “”, “”, “”, “”, “”],
[“Lowest”, “”, “”, “”, “”, “”, “”],
[“Average”, “”, “”, “”, “”, “”, “”]
]);
// Add some formulas for maximums, minimums, and averages.
sheetV.getRange(2, 2, 3, 6).setFormulas([
[“=MAX(B$5:B)”, “=MAX(C$5:C)”, “=MAX(D$5:D)”, “=MAX(E$5:E)”, “=MAX(F$5:F)”, “=MAX(G$5:G)”],
[“=MIN(B$5:B)”, “=MIN(C$5:C)”, “=MIN(D$5:D)”, “=MIN(E$5:E)”, “=MIN(F$5:F)”, “=MIN(G$5:G)”],
[“=AVERAGE(B$5:B)”, “=AVERAGE(C$5:C)”, “=AVERAGE(D$5:D)”, “=AVERAGE(E$5:E)”, “=AVERAGE(F$5:F)”, “=AVERAGE(G$5:G)”]
]);
sheetV.getRange(1, 1, 1, 7).setFontWeight(“bold”).setNumberFormat(‘@[email protected]’);
sheetV.autoResizeColumn(1);
sheetV.autoResizeColumn(2);
sheetV.autoResizeColumn(3);
sheetV.autoResizeColumn(4);
sheetV.autoResizeColumn(5);
sheetV.autoResizeColumn(6);
sheetV.autoResizeColumn(7);

// Store the spreadsheet.
var file = DriveApp.getFileById(spreadsheet.getId());
folder.addFile(file);
var parentFolder = file.getParents().next();
parentFolder.removeFile(file);
return folder.getFilesByName(name).next();
}

/*
* Replaces the About sheet in the summary spreadsheet with a fresh one from the master sheet. This way, the sheet (including the FAQ) stays up to date.
* Also replaces the Dashboard with a fresh copy (resulting in an empty sheet with the correct conditional formatting).
* If there’s a new version, a sheet “New Version Available!” is added.
*/
function updateInfo(summarySpreadsheet, version){
var templateSpreadsheet = SpreadsheetApp.openByUrl(“//docs.google.com/spreadsheets/d/1qnTYdpBCgHP_5u5eQcXmc5gP0NrOrBK51JnTCTlc0_g/”);

var oldSheet = summarySpreadsheet.getSheetByName(“Dashboard”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);
templateSpreadsheet.getSheetByName(“Dashboard v” + version).copyTo(summarySpreadsheet).setName(“Dashboard”);

var oldSheet = summarySpreadsheet.getSheetByName(“About + FAQ”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);
templateSpreadsheet.getSheetByName(“About v” + version).copyTo(summarySpreadsheet).setName(“About + FAQ”);

var oldSheet = summarySpreadsheet.getSheetByName(“New Version Available!”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);

// Check if there is a newer version.
var versionHistory = templateSpreadsheet.getSheetByName(“Version History”).getDataRange().getValues();
if(versionHistory[0][0] != version){
// There’s a new version available (at least one).
// Look for the row which has the info about the current (old) version.
var oldVersionRow = 1;
while(oldVersionRow < versionHistory.length && versionHistory[oldVersionRow][0] != version){
oldVersionRow++;
}

// Copy the entire version history.
var newVersionSheet = templateSpreadsheet.getSheetByName(“Version History”).copyTo(summarySpreadsheet).setName(“New Version Available!”);
// Remove everything about the old version.
newVersionSheet.deleteRows(oldVersionRow + 1, versionHistory.length – oldVersionRow);
// Add new Rows at the beginning.
newVersionSheet.insertRows(1, 6);
newVersionSheet.getRange(1, 1, 6, 2).setValues([[“Latest version:”, versionHistory[0][0]], [“Your version:”, version], [“”, “”], [“Get the latest version at”, “//www.ppc-epiphany.com/qstracker/latest”], [“”, “”], [“Newer Versions”, “”]]);
newVersionSheet.getRange(1, 1, 1, 2).setFontWeight(“bold”);
newVersionSheet.getRange(6, 1, 1, 1).setFontWeight(“bold”);
newVersionSheet.autoResizeColumn(1);
newVersionSheet.autoResizeColumn(2);
}
}

/*
* Inserts a line or column chart into the dashboard sheet.
* The chart is based on data from the Percentages or Averages sheet.
*/
function addChartToDashboard(name, type, sheets, row, col, lastRow, lastCol, vCol, compareStepsBack){
var chartBuilder = sheets[‘charts’].newChart();
chartBuilder
.setOption(‘title’, name)
.setOption(‘width’, 800)
.setOption(‘height’, 349)
.setOption(‘colors’, [‘#fa9d1c’,’#00507d’])
.setPosition(row, col, 0, 0);

switch(type){
case “column”:
var statsRow = (vCol – 2) * 12 + 1;
// First range for a column chart is always the same column with QS from 1 to 10.
var dataRanges = [sheets[‘dataH’].getRange(1, 1, 11, 1)];
if(compareStepsBack && lastCol > 2){
// The column for comparison is either the specified number of columns behind lastCol, or 2 (the first column with data).
dataRanges.push(sheets[‘dataH’].getRange(statsRow, Math.max(2, lastCol – compareStepsBack), 11, 1));
}
dataRanges.push(sheets[‘dataH’].getRange(statsRow, lastCol, 11, 1));
chartBuilder = chartBuilder.asColumnChart();
break;
case “line”:
var dataRanges = [sheets[‘dataV’].getRange(5, 1, lastRow – 2, 1), sheets[‘dataV’].getRange(5, vCol, lastRow – 2, 1)];
chartBuilder = chartBuilder.asLineChart();
chartBuilder.setOption(“vAxis.maxValue”, 10);
chartBuilder.setOption(“vAxis.ticks”, [0,2,4,6,8,10]);
chartBuilder.setLegendPosition(Charts.Position.NONE);
break;
}

for(var i in dataRanges) chartBuilder.addRange(dataRanges[i]);
sheets[‘charts’].insertChart(chartBuilder.build());
}

/*
* Tracks the execution of the script as an event in Google Analytics.
* Sends the version number and a random UUID (basically just a random number, required by Analytics).
* Basically tells that somewhere someone ran the script with a certain version.
* Credit for the idea goes to Russel Savage, who posted his version at //www.freeadwordsscripts.com/2013/11/track-adwords-script-runs-with-google.html.
*/
function trackInAnalytics(version){
// Create the random UUID from 30 random hex numbers gets them into the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (with y being 8, 9, a, or b).
var uuid = “”;
for(var i = 0; i < 30; i++){
uuid += parseInt(Math.random()*16).toString(16);
}
uuid = uuid.substr(0, 8) + “-” + uuid.substr(8, 4) + “-4” + uuid.substr(12, 3) + “-” + parseInt(Math.random() * 4 + 8).toString(16) + uuid.substr(15, 3) + “-” + uuid.substr(18, 12);

var url = “//www.google-analytics.com/collect?v=1&t=event&tid=UA-74705456-1&cid=” + uuid + “&ds=adwordsscript&an=qstracker&av=”
+ version
+ “&ec=AdWords%20Scripts&ea=Script%20Execution&el=QS%20Tracker%20v” + version;
UrlFetchApp.fetch(url);
}

 

2. Contabilize a quantidade de execução de scripts com Google Analytics – Russell Savage. Vincule sua conta do Google Analytics para contabilizar a quantidade de vezes que seus scripts foram executados no relatório do Analytics. Cada vez que um script for executado ele será contabilizado nas visualizações de página.

/********************************
* Track Script Runs in Google Analytics
* Created By: Russ Savage
* FreeAdWordsScripts.com
********************************/
function beacon() {
var TAG_ID = ‘UA-XXXXXXXX-X’;
var CAMPAIGN_SOURCE = ‘adwords’;
var CAMPAIGN_MEDIUM = ‘scripts’;
var CAMPAIGN_NAME = ‘Your Script Name And Version’;
var HOSTNAME = ‘www.freeadwordsscripts.com’;
var PAGE = ‘/Some/Virtual/Page/Similar/To/Campaign/Name/Probably’;
var DOMAIN_LINK = ‘//’+HOSTNAME+PAGE;

//Pulled from: //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var uuid = ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g,
function(c) {var r = Math.random()*16|0,v=c==’x’?r:r&0x3|0x8;return v.toString(16);});

var url = ‘//www.google-analytics.com/collect?’;
var payload = {
‘v’:1,’tid’:TAG_ID,’cid’:uuid,
‘t’:’pageview’,’cs’:CAMPAIGN_SOURCE,’cm’:CAMPAIGN_MEDIUM,’cn’:CAMPAIGN_NAME,
‘dl’:DOMAIN_LINK
};
var qs = ”;
for(var key in payload) {
qs += key + ‘=’ + encodeURIComponent(payload[key]) + ‘&’;
}
url += qs.substring(0,qs.length-1);
UrlFetchApp.fetch(url);
}

 

3. Dashboard de MCC – Por Frederick Vallaeys. Acompanhe o desempenho de todas as suas contas do MCC comparando os gastos do dia de ontem, anteontem e do mesmo dia da semana passada.

function main() {
var newSpreadSheet = SpreadsheetApp.openByUrl(“put a link to a Google Sheet
here – this is where the results will be placed”);
var numOfSheets = newSpreadSheet.getSheets();
if(numOfSheets.length>0){
for(var i=1,len=numOfSheets.length;i<len;i++)
newSpreadSheet.deleteSheet(numOfSheets[i]);
}
newSpreadSheet.getActiveSheet().clear().setName(“Last 1 Day”);

var allHeaders = [“Conversions”,”Impressions”,”Clicks”,”Cost”,”ConversionValue”];
var headerIndexes = {};
var headerString = “”;
var daysForMonth = 28;
var sheet = newSpreadSheet.getActiveSheet();

var tempIndex = 3;

for(var i=0,len=allHeaders.length;i<len;i++){
sheet.activate();
sheet.getRange(1,tempIndex).setValue(allHeaders[i]);

headerIndexes[allHeaders[i]]=tempIndex;
headerString=headerString+allHeaders[i]+”, “;
tempIndex = tempIndex+7;
}

sheet.getRange(“1:1”).setFontWeight(“bold”);
sheet.appendRow([“Account Name”,”Account Id”,””,””,”difference”,”% diff”]);

newSpreadSheet.duplicateActiveSheet().setName(“Last 7 Days”);
newSpreadSheet.duplicateActiveSheet().setName(“Last 30 Days”);

var allSheets = newSpreadSheet.getSheets();

allSheets[1].setName(“Last 7 Days”);
allSheets[2].setName(“Last 30 Days”);

headerString = headerString.substring(0,headerString.length-2);

Logger.log(headerIndexes);
var datesForFirst=[];var dateForSecond=[];var dateForThird = [];
var currentDate = new Date();var prevDate = new Date();var anotherPrevDate = new Date();
var sheetIndex =0;

//insert dates in the next rows
//for 1st sheet
var fixedDate = new Date(Utilities.formatDate(new Date(),AdWordsApp.currentAccount()
.getTimeZone(), “MMM dd,yyyy HH:mm:ss”));

var time = fixedDate.getTime() -(1 * 24 * 60 * 60 * 1000);
fixedDate = new Date(time);

time = fixedDate.getTime() -(1 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = fixedDate.getTime() -(7 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
datesForFirst.push(fixedDate);datesForFirst.push(prevDate);datesForFirst.push(anotherPrevDate);

appendDates(sheetIndex, datesForFirst, 0);

//for second sheet
sheetIndex = 1;
time = fixedDate.getTime() -(7 * 24 * 60 * 60 * 1000);
currentDate = new Date(time);
time = fixedDate.getTime() -(14 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = currentDate.getTime() -(30 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
dateForSecond.push(currentDate);dateForSecond.push(prevDate);dateForSecond.push(anotherPrevDate);

appendDates(sheetIndex, dateForSecond,7);

//for third sheet
sheetIndex = 2;
time = fixedDate.getTime() -(daysForMonth * 24 * 60 * 60 * 1000);
currentDate = new Date(time);
time = fixedDate.getTime() -(daysForMonth*2 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = currentDate.getTime() -(daysForMonth*12 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
dateForThird.push(currentDate);dateForThird.push(prevDate);dateForThird.push(anotherPrevDate);

appendDates(sheetIndex, dateForThird,daysForMonth);
//dates inserted

//get accounts and data respectively
var accounts_iterator = MccApp.accounts().withCondition(“Impressions>0”).forDateRange(“YESTERDAY”).get();
var current_mccaccount = AdWordsApp.currentAccount();
var all_accounts=[];
while(accounts_iterator.hasNext()){
all_accounts.push(accounts_iterator.next());
}

Logger.log(“no of accounts”+all_accounts.length);
for(var i=0,len=all_accounts.length;i<len;i++){
MccApp.select(all_accounts[i]);
appendData(0,datesForFirst,0);
appendData(1,dateForSecond,7);
appendData(2,dateForThird,daysForMonth);

for(var j=0;j<3;j++){
var sheetCurrent = allSheets[j];

sheetCurrent.activate();
var lRow = sheetCurrent.getLastRow();
for(var key in headerIndexes){
var index = headerIndexes[key];
var positiveColor = “green”;
var negativeColor = “red”;
if(key==”Cost”){
positiveColor=”red”;
negativeColor=”green”;
}

var firstVal = sheetCurrent.getRange(lRow,index).getValue();
var secondVal = sheetCurrent.getRange(lRow,index+1).getValue();

var diff = firstVal-secondVal;
sheetCurrent.getRange(lRow, index+2).setValue(diff);
var pcent = (diff/secondVal)*100;
if(secondVal==0)
pcent=firstVal*100;
if(diff==0)
pcent=0;

sheetCurrent.getRange(lRow, index+3).setValue(pcent+”%”);
if(pcent>0){
sheetCurrent.getRange(lRow, index+3).setFontColor(positiveColor);
}
else{
sheetCurrent.getRange(lRow, index+3).setFontColor(negativeColor);
}
secondVal = sheetCurrent.getRange(lRow,index+4).getValue();
var diff = firstVal-secondVal;
sheetCurrent.getRange(lRow, index+5).setValue(diff);
var pcent = (diff/secondVal)*100;
if(diff==0)
pcent=0;
if(secondVal==0)
pcent=firstVal*100;
sheetCurrent.getRange(lRow, index+6).setValue(pcent+”%”);
if(pcent>0){
sheetCurrent.getRange(lRow, index+6).setFontColor(positiveColor);
}
else{
sheetCurrent.getRange(lRow, index+6).setFontColor(negativeColor);
}
}
}
}

MailApp.sendEmail(“[email protected]”,”Mcc accounts performance”,
“Click this url -\n\n”+newSpreadSheet.getUrl());

function appendData(indexForSheet,dateArray,days){

currentSheet = allSheets[indexForSheet];
currentSheet.activate();
currentRow = currentSheet.getLastRow()+1;
var date_range = “”;
var fieldGap = [0,1,4];
for(var i=0,len=dateArray.length;i<len;i++){
var toDate = dateArray[i];
if(indexForSheet!=0){
tempDate = dateArray[i].getTime()+(days * 24 * 60 * 60 * 1000);
toDate = new Date(tempDate);

}
date_range = “”+Utilities.formatDate(dateArray[i], “PST”, “yyyyMMdd”)+”,
“+Utilities.formatDate(toDate, “PST”, “yyyyMMdd”);

var report = AdWordsApp.report(“SELECT “+headerString+
” FROM ACCOUNT_PERFORMANCE_REPORT “+
“DURING “+date_range);
var rows = report.rows();
while(rows.hasNext()){
var row = rows.next();

var currentIndex = 0;
currentSheet.getRange(currentRow, 1).setValue(AdWordsApp.currentAccount().getName());
currentSheet.getRange(currentRow, 2).setValue(AdWordsApp.currentAccount().getCustomerId());
for(var key in headerIndexes){
var index = headerIndexes[key];
currentSheet.getRange(currentRow, index+fieldGap[i]).setValue(row[key]);
}
}
}

}

function appendDates(sheetIndex, dateArray, days){
currentSheet = allSheets[sheetIndex];
currentSheet.activate();
currentRow = currentSheet.getLastRow()+1;

var date_range = [];

for(var i=0,len=dateArray.length;i<len;i++){
var toDate = dateArray[i];
if(sheetIndex!=0){
tempDate = dateArray[i].getTime()+(days * 24 * 60 * 60 * 1000);
toDate = new Date(tempDate);
date_range.push(“”+Utilities.formatDate(dateArray[i], “PST”, “MM/dd/yyyy”)+” –
“+Utilities.formatDate(toDate, “PST”, “MM/dd/yyyy”));
}
else{
date_range.push(Utilities.formatDate(dateArray[i], “PST”, “MM/dd/yyyy”));
}
}
for(var key in headerIndexes){
var index = headerIndexes[key];
currentSheet.getRange(currentRow, index).setValue(date_range[0]);
currentSheet.getRange(currentRow, parseInt(index)+1).setValue(date_range[1]);
currentSheet.getRange(currentRow, parseInt(index)+4).setValue(date_range[2]);
}
}

}

 

4. Acompanhe campanhas por CPA – por Sean Dolan. Acompanhe os CPAs de suas palavras-chave e faça alterações para diminuir seus custos.

function main() {

var cpaHigh = 0;
var cpaLow = 0;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/cpa-tagger.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}

var code = getCode(codeURL);

eval(code);
}

 

 

 

Automação Google Ads: Termos adicionados e Rótulos

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com termos adicionados e Rótulos”.

 

1. Rótulo de contagem regressiva para termos adicionados – Por Russel Savage. Este é um excelente script para manter o controle de termos que foram recentemente adicionados, e portanto, não apresentam um histórico de estatísticas consolidados, assim, você evitar de pausá-los ou fazer alterações antes do tempo.

Sempre que você adicionar novos elementos à sua conta, poderá aplicar um rótulo a ele usando o formato LABEL_PREFIX_. Portanto, se você quiser que seus scripts ignorem um novo elemento por 30 dias, aplique o rótulo “days_left_30” nesse elemento. Se você programar o script para ser executado todos os dias, o número de dias restantes no marcador será reduzido em um por dia. Quando o número de dias chegar a zero, o rótulo será removido da entidade.

Nos scripts que você deseja ignorar novos elementos, inclua a seguinte função na parte inferior do script (antes da última chave):

function _build_label_list() {
//Build a list of labels to exclude in your .withCondition()
var LABEL_PREFIX = ‘days_left_’;
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var label_array = [];
while(label_iter.hasNext()) { label_array.push(label_iter.next().getName()); }
return “‘”+label_array.join(“‘,'”)+”‘”
}

 

E, em seguida, adicione o seguinte
.withCondition (“LabelNames CONTAINS_NONE [” + _build_label_list () + “]”)
para qualquer iterador que você tenha em seus outros scripts. Boa sorte, e se você tiver alguma dúvida, não hesite em perguntar.

//———————————–
// Label Countdown
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var LABEL_PREFIX = “days_left_”; // you can change this if you want

// First lets build a list of labels to work with
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var labels_array = [];
while(label_iter.hasNext()) {
labels_array.push(label_iter.next().getName());
}
if(labels_array.length > 0) {
var labels_str = “[‘” + labels_array.join(“‘,'”) + “‘]”;
// grab all the keywords with the labels we want to countdown
var kw_iter = AdWordsApp.keywords().withCondition(“LabelNames CONTAINS_ANY “+labels_str).get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var l_iter = kw.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var label = l_iter.next(); // lazy here because we know this keyword has a label
var days_left = parseInt(label.getName().substr(LABEL_PREFIX.length)) – 1;
kw.removeLabel(label.getName());
if(days_left != 0) {
var new_label_name = LABEL_PREFIX+days_left;
// Create a new label if it doesn’t exist
if(labels_array.indexOf(new_label_name) == -1) {
AdWordsApp.createLabel(new_label_name);
labels_array.push(new_label_name);
}
kw.applyLabel(new_label_name);
}
}
}
}

 

2. Junte Rótulos de Múltiplas Campanhas – Russel Savage. O script auxilia outro script de agrupar campanhas do autor, ele mantém todas os rótulos das palavras-chave das campanhas agrupadas.]

//———————————–
// Merge Labels from Multiple Campaigns
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
var DESTINATION_CAMPAIGN_NAME = “Destination Campaign Name”;
var ORIGIN_CAMPAIGN_NAMES = [“Origin Campaign Name 1″,”Origin Campaign Name 2”];

function main() {
var label_iter = AdWordsApp.labels().get();
while(label_iter.hasNext()) {
var label = label_iter.next();
//Pre-build all the iterators
var iters = [
label.campaigns().withCondition(“Name IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.adGroups().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.ads().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.keywords().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get()
];
for(var i in iters) {
var iter = iters[i];
while(iter.hasNext()) {
_copyLabels(iter.next());
}
}
}
}

//Copies the labels from entity in Origin campaign
//to entity with the same name in dest campaign
function _copyLabels(entity) {
var iter;
if(_getEntityType(entity) == “Campaign”) {
// it’s a campaign
iter = AdWordsApp.campaigns()
.withCondition(“Name = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();
} else if(_getEntityType(entity) == “AdGroup”) {
// it’s an adgroup
iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“Name = ‘”+entity.getName()+”‘”)
.get();
} else if(_getEntityType(entity) == “Ad”) {
// it’s an ad
iter = AdWordsApp.ads()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Headline = ‘”+entity.getHeadline()+”‘”)
.withCondition(“Description1 = ‘”+entity.getDescription1()+”‘”)
.withCondition(“Description2 = ‘”+entity.getDescription2()+”‘”)
.withCondition(“DisplayUrl = ‘”+entity.getDisplayUrl()+”‘”)
.get();
} else if(_getEntityType(entity) == “Keyword”) {
// it’s a keyword
iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Text = ‘”+entity.getText()+”‘”)
.withCondition(“KeywordMatchType = ‘”+entity.getMatchType()+”‘”)
.get();
}

while(iter.hasNext()) {
_copyLabelsHelper(entity,iter.next());
}

}

//Copy the labels form orig entity to dest entity
function _copyLabelsHelper(orig,dest) {
var label_iter = orig.labels().get();
while(label_iter.hasNext()) {
dest.applyLabel(label_iter.next().getName());
}
}

//Returns a text representation of an entity
//For a better way, check: //goo.gl/kZL3X
function _getEntityType(obj) {
if(typeof(obj[‘getCampaign’]) == “undefined”) {
return ‘Campaign’;
}
if(typeof(obj[‘getAdGroup’]) == “undefined”) {
return ‘AdGroup’;
}
if(typeof(obj[‘getHeadline’]) != “undefined”) {
return “Ad”;
}
if(typeof(obj[‘getText’]) != “undefined”) {
return “Keyword”;
}
return null;
}//———————————–
// Merge Labels from Multiple Campaigns
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
var DESTINATION_CAMPAIGN_NAME = “Destination Campaign Name”;
var ORIGIN_CAMPAIGN_NAMES = [“Origin Campaign Name 1″,”Origin Campaign Name 2”];

function main() {
var label_iter = AdWordsApp.labels().get();
while(label_iter.hasNext()) {
var label = label_iter.next();
//Pre-build all the iterators
var iters = [
label.campaigns().withCondition(“Name IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.adGroups().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.ads().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.keywords().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get()
];
for(var i in iters) {
var iter = iters[i];
while(iter.hasNext()) {
_copyLabels(iter.next());
}
}
}
}

//Copies the labels from entity in Origin campaign
//to entity with the same name in dest campaign
function _copyLabels(entity) {
var iter;
if(_getEntityType(entity) == “Campaign”) {
// it’s a campaign
iter = AdWordsApp.campaigns()
.withCondition(“Name = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();
} else if(_getEntityType(entity) == “AdGroup”) {
// it’s an adgroup
iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“Name = ‘”+entity.getName()+”‘”)
.get();
} else if(_getEntityType(entity) == “Ad”) {
// it’s an ad
iter = AdWordsApp.ads()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Headline = ‘”+entity.getHeadline()+”‘”)
.withCondition(“Description1 = ‘”+entity.getDescription1()+”‘”)
.withCondition(“Description2 = ‘”+entity.getDescription2()+”‘”)
.withCondition(“DisplayUrl = ‘”+entity.getDisplayUrl()+”‘”)
.get();
} else if(_getEntityType(entity) == “Keyword”) {
// it’s a keyword
iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Text = ‘”+entity.getText()+”‘”)
.withCondition(“KeywordMatchType = ‘”+entity.getMatchType()+”‘”)
.get();
}

while(iter.hasNext()) {
_copyLabelsHelper(entity,iter.next());
}

}

//Copy the labels form orig entity to dest entity
function _copyLabelsHelper(orig,dest) {
var label_iter = orig.labels().get();
while(label_iter.hasNext()) {
dest.applyLabel(label_iter.next().getName());
}
}

//Returns a text representation of an entity
//For a better way, check: //goo.gl/kZL3X
function _getEntityType(obj) {
if(typeof(obj[‘getCampaign’]) == “undefined”) {
return ‘Campaign’;
}
if(typeof(obj[‘getAdGroup’]) == “undefined”) {
return ‘AdGroup’;
}
if(typeof(obj[‘getHeadline’]) != “undefined”) {
return “Ad”;
}
if(typeof(obj[‘getText’]) != “undefined”) {
return “Keyword”;
}
return null;
}

 

3. Rótulos com as datas de criação – Por Russel Savage. Rotule com a data de criação seus anúncios, grupo de anúncios, campanhas e palavras-chave com esse script. Assim, é possível ter um controle histórico de suas alterações.

Essas informações simplesmente não são rastreadas no Google AdWords. A melhor coisa é descobrir quando seu anúncio começou a receber impressões e presumir que foi quando foi criado (se um anúncio for criado, mas ninguém o vir, ele realmente existe?).

Por isso, para ajudar-me a acompanhar a criação dos meus anúncios , juntei o seguinte script para aplicar rótulos em meus anúncios com a data da primeira impressão. Dessa forma, posso descobrir quais anúncios eu criei e garantir que não tomo providências em relação a algo que seja muito novo. Eu também posso fazer alterações em todos os anúncios construídos em um determinado dia com relativa facilidade na interface do usuário do Google Ads, basta selecionar o rótulo correto.

 

/**************************************
* Track Entity Creation Date
* Version 1.4
* Changelog v1.4
* – Removed apiVersion from reporting call
* Changelog v1.3
* – Updated script to handle all entities
* Changelog v1.2
* – Fixed an issue with comparing dates
* ChangeLog v1.1
* – Updated logic to work with larger accounts
* Created By: Russ Savage
* //www.FreeAdWordsScripts.com
**************************************/
//All my labels will start with this. For example: Created:2013-05-01
var LABEL_PREFIX = ‘Created:’;
var DAYS_IN_REPORT = 30;
var ENTITY = ‘ad’; //or adgroup or keyword or campaign

function main() {
//First we get the impression history of our entity
var ret_map = getImpressionHistory();
//Then we apply our labels
applyLabels(ret_map);
}

//Function to apply labels to the ads in an account
function applyLabels(ret_map) {
var iter;
if(ENTITY === ‘campaign’) { iter = AdWordsApp.campaigns().get(); }
if(ENTITY === ‘adgroup’) { iter = AdWordsApp.adGroups().get(); }
if(ENTITY === ‘ad’) { iter = AdWordsApp.ads().get(); }
if(ENTITY === ‘keyword’) { iter = AdWordsApp.keywords().get(); }

while(iter.hasNext()) {
var entity = iter.next();
var id = entity.getId();
if(ret_map[id]) {
var label_name = LABEL_PREFIX+Utilities.formatDate(ret_map[id], AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd”);
createLabelIfNeeded(label_name);
entity.applyLabel(label_name);
}
}
}

//This is a helper function to create the label if it does not already exist
function createLabelIfNeeded(name) {
if(!AdWordsApp.labels().withCondition(“Name = ‘”+name+”‘”).get().hasNext()) {
AdWordsApp.createLabel(name);
}
}

//A helper function to find the date days ago
function getDateDaysAgo(days) {
var the_past = new Date();
the_past.setDate(the_past.getDate() – days);
return Utilities.formatDate(the_past,AdWordsApp.currentAccount().getTimeZone(),”yyyyMMdd”);
}

//A helper function to compare dates.
//Copied from: //goo.gl/uW48a
function diffDays(firstDate,secondDate) {
var oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
return Math.round(Math.abs((firstDate.getTime() – secondDate.getTime())/(oneDay)));
}

function getImpressionHistory() {
var API_VERSION = { includeZeroImpressions : false };
var first_date = new Date(’10/23/2000’);
var max_days_ago = diffDays(first_date,new Date());
var cols = [‘Date’,’Id’,’Impressions’];
var report = {
‘campaign’ : ‘CAMPAIGN_PERFORMANCE_REPORT’,
‘adgroup’ : ‘ADGROUP_PERFORMANCE_REPORT’,
‘ad’ : ‘AD_PERFORMANCE_REPORT’,
‘keyword’ : ‘KEYWORDS_PERFORMANCE_REPORT’}[ENTITY];
var ret_map = {};
var prev_days_ago = 0;
for(var i = DAYS_IN_REPORT; i < max_days_ago; i+=DAYS_IN_REPORT) {
var start_date = getDateDaysAgo(i);
var end_date = getDateDaysAgo(prev_days_ago);
var date_range = start_date+’,’+end_date;
Logger.log(‘Getting data for ‘ + date_range);
var query = [‘select’,cols.join(‘,’),’from’,report,’during’,date_range].join(‘ ‘);
var report_iter = AdWordsApp.report(query, API_VERSION).rows();
if(!report_iter.hasNext()) { Logger.log(‘No more impressions found. Breaking.’); break; } // no more entries
while(report_iter.hasNext()) {
var row = report_iter.next();
if(ret_map[row[‘Id’]]) {
var [year,month,day] = (row[‘Date’]).split(‘-‘);
var from_row = new Date(year, parseFloat(month)-1, day);
var from_map = ret_map[row[‘Id’]];

if(from_row < from_map) {
ret_map[row[‘Id’]] = from_row;
}
} else {
var [year,month,day] = (row[‘Date’]).split(‘-‘);
ret_map[row[‘Id’]] = new Date(year, parseFloat(month)-1, day);
}
}
prev_days_ago = i;
}
return ret_map;
}

4. Dashboard da conta por Rótulos – Por Frederick Vallaeys. Agregue os resultados em uma planilha do Google que possuem um rótulo no Google Ads. Crie um dashboard apenas com as palavras-chave que possuem o rótulo de “melhor performance”, por exemplo.

var SPREADSHEET = “new”; // Put the URL of the Google Sheet here – make sure your AdWords username has edit access to this sheet
var LABELS = new Array(“Labels1”, “Labels2”);
var LEVEL = “campaigns”; // Allowed values: campaigns, keywords, ad groups
var METRICS = new Array({name:”Conversions”, type:”long”, ratio:0, modifierForNewMetric:1, modifierForOldMetric:1}, {name:”ConversionValue”, type:”long”, ratio:0}, {name:”Clicks”, type:”int”, ratio:0}, {name:”Cost”, type:”long”, ratio:0}, {name:”Impressions”, type:”long”, ratio:0});
var CALCULATEDMETRICS = new Array({name:”ROAS”, type:”ratio”, formula:”ConversionValue/Cost”}, {name:”100% ROAS”, type:”static”, formula:”1″}, {name:”Ctr”, type:”ratio”, formula:”Clicks/Impressions”}, {name:”Conversion Rate”, type:”ratio”, formula:”Conversions/Clicks”}, {name:”Conversions Per Imp”, type:”ratio”, formula:”Conversions/Impressions”});
var CHARTMETRICS = new Array({name:”Conversions”, includeComparison:1}, {name:”ROAS”, includeComparison:1, secondMetric:”100% ROAS”}, {name:”Clicks”, includeComparison:1}, {name:”Impressions”, includeComparison:1}, {name:”Ctr”, includeComparison:1}, {name:”Conversion Rate”, includeComparison:1}, {name:”Conversions Per Imp”, includeComparison:1});
var AGGREGATESTATSBYLABEL = 1;
var STARTDAYSAGO = 365;
var TIMESEGMENT = “Week”; // Allowed Values: Week, Date, Month, Quarter
var THOUSANDSSEPARATOR = “,”;
var INCLUDETOTALS = 1;
var CHARTWIDTH = 400;
var CHARTHEIGHT = 200;

var COMPARETODAYSAGO = STARTDAYSAGO;

function main() {

// Set up Dates
// ————
var oldStartDate = getDateInThePast(STARTDAYSAGO + COMPARETODAYSAGO);
var oldEndDate = getDateInThePast(COMPARETODAYSAGO + 1);
var newStartDate = getDateInThePast(STARTDAYSAGO);
var newEndDate = getDateInThePast(1);
//Logger.log(“oldStartDate: ” + oldStartDate);
//Logger.log(“oldEndDate: ” + oldEndDate);
//Logger.log(“newStartDate: ” + newStartDate);
//Logger.log(“newEndDate: ” + newEndDate);

curr = new Date();
var diffLastMonday = – curr.getDay() + 1;
var quarterMonth = (Math.floor(curr.getMonth()/3)*3)+1;

if(TIMESEGMENT == “Week”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 7);
//Logger.log(“weeksAgo: ” + periodsAgo);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (diffLastMonday * 3600 * 24 * 1000) + (3600 * 24 * 1000 * 7 * i));
//Logger.log(“mondayDate ” + i + “: ” + mondayDate.yyyymmdd());
reportDates.push(periodStartDate.yyyymmdd());
}
} else if(TIMESEGMENT == “Date”) {
var reportDates = new Array();
var periodsAgo = 1 – (STARTDAYSAGO + COMPARETODAYSAGO);
for(var i = periodsAgo; i < 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
reportDates.push(periodStartDate.yyyymmdd());
}
} else if(TIMESEGMENT == “Month”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 30.41);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * 30.41 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
var yyyy = periodStartDate.getFullYear().toString();
var mm = (periodStartDate.getMonth()+1).toString(); // getMonth() is zero-based
var startDate = yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + “01”; // padding
reportDates.push(startDate);
}
} else if(TIMESEGMENT == “Quarter”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 91.25);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * 91.25 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
var yyyy = periodStartDate.getFullYear().toString();
var mm = (Math.floor(periodStartDate.getMonth()/3)*3)+1; // getMonth() is zero-based
mm = mm.toString();
var startDate = yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + “01”; // padding
reportDates.push(startDate);
}
}

for(var l = 0; l < reportDates.length; l++){
var dateOut = reportDates[l];
//Logger.log(dateOut);
}

// SET UP SPREADSHEET
// ——————-
if(SPREADSHEET.toLowerCase().indexOf(“new”) != -1)
{
var spreadsheet = SpreadsheetApp.create(“Dashboard: ” + LEVEL + ” ” + TIMESEGMENT);
var spreadsheetUrl = spreadsheet.getUrl();
} else {
var spreadsheetUrl = SPREADSHEET;
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
//spreadsheet.addEditor(“[email protected]”);
//Logger.log(“acct mgr: ” + accountManagers);
//var accountManagersArray = accountManagers.trim().split(“,”);
//spreadsheet.addEditors(accountManagersArray);

var chartSheet = spreadsheet.getSheetByName(“Charts”);
if(chartSheet) {
spreadsheet.deleteSheet(chartSheet)
chartSheet = spreadsheet.insertSheet(“Charts”);
//chartSheet.clear();
} else {
var chartSheet = spreadsheet.insertSheet(“Charts”);
}
chartSheet.insertColumnsAfter(7, 7*CHARTMETRICS.length);

var totalsSheet = spreadsheet.getSheetByName(“Totals”);
if(totalsSheet) {
spreadsheet.deleteSheet(totalsSheet);
var totalsSheet = spreadsheet.insertSheet(“Totals”);
//totalsSheet.clear();
} else {
var totalsSheet = spreadsheet.insertSheet(“Totals”);
}
totalsSheet.insertColumnsAfter(7, 7*CHARTMETRICS.length);

Logger.log(“Your Dashboard is located at ” + spreadsheetUrl);

var aggregateTotals = new Array();

// Process each label
for(var labelCounter = 0; labelCounter < LABELS.length; labelCounter++) {
var label = LABELS[labelCounter];
Logger.log(“Label: ” + label);

var metricArray = new Array();
var metricArrayForQuery = new Array();

var headerRow = new Array(“New Date”, “Compared To Date”);

for(var metricCounter = 0; metricCounter<METRICS.length; metricCounter++) {
metricArray.push(METRICS[metricCounter].name);
metricArrayForQuery.push(METRICS[metricCounter].name);
var newModifier = METRICS[metricCounter].modifierForNewMetric || 1;
var oldModifier = METRICS[metricCounter].modifierForOldMetric || 1;
var newMetricText = “Current ” + METRICS[metricCounter].name + ” (x” + newModifier + “)”;
var oldMetricText = “Previous ” + METRICS[metricCounter].name + ” (x” + oldModifier + “)”;
headerRow.push(newMetricText, oldMetricText);
}

for(var metricCounter = 0; metricCounter<CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
metricArray.push(name);
var formula = CALCULATEDMETRICS[metricCounter].formula;
var newMetricText = “Current ” + name + ” (” + formula + “)”;
var oldMetricText = “Previous ” + name + ” (” + formula + “)”;
headerRow.push(newMetricText, oldMetricText);
}

if(labelCounter == 0) {
totalsSheet.appendRow(headerRow);
}

var results = getIds(label, LEVEL);
var ids = results.ids;
var idNames = results.names;
if(AGGREGATESTATSBYLABEL == 1) {
var chartTitle = label;
var newTotals = getStatsForIds(ids, metricArrayForQuery, newStartDate, newEndDate, label, “new”);
var oldTotals = getStatsForIds(ids, metricArrayForQuery, oldStartDate, oldEndDate, label, “old”);

var sheet = spreadsheet.getSheetByName(label);
if(sheet) {
sheet.clear();
} else {
var sheet = spreadsheet.insertSheet(label);
}
sheet.appendRow(headerRow);

// Loop through all the date ranges
var halfWay = parseInt(reportDates.length / 2);
for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
//var newMetrics = newTotals[newKey];
//var oldMetrics = oldTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!newTotals[newKey]) {
var newStat = 0;
} else {
var newStat = newTotals[newKey][metric];
}
if(!oldTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = oldTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

// Keep total stats by time segment
if(!aggregateTotals[newKey]) {
aggregateTotals[newKey] = new Object();
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
if(!aggregateTotals[newKey][metric]) {
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
aggregateTotals[newKey][metric] += newStat;
}
}

if(!aggregateTotals[oldKey]) {
aggregateTotals[oldKey] = new Object();
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
if(!aggregateTotals[oldKey][metric]) {
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
aggregateTotals[oldKey][metric] += oldStat;
}
}
//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
if(newTotals[newKey]) {
var newTop = newTotals[newKey][formulaParts[0]];
var newBottom = newTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
} else {
var newStat = 0;
}

if(oldTotals[oldKey]) {
var oldTop = oldTotals[oldKey][formulaParts[0]];
var oldBottom = oldTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
} else {
var oldStat = 0;
}
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

sheet.appendRow(thisLine);

}

// Create the charts
insertChart(sheet, chartSheet, labelCounter, chartTitle, metricArray);
var numChartsCreated = labelCounter;

// FOR NOT AGGREGATED STATS
} else {
// Go one ID at a time
for(var idCounter = 0; idCounter < ids.length; idCounter++) {
var id = “” + ids[idCounter];
var name = idNames[idCounter];
var chartTitle = name;
//Logger.log(“id: ” + id);
var thisIdArray = new Array(id);
var newTotals = getStatsForIds(thisIdArray, metricArrayForQuery, newStartDate, newEndDate, label, “new”);
var oldTotals = getStatsForIds(thisIdArray, metricArrayForQuery, oldStartDate, oldEndDate, label, “old”);

var sheet = spreadsheet.getSheetByName(name);
if(sheet) {
sheet.clear();
} else {
var sheet = spreadsheet.insertSheet(name);
}
sheet.appendRow(headerRow);

// Loop through all the date ranges
var halfWay = parseInt(reportDates.length / 2);
for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
//var newMetrics = newTotals[newKey];
//var oldMetrics = oldTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!newTotals[newKey]) {
var newStat = 0;
} else {
var newStat = newTotals[newKey][metric];
}
if(!oldTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = oldTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

// Keep total stats by time segment
if(!aggregateTotals[newKey]) {
aggregateTotals[newKey] = new Object();
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
if(!aggregateTotals[newKey][metric]) {
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
aggregateTotals[newKey][metric] += newStat;
}
}

if(!aggregateTotals[oldKey]) {
aggregateTotals[oldKey] = new Object();
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
if(!aggregateTotals[oldKey][metric]) {
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
aggregateTotals[oldKey][metric] += oldStat;
}
}
//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
if(newTotals[newKey]) {
var newTop = newTotals[newKey][formulaParts[0]];
var newBottom = newTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
} else {
var newStat = 0;
}

if(oldTotals[oldKey]) {
var oldTop = oldTotals[oldKey][formulaParts[0]];
var oldBottom = oldTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
} else {
var oldStat = 0;
}
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

sheet.appendRow(thisLine);

}

// Create the charts

insertChart(sheet, chartSheet, idCounter, chartTitle, metricArray);
}
var numChartsCreated = idCounter;
}

} // END for(var labelCounter = 0; labelCounter < LABELS.length; labelCounter++)

if(INCLUDETOTALS == 1) {
// Output totals to another sheet
processTotals(aggregateTotals, totalsSheet, halfWay, reportDates);
// Create the chart
chartTitle = “Totals”;
var offsetCounter = numChartsCreated+1;
insertChart(totalsSheet, chartSheet, offsetCounter, chartTitle, metricArray);
}
}

// FUNCTION processTotals
function processTotals(aggregateTotals, totalsSheet, halfWay, reportDates) {

for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
var newMetrics = aggregateTotals[newKey];
var oldMetrics = aggregateTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!aggregateTotals[newKey]) {
var newStat = 0;
} else {
var newStat = aggregateTotals[newKey][metric];
}
if(!aggregateTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = aggregateTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
var newTop = aggregateTotals[newKey][formulaParts[0]];
var newBottom = aggregateTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
var oldTop = aggregateTotals[oldKey][formulaParts[0]];
var oldBottom = aggregateTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

totalsSheet.appendRow(thisLine);

}
}

// FUNCTION insertChart
function insertChart(sheet, chartSheet, offsetCounter, chartTitle, metricArray) {
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
//var numCols = rows.getNumColumns();

for(var chartCounter = 0; chartCounter < CHARTMETRICS.length; chartCounter++) {
var metric = CHARTMETRICS[chartCounter].name;
var secondMetric = CHARTMETRICS[chartCounter].secondMetric;
var width = CHARTWIDTH;
var height = CHARTHEIGHT;

//Logger.log(“chart for ” + chartTitle + ” ” + metric);
var metricIndex = metricArray.indexOf(metric);
var metricColumn1 = metricIndex*2+3;
var metricColumn2 = metricIndex*2+4;

var secondMetricIndex = metricArray.indexOf(secondMetric);
var secondMetricColumn1 = secondMetricIndex*2+3;
var secondMetricColumn2 = secondMetricIndex*2+4;

var chartStartRow = 1+offsetCounter*22;
var chartStartCol = 1+chartCounter*7;

var xOffset = width*chartCounter;
var yOffset = offsetCounter*height;
var numCols = 1+CHARTMETRICS[chartCounter].includeComparison;
if(!secondMetric) {
var chart = chartSheet.newChart()
.setPosition(1, 1, xOffset, yOffset)
.setOption(“width”, width)
.setOption(“height”, height)
.asLineChart()
.setTitle(chartTitle)
.setYAxisTitle(metric)
.addRange(sheet.getRange(1,1,numRows,1))
.addRange(sheet.getRange(1, metricColumn1, numRows, numCols))
.build();
} else {
var chart = chartSheet.newChart()
.setPosition(1, 1, xOffset, yOffset)
.setOption(“width”, width)
.setOption(“height”, height)
.asLineChart()
.setTitle(chartTitle)
.setYAxisTitle(metric)
.addRange(sheet.getRange(1,1,numRows,1))
.addRange(sheet.getRange(1, metricColumn1, numRows, numCols))
.addRange(sheet.getRange(1, secondMetricColumn1, numRows, numCols))
.build();
}
chartSheet.insertChart(chart);
var chartSheetRows = chartSheet.getDataRange();
var chartSheetNumRows = chartSheetRows.getNumRows();
var chartSheetNumColumns = chartSheetRows.getNumColumns();
}
chartSheet.insertRowsAfter(chartSheetNumRows, 22);
}

// FUNCTION getIds — Fetches the IDs of the items with the specified label
function getIds(label, LEVEL) {

var ids = new Array();
var results = new Object();
results.ids = new Array();
results.names = new Array();

if(LEVEL.toLowerCase().indexOf(“campaigns”) != -1) {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
var id = campaign.getId();
results.ids.push(id);
results.names.push(campaignName);
//Logger.log(campaignName + ” id: ” + id);
}
} else if(LEVEL.toLowerCase().indexOf(“ad groups”) != -1) {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var adGroupName = adGroup.getName();
var id = adGroup.getId();
results.ids.push(id);
results.names.push(adGroupName);
//Logger.log(adGroupName + ” id: ” + id);
}
} else if(LEVEL.toLowerCase().indexOf(“keywords”) != -1) {
var keywordIterator = AdWordsApp.keywords()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var keywordText = keyword.getText();
var id = keyword.getId();
results.ids.push(id);
results.names.push(keywordText);
//Logger.log(keywordText + ” id: ” + id);
}
}

return results;

}

// FUNCTION getStatsForIds — gets the stats for the items
function getStatsForIds(ids, metricArrayForQuery, startDate, endDate, label, oldOrNew) {

var totals = new Array();
var keys = new Array();

var idString = ids.join();

if(LEVEL.toLowerCase().indexOf(“campaigns”) != -1) {
var reportType = “CAMPAIGN_PERFORMANCE_REPORT”;
var idType = “CampaignId”;
} else if(LEVEL.toLowerCase().indexOf(“ad groups”) != -1) {
var reportType = “ADGROUP_PERFORMANCE_REPORT”;
var idType = “AdGroupId”;
} else if(LEVEL.toLowerCase().indexOf(“keywords”) != -1) {
var reportType = “KEYWORDS_PERFORMANCE_REPORT”;
var idType = “Id”;
}

var report1 = AdWordsApp.report(
‘SELECT ‘ + metricArrayForQuery.join() + ‘,’ + TIMESEGMENT + ‘ ‘ +
‘FROM ‘ + reportType + ‘ ‘ +
‘WHERE ‘ + idType + ‘ IN [‘ + idString + ‘] ‘ +
‘DURING ‘ + startDate + “,” + endDate );
var rows1 = report1.rows();

while (rows1.hasNext()) {
var row = rows1.next();
var stats = new Array();
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
if(oldOrNew.toLowerCase().indexOf(“new”) != -1) {
var modifierForMetric = METRICS[i].modifierForNewMetric || 1;
} else if(oldOrNew.toLowerCase().indexOf(“old”) != -1) {
var modifierForMetric = METRICS[i].modifierForOldMetric || 1;
}
//Logger.log(“modifierForMetric: ” + modifierForMetric);
//Logger.log(“Metric: ” + metric);
stats[metric] = {};
stats[metric].value = row[metric];
stats[metric].modifierForMetric = modifierForMetric;
//Logger.log(label + ” – ” + metric + “: ” + stats[metric].value);
}
//var conversions = parseInt(row[‘Conversions’]);
if(TIMESEGMENT.toLowerCase().indexOf(“week”) != -1) {
var timeKey = row[‘Week’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“date”) != -1) {
var timeKey = row[‘Date’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“month”) != -1) {
var timeKey = row[‘Month’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“quarter”) != -1) {
var timeKey = row[‘Quarter’];
}
//Logger.log(“timekey: ” + timeKey);
if(!totals[timeKey]) {
totals[timeKey] = new Object();
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
var type = METRICS[i].type;
if(type.toLowerCase().indexOf(“int”) != -1) {
var value = parseInt(stats[metric].value);
} else if(type.toLowerCase().indexOf(“double”) != -1) {
var value = parseFloat(stats[metric].value);
} else if(type.toLowerCase().indexOf(“long”) != -1) {
var value = parseFloat(stats[metric].value.replace(THOUSANDSSEPARATOR,””));
}
modifierForMetric = stats[metric].modifierForMetric;
totals[timeKey][metric] = value * modifierForMetric;
//Logger.log(“modifierForMetric: ” + modifierForMetric);
//Logger.log(timeKey + “: ” + totals[timeKey][metric]);
}
totals[timeKey].date = timeKey;
} else {
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
var type = METRICS[i].type;
if(type.toLowerCase().indexOf(“int”) != -1) {
var value = parseInt(stats[metric].value);
} else if(type.toLowerCase().indexOf(“double”) != -1) {
var value = parseFloat(stats[metric].value);
} else if(type.toLowerCase().indexOf(“long”) != -1) {
var value = parseFloat(stats[metric].value.replace(THOUSANDSSEPARATOR,””));
}
modifierForMetric = stats[metric].modifierForMetric;
totals[timeKey][metric] += value * modifierForMetric;
//Logger.log(timeKey + “: ” + totals[timeKey][metric]);
}
}
}

var sortedTotals = sortObj(totals);

return sortedTotals;
//return totals;

}

function sortObj(arr){
// Setup Arrays
var sortedKeys = new Array();
var sortedObj = {};

// Separate keys and sort them
for (var i in arr){
sortedKeys.push(i);
}
sortedKeys.sort();

// Reconstruct sorted obj based on keys
for (var i in sortedKeys){
sortedObj[sortedKeys[i]] = arr[sortedKeys[i]];
}
return sortedObj;
}

// Returns YYYYMMDD-formatted date.
function getDateInThePast(numDays) {
var today = new Date();
today.setDate(today.getDate() – numDays);
return Utilities.formatDate(today, “PST”, “yyyyMMdd”);
}

Date.prototype.yyyymmdd = function() {
var yyyy = this.getFullYear().toString();
var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
var dd = this.getDate().toString();
return yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + (dd[1]?dd:”0″+dd[0]); // padding
};

 

5. Rótulos de conta – Por Google Ads. Este é um compilado de scripts do Google que possibilita por exemplo, edições em massa de rótulos.

Crie um rótulo de conta

function createAccountLabels() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

MccApp.createAccountLabel(labelName);
Logger.log(“Label with text = ‘%s’ created.”, labelName);
}

 

Aplicar um marcador de conta a várias contas

function applyAccountLabels() {
var accountIds = [‘INSERT_ACCOUNT_ID_HERE’, ‘INSERT_ACCOUNT_ID_HERE’];
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accounts = MccApp.accounts().withIds(accountIds).get();
while (accounts.hasNext()) {
var account = accounts.next();
account.applyLabel(labelName);

Logger.log(‘Label with text = “%s” applied to customer id %s.’,
labelName, account.getCustomerId());
}
}

 

Remover um rótulo de conta de várias contas

function removeLabelFromAccounts() {
var accountIds = [‘INSERT_ACCOUNT_ID_HERE’, ‘INSERT_ACCOUNT_ID_HERE’];
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accounts = MccApp.accounts().withIds(accountIds).get();
while (accounts.hasNext()) {
var account = accounts.next();
account.removeLabel(labelName);

Logger.log(‘Label with text = “%s” removed from customer id %s.’,
labelName, account.getCustomerId());
}
}

 

Selecione uma conta pelo nome do marcador

function selectAccountsByLabelName() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accountIterator = MccApp.accounts()
.withCondition(“LabelNames CONTAINS ‘” + labelName + “‘”)
.get();

while (accountIterator.hasNext()) {
var account = accountIterator.next();
var accountName = account.getName() ? account.getName() : ‘–‘;
Logger.log(‘%s,%s,%s,%s’, account.getCustomerId(), accountName,
account.getTimeZone(), account.getCurrencyCode());
}
}

 

Recuperar todos os marcadores de conta

function getAllAccountLabels() {
var labelIterator = MccApp.accountLabels().get();
while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(‘Label with id = %s and text = %s was found.’,
label.getId().toFixed(0), label.getName());
}
}

 

Recuperar um rótulo de conta pelo nome dele

function getLabelByName() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var labelIterator = MccApp.accountLabels()
.withCondition(“Name CONTAINS ‘” + labelName + “‘”)
.get();

while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(‘Label with id = %s and text = %s was found.’,
label.getId().toFixed(0), label.getName());
}
}

 

Recuperar rótulos de conta por seus IDs

function getLabelById() {
var labelIterator = MccApp.accountLabels()
.withIds([12345, 67890]) // Replace with label IDs here
.get();

while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(“Label with id = %s and text = ‘%s’ was found.”,
label.getId().toFixed(0), label.getName());
}
}

6. Rotulador de Palavra-chave – Por Google Ads. Rotule em grande escala as palavras-chave com esse script e depois otimize-as com o filtro de rótulos no Google Ads.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Keyword Labeler
*
* @overview The Keyword Labeler script labels keywords based on rules that
* you define. For example, you can create a rule to label keywords that
* are underperforming. Later, you can filter for this label in AdWords
* to decide whether to pause or remove those keywords. Rules don’t have
* to be based solely on a keyword’s performance. They can also be based
* on properties of a keyword such as its text or match type. For example,
* you could define “branded” keywords as those containing proper nouns
* associated with your brand, then label those keywords based on
* different performance thresholds versus “non-branded” keywords.
* Finally, the script sends an email linking to a spreadsheet when new
* keywords have been labeled. See
* //developers.google.com/adwords/scripts/docs/solutions/labels
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.1.2
*
* @changelog
* – version 1.1.2
* – Added validation for external spreadsheet setup.
* – version 1.1.1
* – Improvements to time zone handling.
* – version 1.1
* – Modified to allow generic rules and labeling.
* – version 1.0
* – Released initial version.
*/

var CONFIG = {
// URL of the spreadsheet template.
// This should be a copy of //goo.gl/uhK6nS.
SPREADSHEET_URL: ‘YOUR_SPREADSHEET_URL’,

// Array of addresses to be alerted via email if labels are applied.
RECIPIENT_EMAILS: [
‘YOUR_EMAIL_HERE’
],

// Selector conditions to apply for all rules.
GLOBAL_CONDITIONS: [
‘CampaignStatus = ENABLED’,
‘AdGroupStatus = ENABLED’,
‘Status = ENABLED’
],

// Default date range over which statistics fields are retrieved.
// Used when fetching keywords if a rule doesn’t specify a date range.
DEFAULT_DATE_RANGE: ‘LAST_7_DAYS’
};

/**
* Defines the rules by which keywords will be labeled.
* The labelName field is required. Other fields may be null.
* @type {Array.<{
* conditions: Array.<string>,
* dateRange: string,
* filter: function(Object): boolean,
* labelName: string,
* }>
* }
*/
var RULES = [
{
conditions: [
‘Ctr < 0.02’,
‘AverageCpc > 1’,
],
filter: function(keyword) {
var brands = [‘Product A’, ‘Product B’, ‘Product C’];
var text = keyword.getText();
for (var i = 0; i < brands.length; i++) {
if (text.indexOf(brand[i]) >= 0) {
return true;
}
}
return false;
},
labelName: ‘Underperforming Branded’
},

{
conditions: [
‘Ctr < 0.01’,
‘AverageCpc > 2’,
],
labelName: ‘Underperforming’
}
];

function main() {
validateEmailAddresses();
var results = processAccount();
processResults(results);
}

/**
* Processes the rules on the current account.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processAccount() {
ensureAccountLabels();
var changes = applyLabels();

return changes;
}

/**
* Processes the results of the script.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processResults(changes) {
if (changes.length > 0) {
var spreadsheetUrl = saveToSpreadsheet(changes, CONFIG.RECIPIENT_EMAILS);
sendEmail(spreadsheetUrl, CONFIG.RECIPIENT_EMAILS);
} else {
Logger.log(‘No labels were applied.’);
}
}

/**
* Retrieves the names of all labels in the account.
*
* @return {Array.<string>} An array of label names.
*/
function getAccountLabelNames() {
var labelNames = [];
var iterator = AdWordsApp.labels().get();

while (iterator.hasNext()) {
labelNames.push(iterator.next().getName());
}

return labelNames;
}

/**
* Checks that the account has a label for each rule and
* creates the rule’s label if it does not already exist.
* Throws an exception if a rule does not have a labelName.
*/
function ensureAccountLabels() {
var labelNames = getAccountLabelNames();

for (var i = 0; i < RULES.length; i++) {
var labelName = RULES[i].labelName;

if (!labelName) {
throw ‘Missing labelName for rule #’ + i;
}

if (labelNames.indexOf(labelName) == -1) {
AdWordsApp.createLabel(labelName);
labelNames.push(labelName);
}
}
}

/**
* Retrieves the keywords in an account satisfying a rule
* and that do not already have the rule’s label.
*
* @param {Object} rule An element of the RULES array.
* @return {Array.<Object>} An array of keywords.
*/
function getKeywordsForRule(rule) {
var selector = AdWordsApp.keywords();

// Add global conditions.
for (var i = 0; i < CONFIG.GLOBAL_CONDITIONS.length; i++) {
selector = selector.withCondition(CONFIG.GLOBAL_CONDITIONS[i]);
}

// Add selector conditions for this rule.
if (rule.conditions) {
for (var i = 0; i < rule.conditions.length; i++) {
selector = selector.withCondition(rule.conditions[i]);
}
}

// Exclude keywords that already have the label.
selector.withCondition(‘LabelNames CONTAINS_NONE [“‘ + rule.labelName + ‘”]’);

// Add a date range.
selector = selector.forDateRange(rule.dateRange || CONFIG.DEFAULT_DATE_RANGE);

// Get the keywords.
var iterator = selector.get();
var keywords = [];

// Check filter conditions for this rule.
while (iterator.hasNext()) {
var keyword = iterator.next();

if (!rule.filter || rule.filter(keyword)) {
keywords.push(keyword);
}
}

return keywords;
}

/**
* For each rule, determines the keywords matching the rule and which
* need to have a label newly applied, and applies it.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function applyLabels() {
var changes = [];
var customerId = AdWordsApp.currentAccount().getCustomerId();

for (var i = 0; i < RULES.length; i++) {
var rule = RULES[i];
var keywords = getKeywordsForRule(rule);
var labelName = rule.labelName;

for (var j = 0; j < keywords.length; j++) {
var keyword = keywords[j];

keyword.applyLabel(labelName);

changes.push({
customerId: customerId,
campaignName: keyword.getCampaign().getName(),
adGroupName: keyword.getAdGroup().getName(),
labelName: labelName,
keywordText: keyword.getText(),
});
}
}

return changes;
}

/**
* Outputs a list of applied labels to a new spreadsheet and gives editor access
* to a list of provided emails.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
* @param {Array.<Object>} emails An array of email addresses.
* @return {string} The URL of the spreadsheet.
*/
function saveToSpreadsheet(changes, emails) {
var template = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
var spreadsheet = template.copy(‘Keyword Labels Applied’);

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

Logger.log(‘Saving changes to spreadsheet at ‘ + spreadsheet.getUrl());

var headers = spreadsheet.getRangeByName(‘Headers’);
var outputRange = headers.offset(1, 0, changes.length);

var outputValues = [];
for (var i = 0; i < changes.length; i++) {
var change = changes[i];
outputValues.push([
change.customerId,
change.campaignName,
change.adGroupName,
change.keywordText,
change.labelName
]);
}
outputRange.setValues(outputValues);

spreadsheet.getRangeByName(‘RunDate’).setValue(new Date());

for (var i = 0; i < emails.length; i++) {
spreadsheet.addEditor(emails[i]);
}

return spreadsheet.getUrl();
}

/**
* Sends an email to a list of email addresses with a link to a spreadsheet.
*
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @param {Array.<Object>} emails An array of email addresses.
*/
function sendEmail(spreadsheetUrl, emails) {
MailApp.sendEmail(emails.join(‘,’), ‘Keywords Newly Labeled’,
‘Keywords have been newly labeled in your’ +
‘AdWords account(s). See ‘ +
spreadsheetUrl + ‘ for details.’);
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and email addresses at the top of the file
* only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
return spreadsheet;
}

/**
* Validates the provided email address to make sure it’s not the default.
* Throws a descriptive error message if validation fails.
*
* @throws {Error} If the list of email addresses is still the default
*/
function validateEmailAddresses() {
if (CONFIG.RECIPIENT_EMAILS &&
CONFIG.RECIPIENT_EMAILS[0] == ‘YOUR_EMAIL_HERE’) {
throw new Error(‘Please specify a valid email address.’);
}
}

 

Como Configurar

Automação Google Ads: Automatizar Tarefas

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Automação de Tarefas”.

 

1. Reduza Lances para Palavras-chave com Custo/Conversão Alto – Por Russell Savage. Evite que suas palavras-chave consumam todo orçamento de sua campanha caso ela não esteja trazendo resultados. O script diminui automaticamente lances de palavras-chave com baixa performance em qualquer período de tempo.

//———————————–
// Reduce Bids on High Cost per Conversion Keywords
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
//Let’s reduce keywords with a CPC greater than $15 by 35%
var WAY_TOO_HIGH_COST_PER_CONV = 15;
var WAY_TOO_HIGH_BID_REDUCTION_AMOUNT = .35;

//And keywords with CPC between $10 and $15 by 20%
var TOO_HIGH_COST_PER_CONV = 10;
var TOO_HIGH_BID_REDUCTION_AMOUNT = .20;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions > 0) {
var cost_per_conversion = (cost/(conversions*1.0));
//Here is the magic. If it is way too high, reduce it by the way too high amount
if(cost_per_conversion >= WAY_TOO_HIGH_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1-WAY_TOO_HIGH_BID_REDUCTION_AMOUNT));
}
//otherwise, if it is still too high, reduce it with just the too high amount
else if(cost_per_conversion >= TOO_HIGH_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1-TOO_HIGH_BID_REDUCTION_AMOUNT));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

2. Aumente Lances para Palavras-chave com Custo/Conversão Baixo – Por Russell Savage. Assim como seu nome, esse script visa aumentar os lances para palavras-chave que estão custando barato.

//———————————–
// Increase Bids Cheap Conversion Keywords
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
//For keywords with less than $5 CPC, let’s pump those bids up by 35%
var AMAZING_COST_PER_CONV = 5;
var AMAZING_BID_INCREASE_AMOUNT = .35;

//For keywords with between $5 and $8 CPCs, we will only increase the bids by 20%
var GREAT_COST_PER_CONV = 8;
var GREAT_BID_INCREASE_AMOUNT = .20;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions > 0) {
var cost_per_conversion = (cost/(conversions*1.0));

if(cost_per_conversion <= AMAZING_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+AMAZING_BID_INCREASE_AMOUNT));
}
else if(cost_per_conversion <= GREAT_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+GREAT_BID_INCREASE_AMOUNT));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

3. Pause Palavras-chave que não performam – Por Russel Savage. Para utilizar esse script é necessário ter o valor da conversão sendo rastreado. Assim, caso o custo da palavra-chave esteja ultrapassando um valor determinado ela é automaticamente pausada.

//———————————–
// Pause Keywords That Are Not Performing
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var THE_VALUE_OF_ONE_CONVERSION = 10;
var DECREASE_BIDS_BY_PERCENTAGE = .5;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions == 0) {
if(THE_VALUE_OF_ONE_CONVERSION * 5 > cost) {
kw.pause();
}
else if(THE_VALUE_OF_ONE_CONVERSION * 2 > cost) {
kw.setMaxCpc(kw.getMaxCpc() * (1-DECREASE_BIDS_BY_PERCENTAGE));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

Automação Google Ads: Anúncios Sazonais

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com anúncios sazonais”.

 

1. Atualize Anúncios para o Ano Novo – Por Russel Savage. Tenha seus anúncios atualizados assim que o ano novo chegar. Esse script pode auxiliar anunciantes de empresas que precisem ter o ano em seus textos.

//———————————–
// Update Ads for 2012
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var OLD_YEAR = “2011”;
var NEW_YEAR = “2012”;
//You probably shouldn’t update destination urls unless you know what you are doing.
var FIELDS_CONTAINING_YEAR = [“Headline”,”Description1″,
“Description2″,”DisplayUrl”
/*,”DestinationUrl”*/
];

for(i in FIELDS_CONTAINING_YEAR) {
var field_iter = AdWordsApp.ads()
.withCondition(FIELDS_CONTAINING_YEAR[i] + ” CONTAINS ” + OLD_YEAR)
.withCondition(“Status = ENABLED”)
.get();

_iterateThroughAds(field_iter);
}

//—————
// Private Helper Functions
//—————
function _iterateThroughAds(ad_iter) {
while (ad_iter.hasNext()) {
var ad = ad_iter.next();
var ag = ad.getAdGroup();
_createNewAdFromOldAd(ag,ad);
}
}

function _createNewAdFromOldAd(adgroup, old_ad) {
//get the updated ad texts replacing all the old years with the new years
var new_headline = old_ad.getHeadline().replace(OLD_YEAR,NEW_YEAR);
var new_desc1 = old_ad.getDescription1().replace(OLD_YEAR,NEW_YEAR);
var new_desc2 = old_ad.getDescription2().replace(OLD_YEAR,NEW_YEAR);
var new_display_url = old_ad.getDisplayUrl().replace(OLD_YEAR,NEW_YEAR);
var new_dest_url = old_ad.getDestinationUrl();/*.replace(OLD_YEAR,NEW_YEAR);*/

//now create the new ad and pause the old one.
adgroup.createTextAd(new_headline,new_desc1,new_desc2,new_display_url,new_dest_url);
old_ad.pause();
}
}

 

2. Atualize suas Palavras-chave em Feriados e Datas Comemorativas – Por Russel Savage. Todas as suas palavras-chave que tiverem o ano atual em seu texto será substituído com o ano atual. Assim, caso você não precisa mudar manualmente todas essas palavras-chave.

/*********************************************
* Update Keywords for the New Year
* Version 1.1
* Changelog v1.1
* – Updated for speed and added comments
* Created By: Russ Savage
* FreeAdWordsScripts.com
**********************************************/
function main() {
var sameDayLastYear = new Date();
sameDayLastYear.setYear(sameDayLastYear.getYear()-1);
var oldYearStr = sameDayLastYear.getYear().toString();
var newYearStr = new Date().getYear().toString();

Logger.log(‘Updating keywords with old year: ‘+oldYearStr+’ to new year: ‘+newYearStr);

// Let’s start by getting all of the keywords
var kwIter = AdWordsApp.keywords()
.withCondition(“Text CONTAINS ” + oldYearStr)
.withCondition(“Status = ENABLED”)
.withCondition(“AdGroupStatus = ENABLED”)
.withCondition(“CampaignStatus = ENABLED”)
.get();

// It is always better to store and batch process afterwards
var toPause = [];
var toCreate = [];
while (kwIter.hasNext()) {
var kw = kwIter.next();
var ag = kw.getAdGroup();
var oldText = kw.getText();
var newText = oldText.replace(oldYearStr,newYearStr);
// Save the info so that we can create them as a batch later
toCreate.push({ ag: ag, text: newText, cpc:kw.getMaxCpc(), destUrl : kw.getDestinationUrl() });
// Same with the ones we want to pause
toPause.push(kw)
}
// Now we create the new keywords all at once
for(var i in toCreate) {
var elem = toCreate[i];
elem.ag.createKeyword(elem.text, elem.cpc, elem.destUrl);
}
// And pause the old ones all at once
for(var i in toPause) {
toPause[i].pause();
//or toPause[i].remove(); to delete the old keyword
}
}

 

 

Automação Google Ads: Como gerar relatórios

rotadesk No Comments

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para Geração de Relatórios.

 

1. Relatório de Resumo da Conta – por Google Ads. Utilize esse script para criar relatórios em uma planilha do Google. Nesta planilha você tem uma visão do desempenho de toda a conta do Google Ads e, além disso, envia um e-mail todos os dias com estatísticas das contas atuais.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Account Summary Report
*
* @overview The Account Summary Report script generates an at-a-glance report
* showing the performance of an entire AdWords account. See
* //developers.google.com/adwords/scripts/docs/solutions/account-summary
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.1
*
* @changelog
* – version 1.1
* – Add user-updateable fields, and ensure report row ordering.
* – version 1.0.4
* – Improved code readability and comments.
* – version 1.0.3
* – Added validation for external spreadsheet setup.
* – version 1.0.2
* – Fixes date formatting bug in certain timezones.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

/**
* To add additional fields to the report, follow the instructions at the link
* in the header above, and add fields to this variable, taken from the Account
* Performance Report reference:
* //developers.google.com/adwords/api/docs/appendix/reports/account-performance-report
*/
var REPORT_FIELDS = [
{columnName: ‘Cost’, displayName: ‘Cost’},
{columnName: ‘AverageCpc’, displayName: ‘Avg. CPC’},
{columnName: ‘Ctr’, displayName: ‘CTR’},
{columnName: ‘AveragePosition’, displayName: ‘Avg. Pos.’},
{columnName: ‘Impressions’, displayName: ‘Impressions’},
{columnName: ‘Clicks’, displayName: ‘Clicks’}
];

function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet();
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
spreadsheet.getRangeByName(‘account_id_report’).setValue(
AdWordsApp.currentAccount().getCustomerId());

var yesterday = getYesterday();
var date = getFirstDayToCheck(spreadsheet, yesterday);

var rows = [];
var existingDates = getExistingDates();

while (date.getTime() <= yesterday.getTime()) {
if (!existingDates[date]) {
var row = getReportRowForDate(date);
rows.push([new Date(date)].concat(REPORT_FIELDS.map(function(field) {
return row[field.columnName];
})));
spreadsheet.getRangeByName(‘last_check’).setValue(date);
}
date.setDate(date.getDate() + 1);
}

if (rows.length > 0) {
writeToSpreadsheet(rows);

var email = spreadsheet.getRangeByName(‘email’).getValue();
if (email) {
sendEmail(email);
}
}
}

/**
* Retrieves a lookup of dates for which rows already exist in the spreadsheet.
*
* @return {!Object} A lookup of existing dates.
*/
function getExistingDates() {
var spreadsheet = validateAndGetSpreadsheet();
var sheet = spreadsheet.getSheetByName(‘Report’);

var data = sheet.getDataRange().getValues();
var existingDates = {};
data.slice(5).forEach(function(row) {
existingDates[row[1]] = true;
});
return existingDates;
}

/**
* Sorts the data in the spreadsheet into ascending date order.
*/
function sortReportRows() {
var spreadsheet = validateAndGetSpreadsheet();
var sheet = spreadsheet.getSheetByName(‘Report’);

var data = sheet.getDataRange().getValues();
var reportRows = data.slice(5);
if (reportRows.length) {
reportRows.sort(function(rowA, rowB) {
if (!rowA || !rowA.length) {
return -1;
} else if (!rowB || !rowB.length) {
return 1;
} else if (rowA[1] < rowB[1]) {
return -1;
} else if (rowA[1] > rowB[1]) {
return 1;
}
return 0;
});
sheet.getRange(6, 1, reportRows.length, reportRows[0].length)
.setValues(reportRows);
}
}

/**
* Append the data rows to the spreadsheet.
*
* @param {Array<Array<string>>} rows The data rows.
*/
function writeToSpreadsheet(rows) {
var access = new SpreadsheetAccess(SPREADSHEET_URL, ‘Report’);
var emptyRow = access.findEmptyRow(6, 2);
if (emptyRow < 0) {
access.addRows(rows.length);
emptyRow = access.findEmptyRow(6, 2);
}
access.writeRows(rows, emptyRow, 2);
sortReportRows();
}

function sendEmail(email) {
var day = getYesterday();
var yesterdayRow = getReportRowForDate(day);
day.setDate(day.getDate() – 1);
var twoDaysAgoRow = getReportRowForDate(day);
day.setDate(day.getDate() – 5);
var weekAgoRow = getReportRowForDate(day);

var html = [];
html.push(
‘<html>’,
‘<body>’,
‘<table width=800 cellpadding=0 border=0 cellspacing=0>’,
‘<tr>’,
‘<td colspan=2 align=right>’,
“<div style=’font: italic normal 10pt Times New Roman, serif; ” +
“margin: 0; color: #666; padding-right: 5px;’>” +
‘Powered by AdWords Scripts</div>’,
‘</td>’,
‘</tr>’,
“<tr bgcolor=’#3c78d8′>”,
‘<td width=500>’,
“<div style=’font: normal 18pt verdana, sans-serif; ” +
“padding: 3px 10px; color: white’>Account Summary report</div>”,
‘</td>’,
‘<td align=right>’,
“<div style=’font: normal 18pt verdana, sans-serif; ” +
“padding: 3px 10px; color: white’>”,
AdWordsApp.currentAccount().getCustomerId(), ‘</h1>’,
‘</td>’,
‘</tr>’,
‘</table>’,
‘<table width=800 cellpadding=0 border=0 cellspacing=0>’,
“<tr bgcolor=’#ddd’>”,
‘<td></td>’,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5px 5px; background-color: #ddd; ‘ +
“text-align: left’>Yesterday</td>”,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5px 5px; background-color: #ddd; ‘ +
“text-align: left’>Two Days Ago</td>”,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5x 5px; background-color: #ddd; ‘ +
“text-align: left’>A week ago</td>”,
‘</tr>’);
REPORT_FIELDS.forEach(function(field) {
html.push(emailRow(
field.displayName, field.columnName, yesterdayRow, twoDaysAgoRow,
weekAgoRow));
});
html.push(‘</table>’, ‘</body>’, ‘</html>’);
MailApp.sendEmail(email, ‘AdWords Account ‘ +
AdWordsApp.currentAccount().getCustomerId() + ‘ Summary Report’, ”,
{htmlBody: html.join(‘\n’)});
}

function emailRow(title, column, yesterdayRow, twoDaysAgoRow, weekAgoRow) {
var html = [];
html.push(‘<tr>’,
“<td style=’padding: 5px 10px’>” + title + ‘</td>’,
“<td style=’padding: 0px 10px’>” + yesterdayRow[column] + ‘</td>’,
“<td style=’padding: 0px 10px’>” + twoDaysAgoRow[column] +
formatChangeString(yesterdayRow[column], twoDaysAgoRow[column]) +
‘</td>’,
“<td style=’padding: 0px 10px’>” + weekAgoRow[column] +
formatChangeString(yesterdayRow[column], weekAgoRow[column]) +
‘</td>’,
‘</tr>’);
return html.join(‘\n’);
}

function getReportRowForDate(date) {
var timeZone = AdWordsApp.currentAccount().getTimeZone();
var dateString = Utilities.formatDate(date, timeZone, ‘yyyyMMdd’);
return getReportRowForDuring(dateString + ‘,’ + dateString);
}

function getReportRowForDuring(during) {
var report = AdWordsApp.report(
‘SELECT ‘ +
REPORT_FIELDS
.map(function(field) {
return field.columnName;
})
.join(‘,’) +
‘ FROM ACCOUNT_PERFORMANCE_REPORT ‘ +
‘DURING ‘ + during,
REPORTING_OPTIONS);
return report.rows().next();
}

function formatChangeString(newValue, oldValue) {
var x = newValue.indexOf(‘%’);
if (x != -1) {
newValue = newValue.substring(0, x);
var y = oldValue.indexOf(‘%’);
oldValue = oldValue.substring(0, y);
}

var change = parseFloat(newValue – oldValue).toFixed(2);
var changeString = change;
if (x != -1) {
changeString = change + ‘%’;
}

if (change >= 0) {
return “<span style=’color: #38761d; font-size: 8pt’> (+” +
changeString + ‘)</span>’;
} else {
return “<span style=’color: #cc0000; font-size: 8pt’> (” +
changeString + ‘)</span>’;
}
}

function SpreadsheetAccess(spreadsheetUrl, sheetName) {
this.spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
this.sheet = this.spreadsheet.getSheetByName(sheetName);

// what column should we be looking at to check whether the row is empty?
this.findEmptyRow = function(minRow, column) {
var values = this.sheet.getRange(minRow, column,
this.sheet.getMaxRows(), 1).getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) {
return i + minRow;
}
}
return -1;
};
this.addRows = function(howMany) {
this.sheet.insertRowsAfter(this.sheet.getMaxRows(), howMany);
};
this.writeRows = function(rows, startRow, startColumn) {
this.sheet.getRange(startRow, startColumn, rows.length, rows[0].length).
setValues(rows);
};
}

/**
* Gets a date object that is 00:00 yesterday.
*
* @return {Date} A date object that is equivalent to 00:00 yesterday in the
* account’s time zone.
*/
function getYesterday() {
var yesterday = new Date(new Date().getTime() – 24 * 3600 * 1000);
return new Date(getDateStringInTimeZone(‘MMM dd, yyyy 00:00:00 Z’,
yesterday));
}

/**
* Returned the last checked date + 1 day, or yesterday if there isn’t
* a specified last checked date.
*
* @param {Spreadsheet} spreadsheet The export spreadsheet.
* @param {Date} yesterday The yesterday date.
*
* @return {Date} The date corresponding to the first day to check.
*/
function getFirstDayToCheck(spreadsheet, yesterday) {
var last_check = spreadsheet.getRangeByName(‘last_check’).getValue();
var date;
if (last_check.length == 0) {
date = new Date(yesterday);
} else {
date = new Date(last_check);
date.setDate(date.getDate() + 1);
}
return date;
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided spreadsheet URL to make sure that it’s set up
* properly. Throws a descriptive error message if validation fails.
*
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
*/
function validateAndGetSpreadsheet() {
if (‘YOUR_SPREADSHEET_URL’ == SPREADSHEET_URL) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var email = spreadsheet.getRangeByName(‘email’).getValue();
if (‘[email protected]’ == email) {
throw new Error(‘Please either set a custom email address in the’ +
‘ spreadsheet, or set the email field in the spreadsheet to blank’ +
‘ to send no email.’);
}
return spreadsheet;
}

 

Como Configurar

 

2. Relatório de Desempenho do Anúncio – por Google Ads. Receba relatórios de desempenhos de seus anúncios em uma planilha do Google com esse script. Toda vez que esse script for executado ele cria um novo relatório no Google Drive. Faça testes e compare os desempenhos de um novo título ou URL.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Ad Performance Report
*
* @overview The Ad Performance Report generates a Google Spreadsheet that
* contains ad performance stats like Impressions, Cost, Click Through Rate,
* etc. as several distribution charts for an advertiser account. See
* //developers.google.com/adwords/scripts/docs/solutions/ad-performance
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.1
*
* @changelog
* – version 1.1
* – Updated to use expanded text ads.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/
// Comma-separated list of recipients. Comment out to not send any emails.
var RECIPIENT_EMAIL = ‘[email protected]’;

// URL of the default spreadsheet template. This should be a copy of
// //goo.gl/21FW5i
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* This script computes an Ad performance report
* and outputs it to a Google spreadsheet.
*/
function main() {
Logger.log(‘Using template spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = copySpreadsheet(SPREADSHEET_URL);
Logger.log(‘Generated new reporting spreadsheet %s based on the template ‘ +
‘spreadsheet. The reporting data will be populated here.’,
spreadsheet.getUrl());

var headlineSheet = spreadsheet.getSheetByName(‘Headline’);
headlineSheet.getRange(1, 2, 1, 1).setValue(‘Date’);
headlineSheet.getRange(1, 3, 1, 1).setValue(new Date());
var finalUrlSheet = spreadsheet.getSheetByName(‘Final Url’);
finalUrlSheet.getRange(1, 2, 1, 1).setValue(‘Date’);
finalUrlSheet.getRange(1, 3, 1, 1).setValue(new Date());
spreadsheet.getRangeByName(‘account_id_headline’).setValue(
AdWordsApp.currentAccount().getCustomerId());
spreadsheet.getRangeByName(‘account_id_final_url’).setValue(
AdWordsApp.currentAccount().getCustomerId());

// Only include ad types on the headline sheet for which the concept of a
// headline makes sense.
outputSegmentation(headlineSheet, ‘Headline’, function(ad) {
var headline;
// There is no AdTypeSpace method for textAd
if (ad.getType() === ‘TEXT_AD’) {
headline = ad.getHeadline();
} else if (ad.isType().expandedTextAd()) {
var eta = ad.asType().expandedTextAd();
headline = eta.getHeadlinePart1() + ‘ – ‘ + eta.getHeadlinePart2();
} else if (ad.isType().gmailMultiProductAd()) {
var gmailMpa = ad.asType().gmailMultiProductAd();
headline = gmailMpa.getHeadline();
} else if (ad.isType().gmailSinglePromotionAd()) {
var gmailSpa = ad.asType().gmailSinglePromotionAd();
headline = gmailSpa.getHeadline();
} else if (ad.isType().responsiveDisplayAd()) {
var responsiveDisplayAd = ad.asType().responsiveDisplayAd();
headline = responsiveDisplayAd.getLongHeadline();
}
return headline;
});
outputSegmentation(finalUrlSheet, ‘Final Url’, function(ad) {
return ad.urls().getFinalUrl();
});
Logger.log(‘Ad performance report available at\n’ + spreadsheet.getUrl());
if (validateEmailAddress(RECIPIENT_EMAIL)) {
MailApp.sendEmail(RECIPIENT_EMAIL,
‘Ad Performance Report is ready’,
spreadsheet.getUrl());
}
}

/**
* Retrieves the spreadsheet identified by the URL.
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @return {SpreadSheet} The spreadsheet.
*/
function copySpreadsheet(spreadsheetUrl) {
var spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl).copy(
‘Ad Performance Report – ‘ +
getDateStringInTimeZone(‘MMM dd, yyyy HH:mm:ss z’));

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
return spreadsheet;
}

/**
* Generates statistical data for this segment.
* @param {Sheet} sheet Sheet to write to.
* @param {string} segmentName The Name of this segment for the header row.
* @param {function(AdWordsApp.Ad): string} segmentFunc Function that returns
* a string used to segment the results by.
*/
function outputSegmentation(sheet, segmentName, segmentFunc) {
// Output header row.
var rows = [];
var header = [
segmentName,
‘Num Ads’,
‘Impressions’,
‘Clicks’,
‘CTR (%)’,
‘Cost’
];
rows.push(header);

var segmentMap = {};

// Compute data.
var adIterator = AdWordsApp.ads()
.forDateRange(‘LAST_WEEK’)
.withCondition(‘Impressions > 0’).get();
while (adIterator.hasNext()) {
var ad = adIterator.next();
var stats = ad.getStatsFor(‘LAST_WEEK’);
var segment = segmentFunc(ad);
// In the case of the headline segmentation segmentFunc will return null
// where there is no headline e.g. an HTML5 ad or other non-text ad, for
// which metrics are therefore not aggregated.
if (segment) {
if (!segmentMap[segment]) {
segmentMap[segment] =
{numAds: 0, totalImpressions: 0, totalClicks: 0, totalCost: 0.0};
}
var data = segmentMap[segment];
data.numAds++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}

// Write data to our rows.
for (var key in segmentMap) {
if (segmentMap.hasOwnProperty(key)) {
var ctr = 0;
if (segmentMap[key].numAds > 0) {
ctr = (segmentMap[key].totalClicks /
segmentMap[key].totalImpressions) * 100;
}
var row = [
key,
segmentMap[key].numAds,
segmentMap[key].totalImpressions,
segmentMap[key].totalClicks,
ctr.toFixed(2),
segmentMap[key].totalCost];
rows.push(row);
}
}
sheet.getRange(3, 2, rows.length, 6).setValues(rows);
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided email address
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} emailAddress The email address to send the results.
* @return {Spreadsheet} The email address, if it is not the default fake one.
* @throws {Error} If the email address has not been changed from the default.
*/
function validateEmailAddress(emailAddress) {
if (emailAddress == ‘[email protected]’) {
throw new Error(‘Please specify a valid email or leave empty to not’ +
‘ send any email.’);
}
return emailAddress;
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como configurar

 

3. Relatório de Grupos de Anúncios Rebaixados – por Google Ads. Encontre facilmente grupos de anúncios que estão com seu desempenho em declínio, assim, você pode otimizar seu grupo de anúncios ou pausa-los. O script deve ser rodado semanalmente para uma performance melhor.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Declining AdGroups
*
* @overview The Declining AdGroups script fetches ad groups in an advertiser
* account, whose performance is considered to be worsening. By default, ad
* groups whose Click Through Rate has been decreasing for three consecutive
* weeks is considered worsening. A more sophisticated measure of
* “worsening” may be developed if required. See
* //developers.google.com/adwords/scripts/docs/solutions/declining-adgroups
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Added validation for spreadsheet URL.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

var sheet = spreadsheet.getSheets()[0];
spreadsheet.getRangeByName(‘account_id’).setValue(
AdWordsApp.currentAccount().getCustomerId());
sheet.getRange(1, 2, 1, 1).setValue(‘Date’);
sheet.getRange(1, 3, 1, 1).setValue(new Date());
sheet.getRange(7, 1, sheet.getMaxRows() – 7, sheet.getMaxColumns()).clear();

var adGroupsIterator = AdWordsApp.adGroups()
.withCondition(“Status = ‘ENABLED'”)
.withCondition(“CampaignStatus = ‘ENABLED'”)
.forDateRange(‘LAST_7_DAYS’)
.orderBy(‘Ctr ASC’)
.withLimit(100)
.get();

var today = getDateStringInPast(0);
var oneWeekAgo = getDateStringInPast(7);
var twoWeeksAgo = getDateStringInPast(14);
var threeWeeksAgo = getDateStringInPast(21);

var reportRows = [];

while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
// Let’s look at the trend of the ad group’s CTR.
var statsThreeWeeksAgo = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo);
var statsTwoWeeksAgo = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo);
var statsLastWeek = adGroup.getStatsFor(oneWeekAgo, today);

// Week over week, the ad group is declining – record that!
if (statsLastWeek.getCtr() < statsTwoWeeksAgo.getCtr() &&
statsTwoWeeksAgo.getCtr() < statsThreeWeeksAgo.getCtr()) {
reportRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getCtr(), statsLastWeek.getCost(),
statsTwoWeeksAgo.getCtr(), statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getCtr(), statsThreeWeeksAgo.getCost()]);
}
}
if (reportRows.length > 0) {
sheet.getRange(7, 2, reportRows.length, 8).setValues(reportRows);
sheet.getRange(7, 4, reportRows.length, 1).setNumberFormat(‘#0.00%’);
sheet.getRange(7, 6, reportRows.length, 1).setNumberFormat(‘#0.00%’);
sheet.getRange(7, 8, reportRows.length, 1).setNumberFormat(‘#0.00%’);

sheet.getRange(7, 5, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
sheet.getRange(7, 7, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
sheet.getRange(7, 9, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
}

var email = spreadsheet.getRangeByName(‘email’).getValue();
if (email) {
var body = [];
body.push(‘The Ctr of the following ad groups is declining over the ‘ +
‘last three weeks.\n’);
body.push(‘Full report at ‘ + SPREADSHEET_URL + ‘\n\n’);
for (var i = 0; i < reportRows.length; i++) {
body.push(reportRows[i][0] + ‘ > ‘ + reportRows[i][1]);
body.push(‘ ‘ + ctr(reportRows[i][6]) + ‘ > ‘ + ctr(reportRows[i][4]) +
‘ > ‘ + ctr(reportRows[i][2]) + ‘\n’);
}
MailApp.sendEmail(email, ” +
reportRows.length + ‘ ad groups are declining in AdWords account ‘ +
AdWordsApp.currentAccount().getCustomerId(), body.join(‘\n’));
}
}

function ctr(number) {
return parseInt(number * 10000) / 10000 + ‘%’;
}

// Returns YYYYMMDD-formatted date.
function getDateStringInPast(numDays, date) {
date = date || new Date();
var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
var past = new Date(date.getTime() – numDays * MILLIS_PER_DAY);
return getDateStringInTimeZone(‘yyyyMMdd’, past);
}

function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como Configurar

 

4. Relatório de desempenho por palavra-chave – por Google Ads. Este script gera relatórios com vários gráficos de distribuição, como por exemplo, posição média e índice de qualidade. Toda vez que o script é executado ele cria um novo relatório no Google Drive.

// Copyright 2016, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Keyword Performance Report
*
* @overview The Keyword Performance Report script generates a Google
* Spreadsheet that contains keyword performance stats like quality score
* and average position of ads, as well as several distribution charts for
* an advertiser account. See
* //developers.google.com/adwords/scripts/docs/solutions/keyword-performance
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.4
*
* @changelog
* – version 1.0.4
* – Fixed issue of calling getRangeByName on spreadsheet vs sheet.
* – version 1.0.3
* – Refactored to improve readability. Added documentation.
* – version 1.0.2
* – Added validation for spreadsheet url and email address.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

/**
* Comma-separated list of recipients. Comment out to not send any emails.
*/
var RECIPIENT_EMAIL = ‘[email protected]’;

// URL of the default spreadsheet template. This should be a copy of
// //goo.gl/oR5VmF
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* The size of the quality score map to output.
* DO NOT change this value.
*/
var QUALITY_SCORE_MAP_SIZE = 10;

/**
* The size of the position map to output.
* DO NOT change this value.
*/
var POSITION_MAP_SIZE = 12;

/**
* This script computes a keyword performance report
* and outputs it to a Google spreadsheet. The spreadsheet
* url is logged and emailed.
*/
function main() {
validateEmail(RECIPIENT_EMAIL);
Logger.log(‘Using template spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = copySpreadsheet(SPREADSHEET_URL);
Logger.log(
‘Generated new reporting spreadsheet %s based on the template ‘ +
‘spreadsheet. The reporting data will be populated here.’,
spreadsheet.getUrl());

spreadsheet.getRangeByName(‘date_label’).setValue(‘Date’);
spreadsheet.getRangeByName(‘date_value’).setValue(new Date());
spreadsheet.getRangeByName(‘account_id’)
.setValue(AdWordsApp.currentAccount().getCustomerId());
outputQualityScoreData(spreadsheet);
outputPositionData(spreadsheet);
Logger.log(
‘Keyword performance report available at\n’ + spreadsheet.getUrl());
if (RECIPIENT_EMAIL) {
MailApp.sendEmail(
RECIPIENT_EMAIL, ‘Keyword Performance Report is ready’,
spreadsheet.getUrl());
}
}

/**
* Retrieves the spreadsheet identified by the URL.
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @return {SpreadSheet} The spreadsheet.
*/
function copySpreadsheet(spreadsheetUrl) {
var spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl)
.copy(
‘Keyword Performance Report – ‘ +
getDateStringInTimeZone(‘MMM dd, yyyy HH:mm:ss z’));

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
return spreadsheet;
}

/**
* Gets an iterator for keywords that had impressions last week.
* @return {Iterator} an iterator of the keywords
*/
function getLastWeekKeywordsWithPositiveImpressions() {
return AdWordsApp.keywords()
.forDateRange(‘LAST_WEEK’)
.withCondition(‘Impressions > 0’)
.get();
}

/**
* Outputs Quality score related data to the spreadsheet
* @param {Spreadsheet} spreadsheet The sheet to output to.
*/
function outputQualityScoreData(spreadsheet) {
// Output header row
var header = [
‘Quality Score’, ‘Num Keywords’, ‘Impressions’, ‘Clicks’, ‘CTR (%)’, ‘Cost’
];
spreadsheet.getRangeByName(‘quality_score_headings’).setValues([header]);

// Initialize
var qualityScoreMap = getEmptyStatMap(QUALITY_SCORE_MAP_SIZE);

// Compute data
computeQualityData(
getLastWeekKeywordsWithPositiveImpressions(), qualityScoreMap);

// Output data to spreadsheet
var rows = [];
for (var key in qualityScoreMap) {
var ctr = calculateCtr(qualityScoreMap[key]);
var row = [
key, qualityScoreMap[key].numKeywords,
qualityScoreMap[key].totalImpressions, qualityScoreMap[key].totalClicks,
ctr.toFixed(2), qualityScoreMap[key].totalCost
];
rows.push(row);
}
spreadsheet.getRangeByName(‘quality_score_body’).setValues(rows);
}

/**
* Outputs average position related data to the spreadsheet.
* @param {Spreadsheet} spreadsheet The spreadsheet to output to.
*/
function outputPositionData(spreadsheet) {
// Output header row
headerRow = [];
var header = [
‘Avg Position’, ‘Num Keywords’, ‘Impressions’, ‘Clicks’, ‘CTR (%)’, ‘Cost’
];
headerRow.push(header);
spreadsheet.getRangeByName(‘position_headings’).setValues(headerRow);

// Initialize
var positionMap = getEmptyStatMap(POSITION_MAP_SIZE);

// Compute data
computePositionData(
getLastWeekKeywordsWithPositiveImpressions(), positionMap);

// Output data to spreadsheet
var rows = [];
for (var key in positionMap) {
var ctr = calculateCtr(positionMap[key]);
var mapSizeLessOne = POSITION_MAP_SIZE – 1;
var row = [
key <= mapSizeLessOne ? key – 1 + ‘ to ‘ + key : ‘>’ + mapSizeLessOne,
positionMap[key].numKeywords, positionMap[key].totalImpressions,
positionMap[key].totalClicks, ctr.toFixed(2), positionMap[key].totalCost
];
rows.push(row);
}
spreadsheet.getRangeByName(‘position_body’).setValues(rows);
}

/**
* Calculates the click through rate given an entry from a map.
* @param {object} mapEntry – an entry from the map
* @return {number} the click through rate
*/
function calculateCtr(mapEntry) {
var ctr = 0;
if (mapEntry.numKeywords > 0) {
ctr = (mapEntry.totalClicks / mapEntry.totalImpressions) * 100;
}
return ctr;
}

/**
* Gets an empty stat map.
* @param {number} size – the number of entries in the stat map.
* @return {array} the empty quality stat map.
*/
function getEmptyStatMap(size) {
var qualityScoreMap = [];
for (i = 1; i <= size; i++) {
qualityScoreMap[i] =
{numKeywords: 0, totalImpressions: 0, totalClicks: 0, totalCost: 0.0};
}
return qualityScoreMap;
}

/**
* Uses the given keyword iterator and populates the given quality score map.
* @param {Iterator} keywordIterator – the keywords to use for getting scores.
* @param {array} qualityScoreMap – the score map to fill with keyword data.
*/
function computeQualityData(keywordIterator, qualityScoreMap) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var stats = keyword.getStatsFor(‘LAST_WEEK’);
var data = qualityScoreMap[keyword.getQualityScore()];
if (data) {
data.numKeywords++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}
}

/**
* Uses the given keyword iterator and populates the given position map.
* @param {Iterator} keywordIterator – the keywords to use for getting scores.
* @param {array} positionMap – the map to fill with keyword data.
*/
function computePositionData(keywordIterator, positionMap) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var stats = keyword.getStatsFor(‘LAST_WEEK’);
var index =
Math.min(Math.ceil(stats.getAveragePosition()), POSITION_MAP_SIZE);
var data = positionMap[index];
data.numKeywords++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided email and throws a descriptive error if the user
* has not changed the email from the default fake address.
*
* @param {string} email The email address.
* @throws {Error} If the email is the default fake address.
*/
function validateEmail(email) {
if (email == ‘[email protected]’) {
throw new Error(‘Please use a valid email address.’);
}
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(
‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como Configurar

 

5. Relatório da Consulta da Pesquisa – Por Google Ads. Este é um bom script para gerar insights de novas palavras-chave que estão gerando resultado para sua conta ou para negativar termos que usuários estão pesquisando sem correlação com seu negócio. O script utiliza o Relatório de desempenho da consulta da pesquisa para encontrar esses termos.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Search Query Report
*
* @overview The Search Query Report script uses the Search Query Performance
* Report to find undesired search terms and add them as negative (or
* positive) exact keywords. See
* //developers.google.com/adwords/scripts/docs/solutions/search-query
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.1
*
* @changelog
* – version 1.0.1
* – Upgrade to API version v201609.
* – version 1.0
* – Released initial version.
*/

// Minimum number of impressions to consider “enough data”
var IMPRESSIONS_THRESHOLD = 100;
// Cost-per-click (in account currency) we consider an expensive keyword.
var AVERAGE_CPC_THRESHOLD = 1; // $1
// Threshold we use to decide if a keyword is a good performer or bad.
var CTR_THRESHOLD = 0.5; // 0.5%
// If ctr is above threshold AND our conversion cost isn’t too high,
// it’ll become a positive keyword.
var COST_PER_CONVERSION_THRESHOLD = 10; // $10
// One currency unit is one million micro amount.
var MICRO_AMOUNT_MULTIPLIER = 1000000;

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

function main() {
var report = AdWordsApp.report(
‘SELECT Query, Clicks, Cost, Ctr, ConversionRate,’ +
‘ CostPerConversion, Conversions, CampaignId, AdGroupId ‘ +
‘ FROM SEARCH_QUERY_PERFORMANCE_REPORT ‘ +
‘ WHERE ‘ +
‘ Conversions > 0’ +
‘ AND Impressions > ‘ + IMPRESSIONS_THRESHOLD +
‘ AND AverageCpc > ‘ +
(AVERAGE_CPC_THRESHOLD * MICRO_AMOUNT_MULTIPLIER) +
‘ DURING LAST_7_DAYS’, REPORTING_OPTIONS);
var rows = report.rows();

var negativeKeywords = {};
var positiveKeywords = {};
var allAdGroupIds = {};
// Iterate through search query and decide whether to
// add them as positive or negative keywords (or ignore).
while (rows.hasNext()) {
var row = rows.next();
if (parseFloat(row[‘Ctr’]) < CTR_THRESHOLD) {
addToMultiMap(negativeKeywords, row[‘AdGroupId’], row[‘Query’]);
allAdGroupIds[row[‘AdGroupId’]] = true;
} else if (parseFloat(row[‘CostPerConversion’]) <
COST_PER_CONVERSION_THRESHOLD) {
addToMultiMap(positiveKeywords, row[‘AdGroupId’], row[‘Query’]);
allAdGroupIds[row[‘AdGroupId’]] = true;
}
}

// Copy all the adGroupIds from the object into an array.
var adGroupIdList = [];
for (var adGroupId in allAdGroupIds) {
adGroupIdList.push(adGroupId);
}

// Add the keywords as negative or positive to the applicable ad groups.
var adGroups = AdWordsApp.adGroups().withIds(adGroupIdList).get();
while (adGroups.hasNext()) {
var adGroup = adGroups.next();
if (negativeKeywords[adGroup.getId()]) {
for (var i = 0; i < negativeKeywords[adGroup.getId()].length; i++) {
adGroup.createNegativeKeyword(
‘[‘ + negativeKeywords[adGroup.getId()][i] + ‘]’);
}
}
if (positiveKeywords[adGroup.getId()]) {
for (var i = 0; i < positiveKeywords[adGroup.getId()].length; i++) {
var keywordOperation = adGroup.newKeywordBuilder()
.withText(‘[‘ + positiveKeywords[adGroup.getId()][i] + ‘]’)
.build();
}
}
}
}

function addToMultiMap(map, key, value) {
if (!map[key]) {
map[key] = [];
}
map[key].push(value);
}

 

Como Configurar

 

6. Relatório Kratu – por Google Ads. Em nível de MCC, o script traz um relatório geral de suas múltiplas contas. Além disso, os resultados são mostrados visualmente com um mapa de calor.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Kratu
*
* @overview The Kratu script is a flexible MCC-level report showing several
* performance signals for each account visually as a heat map. See
* //developers.google.com/adwords/scripts/docs/solutions/kratu
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Fixed bug with run frequency to allow the script to run daily.
* – version 1.0.1
* – Added validation for external spreadsheet setup.
* – Updated reporting version to v201609.
* – version 1.0
* – Released initial version.
*/

var CONFIG = {
// URL to the main / template spreadsheet
SPREADSHEET_URL: ‘YOUR_SPREADSHEET_URL’
};

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

/**
* Main method, coordinate and trigger either new report creation or continue
* unfinished report.
*/
function main() {
init();

if (spreadsheetManager.hasUnfinishedRun()) {
continueUnfinishedReport();
} else {
var reportFrequency = settingsManager.getSetting(‘ReportFrequency’, true);
var lastReportStart = spreadsheetManager.getLastReportStartTimestamp();

if (!lastReportStart ||
dayDifference(lastReportStart, getTimestamp()) >= reportFrequency) {
startNewReport();
} else {
debug(‘Nothing to do’);
}
}
}

/**
* Initialization procedures to be done before anything else.
*/
function init() {
spreadsheetManager.readSignalDefinitions();
settingsManager.readSettings();
}

/**
* Continues an unfinished report. This happens whenever there are accounts
* that are not processed within the last report. This method picks these
* up, processes them and marks the report as completed if no accounts are
* left.
*/
function continueUnfinishedReport() {
debug(‘Continuing unfinished report: ‘ +
spreadsheetManager.getCurrentRunSheet().getUrl());

var iterator = spreadsheetManager.getUnprocessedAccountIterator();
var processed = 0;
while (iterator.hasNext() &&
processed++ < settingsManager.getSetting(‘NumAccountsProcess’, true)) {

var account = iterator.next();
processAccount(account);
}

writeAccountDataToSpreadsheet();

if (processed > 0 && spreadsheetManager.allAccountsProcessed()) {
debug(‘All accounts processed, marking report as complete’);

// Remove protection from sheets, allow changes again
spreadsheetManager.removeProtection();

spreadsheetManager.markRunAsProcessed();
sendEmail();
}

debug(‘Processed ‘ + processed + ‘ accounts’);
}

/**
* Creates a new report by copying the report template to a new spreadsheet,
* gathering all accounts under the MCC and mark them as not processed.
* Please note that this method will not actually process any accounts.
*/
function startNewReport() {
debug(‘Creating new report’);

// Protect the sheets that shouldn’t be changed during execution
spreadsheetManager.setProtection();

// Delete all account info
spreadsheetManager.clearAllAccountInfo();

// Iterate over accounts
var accountSelector = MccApp.accounts();
var accountLabel = settingsManager.getSetting(‘AccountLabel’, false);
if (accountLabel) {
accountSelector.withCondition(“LabelNames CONTAINS ‘” + accountLabel + “‘”);
}
var accountIterator = accountSelector.get();

while (accountIterator.hasNext()) {
var account = accountIterator.next();
debug(‘Adding account: ‘ + account.getCustomerId());

spreadsheetManager.addAccount(account.getCustomerId());
}

// Now add the run
var newRunSheet = spreadsheetManager.addRun();
debug(‘New report created at ‘ + newRunSheet.getUrl());
}

/**
* Processes a single account.
*
* @param {object} account the AdWords account object
*/
function processAccount(account) {
debug(‘- Processing ‘ + account.getCustomerId());
MccApp.select(account);
signalManager.processAccount(account);

spreadsheetManager.markAccountAsProcessed(account.getCustomerId());
}

/**
* After processing & gathering data for all accounts,
* write it to the spreadsheet.
*/
function writeAccountDataToSpreadsheet() {
var accountInfos = signalManager.getAccountInfos();

spreadsheetManager.writeHeaderRow();

for (var i = 0; i < accountInfos.length; i++) {
var accountInfo = accountInfos[i];
spreadsheetManager.writeDataRow(accountInfo);
}
}

/**
* Sends email if an email was provided in the settings.
* Otherwise does nothing.
*/
var sendEmail = function() {
var recipientEmail = settingsManager.getSetting(‘RecipientEmail’, false);

if (recipientEmail) {
MailApp.sendEmail(recipientEmail,
‘Kratu Report is ready’,
spreadsheetManager.getCurrentRunSheet().getUrl());
debug(‘Email sent to ‘ + recipientEmail);
}
};

/**
* Returns the number of days between two timestamps.
*
* @param {number} time1 the newer (more recent) timestamps
* @param {number} time2 the older timestamps
* @return {number} number of full days between the given dates
*/
var dayDifference = function(time1, time2) {
return parseInt((time2 – time1) / (24 * 3600 * 1000));
};

/**
* Returns the current timestamp.
*
* @return {number} the current timestamp
*/
function getTimestamp() {
return new Date().getTime();
}

/**
* Module for calculating account signals and infos to be shown in the report.
*
* @return {object} callable functions corresponding to the available
* actions
*/
var signalManager = (function() {
var accountInfos = new Array();

/**
* Processes one account, which in 2 steps adds an accountInfo object
* to the list.
* – Calculate the raw signals
* – Postprocess the raw signals (normalize scores, …)
*
* @param {object} account the AdWords account object
*/
var processAccount = function(account) {
var rawSignals = calculateRawSignals(account);

var accountInfo = {
account: account,
rawSignals: rawSignals
};

processSignals(accountInfo);

accountInfos.push(accountInfo);
};

/**
* Returns an array of all processed accounts so far. These are ordered by
* decreasing score.
*
* @return {object} array of the accountInfo objects
*/
var getAccountInfos = function() {
accountInfos.sort(function(a, b) {
return b.score – a.score;
});

return accountInfos;
};

/**
* Normalizes a raw signal value based in the signal’s definition
* (min, max values).
*
* @param {object} signalDefinition definition of the signal
* @param {number} value numeric value of that signal
* @return {number} the normalized value
*/
var normalize = function(signalDefinition, value) {
var min = signalDefinition.min;
var max = signalDefinition.max;

if (signalDefinition.direction == ‘High’) {
if (value >= max)
return 1;
if (value <= min)
return 0;

return (value – min) / (max – min);
} else if (signalDefinition.direction == ‘Low’) {
if (value >= max)
return 0;
if (value <= min)
return 1;

return 1 – ((value – min) / (max – min));
} else {
return value;
}
};

/**
* Post-processes the raw signals.
*
* @param {object} accountInfo the object storing all info about that account
* (including raw signals)
*/
var processSignals = function(accountInfo) {
var signalDefinitions = spreadsheetManager.getSignalDefinitions();
var sumWeights = spreadsheetManager.getSumWeights();
var sumScore = 0;

accountInfo.signals = {};

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var value = accountInfo.rawSignals[signalDefinition.name];

accountInfo.signals[signalDefinition.name] = {
definition: signalDefinition,
value: value,
displayValue: value
};

if (signalDefinition.type == ‘Number’) {
var normalizedValue = normalize(signalDefinition, value);
var signalScore = normalizedValue * signalDefinition.weight;
sumScore += signalScore;

accountInfo.signals[signalDefinition.name].normalizedValue =
normalizedValue;
accountInfo.signals[signalDefinition.name].signalScore = signalScore;
}
}
}

accountInfo.scoreSum = sumScore;
accountInfo.scoreWeights = sumWeights;
accountInfo.score = sumScore / sumWeights;
};

/**
* Calculate the raw signals.
*
* @param {object} account the AdWords account object
* @return {object} an associative array containing raw signals
* (as name -> value pairs)
*/
var calculateRawSignals = function(account) {
// Use reports for signal creation, dynamically create an AWQL query here
var signalDefinitions = spreadsheetManager.getSignalDefinitions();

var signalFields = [];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
signalFields.push(signalDefinition.name);
}

var query = ‘SELECT ‘ + signalFields.join(‘,’) +
‘ FROM ACCOUNT_PERFORMANCE_REPORT DURING ‘ +
settingsManager.getSetting(‘ReportPeriod’, true);

var report = AdWordsApp.report(query, REPORTING_OPTIONS);
var rows = report.rows();

// analyze the rows (should be only one)
var rawSignals = {};
while (rows.hasNext()) {
var row = rows.next();

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];

var value = row[signalDefinition.name];
if (value.indexOf(‘%’) > -1) {
value = parseFloat(value) / 100.0;
}

rawSignals[signalDefinition.name] = value;
}

}

return rawSignals;
};

// Return the external interface.
return {
processAccount: processAccount,
getAccountInfos: getAccountInfos
};

})();

/**
* Module for interacting with the spreadhsheets. Offers several
* functions that other modules can use when storing / retrieving data
* In general, there are two spreadsheets involved:
* – a main spreadsheet containing processing information, settings
* and a template for the reports
* – a report spreadsheet for each run (one loop over all accounts)
*
* @return {object} callable functions corresponding to the available
* actions
*/
var spreadsheetManager = (function() {
validateConfig();
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
var currentRunSheet = null;
var accountsTab = spreadsheet.getSheetByName(‘Accounts’);
var historyTab = spreadsheet.getSheetByName(‘History’);
var signalsTab = spreadsheet.getSheetByName(‘Signals’);
var settingsTab = spreadsheet.getSheetByName(‘Settings’);
var templateTab = spreadsheet.getSheetByName(‘Template’);
var processedAccounts = 0;
var signalDefinitions;
var sumWeights;

/**
* Adds protection and notes to all sheets that should not be
* changed while a report is being processed.
*/
var setProtection = function() {
setSheetProtection(signalsTab);
setSheetProtection(settingsTab);
setSheetProtection(templateTab);
};

/**
* Adds protection and notes to a sheet / tab.
*
* @param {object} the sheet to add protection to
*/
var setSheetProtection = function(tab) {
var protection = tab.protect().setDescription(tab.getName() +
‘ Protection’);

protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
tab.getRange(‘A1’).setNote(‘A report is currently being executed, ‘ +
‘you can not edit this sheet until it is finished.’);
};

/**
* Adds a protection and notes to all sheets that should not be
* changed while a report is being processed.
*/
var removeProtection = function() {
removeSheetProtection(signalsTab);
removeSheetProtection(settingsTab);
removeSheetProtection(templateTab);
};

/**
* Remove the protection from a sheet / tab.
*
* @param {object} the sheet to remove protection from
*/
var removeSheetProtection = function(tab) {
var protection = tab.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
if (protection && protection.canEdit()) {
protection.remove();
}
tab.clearNotes();
};

/**
* Reads and returns the range of settings in the main spreadsheet.
*
* @return {object} the range object containing all settings
*/
var readSettingRange = function() {
return settingsTab.getRange(2, 1, settingsTab.getLastRow(), 3);
};

/**
* Read and return the signal definitions as defined in the Signals tab
* of the general spreadsheet. See below for how a signal definition object
* looks like.
*
* @param {object} range the range of cells
* @return {object} an array of signal definition objects
*/
var readSignalDefinitions = function() {
signalDefinitions = new Array();

var range = signalsTab.getRange(2, 1, signalsTab.getLastRow(), 9);
var values = range.getValues();
for (var i = 0; i < range.getNumRows(); i++) {
if (values[i][0] == ”)
continue;

var signalDefinition = {
name: values[i][0],
displayName: values[i][1],
includeInReport: values[i][2],
type: values[i][3],
direction: values[i][4],
format: values[i][5],
weight: values[i][6],
min: values[i][7],
max: values[i][8]
};

signalDefinitions.push(signalDefinition);
}

calculateSumWeights();

debug(‘Using ‘ + signalDefinitions.length + ‘ signals’);
};

/**
* Returns an array of signal definitions to work with.
*
* @return {object} array of signal definitions to work with
*/
var getSignalDefinitions = function() {
return signalDefinitions;
};

/**
* Returns the sum of weights of all signal definitions
*
* @return {number} sum of weights of all signal definitions
*/
var getSumWeights = function() {
return sumWeights;
};

/**
* Calculates the overall sum of score weights for normalization of the score.
*/
var calculateSumWeights = function() {
sumWeights = 0;

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.type == ‘Number’ &&
signalDefinition.includeInReport == ‘Yes’) {
sumWeights += signalDefinition.weight;
}
}
};

/**
* Adds a “run” (loop over all accounts) to the general spreadsheet.
*/
var addRun = function() {
// use formatted date in spreadsheet name and date cell
var timezone = AdWordsApp.currentAccount().getTimeZone();
var formattedDate = Utilities.formatDate(new Date(),
timezone, ‘MMM dd, yyyy’);

var runSpreadsheet = spreadsheet.copy(spreadsheet.getName() +
‘ – ‘ + formattedDate);

runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Accounts’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘History’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Settings’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Parameters’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Signals’));
runSpreadsheet.getSheetByName(‘Template’).setName(‘Report’);
removeSheetProtection(runSpreadsheet.getSheetByName(‘Report’));

historyTab.appendRow([getTimestamp(), null, runSpreadsheet.getUrl()]);
historyTab.getRange(historyTab.getLastRow(), 1, 1, 3).clearFormat();

runSpreadsheet.getRangeByName(‘AccountID’).setValue(
AdWordsApp.currentAccount().getCustomerId());
runSpreadsheet.getRangeByName(‘Date’).setValue(formattedDate);

return runSpreadsheet;
};

/**
* Checks if there is an unfinished (=not all accounts processed yet)
* report in the run history list.
*
* @return {boolean} whether there is an unfinished report
*/
var hasUnfinishedRun = function() {
var lastRow = historyTab.getLastRow();

// has no run at all
if (lastRow == 1) {
return false;
}

var lastRunEndDate = historyTab.getRange(lastRow, 2, 1, 1).getValue();
if (lastRunEndDate) {
return false;
}

return true;
};

/**
* Marks the current report (a.k.a run) as finished by adding an end date.
*/
var markRunAsProcessed = function() {
var lastRow = historyTab.getLastRow();
if (lastRow > 1) {
historyTab.getRange(lastRow, 2, 1, 1).setValue(getTimestamp());
}
};

/**
* Returns the start timestamp of the last unfinished report.
*
* @return {number} the timestamp of the last unfinished report (null if
* there is none)
*/
var getLastReportStartTimestamp = function() {
var lastRow = historyTab.getLastRow();
if (lastRow > 1) {
return historyTab.getRange(lastRow, 1, 1, 1).getValue();
} else {
return null;
}
};

/**
* Returns the current run sheet to be used for report generation.
* This is always the last one in the History tab of the general sheet.
*
* @return {object} the current run sheet
*/
var getCurrentRunSheet = function() {
if (currentRunSheet != null)
return currentRunSheet;

var range = historyTab.getRange(historyTab.getLastRow(), 3, 1, 1);
var url = range.getValue();
currentRunSheet = SpreadsheetApp.openByUrl(url);
return currentRunSheet;
};

/**
* Adds an account to the list of ‘known’ accounts.
*
* @param {string} cid the cid of the account
*/
var addAccount = function(cid) {
var maxRow = accountsTab.appendRow([cid]);
accountsTab.getRange(accountsTab.getLastRow(), 1, 1, 2).clearFormat();
};

/**
* Marks an account as processed in the general sheet. Like this,
* the script can be executed several times and will always
* run for a batch of unprocessed accounts.
*
* @param {string} cid the customer id of the account that has been processed
*/
var markAccountAsProcessed = function(cid) {
var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

var values = range.getValues();
for (var i = 0; i < range.getNumRows(); i++) {
var rowCid = values[i][0];
if (cid == rowCid) {
accountsTab.getRange(i + 2, 2).setValue(getTimestamp());
processedAccounts++;
}
}

};

/**
* Clears the list of ‘known’ accounts.
*/
var clearAllAccountInfo = function() {
var lastRow = accountsTab.getLastRow();

if (lastRow > 1) {
accountsTab.deleteRows(2, lastRow – 1);
}
};

/**
* Creates a selector for the next batch of accounts that are not
* processed yet.
*
* @return {object} a selector that can be used for parallel processing or
* getting an iterator
*/
var getUnprocessedAccountIterator = function() {
var accounts = getUnprocessedAccounts();

var selector = MccApp.accounts().withIds(accounts);
var iterator = selector.get();
return iterator;
};

/**
* Reads and returns the next batch of unprocessed accounts from the general
* spreadsheet.
*
* @return {object} an array of unprocessed cids
*/
var getUnprocessedAccounts = function() {
var accounts = [];

var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

for (var i = 0; i < range.getNumRows(); i++) {
var cid = range.getValues()[i][0];
var processed = range.getValues()[i][1];

if (processed != ” || accounts.length >=
settingsManager.getSetting(‘NumAccountsProcess’, true)) {
continue;
}

accounts.push(cid);
}

return accounts;
};

/**
* Scans the list of accounts and returns true if all of them
* are processed.
*
* @return {boolean} true, if all accounts are processed
*/
var allAccountsProcessed = function() {
var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

for (var i = 0; i < range.getNumRows(); i++) {
var cid = range.getValues()[i][0];
var processed = range.getValues()[i][1];

if (processed) {
continue;
}

return false;
}

return true;
};

/**
* Writes the data headers (signal names) in the current run sheet.
*/
var writeHeaderRow = function() {
var sheet = getCurrentRunSheet();
var reportTab = sheet.getSheetByName(‘Report’);

var row = [”];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
row.push(signalDefinition.displayName);
}
}
row.push(‘Score’);

var range = reportTab.getRange(4, 1, 1, row.length);
range.setValues([row]);
range.clearFormat();
range.setFontWeight(‘bold’);
range.setBackground(‘#38c’);
range.setFontColor(‘#fff’);
};

/**
* Writes a row of data (signal values) in the current run sheet.
*
* @param {object} accountInfo the accountInfo object containing the
* calculated signals
*/
var writeDataRow = function(accountInfo) {
// prepare the data
var sheet = getCurrentRunSheet();
var tab = sheet.getSheetByName(‘Report’);

var row = [”];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var displayValue =
accountInfo.signals[signalDefinition.name].displayValue;

row.push(displayValue);
}
}
row.push(accountInfo.score);

// write it
tab.appendRow(row);

// now do the formatting
var currentRow = tab.getLastRow();
var rowRange = tab.getRange(currentRow, 1, 1, row.length);
rowRange.clearFormat();

// arrays for number formats and colors, first fill them with values
// and later apply to the row
var dataRange = tab.getRange(currentRow, 2, 1, row.length – 1);
var fontColors = [[]];
var backgroundColors = [[]];
var numberFormats = [[]];
var colIndex = 0;

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var value = accountInfo.signals[signalDefinition.name].value;
var displayValue =
accountInfo.signals[signalDefinition.name].displayValue;
var normalizedValue =
accountInfo.signals[signalDefinition.name].normalizedValue;

var colors = [2];
if (signalDefinition.type == ‘Number’) {
numberFormats[0][colIndex] = signalDefinition.format;
colors = getNumberColors(normalizedValue);
} else if (signalDefinition.type == ‘String’) {
colors = getStringColors(value);
}

fontColors[0][colIndex] = colors[0];
backgroundColors[0][colIndex] = colors[1];

colIndex++;
}
}

// formatting for the score (last column)
numberFormats[0][colIndex] = ‘0.00%’;
var scoreColors = getNumberColors(accountInfo.score);
fontColors[0][colIndex] = scoreColors[0];
backgroundColors[0][colIndex] = scoreColors[1];

// now actually apply the formats
dataRange.setNumberFormats(numberFormats);
dataRange.setFontColors(fontColors);
dataRange.setBackgroundColors(backgroundColors);
};

/**
* Helper method for creating the array of colors based on the given
* setting names.
*
* @param {string} settingFontColor name of the setting to use as font color
* @param {string} settingBackgroundColor name of the setting to use as
* background color
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getColors = function(settingFontColor, settingBackgroundColor) {
var colors = [];

colors[0] = settingsManager.getSetting(settingFontColor, false);
colors[1] = settingsManager.getSetting(settingBackgroundColor, false);

return colors;
};

/**
* Helper method for returning the “string” colors for a certain value.
*
* @param {string} stringValue the value of the cell
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getStringColors = function(stringValue) {
return getColors(‘StringFgColor’, ‘StringBgColor’);
};

/**
* Helper method for applying the “number” format to a certain range.
* Numeric value cells have different formats depending on their score value
* (defined by the settings), this method applies these formats.
*
* @param {number} numericValue the value of the cell
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getNumberColors = function(numericValue) {
var level1MinValue = settingsManager.getSetting(‘Level1MinValue’, false);
var level2MinValue = settingsManager.getSetting(‘Level2MinValue’, false);
var level3MinValue = settingsManager.getSetting(‘Level3MinValue’, false);
var level4MinValue = settingsManager.getSetting(‘Level4MinValue’, false);
var level5MinValue = settingsManager.getSetting(‘Level5MinValue’, false);

if (level5MinValue && numericValue > level5MinValue) {
return getColors(‘Level5FgColor’, ‘Level5BgColor’);
} else if (level4MinValue && numericValue > level4MinValue) {
return getColors(‘Level4FgColor’, ‘Level4BgColor’);
} else if (level3MinValue && numericValue > level3MinValue) {
return getColors(‘Level3FgColor’, ‘Level3BgColor’);
} else if (level2MinValue && numericValue > level2MinValue) {
return getColors(‘Level2FgColor’, ‘Level2BgColor’);
} else if (level1MinValue && numericValue > level1MinValue) {
return getColors(‘Level1FgColor’, ‘Level1BgColor’);
}

// if no level reached, no coloring
var defaultColors = [null, null];
return defaultColors;
};

// Return the external interface.
return {
setProtection: setProtection,
removeProtection: removeProtection,
readSettingRange: readSettingRange,
readSignalDefinitions: readSignalDefinitions,
getSignalDefinitions: getSignalDefinitions,
getSumWeights: getSumWeights,
addRun: addRun,
hasUnfinishedRun: hasUnfinishedRun,
markRunAsProcessed: markRunAsProcessed,
getLastReportStartTimestamp: getLastReportStartTimestamp,
getCurrentRunSheet: getCurrentRunSheet,
addAccount: addAccount,
markAccountAsProcessed: markAccountAsProcessed,
clearAllAccountInfo: clearAllAccountInfo,
getUnprocessedAccountIterator: getUnprocessedAccountIterator,
allAccountsProcessed: allAccountsProcessed,
writeHeaderRow: writeHeaderRow,
writeDataRow: writeDataRow
};

})();

/**
* Module responsible for maintaining a list of common settings. These
* settings are read from the general spreadsheet (using the
* spreadsheetManager) and are then retrieved by other modules during
* processing.
*
* @return {object} callable functions corresponding to the available
* actions
*/
var settingsManager = (function() {
var settings = [];

/**
* Reads the settings from the general spreadsheet.
*/
var readSettings = function() {
var settingsRange = spreadsheetManager.readSettingRange();

for (var i = 1; i <= settingsRange.getNumRows(); i++) {
var key = settingsRange.getCell(i, 1).getValue();
var type = settingsRange.getCell(i, 2).getValue();
var value = settingsRange.getCell(i, 3).getValue();

if (type == ‘Color’) {
value = settingsRange.getCell(i, 3).getBackground();
}

if (!key || !value) {
continue;
}

var setting = {
key: key,
type: type,
value: value
};

settings.push(setting);
}

debug(‘Read ‘ + settings.length + ‘ settings’);
};

/**
* Returns the value of a particular setting.
*
* @param {string} key the name of the setting
* @param {boolean} mandatory flag indicating this is a mandatory setting
* (has to return a value)
* @return {object} the value of the setting
*/
var getSetting = function(key, mandatory) {
for (var i = 0; i < settings.length; i++) {
var setting = settings[i];
if (setting.key == key && setting.value)
return setting.value;
}

if (mandatory) {
throw ‘Setting \” + key + ‘\’ is not set!’;
}

return null;
};

// Return the external interface.
return {
readSettings: readSettings,
getSetting: getSetting
};
})();

/**
* Wrapper for Logger.log.
*
* @param {string} t The text to log
*/
function debug(t) {
Logger.log(t);
}

/**
* Validates the provided spreadsheet URL to make sure that it’s set up
* properly. Throws a descriptive error message if validation fails.
*
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateConfig() {
if (CONFIG.SPREADSHEET_URL == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
}

 

Como Configurar

 

7. PageSpeed Insights: análise para dispositivos móveis – Por Google Ads. O Google tem enfatizado a experiência mobile cada vez mais em suas atualizações, sendo assim, esse script traz um relatório do Google Page Insights para suas páginas de destino.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Mobile Performance from PageSpeed Insights – Single Account
*
* Produces a report showing how well landing pages are set up for mobile
* devices and highlights potential areas for improvement. See :
* //developers.google.com/adwords/scripts/docs/solutions/mobile-pagespeed
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.3.3
*
* @changelog
* – version 1.3.3
* – Added guidance for desktop analysis.
* – version 1.3.2
* – Bugfix to improve table sorting comparator.
* – version 1.3.1
* – Bugfix for handling the absence of optional values in PageSpeed response.
* – version 1.3
* – Removed the need for the user to take a copy of the spreadsheet.
* – Added the ability to customize the Campaign and Ad Group limits.
* – version 1.2.1
* – Improvements to time zone handling.
* – version 1.2
* – Bug fix for handling empty results from URLs.
* – Error reporting in spreadsheet for failed URL fetches.
* – version 1.1
* – Updated the comments to be in sync with the guide.
* – version 1.0
* – Released initial version.
*/

// See “Obtaining an API key” at
// //developers.google.com/adwords/scripts/docs/solutions/mobile-pagespeed
var API_KEY = ‘INSERT_PAGESPEED_API_KEY_HERE’;
var EMAIL_RECIPIENTS = [‘INSERT_EMAIL_ADDRESS_HERE’];

// If you wish to add extra URLs to checked, list them here as a
// comma-separated list eg. [‘//abc.xyz’, ‘//www.google.com’]
var EXTRA_URLS_TO_CHECK = [];

// By default, the script returns analysis of how the site performs on mobile.
// Change the following from ‘mobile’ to ‘desktop’ to perform desktop analysis.
var PLATFORM_TYPE = ‘mobile’;

/**
* The URL of the template spreadsheet for each report created.
*/
var SPREADSHEET_TEMPLATE =
‘//docs.google.com/spreadsheets/d/1SKLXUiorvgs2VuPKX7NGvcL68pv3xEqD7ZcqsEwla4M/edit’;

var PAGESPEED_URL =
‘//www.googleapis.com/pagespeedonline/v2/runPagespeed?’;

/*
* The maximum number of Campaigns to sample within the account.
*/
var CAMPAIGN_LIMIT = 50000;

/*
* The maximum number of Ad Groups to sample within the account.
*/
var ADGROUP_LIMIT = 50000;

/**
* These are the sampling limits for how many of each element will be examined
* in each AdGroup.
*/
var KEYWORD_LIMIT = 20;
var SITELINK_LIMIT = 20;
var AD_LIMIT = 30;

/**
* Specifies the amount of time in seconds required to do the URL fetching and
* result generation. As this is the last step, entities in the account will be
* iterated over until this point.
*/
var URL_FETCH_TIME_SECS = 8 * 60;

/**
* Specifies the amount of time in seconds required to write to and format the
* spreadsheet.
*/
var SPREADSHEET_PREP_TIME_SECS = 4 * 60;

/**
* Represents the number of retries to use with the PageSpeed service.
*/
var MAX_RETRIES = 3;

/**
* The main entry point for execution.
*/
function main() {
if (!defaultsChanged()) {
Logger.log(‘Please change the default configuration values and retry’);
return;
}
var accountName = AdWordsApp.currentAccount().getName();
var urlStore = getUrlsFromAccount();
var result = getPageSpeedResultsForUrls(urlStore);
var spreadsheet = createPageSpeedSpreadsheet(accountName +
‘: PageSpeed Insights – Mobile Analysis’, result);
spreadsheet.addEditors(EMAIL_RECIPIENTS);
sendEmail(spreadsheet.getUrl());
}

/**
* Sends an email to the user with the results of the run.
*
* @param {string} url URL of the spreadsheet.
*/
function sendEmail(url) {
var footerStyle = ‘color: #aaaaaa; font-style: italic;’;
var scriptsLink = ‘//developers.google.com/adwords/scripts/’;
var subject = ‘AdWords PageSpeed URL-Sampling Script Results – ‘ +
getDateStringInTimeZone(‘dd MMM yyyy’);
var htmlBody = ‘<html><body>’ +
‘<p>Hello,</p>’ +
‘<p>An AdWords Script has run successfully and the output is available ‘ +
‘here:’ +
‘<ul><li><a href=”‘ + url +
‘”>AdWords PageSpeed URL-Sampling Script Results</a></li></ul></p>’ +
‘<p>Regards,</p>’ +
‘<span style=”‘ + footerStyle + ‘”>This email was automatically ‘ +
‘generated by <a href=”‘ + scriptsLink + ‘”>AdWords Scripts</a>.<span>’ +
‘</body></html>’;
var body = ‘Please enable HTML to view this report.’;
var options = {htmlBody: htmlBody};
MailApp.sendEmail(EMAIL_RECIPIENTS, subject, body, options);
}

/**
* Checks to see that placeholder defaults have been changed.
*
* @return {boolean} true if placeholders have been changed, false otherwise.
*/
function defaultsChanged() {
if (API_KEY == ‘INSERT_PAGESPEED_API_KEY_HERE’ ||
SPREADSHEET_TEMPLATE == ‘INSERT_SPREADSHEET_URL_HERE’ ||
JSON.stringify(EMAIL_RECIPIENTS) ==
JSON.stringify([‘INSERT_EMAIL_ADDRESS_HERE’])) {
return false;
}
return true;
}

/**
* Creates a new PageSpeed spreadsheet and populates it with result data.
*
* @param {string} name The name to give to the spreadsheet.
* @param {Object} pageSpeedResult The result from PageSpeed, and the number of
* URLs that could have been chosen from.
* @return {Spreadsheet} The newly-created spreadsheet.
*/
function createPageSpeedSpreadsheet(name, pageSpeedResult) {
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_TEMPLATE).copy(name);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
var data = pageSpeedResult.table;
var activeSheet = spreadsheet.getActiveSheet();
var rowStub = spreadsheet.getRangeByName(‘ResultRowStub’);
var top = rowStub.getRow();
var left = rowStub.getColumn();
var cols = rowStub.getNumColumns();
if (data.length > 2) { // No need to extend the template if num rows <= 2
activeSheet.insertRowsAfter(
spreadsheet.getRangeByName(‘EmptyUrlRow’).getRow(), data.length);
rowStub.copyTo(activeSheet.getRange(top + 1, left, data.length – 2, cols));
}
// Extend the formulas and headings to accommodate the data.
if (data.length && data[0].length > 4) {
var metricsRange = activeSheet
.getRange(top – 6, left + cols, data.length + 5, data[0].length – 4);
activeSheet.getRange(top – 6, left + cols – 1, data.length + 5)
.copyTo(metricsRange);
// Paste in the data values.
activeSheet.getRange(top – 1, left, data.length, data[0].length)
.setValues(data);
// Move the ‘Powered by AdWords Scripts’ to right corner of table.
spreadsheet.getRangeByName(‘PoweredByText’).moveTo(activeSheet.getRange(1,
data[0].length + 1, 1, 1));
// Set summary – date and number of URLs chosen from.
var summaryDate = getDateStringInTimeZone(‘dd MMM yyyy’);
spreadsheet.getRangeByName(‘SummaryDate’).setValue(‘Summary as of ‘ +
summaryDate + ‘. Results drawn from ‘ + pageSpeedResult.totalUrls +
‘ URLs.’);
}
// Add errors if they exist
if (pageSpeedResult.errors.length) {
var nextRow = spreadsheet.getRangeByName(‘FirstErrorRow’).getRow();
var errorRange = activeSheet.getRange(nextRow, 2,
pageSpeedResult.errors.length, 2);
errorRange.setValues(pageSpeedResult.errors);
}
return spreadsheet;
}

/**
* This function takes a collection of URLs as provided by the UrlStore object
* and gets results from the PageSpeed service. However, two important things :
* (1) It only processes a handful, as determined by URL_LIMIT.
* (2) The URLs returned from iterating on the UrlStore are in a specific
* order, designed to produce as much variety as possible (removing the need
* to process all the URLs in an account.
*
* @param {UrlStore} urlStore Object containing URLs to process.
* @return {Object} An object with three properties: ‘table’ – the 2d table of
* results, ‘totalUrls’ – the number of URLs chosen from, and errors.
*/
function getPageSpeedResultsForUrls(urlStore) {
var count = 0;
// Associative array for column headings and contextual help URLs.
var headings = {};
var errors = {};
// Record results on a per-URL basis.
var pageSpeedResults = {};
var urlTotalCount = 0;

for (var url in urlStore) {
if (hasRemainingTimeForUrlFetches()) {
var result = getPageSpeedResultForSingleUrl(url);
if (!result.error) {
pageSpeedResults[url] = result.pageSpeedInfo;
var columnsResult = result.columnsInfo;
// Loop through each heading element; the PageSpeed Insights API
// doesn’t always return URLs for each column heading, so aggregate
// these across each call to get the most complete list.
var columnHeadings = Object.keys(columnsResult);
for (var i = 0, lenI = columnHeadings.length; i < lenI; i++) {
var columnHeading = columnHeadings[i];
if (!headings[columnHeading] || (headings[columnHeading] &&
headings[columnHeading].length <
columnsResult[columnHeading].length)) {
headings[columnHeading] = columnsResult[columnHeading];
}
}
} else {
errors[url] = result.error;
}
count++;
}
urlTotalCount++;
}

var tableHeadings = [‘URL’, ‘Speed’, ‘Usability’];
var headingKeys = Object.keys(headings);
for (var y = 0, lenY = headingKeys.length; y < lenY; y++) {
tableHeadings.push(headingKeys[y]);
}

var table = [];
var pageSpeedResultsUrls = Object.keys(pageSpeedResults);
for (var r = 0, lenR = pageSpeedResultsUrls.length; r < lenR; r++) {
var resultUrl = pageSpeedResultsUrls[r];
var row = [toPageSpeedHyperlinkFormula(resultUrl)];
var data = pageSpeedResults[resultUrl];
for (var j = 1, lenJ = tableHeadings.length; j < lenJ; j++) {
row.push(data[tableHeadings[j]]);
}
table.push(row);
}
// Present the table back in the order worst-performing-first.
table.sort(function(first, second) {
var f1 = isNaN(parseInt(first[1])) ? 0 : parseInt(first[1]);
var f2 = isNaN(parseInt(first[2])) ? 0 : parseInt(first[2]);
var s1 = isNaN(parseInt(second[1])) ? 0 : parseInt(second[1]);
var s2 = isNaN(parseInt(second[2])) ? 0 : parseInt(second[2]);

if (f1 + f2 < s1 + s2) {
return -1;
} else if (f1 + f2 > s1 + s2) {
return 1;
}
return 0;
});

// Add hyperlinks to all column headings where they are available.
for (var h = 0, lenH = tableHeadings.length; h < lenH; h++) {
// Sheets cannot have multiple links in a single cell at the moment :-/
if (headings[tableHeadings[h]] &&
typeof(headings[tableHeadings[h]]) === ‘object’) {
tableHeadings[h] = ‘=HYPERLINK(“‘ + headings[tableHeadings[h]][0] +
‘”,”‘ + tableHeadings[h] + ‘”)’;
}
}

// Form table from errors
var errorTable = [];
var errorKeys = Object.keys(errors);
for (var k = 0; k < errorKeys.length; k++) {
errorTable.push([errorKeys[k], errors[errorKeys[k]]]);
}
table.unshift(tableHeadings);
return {
table: table,
totalUrls: urlTotalCount,
errors: errorTable
};
}

/**
* Given a URL, returns a spreadsheet formula that displays the URL yet links to
* the PageSpeed URL for examining this.
*
* @param {string} url The URL to embed in the Hyperlink formula.
* @return {string} A string representation of the spreadsheet formula.
*/
function toPageSpeedHyperlinkFormula(url) {
return ‘=HYPERLINK(“‘ +
‘//developers.google.com/speed/pagespeed/insights/?url=’ + url +
‘&tab=’ + PLATFORM_TYPE +'”,”‘ + url + ‘”)’;
}

/**
* Creates an object of results metrics from the parsed results of a call to
* the PageSpeed service.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array with entries for each metric.
*/
function extractResultRow(parsedPageSpeedResponse) {
var urlScores = {};
if (parsedPageSpeedResponse.ruleGroups) {
var ruleGroups = parsedPageSpeedResponse.ruleGroups;
// At least one of the SPEED or USABILITY properties will exist, but not
// necessarily both.
urlScores.Speed = ruleGroups.SPEED ? ruleGroups.SPEED.score : ‘-‘;
urlScores.Usability = ruleGroups.USABILITY ?
ruleGroups.USABILITY.score : ‘-‘;
}
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
urlScores[part.localizedRuleName] = part.ruleImpact;
}
}
return urlScores;
}

/**
* Extracts the headings for the metrics returned from PageSpeed, and any
* associated help URLs.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array used to store column-headings seen in
* the response. This can take two forms:
* (1) {‘heading’:’heading’, …} – this form is where no help URLs are
* known.
* (2) {‘heading’: [url1, …]} – where one or more URLs is returned that
* provides help on the particular heading item.
*/
function extractColumnsInfo(parsedPageSpeedResponse) {
var columnsInfo = {};
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
if (!columnsInfo[part.localizedRuleName]) {
columnsInfo[part.localizedRuleName] = part.localizedRuleName;
}
// Find help URLs in the response
var summary = part.summary;
if (summary && summary.args) {
var argList = summary.args;
for (var i = 0, lenI = argList.length; i < lenI; i++) {
var arg = argList[i];
if ((arg.type) && (arg.type == ‘HYPERLINK’) &&
(arg.key) && (arg.key == ‘LINK’) &&
(arg.value)) {
columnsInfo[part.localizedRuleName] = [arg.value];
}
}
}
if (part.urlBlocks) {
var blocks = part.urlBlocks;
var urls = [];
for (var j = 0, lenJ = blocks.length; j < lenJ; j++) {
var block = blocks[j];
if (block.header) {
var header = block.header;
if (header.args) {
var args = header.args;
for (var k = 0, lenK = args.length; k < lenK; k++) {
var argument = args[k];
if ((argument.type) &&
(argument.type == ‘HYPERLINK’) &&
(argument.key) &&
(argument.key == ‘LINK’) &&
(argument.value)) {
urls.push(argument.value);
}
}
}
}
}
if (urls.length > 0) {
columnsInfo[part.localizedRuleName] = urls;
}
}
}
}
return columnsInfo;
}

/**
* Extracts a suitable error message to display for a failed URL. The error
* could be passed in in the nested PageSpeed error format, or there could have
* been a more fundamental error in the fetching of the URL. Extract the
* relevant message in each case.
*
* @param {string} errorMessage The error string.
* @return {string} A formatted error message.
*/
function formatErrorMessage(errorMessage) {
var formattedMessage = null;
if (!errorMessage) {
formattedMessage = ‘Unknown error message’;
} else {
try {
var parsedError = JSON.parse(errorMessage);
// This is the nested structure expected from PageSpeed
if (parsedError.error && parsedError.error.errors) {
var firstError = parsedError.error.errors[0];
formattedMessage = firstError.message;
} else if (parsedError.message) {
formattedMessage = parsedError.message;
} else {
formattedMessage = errorMessage.toString();
}
} catch (e) {
formattedMessage = errorMessage.toString();
}
}
return formattedMessage;
}

/**
* Calls the PageSpeed API for a single URL, and attempts to parse the resulting
* JSON. If successful, produces an object for the metrics returned, and an
* object detailing the headings and help URLs seen.
*
* @param {string} url The URL to run PageSpeed for.
* @return {Object} An object with pageSpeed metrics, column-heading info
* and error properties.
*/
function getPageSpeedResultForSingleUrl(url) {
var parsedResponse = null;
var errorMessage = null;
var retries = 0;

while ((!parsedResponse || parsedResponse.responseCode !== 200) &&
retries < MAX_RETRIES) {
errorMessage = null;
var fetchResult = checkUrl(url);
if (fetchResult.responseText) {
try {
parsedResponse = JSON.parse(fetchResult.responseText);
break;
} catch (e) {
errorMessage = formatErrorMessage(e);
}
} else {
errorMessage = formatErrorMessage(fetchResult.error);
}
retries++;
Utilities.sleep(1000 * Math.pow(2, retries));
}
if (!errorMessage) {
var columnsInfo = extractColumnsInfo(parsedResponse);
var urlScores = extractResultRow(parsedResponse);
}
return {
pageSpeedInfo: urlScores,
columnsInfo: columnsInfo,
error: errorMessage
};
}

/**
* Gets the most representative URL that would be used on a mobile device
* taking into account Upgraded URLs.
*
* @param {Entity} entity An AdWords entity such as an Ad, Keyword or Sitelink.
* @return {string} The URL.
*/
function getMobileUrl(entity) {
var urls = entity.urls();
var url = null;
if (urls) {
if (urls.getMobileFinalUrl()) {
url = urls.getMobileFinalUrl();
} else if (urls.getFinalUrl()) {
url = urls.getFinalUrl();
}
}
if (!url) {
switch (entity.getEntityType()) {
case ‘Ad’:
case ‘Keyword’:
url = entity.getDestinationUrl();
break;
case ‘Sitelink’:
case ‘AdGroupSitelink’:
case ‘CampaignSitelink’:
url = entity.getLinkUrl();
break;
default:
Logger.log(‘No URL found’ + entity.getEntityType());
}
}
if (url) {
url = encodeURI(decodeURIComponent(url));
}
return url;
}

/**
* Determines whether there is enough remaining time to continue iterating
* through the account.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* iterating.
*/
function hasRemainingTimeForAccountIteration() {
var remainingTime = AdWordsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS + URL_FETCH_TIME_SECS;
}

/**
* Determines whether there is enough remaining time to continue fetching URLs.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* fetching.
*/
function hasRemainingTimeForUrlFetches() {
var remainingTime = AdWordsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS;
}

/**
* Iterates through all the available Campaigns and AdGroups, to a limit of
* defined in CAMPAIGN_LIMIT and ADGROUP_LIMIT until the time limit is reached
* allowing enough time for the post-iteration steps, e.g. fetching and
* analysing URLs and building results.
*
* @return {UrlStore} An UrlStore object with URLs from the account.
*/
function getUrlsFromAccount() {
var urlStore = new UrlStore(EXTRA_URLS_TO_CHECK);
var campaigns = AdWordsApp.campaigns()
.forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(CAMPAIGN_LIMIT)
.get();
while (campaigns.hasNext() && hasRemainingTimeForAccountIteration()) {
var campaign = campaigns.next();
var campaignUrls = getUrlsFromCampaign(campaign);
urlStore.addUrls(campaignUrls);
}
var adGroups = AdWordsApp.adGroups()
.forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(ADGROUP_LIMIT)
.get();
while (adGroups.hasNext() && hasRemainingTimeForAccountIteration()) {
var adGroup = adGroups.next();
var adGroupUrls = getUrlsFromAdGroup(adGroup);
urlStore.addUrls(adGroupUrls);
}
return urlStore;
}

/**
* Work through an ad group’s members in the account, but only up to the maximum
* specified by the SITELINK_LIMIT.
*
* @param {AdGroup} adGroup The adGroup to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromAdGroup(adGroup) {
var uniqueUrls = {};
var sitelinks =
adGroup.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
var url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}

/**
* Work through a campaign’s members in the account, but only up to the maximum
* specified by the AD_LIMIT, KEYWORD_LIMIT and SITELINK_LIMIT.
*
* @param {Campaign} campaign The campaign to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromCampaign(campaign) {
var uniqueUrls = {};
var url = null;
var sitelinks = campaign
.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
var ads = campaign.ads().forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(AD_LIMIT)
.get();
while (ads.hasNext()) {
var ad = ads.next();
url = getMobileUrl(ad);
if (url) {
uniqueUrls[url] = true;
}
}
var keywords = campaign.keywords().forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(KEYWORD_LIMIT)
.get();
while (keywords.hasNext()) {
var keyword = keywords.next();
url = getMobileUrl(keyword);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {Date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* UrlStore – this is an object that takes URLs, added one by one, and then
* allows them to be iterated through in a particular order, which aims to
* maximise the variety between the returned URLs.
*
* This works by splitting the URL into three parts: host, path and params
* In comparing two URLs, most weight is given if the hosts differ, then if the
* paths differ, and finally if the params differ.
*
* UrlStore sets up a tree with 3 levels corresponding to the above. The full
* URL exists at the leaf level. When a request is made for an iterator, a copy
* is taken, and a path through the tree is taken, using the first host. Each
* entry is removed from the tree as it is used, and the layers are rotated with
* each call such that the next call will result in a different host being used
* (where possible).
*
* Where opt_manualUrls are supplied at construction time, these will take
* precedence over URLs added subsequently to the object.
*
* @param {?Array.<string>=} opt_manualUrls An optional list of URLs to check.
* @constructor
*/
function UrlStore(opt_manualUrls) {
this.manualUrls = opt_manualUrls || [];
this.paths = {};
this.re = /^(https?:\/\/[^\/]+)([^?#]*)(.*)$/;
}

/**
* Adds a URL to the UrlStore.
*
* @param {string} url The URL to add.
*/
UrlStore.prototype.addUrl = function(url) {
if (!url || this.manualUrls.indexOf(url) > -1) {
return;
}
var matches = this.re.exec(url);
if (matches) {
var host = matches[1];
var path = matches[2];
var param = matches[3];
if (!this.paths[host]) {
this.paths[host] = {};
}
var hostObj = this.paths[host];
if (!path) {
path = ‘/’;
}
if (!hostObj[path]) {
hostObj[path] = {};
}
var pathObj = hostObj[path];
pathObj[url] = url;
}
};

/**
* Adds multiple URLs to the UrlStore.
*
* @param {!Array.<string>} urls The URLs to add.
*/
UrlStore.prototype.addUrls = function(urls) {
for (var i = 0; i < urls.length; i++) {
this.addUrl(urls[i]);
}
};

/**
* Creates and returns an iterator that tries to iterate over all available
* URLs return them in an order to maximise the difference between them.
*
* @return {UrlStoreIterator} The new iterator object.
*/
UrlStore.prototype.__iterator__ = function() {
return new UrlStoreIterator(this.paths, this.manualUrls);
};

var UrlStoreIterator = (function() {
function UrlStoreIterator(paths, manualUrls) {
this.manualUrls = manualUrls.slice();
this.urls = objectToArray_(paths);
}
UrlStoreIterator.prototype.next = function() {
if (this.manualUrls.length) {
return this.manualUrls.shift();
}
if (this.urls.length) {
return pick_(this.urls);
} else {
throw StopIteration;
}
};
function rotate_(a) {
if (a.length < 2) {
return a;
} else {
var e = a.pop();
a.unshift(e);
}
}
function pick_(a) {
if (typeof a[0] === ‘string’) {
return a.shift();
} else {
var element = pick_(a[0]);
if (!a[0].length) {
a.shift();
} else {
rotate_(a);
}
return element;
}
}

function objectToArray_(obj) {
if (typeof obj !== ‘object’) {
return obj;
}

var a = [];
for (var k in obj) {
a.push(objectToArray_(obj[k]));
}
return a;
}
return UrlStoreIterator;
})();

/**
* Runs the PageSpeed fetch.
*
* @param {string} url
* @return {Object} An object containing either the successful response from the
* server, or an error message.
*/
function checkUrl(url) {
var result = null;
var error = null;
var fullUrl = PAGESPEED_URL + ‘key=’ + API_KEY + ‘&url=’ + encodeURI(url) +
‘&prettyprint=false&strategy=mobile’;
var params = {muteHttpExceptions: true};
try {
var pageSpeedResponse = UrlFetchApp.fetch(fullUrl, params);
if (pageSpeedResponse.getResponseCode() === 200) {
result = pageSpeedResponse.getContentText();
} else {
error = pageSpeedResponse.getContentText();
}
} catch (e) {
error = e.message;
}
return {
responseText: result,
error: error
};
}

 

Como Configurar

 

 

8. Armazene o Índice de Qualidade de sua Conta, Campanha, Grupo de Anúncios e Palavras-chave – Por Russel Savage. Tenha o histórico do Índice de Qualidade organizado por datas em uma planilha do Google. Mensure o desenvolvimento de seu Índice de Qualidade com o decorrer do tempo.

/************************************
* Store Account, Campaign, and AdGroup Level Quality Score
* Version 2.3
* ChangeLog v2.3
* – Solved #NUM! issue by filtering out — values
* ChangeLog v2.2
* – Updated KeywordText to Criteria
* ChangeLog v2.1
* – Ignore negatives
* ChangeLog v2.0
* – Rewrote for speed using the reporting api
* – Added ability to store data in .csv file
* – Added the ability for custom date ranges
* – Added the ability for Spreadsheet Names
* ChangeLog v1.3
* – Updated writeToSpreadsheet function
* – Added keyword level reporting
* ChangeLog v1.2
* – Changed status to ENABLED
* ChangeLog v1.1
* – Added APPEND option
* – Added ability to create spreadsheet sheets
* – Updated logic for faster spreadsheet insertion
* Created By: Russ Savage
* FreeAdWordsScripts.com
**************************************/
var DECIMALS = 4; //this will give you 4 decimal places of accuracy
//You can set this to anything in this list: TODAY, YESTERDAY, LAST_7_DAYS,
// THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS,
// LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH
var DATE_RANGE = ‘LAST_30_DAYS’;
// Or you can set this to any number of days you like. it overrides the DATE_RANGE set above
var LAST_N_DAYS = 0;

var CSV_FILE_PREFIX = “”; //Set this if you want to write to a set of CSV files, one for each account level.
var SPREADSHEET_URL = “”; //Set this if you have the url of a spreadsheet you want to update
var SPREADSHEET_NAME = “”; //Set this if you want to write to the name of a spreadsheet instead

function main() {
var isCSV = (CSV_FILE_PREFIX !== “”);
var allData = getKeywordsReport();
var tabs = [‘Account’,’Campaign’,’AdGroup’,’Keyword’];
for(var i in tabs) {
var tab = tabs[i];
var dataToWrite = [];
var cols = getCols(tab);
var rowKeys = getRowKeys(tab,Object.keys(allData));
for(var x in rowKeys) {
var rowArray = [];
var key = rowKeys[x];
var row = allData[key];
for(var y in cols) {
rowArray.push(row[cols[y]]);
}
dataToWrite.push(rowArray);
}
if(isCSV) {
writeDataToCSV(tab,dataToWrite);
} else {
writeDataToSpreadsheet(tab,dataToWrite);
}
}
}

function getRowKeys(tab,allKeys) {
return allKeys.filter(function(e) { return (e.indexOf(tab) >= 0); });
}

function getCols(tab) {
return {
‘Account’ : [‘Date’,’Account’,’ImpsWeightedQS’],
‘Campaign’: [‘Date’,’Account’,’Campaign’,’ImpsWeightedQS’],
‘AdGroup’ : [‘Date’,’Account’,’Campaign’,’AdGroup’,’ImpsWeightedQS’],
‘Keyword’ : [‘Date’,’Account’,’Campaign’,’AdGroup’,’Keyword’,’QS’,’ImpsWeightedQS’]
}[tab];
}

// Super fast spreadsheet insertion
function writeDataToSpreadsheet(tab,toWrite) {
//This is where i am going to store all my data
var spreadsheet;
if(SPREADSHEET_NAME) {
var fileIter = DriveApp.getFilesByName(SPREADSHEET_NAME);
if(fileIter.hasNext()) {
var file = fileIter.next();
spreadsheet = SpreadsheetApp.openById(file.getId());
} else {
spreadsheet = SpreadsheetApp.create(SPREADSHEET_NAME);
}
} else if(SPREADSHEET_URL) {
spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
} else {
throw ‘You need to set at least one of the SPREADSHEET_URL or SPREADSHEET_NAME variables.’;
}
var sheet = spreadsheet.getSheetByName(tab);
if(!sheet) {
sheet = spreadsheet.insertSheet(tab);
sheet.appendRow(getCols(tab));
}

var lastRow = sheet.getLastRow();
var numRows = sheet.getMaxRows();
if((numRows-lastRow) < toWrite.length) {
sheet.insertRowsAfter((lastRow == 0) ? 1 : lastRow,toWrite.length-numRows+lastRow);
}
var range = sheet.getRange(lastRow+1,1,toWrite.length,toWrite[0].length);
range.setValues(toWrite);
}

function writeDataToCSV(tab,toWrite) {
if(!toWrite) { return; }
var fileName = CSV_FILE_PREFIX + ‘_’ + tab + ‘.csv’;
var file;
var fileIter = DriveApp.getFilesByName(fileName);
if(fileIter.hasNext()) {
file = fileIter.next();
} else {
file = DriveApp.createFile(fileName, formatCsvRow(getCols(tab)));
}
var fileData = file.getBlob().getDataAsString();
for(var i in toWrite) {
fileData += formatCsvRow(toWrite[i]);
}
file.setContent(fileData);
return file.getUrl();
}

function formatCsvRow(row) {
for(var i in row) {
if(row[i].toString().indexOf(‘”‘) == 0) {
row[i] = ‘””‘+row[i]+'””‘;
}
if(row[i].toString().indexOf(‘+’) == 0) {
row[i] = “‘”+row[i];
}
if(row[i].toString().indexOf(‘,’) >= 0 &&
row[i].toString().indexOf(‘”””‘) != 0)
{
row[i] = (‘”‘+row[i]+'”‘);
}
}
return row.join(‘,’)+’\n’;
}

function getKeywordsReport() {
var theDate = DATE_RANGE;
if(LAST_N_DAYS != 0) {
theDate = getDateDaysAgo(LAST_N_DAYS)+’,’+getDateDaysAgo(1);
}
Logger.log(‘Using date range: ‘+theDate);
var OPTIONS = { includeZeroImpressions : true };
var cols = [‘ExternalCustomerId’,
‘CampaignId’,’CampaignName’,
‘AdGroupId’,’AdGroupName’,
‘Id’,’Criteria’,’KeywordMatchType’,
‘IsNegative’,’Impressions’, ‘QualityScore’];
var report = ‘KEYWORDS_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,
‘where AdNetworkType1 = SEARCH’,
‘and CampaignStatus = ENABLED’,
‘and AdGroupStatus = ENABLED’,
‘and Status = ENABLED’,
‘during’,theDate].join(‘ ‘);
var results = {};
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(row.QualityScore == “–“) { continue; }
if(row.IsNegative == true || row.IsNegative === ‘true’) { continue; }
loadHashEntry(‘Account:’+row.ExternalCustomerId,row,results);
loadHashEntry(‘Campaign:’+row.CampaignId,row,results);
loadHashEntry(‘AdGroup:’+[row.CampaignId,row.AdGroupId].join(‘-‘),row,results);
loadHashEntry(‘Keyword:’+[row.CampaignId,row.AdGroupId,row.Id].join(‘-‘),row,results);
}
var dateStr = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘yyyy-MM-dd’);
for(var i in results) {
results[i][‘Date’] = dateStr;
results[i][‘ImpsWeightedQS’] = (results[i][‘totalImps’] === 0) ? 0 : round(results[i][‘ImpsWeightedQS’]/results[i][‘totalImps’]);
}
return results;
}

function loadHashEntry(key,row,results) {
if(!results[key]) {
results[key] = {
QS : 0,
ImpsWeightedQS : 0,
totalImps : 0,
Account : null,
Campaign : null,
AdGroup : null,
Keyword : null
};
}
results[key].QS = parseFloat(row.QualityScore);
results[key].ImpsWeightedQS += (parseFloat(row.QualityScore)*parseFloat(row.Impressions));
results[key].totalImps += parseFloat(row.Impressions);
results[key].Account = row.ExternalCustomerId;
results[key].Campaign = row.CampaignName;
results[key].AdGroup = row.AdGroupName;
results[key].Keyword = (row.KeywordMatchType === ‘Exact’) ? ‘[‘+row.Criteria+’]’ :
(row.KeywordMatchType === ‘Phrase’) ? ‘”‘+row.Criteria+'”‘ : row.Criteria;
}

//A helper function to return the number of days ago.
function getDateDaysAgo(days) {
var thePast = new Date();
thePast.setDate(thePast.getDate() – days);
return Utilities.formatDate(thePast, AdWordsApp.currentAccount().getTimeZone(), ‘yyyyMMdd’);
}

function round(val) {
var divisor = Math.pow(10,DECIMALS);
return Math.round(val*divisor)/divisor;
}

 

 

9. Relatório de Performance da Campanha e Palavra-chave – Por Russel Savage. Tenha uma planilha do Google com relatório do mês passado, semanais, mensais de suas campanhas e de suas palavras-chave. Tudo que você precisa fazer é criar uma planilha do Google e colocar sua URL no script.

/************************************
* Campaign and Keyword Summary Report
* Version: 1.2
* Changelog v1.2 – Fixed INVALID_PREDICATE_ENUM_VALUE
* ChangeLog v1.1 – Removed apiVersion from reporting call
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
var SPREADSHEET_URL = “PASTE GOOGLE SPREADSHEET URL HERE”;

function main() {
//These names are important. change them with caution
var tabs = [‘camp_perf_7_days’,’camp_perf_mtd’,’camp_perf_last_month’,’keyword_perf_7_days’,’keyword_perf_7_days_daily’];
for(var i in tabs) {
var results = runQuery(tabs[i]);
writeToSpreadsheet(tabs[i],results);
}
}

//Helper function to get or create the spreadsheet
function getSheet(tab) {
var s_sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet;
try {
sheet = s_sheet.getSheetByName(tab);
if(!sheet) {
sheet = s_sheet.insertSheet(tab, 0);
}
} catch(e) {
sheet = s_sheet.insertSheet(tab, 0);
}
return sheet
}

//Function to write the rows of the report to the sheet
function writeToSpreadsheet(tab,rows) {
var to_write = convertRowsToSpreadsheetRows(tab,rows);
var s_sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = getSheet(tab);
sheet.clear();

var numRows = sheet.getMaxRows();
if(numRows < to_write.length) {
sheet.insertRows(1,to_write.length-numRows);
}
var range = sheet.getRange(1,1,to_write.length,to_write[0].length);
range.setValues(to_write);
}

//A generic function used to build and run the report query
function runQuery(tab) {
var API_VERSION = { includeZeroImpressions : false };
var cols = getColumns(tab);
var report = getReport(tab);
var date_range = getDateRange(tab);
var where = getWhereClause(tab);
var query = [‘select’,cols.join(‘,’),’from’,report,where,’during’,date_range].join(‘ ‘);
var report_iter = AdWordsApp.report(query, API_VERSION).rows();
var rows = [];
while(report_iter.hasNext()) {
rows.push(report_iter.next());
}
return rows;
}

//This function will convert row data into a format easily pushed into a spreadsheet
function convertRowsToSpreadsheetRows(tab,rows) {
var cols = getColumns(tab);
var ret_val = [cols];
for(var i in rows) {
var r = rows[i];
var ss_row = [];
for(var x in cols) {
ss_row.push(r[cols[x]]);
}
ret_val.push(ss_row);
}
return ret_val;
}

//Based on the tab name, this returns the report type to use for the query
function getReport(tab) {
if(tab.indexOf(‘camp_’) == 0) {
return ‘CAMPAIGN_PERFORMANCE_REPORT’;
}
if(tab.indexOf(‘keyword_’) == 0) {
return ‘KEYWORDS_PERFORMANCE_REPORT’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

//Based on the tab name, this returns the where clause for the query
function getWhereClause(tab) {
if(tab.indexOf(‘camp_’) == 0) {
return ‘where CampaignStatus = ENABLED’;
}
if(tab.indexOf(‘keyword_’) == 0) {
return ‘where CampaignStatus = ENABLED and AdGroupStatus = ENABLED and Status = ENABLED’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

//Based on the tab name, this returns the columns to add into the report
function getColumns(tab) {
var ret_array = [];
if(tab.indexOf(‘daily’) >= 0) {
ret_array.push(‘Date’);
}
ret_array.push(‘CampaignName’);
ret_array.push(‘CampaignStatus’);

if(tab.indexOf(‘keyword_’) == 0) {
ret_array = ret_array.concat([‘AdGroupName’,
‘AdGroupStatus’,
‘Id’,
‘KeywordText’,
‘KeywordMatchType’]);
}
return ret_array.concat([‘Clicks’,
‘Impressions’,
‘Ctr’,
‘AverageCpc’,
‘Cost’,
‘AveragePosition’,
‘Conversions’,
‘ConversionRate’,
‘ConversionValue’]);
}

//Based on the tab name, this returns the date range for the data.
function getDateRange(tab) {
if(tab.indexOf(‘7_days’) >= 0) {
return ‘LAST_7_DAYS’;
}
if(tab.indexOf(‘mtd’) >= 0) {
return ‘THIS_MONTH’;
}
if(tab.indexOf(‘last_month’) >= 0) {
return ‘LAST_MONTH’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

 

 

10. Exporte suas métricas diárias – Por Sean Dolan. Esse script exporta suas métricas diárias para uma planilha do Google, assim, você pode facilmente armazenar seus relatórios.

function main() {
/*
Make a copy of the spreadsheet listed below and save it to your own Google Drive.
Template – //docs.google.com/spreadsheet/ccckey=0Ao4Qdm9yCtaGdHpiWmpYSWoxcE9Wd0dEV0xDY3l5UGc#gid=0
Take the url of your new spreadsheet and enter it in the ssURL field below
*/
var ssURL = “spreadsheet.com”;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/daily-metrics-log.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

 

11. Projeções Mensais – por Sean Dolan. Utilize a ferramenta do PPCHero para enviar um e-mail com projeções mensais dos seus gastos com este script.

function main() {
//include your e-mail below
var email = “[email protected]”;

//include the name of the account to specify the account in the e-mail
var accountName = “your account name”;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/monthly-projections.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

 

12. Auditor de Índice de Qualidade – Por Derek Martin. O script facilita sua análise do Índice de Qualidade de suas campanhas trazendo um comparativo com o CTR, Custo e Conversão. Assim você pode traçar uma correlação com o desempenho e identificar formas de otimizar sua campanha.

/**************************************************************************************
* AdWords Optimization — Quality Score Performance Checker
* This script audits an account’s quality score performance and creates a table that shows the
* distribution of CTR, Cost, & Conversins against Quality Score.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com
**************************************************************************************/

function main() {
var keywordList = [];
var resultsList = [];
// iterate over all keywords in an account
var kwIter = AdWordsApp.keywords().withCondition(‘Status = ENABLED’).withCondition(“Impressions > 0”).forDateRange(“LAST_30_DAYS”).get();

while (kwIter.hasNext()) {
var keyword = kwIter.next();
var keywordName = keyword.getText();
var keywordQualityScore = keyword.getQualityScore();
var keywordMatchType = keyword.getMatchType();
var keywordStats = keyword.getStatsFor(“LAST_30_DAYS”);
var keywordCtr = keywordStats.getCtr();
var keywordCost =keywordStats.getCost();
var keywordConversions = keywordStats.getConvertedClicks();

var newResult = new keywordTrack(keywordName, keywordMatchType, keywordQualityScore, keywordCtr, keywordCost, keywordConversions);

//info(newResult);

keywordList.push(newResult);

} // end of keyword iteration
// info(‘there are ‘ + keywordList.length + ‘ keywords for review.’);
resultsList = analyzeKeywords(keywordList);
displayResults(resultsList);

} // end of main function

function keywordTrack (name, matchType, qualityScore, ctr, cost, conversions) {
this.name = name;
this.matchType = matchType;
this.qualityScore = qualityScore;
this.ctr = ctr;
this.cost = cost;
this.conversions = conversions
} // end of keyword track

function qualityScoreTrack (qualityScore, ctr, cost, conversions) {
this.qualityScore = qualityScore;
this.ctr = ctr;
this.cost = cost;
this.conversions = conversions;
} // end of qualityScoreTrack

function analyzeKeywords(list) {
var kwList = list;
var qsList = [];
var qsGroup = [];
var totalCtr = 0;
var ctrCount = 0;
var totalCost =0;
var costCount = 0;
var totalConversions = 0;
var conversionsCount = 0;
var averageCtr = 0;
var averageCost = 0;
var averageConversions = 0;

for (i = 1; i <= 10; i++) {
qsGroup = _.where(kwList, {qualityScore: i });

if (_.isEmpty(qsGroup)) {
var result = new qualityScoreTrack(i, 0);
qsList.push(result);

} else {
var k = 0;
for each (qualityScore in qsGroup) {
totalCtr += qsGroup[k].ctr;
totalCost += qsGroup[k].cost;
totalConversions += qsGroup[k].conversions;
k++;
} // end of for each statement

ctrCount = qsGroup.length;
averageCtr = (totalCtr / ctrCount) * 100;

costCount = qsGroup.length;
averageCost = (totalCost / costCount);

conversionsCount = qsGroup.length;
averageConversions = (totalConversions / conversionsCount);

var result = new qualityScoreTrack(i, averageCtr, totalCost, totalConversions);
qsList.push(result);

totalCtr = 0;
averageCtr = 0;
ctrCount = 0;

totalCost = 0;
averageCost = 0;
costCount = 0;

totalConversions = 0;
averageConversions = 0;
conversionsCount = 0;

} // end of if statement

} // end of for loop

return qsList;

} // end of analyzeKeywords

function displayResults(results) {
var list = results;
//info(list);
var account = AdWordsApp.currentAccount().getName().split(“-“);

info(‘Below is the 30 Day Quality Score CTR Analysis for ‘ + account[0]);

for (var i = 1; i <= 10; i++) {
info(‘CTR: (‘+ i + ‘/10) – ‘ + _.str.toNumber(list[i-1].ctr,2) + ‘%’);
info(‘Cost: (‘+ i + ‘/10) – $’ + _.str.toNumber(list[i-1].cost, 4));
info(‘Conversions: (‘+ i + ‘/10) – ‘ + _.str.toNumber(list[i-1].conversions, 2));
info(“”);

} // end of for statement
} // end of results
/* UTILITY FUNCTIONS */
function info(msg) {
Logger.log(msg);
}

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

/* UNDERSCORE LIBRARIES */

// Underscore.js 1.6.0
// //underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};”undefined”!=typeof exports?(“undefined”!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION=”1.6.0″;var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O=”Reduce of empty array with no initial value”;j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[–i]:–i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),”value”)};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,”length”).concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,””+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if(“number”!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u–;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error(“bindAll must be passed function names”);return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:j.now(),a=null,i=n.apply(e,u),e=u=null};return function(){var l=j.now();o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r–)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return–n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case”[object String]”:return n==String(t);case”[object Number]”:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case”[object Date]”:case”[object Boolean]”:return+n==+t;case”[object RegExp]”:return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if(“object”!=typeof n||”object”!=typeof t)return!1;for(var i=r.length;i–;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&”constructor”in n&&”constructor”in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if(“[object Array]”==u){if(c=n.length,f=c==t.length)for(;c–&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c–)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return”[object Array]”==l.call(n)},j.isObject=function(n){return n===Object(n)},A([“Arguments”,”Function”,”String”,”Number”,”Date”,”RegExp”],function(n){j[“is”+n]=function(t){return l.call(t)==”[object “+n+”]”}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,”callee”))}),”function”!=typeof/./&&(j.isFunction=function(n){return”function”==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||”[object Boolean]”==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{“&”:”&amp;”,”<“:”&lt;”,”>”:”&gt;”,'”‘:”&quot;”,”‘”:”&#x27;”}};T.unescape=j.invert(T.escape);var I={escape:new RegExp(“[“+j.keys(T.escape).join(“”)+”]”,”g”),unescape:new RegExp(“(“+j.keys(T.unescape).join(“|”)+”)”,”g”)};j.each([“escape”,”unescape”],function(n){j[n]=function(t){return null==t?””:(“”+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+””;return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={“‘”:”‘”,”\\”:”\\”,”\r”:”r”,”\n”:”n”,” “:”t”,”\u2028″:”u2028″,”\u2029″:”u2029″},D=/\\|’|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join(“|”)+”|$”,”g”),i=0,a=”__p+='”;n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return”\\”+B[n]}),r&&(a+=”‘+\n((__t=(“+r+”))==null?”:_.escape(__t))+\n'”),e&&(a+=”‘+\n((__t=(“+e+”))==null?”:__t)+\n'”),u&&(a+=”‘;\n”+u+”\n__p+='”),i=o+t.length,t}),a+=”‘;\n”,r.variable||(a=”with(obj||{}){\n”+a+”}\n”),a=”var __t,__p=”,__j=Array.prototype.join,”+”print=function(){__p+=__j.call(arguments,”);};\n”+a+”return __p;\n”;try{e=new Function(r.variable||”obj”,”_”,a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source=”function(“+(r.variable||”obj”)+”){\n”+a+”}”,c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A([“pop”,”push”,”reverse”,”shift”,”sort”,”splice”,”unshift”],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),”shift”!=n&&”splice”!=n||0!==r.length||delete r[0],z.call(this,r)}}),A([“concat”,”join”,”slice”],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),”function”==typeof define&&define.amd&&define(“underscore”,[],function(){return j})}).call(this);
//# sourceMappingURL=underscore-min.map

// Underscore.string
!function(e,t){“use strict”;var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return””;var n=””;while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?”\\s”:e.source?e.source:”[“+p.escapeRegExp(e)+”]”},f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”)f.push(r[l]);else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!=”number”)throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)));switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a,v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “,m=p[6]-t(a).length,d=p[6]?n(v,m):””,f.push(p[5]?a+d:d+a)}}return f.join(“”)},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push(“%”);else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error(“[_.sprintf] huh?”);if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1]);while((o=o.substring(u[0].length))!==””)if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”);r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:”2.3.0″,isBlank:function(e){return e==null&&(e=””),/^\s*$/.test(e)},stripTags:function(e){return e==null?””:t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){return e=e==null?””:t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e])},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split(“”)},swapCase:function(e){return e==null?””:t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?””:t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){return e==null?””:t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?””:t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===””?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=””),e.join(t)},lines:function(e){return e==null?[]:t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?””:(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?””:t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){return e==null?””:!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””))},ltrim:function(e,n){return e==null?””:!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp(“^”+n+”+”),””))},rtrim:function(e,n){return e==null?””:!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+”+$”),””))},truncate:function(e,n,r){return e==null?””:(e=t(e),r=r||”…”,n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return””;e=t(e),n=~~n,r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,””):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=” “;switch(i){case”right”:return s=n-e.length,e+o(r,s);case”both”:return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t),r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””,t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “,n=n||” and “;var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“}),p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!=”undefined”?(typeof module!=”undefined”&&module.exports&&(module.exports=p),exports._s=p):typeof define==”function”&&define.amd?define(“underscore.string”,[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);

 

 

13. Checklist de sua conta do Google Ads – Por Russel Savage. Faça uma auditoria de sua conta com esse script. O script passará pelos pontos essenciais de sua conta do Google Ads e evidencia quais áreas precisam de uma maior atenção. Para ter acesso ao relatório é necessário rodar o script, “ver detalhes” e depois em logs.

/************************************
* AdWords Account Audit Checklist
* Version 1.1
* ChangeLog v1.1 – Fixed issue with extension selector.
* Based on the blog post by Phil Kowalski
* //www.wordstream.com/blog/ws/2013/07/02/adwords-account-audit-checklist
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
function main() {
//1. Campaigns
// a. Target the right locations
var includedLocList = [‘United States’,’Canada’]; // <– the list of places your campaigns should be targeting
verifyTargetedLocations(includedLocList);

var excludedLocList = [‘Europe’]; // <– the list of places your campaigns should be excluding
verifyExcludedLocations(excludedLocList);

// b. Language – Can’t be done using scripts yet 🙁
// c. Search vs Display
verifySearchAndDisplay();

// d. Check Mobile Strategy
verifyMobileModifiers();

//2. AdGroups
// a. Check for AdGroups with more than 20-30 keywords
var ADGROUP_SIZE = 25; // <– this is the max number of keywords you want in an AdGroup
verifyAdGroupSize(ADGROUP_SIZE);

// b. Check for topic. Difficult to do with scripts
// c. Check for ads
var NUMBER_OF_ADS = 3; // <– this is the minimum number of ads in an AdGroup
verifyAdGroupNumberOfAds(NUMBER_OF_ADS);

//3. Keywords
// a. Check for MatchTypes
printMatchTypes();

//4. Search Queries
// This analysis is probably worth it’s own script

//5. Other
// a. Conversion Tracking
verifyConversionTracking();

// b. AdExtensions
verifyAdExtensions();
}

function verifyConversionTracking() {
//Assume that if the account has not had a conversion in 7 days, something is wrong.
var campsWithConversions = AdWordsApp.campaigns()
.withCondition(‘Status = ENABLED’)
.forDateRange(‘LAST_7_DAYS’)
.withCondition(‘Conversions > 0’)
.get().totalNumEntities();
if(campsWithConversions == 0) {
warn(‘Account is probably missing conversion tracking.’);
}
}

function verifyAdExtensions() {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var phoneNumExtCount = camp.extensions().phoneNumbers().get().totalNumEntities();
if(phoneNumExtCount == 0) {
warn(‘Campaign: “‘+camp.getName()+'” is missing phone number extensions.’);
}
var siteLinksExtCount = camp.extensions().sitelinks().get().totalNumEntities();
if(siteLinksExtCount < 6) {
warn(‘Campaign: “‘+camp.getName()+'” could use more site links. Currently has: ‘+siteLinksExtCount);
}
var mobileAppsExtCount = camp.extensions().mobileApps().get().totalNumEntities();
if(mobileAppsExtCount == 0) {
warn(‘Campaign: “‘+camp.getName()+'” is missing mobile apps extension.’);
}
}
}

function printMatchTypes() {
var numBroad = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = BROAD’)
.get().totalNumEntities();
var numPhrase = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = PHRASE’)
.get().totalNumEntities();
var numExact = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = EXACT’)
.get().totalNumEntities();
var total = numBroad+numPhrase+numExact;
var percBroad = Math.round(numBroad/total*100);
var percPhrase = Math.round(numPhrase/total*100);
var percExact = Math.round(numExact/total*100);
info(‘Out of a total of: ‘+total+’ active keywords in your account:’);
info(‘\tBroad: ‘+numBroad+’ or ‘+percBroad+’%’);
info(‘\tPhrase: ‘+numPhrase+’ or ‘+percPhrase+’%’);
info(‘\tExact: ‘+numExact+’ or ‘+percExact+’%’);
}

function verifyAdGroupNumberOfAds(requiredNumberOfAds) {
var agIter = AdWordsApp.adGroups()
.withCondition(‘Status = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.get();
while(agIter.hasNext()) {
var ag = agIter.next();
var adCount = ag.ads().withCondition(‘Status = ENABLED’).get().totalNumEntities();
if(adCount < requiredNumberOfAds) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” does not have enough ads: ‘+adCount);
}
if(adCount > (requiredNumberOfAds+2)) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” has too many ads: ‘+adCount);
}
}
}

function verifyAdGroupSize(size) {
var agIter = AdWordsApp.adGroups()
.withCondition(‘Status = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.get();
while(agIter.hasNext()) {
var ag = agIter.next();
var kwSize = ag.keywords().withCondition(‘Status = ENABLED’).get().totalNumEntities();
if(kwSize >= size) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” has too many keywords: ‘+kwSize);
}
}
}

function verifyMobileModifiers() {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var desktop = camp.targeting().platforms().desktop().get().next();
//var tablet = camp.targeting().platforms().tablet().get().next();
var mobile = camp.targeting().platforms().mobile().get().next();
//check for mobile modifiers
if(desktop.getBidModifier() == 1 && mobile.getBidModifier() == 1) {
warn(‘Campaign: “‘+camp.getName()+'” has no mobile modifier set.’);
}
}
}

function verifyTargetedLocations(locList) {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var locIter = camp.targeting().targetedLocations().get();
reportOnLocations(camp,locIter,locList);
}
}

function verifyExcludedLocations(locList) {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var locIter = camp.targeting().excludedLocations().get();
reportOnLocations(camp,locIter,locList);
}
}

function reportOnLocations(camp,locIter,locList) {
var campLocList = [];
while(locIter.hasNext()) {
var loc = locIter.next();
campLocList.push(loc.getName());
if(!locList) {
warn(‘Campaign: “‘+camp.getName()+'” targeting: “‘+loc.getName()+'”‘);
}
}
if(locList && campLocList.sort() != locList.sort()) {
for(var i in campLocList) {
if(locList.indexOf(campLocList[i]) == -1) {
warn(‘Campaign: “‘+camp.getName()+'” incorrectly targeting: “‘+campLocList[i]+'”‘);
}
}
for(var i in locList) {
if(campLocList.indexOf(locList[i]) == -1) {
warn(‘Campaign: “‘+camp.getName()+'” not targeting: “‘+locList[i]+'”‘);
}
}
}
}

function verifySearchAndDisplay() {
var API_VERSION = { includeZeroImpressions : false };
var cols = [‘CampaignId’,’CampaignName’,’AdNetworkType1′,’Impressions’];
var report = ‘CAMPAIGN_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,’during’,’LAST_30_DAYS’].join(‘ ‘);
var results = {}; // { campId : { agId : [ row, … ], … }, … }
var reportIter = AdWordsApp.report(query, API_VERSION).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(results[row.CampaignId]) {
warn(‘Campaign: “‘+row.CampaignName+'” is targeting the Display and Search networks.’);
} else {
results[row.CampaignId] = row;
}
}
return results;
}

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}