Publicado em 4 de junho de 2018 no blog corporativo sem navegador.Temos o prazer de anunciar que recentemente cruzamos a linha de
dois milhões de sessões realizadas ! São
milhões de capturas
de tela geradas, PDFs impressos e sites testados. Fizemos quase tudo o que você pode pensar em fazer com um navegador sem cabeça.
Embora seja bom alcançar esse marco, no caminho havia claramente
muitas sobreposições e problemas. Devido à enorme quantidade de tráfego recebido, gostaria de dar um passo atrás e apresentar recomendações gerais para o lançamento de navegadores sem cabeça (e
marionetes ) na produção.
Aqui estão algumas dicas.
1. Não use um navegador sem cabeça

Consumo de recursos sem cabeça do Chrome
De nenhuma maneira, se possível,
não inicie o navegador no modo decapitado . Especialmente na mesma infraestrutura que seu aplicativo (veja acima). O navegador sem cabeça é imprevisível, guloso e criador como o Sr. Misix, de Rick e Morty. Quase tudo o que um navegador pode fazer (exceto interpolar e executar o JavaScript) pode ser feito usando ferramentas simples do Linux. As bibliotecas Cheerio e outras oferecem uma API Node elegante para recuperar dados com solicitações HTTP e raspagem, se esse for seu objetivo.
Por exemplo, você pode pegar uma página (supondo que seja algum tipo de HTML) e descartá-la com comandos simples como estes:
import cheerio from 'cheerio'; import fetch from 'node-fetch'; async function getPrice(url) { const res = await fetch(url); const html = await res.test(); const $ = cheerio.load(html); return $('buy-now.price').text(); } getPrice('https://my-cool-website.com/');
Obviamente, o script não cobre todos os casos de uso e, se você ler este artigo, provavelmente precisará usar um navegador sem cabeça. Então, vamos começar.
2. Não inicie um navegador sem cabeça desnecessariamente
Encontramos vários usuários que estão tentando manter o navegador funcionando, mesmo que não esteja em uso (com conexões abertas). Embora essa possa ser uma boa estratégia para acelerar uma sessão, ela falhará em algumas horas. Principalmente porque os navegadores
gostam de armazenar
em cache tudo em uma fileira e gradualmente consomem memória. Assim que você parar de usar o navegador intensivamente, feche-o imediatamente!
import puppeteer from 'puppeteer'; async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.example.com/');
No modo sem navegador, geralmente corrigimos esse erro para os usuários, sempre definindo algum tipo de cronômetro para a sessão e fechando o navegador quando o WebSocket é desconectado. Mas se você não usar nosso
serviço ou a
imagem de backup do Docker , certifique-se de fechar o navegador
automaticamente , pois será desagradável quando tudo cair no meio da noite.
3. A sua página de amigo. page.evaluate
Tenha cuidado com transpilers como babel ou typescript, porque eles gostam de criar funções auxiliares e assumem que eles são acessíveis com fechamentos. Ou seja, o retorno de chamada .valuate pode não funcionar corretamente.
O Puppeteer possui muitos métodos interessantes, como armazenar seletores DOM e outras coisas em um ambiente Node. Embora seja muito conveniente, você pode facilmente dar um tiro no pé se algo na página forçar esse nó DOM a
sofrer uma
mutação . Pode não ser tão legal, mas, na realidade, é melhor fazer todo o trabalho no lado
do navegador no contexto do navegador . Isso geralmente significa carregar
page.evaulate
para todo o trabalho que precisa ser feito.
Por exemplo, em vez de algo como isto (
três ações assíncronas):
const $anchor = await page.$('a.buy-now'); const link = await $anchor.getProperty('href'); await $anchor.click(); return link;
É melhor fazer isso (uma ação assíncrona):
await page.evaluate(() => { const $anchor = document.querySelector('a.buy-now'); const text = $anchor.href; $anchor.click(); });
Outra vantagem de agrupar ações em uma chamada de
evaluate
é a portabilidade: esse código pode ser executado em um navegador para verificar em vez de tentar reescrever o código do Nó. Obviamente, é sempre recomendável
usar um depurador para reduzir o tempo de desenvolvimento.
Uma regra simples é contar o número de
await
ou
then
no código. Se houver mais de um, provavelmente é melhor executar o código dentro da chamada
page.evaluate
. O motivo é que todas as ações assíncronas vão e voltam entre o tempo de execução do Node e o navegador, o que significa serialização e desserialização JSON constantes. Embora não exista uma quantidade tão grande de análise (porque tudo é suportado pelo WebSockets), ele ainda leva tempo, o que é melhor gasto em outra coisa.
4. Paralelize navegadores, não páginas da web
Então, percebemos que iniciar um navegador não é bom e precisamos fazer isso apenas em caso de emergência. A próxima dica é executar apenas uma sessão por navegador. Embora, na realidade, seja possível economizar recursos paralelizando o trabalho pelas
pages
, mas se uma página cair, poderá travar o navegador inteiro. Além disso, não é garantido que cada página esteja perfeitamente limpa (cookies e armazenamento podem se tornar uma dor de cabeça,
como vemos ).
Em vez disso:
import puppeteer from 'puppeteer';
Melhor fazer isso:
import puppeteer from 'puppeteer'; const runJob = async (url) {
Cada nova instância do navegador recebe um
--user-data-dir
limpo (a
menos que seja indicado de outra forma ). Ou seja, é completamente processado como uma nova sessão nova. Se o Chrome travar por algum motivo, ele não fará outras sessões.
5. Limitação de fila e simultaneidade
Um dos principais recursos do sem navegador é a capacidade de limitar ordenadamente a paralelização e a fila. Portanto, os aplicativos clientes executam o
puppeteer.connect
, mas eles não pensam na implementação da fila. Isso evita uma enorme quantidade de problemas, principalmente nas instâncias simultâneas do Chrome que consomem todos os recursos disponíveis do seu aplicativo.
A melhor e mais fácil maneira é pegar nossa imagem do Docker e executá-la com os parâmetros necessários:
# Pull in Puppeteer@1.4.0 support $ docker pull browserless/chrome:release-puppeteer-1.4.0 $ docker run -e "MAX_CONCURRENT_SESSIONS=10" browserless/chrome:release-puppeteer-1.4.0
Isso limita o número de solicitações simultâneas a dez (incluindo sessões de depuração e mais). A fila é configurada pela variável
MAX_QUEUE_LENGTH
. Normalmente, você pode executar aproximadamente 10 solicitações simultâneas por gigabyte de memória. A porcentagem de utilização da CPU pode variar para tarefas diferentes, mas basicamente você precisará de muita e muita RAM.
6. Não se esqueça da page.waitForNavigation
Um dos problemas mais comuns que encontramos são as ações que começam a carregar páginas com o encerramento repentino subsequente de scripts. Isso ocorre porque as ações que acionam o
pageload
geralmente causam a ingestão de trabalho subsequente. Para contornar o problema, geralmente você precisa chamar a ação de carregamento da página - e imediatamente após aguardar o carregamento.
Por exemplo, esse
console.log
não funciona em um único local (
consulte a demonstração ):
await page.goto('https://example.com'); await page.click('a'); const title = await page.title(); console.log(title);
Mas
funciona em outro (
veja a demonstração ).
await page.goto('https://example.com'); page.click('a'); await page.waitForNavigation(); const title = await page.title(); console.log(title);
Você pode ler mais sobre waitForNavigation
aqui . Esta função possui aproximadamente os mesmos parâmetros de interface que
page.goto
, mas apenas com a parte "wait".
7. Use o Docker para tudo que você precisa.
O Chrome precisa de muitas dependências para funcionar corretamente. Realmente muito. Mesmo depois de instalar tudo, você precisa se preocupar com coisas como fontes e processos fantasmas. Portanto, é ideal usar algum tipo de recipiente para colocar tudo lá. O Docker foi projetado quase especificamente para esta tarefa, porque você pode limitar a quantidade de recursos disponíveis e isolá-lo. Se você deseja criar seu próprio
Dockerfile
, verifique abaixo todas as dependências necessárias:
# Dependencies needed for packages downstream RUN apt-get update && apt-get install -y \ unzip \ fontconfig \ locales \ gconf-service \ libasound2 \ libatk1.0-0 \ libc6 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libexpat1 \ libfontconfig1 \ libgcc1 \ libgconf-2-4 \ libgdk-pixbuf2.0-0 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libstdc++6 \ libx11-6 \ libx11-xcb1 \ libxcb1 \ libxcomposite1 \ libxcursor1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxi6 \ libxrandr2 \ libxrender1 \ libxss1 \ libxtst6 \ ca-certificates \ fonts-liberation \ libappindicator1 \ libnss3 \ lsb-release \ xdg-utils \ wget
E para evitar processos zumbi (algo comum no Chrome), é melhor usar algo como
dumb-init para executar corretamente:
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init RUN chmod +x /usr/local/bin/dumb-init
Se você quiser saber mais, dê uma olhada no
nosso Dockerfile .
8. Lembre-se de dois tempos de execução diferentes.
É útil lembrar que existem
dois tempos de execução do JavaScript (nó e navegador). Isso é ótimo para separar tarefas, mas a confusão inevitavelmente ocorre porque alguns métodos exigirão a passagem explícita de links em vez de elevações.
Por exemplo, considere
page.evaluate
.
Nas entranhas do protocolo, existe uma
string literal da função e sua transferência para o Chrome . Portanto, coisas como fechamentos e elevadores
não funcionarão . Se você precisar passar algumas referências ou valores para a chamada de avaliação, adicione-os como argumentos que serão processados corretamente.
Assim, em vez de fazer referência ao
selector
por meio de fechamentos:
const anchor = 'a'; await page.goto('https://example.com/');
Melhor parâmetro de passagem:
const anchor = 'a'; await page.goto('https://example.com/');
page.evaluate
pode adicionar um ou mais argumentos à função
page.evaluate
, pois ela é variável aqui. Certifique-se de aproveitar isso!
O futuro
Estamos incrivelmente otimistas sobre o futuro dos navegadores sem cabeça e toda a automação que eles podem alcançar. Usando ferramentas poderosas, como marionetistas e sem navegador, esperamos que a depuração e execução da automação sem cabeça na produção sejam mais fáceis e rápidas. Em breve, lançaremos o
faturamento pré-pago para contas e
funções que ajudarão você a lidar melhor com seu trabalho decapitado!