Como tornar o Scraping de dados da web mais rápido: um guia completo

Aprenda a otimizar seu processo de Scraping de dados com técnicas avançadas para uma recuperação mais rápida de dados.
27 min de leitura
How to Make Web Scraping Faster

Neste artigo, você verá:

  • As principais causas de um processo lento de Scraping de dados
  • Várias técnicas para acelerar o Scraping de dados da web
  • Como otimizar um script de scraping Python de amostra para uma recuperação de dados mais rápida

Vamos começar!

Razões pelas quais seu processo de scraping é lento

Explore as principais razões pelas quais seu processo de Scraping de dados pode estar lento.

Razão nº 1: respostas lentas do servidor

Um dos fatores mais notáveis que afetam a velocidade do seu Scraping de dados é o tempo de resposta do servidor. Quando você envia uma solicitação a um site, o servidor processa e responde à sua solicitação. Se o servidor for lento, suas solicitações levarão mais tempo para serem concluídas. Os motivos para um servidor ser lento são tráfego intenso, recursos limitados ou lentidão da rede.

Infelizmente, há pouco que você possa fazer para acelerar um servidor de destino. Isso está além do seu controle, a menos que a lentidão seja causada por um número excessivo de solicitações da sua parte. Se for esse o caso, distribua suas solicitações por um período mais longo, adicionando atrasos aleatórios entre elas.

Motivo nº 2: processamento lento da CPU

A velocidade de processamento da CPU desempenha um papel crucial na rapidez com que seus scripts de scraping podem operar. Quando você executa seus scripts sequencialmente, sua CPU é encarregada de processar cada operação uma de cada vez, o que pode ser demorado. Isso é particularmente perceptível quando seus scripts envolvem cálculos complexos ou transformações de dados.

Além disso, o Parsing de HTML leva algum tempo e pode retardar significativamente o seu processo de scraping de dados. Saiba mais em nosso artigo sobre Scraping de dados na web.

Motivo nº 3: Operações de E/S limitadas

As operações de entrada/saída (I/O) podem facilmente se tornar o gargalo da sua operação de scraping. Isso é especialmente verdadeiro quando o site de destino consiste em várias páginas. Se o seu script for projetado para aguardar respostas de recursos externos antes de prosseguir, isso pode levar a atrasos consideráveis.

Enviar uma solicitação, aguardar a resposta do servidor, processá-la e, em seguida, passar para a próxima solicitação não é uma maneira eficiente de realizar o Scraping de dados.

Outras razões

Outras razões que tornam seu script de Scraping de dados lento são:

  • Código ineficiente: uma lógica de scraping mal otimizada pode tornar todo o processo de scraping lento. Evite estruturas de dados ineficientes, loops desnecessários ou registros excessivos.
  • Limitação de taxa: se o site de destino restringe o número de solicitações que um usuário pode fazer em um período de tempo especificado, seu Scraper automatizado ficará lento como resultado. A solução? Serviços de Proxy!
  • CAPTCHAs e outras soluções anti-scraping: CAPTCHAs e medidas anti-bot podem interromper seu processo de scraping, exigindo a interação do usuário. Descubra outras técnicas anti-scraping.

Técnicas para acelerar o Scraping de dados da web

Nesta seção, você descobrirá os métodos mais populares para acelerar o Scraping de dados. Começaremos com um script básico de scraping em Python e demonstraremos o impacto de várias otimizações nele.

Observação: as técnicas exploradas aqui funcionam com qualquer linguagem de programação ou tecnologia. Python é usado apenas por simplicidade e porque é uma das melhores linguagens de programação para Scraping de dados.

Este é o script inicial de scraping em Python:

import requests
from bs4 import BeautifulSoup
import csv
import time

def scrape_quotes_to_scrape():
    # matriz com as URLs da página a ser raspada
    urls = [
        "http://quotes.toscrape.com/",
        "https://quotes.toscrape.com/page/2/",
        "https://quotes.toscrape.com/page/3/",
        "https://quotes.toscrape.com/page/4/",
        "https://quotes.toscrape.com/page/5/",
        "https://quotes.toscrape.com/page/6/",
        "https://quotes.toscrape.com/page/7/",
        "https://quotes.toscrape.com/page/8/",
        "https://quotes.toscrape.com/page/9/",
        "https://quotes.toscrape.com/page/10/"
    ]

    # onde armazenar os dados coletados
    quotes = []

    # extrair as páginas sequencialmente
    para url em urls:
        imprimir(f"Extraindo página: '{url}'")

        # enviar uma solicitação GET para obter o HTML da página
        resposta = solicitações.get(url)
        # analisar o HTML da página usando BeautifulSoup
        soup = BeautifulSoup(response.content, "html.parser")

        # selecionar todos os elementos de citação na página
        quote_html_elements = soup.select(".quote")

        # iterar sobre os elementos de citação e coletar seu conteúdo
        para quote_html_element em quote_html_elements:
            # extrair o texto da citação
            text = quote_html_element.select_one(".text").get_text()
            # extrair o autor da citação
            author = quote_html_element.select_one(".author").get_text()
            # extrair tags associadas à citação
            tags = [tag.get_text() for tag in quote_html_element.select(".tag")]

            # preencher um novo objeto de citação e adicioná-lo à lista
            citação = {
                "text": texto,
                "author": autor,
                "tags": ", ".join(tags)
            }
            citações.append(citação)

        print(f"Página '{url}' raspada com sucesso")

    print("Exportando dados raspados para CSV")

    # exportar as citações extraídas para um arquivo CSV
    com open("quotes.csv", "w", newline="", encoding="utf-8") como csvfile:
        fieldnames = ["text", "author", "tags"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        writer.writeheader()
        writer.writerows(quotes)

    print("Citações exportadas para CSVn")

# medir o tempo de execução
start_time = time.time()
scrape_quotes_to_scrape()
end_time = time.time()

execution_time = end_time - start_time
print(f"Tempo de execução: {execution_time:.2f} segundos")

O Scraper acima tem como alvo 10 URLs paginadas do site Quotes to Scrape. Para cada URL, o script executa as seguintes operações:

  1. Envia uma solicitação GET usando requests para buscar o HTML da página.
  2. Parses o conteúdo HTML com BeautifulSoup.
  3. Extrai o texto da citação, o autor e as tags para cada elemento de citação na página.
  4. Armazena os dados extraídos em uma lista de dicionários.

Por fim, exporta os dados coletados para um arquivo CSV chamado quotes.csv.

Para executar o script, instale as bibliotecas necessárias com:

pip install requests beautifulsoup4

A chamada da função scrape_quotes_to_scrape() é envolvida por chamadas time.time() para medir quanto tempo leva o processo de extração. Em nossa máquina, o script inicial leva aproximadamente 4,51 segundos para ser concluído.

A execução do script gera um arquivo quotes.csv na pasta do seu projeto. Além disso, você verá logs semelhantes aos seguintes:

Raspando a página: 'http://quotes.toscrape.com/'
Página 'http://quotes.toscrape.com/' raspada com sucesso

Raspando a página: 'https://quotes.toscrape.com/page/2/'
Página 'https://quotes.toscrape.com/page/2/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/3/'
Página 'https://quotes.toscrape.com/page/3/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/4/'
Página 'https://quotes.toscrape.com/page/4/' extraída com sucesso

Extraindo página: 'https://quotes.toscrape.com/page/5/'
Página 'https://quotes.toscrape.com/page/5/' extraída com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/6/'
Página 'https://quotes.toscrape.com/page/6/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/7/'
Página 'https://quotes.toscrape.com/page/7/' extraída com sucesso

Extraindo página: 'https://quotes.toscrape.com/page/8/'
Página 'https://quotes.toscrape.com/page/8/' extraída com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/9/'
Página 'https://quotes.toscrape.com/page/9/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/10/'
Página 'https://quotes.toscrape.com/page/10/' extraída com sucesso

Exportando dados extraídos para CSV
Citações exportadas para CSV

Tempo de execução: 4,63 segundos

Esta saída mostra claramente que o script extrai sequencialmente cada página da web paginada de Quotes to Scrape. Como você verá a seguir, algumas otimizações alterarão significativamente o fluxo e a velocidade desse processo.

Agora, vamos descobrir como tornar o Scraping de dados da web mais rápido!

1. Use uma biblioteca de Parsing HTML mais rápida

A análise de dados consome tempo e recursos, e diferentes analisadores HTML usam várias abordagens para realizar essa tarefa. Alguns se concentram em fornecer um rico conjunto de recursos com uma API autodescritiva, enquanto outros priorizam o desempenho. Para obter mais detalhes, consulte nosso guia sobre os melhores analisadores HTML.

Em Python, o Beautiful Soup é o analisador HTML mais popular, mas não é necessariamente o mais rápido. Veja alguns benchmarks para obter mais contexto.

Na realidade, o Beautiful Soup atua apenas como um invólucro em torno de diferentes analisadores subjacentes. Você pode especificar o analisador que deseja usar ao inicializá-lo, por meio do segundo argumento:

soup = BeautifulSoup(response.content, "html.parser")

Geralmente, o Beautiful Soup é usado em combinação com o html.parser, o analisador integrado da biblioteca padrão do Python. No entanto, se você estiver buscando velocidade, considere o lxml. Este é um dos analisadores HTML mais rápidos disponíveis em Python, pois é baseado em uma implementação em C.

Para instalar o lxml, execute o seguinte comando:

pip install lxml

Depois de instalado, você pode usá-lo com o Beautiful Soup assim:

soup = BeautifulSoup(response.content, "lxml")

Agora, execute seu script de scraping Python novamente. Desta vez, você deverá ver a seguinte saída:

# omitido por brevidade...

Tempo de execução: 4,35 segundos

O tempo de execução caiu de 4,61 segundos para 4,35 segundos. Embora essa mudança possa parecer pequena, o impacto dessa otimização depende muito do tamanho e da complexidade das páginas HTML que estão sendo analisadas e de quantos elementos estão sendo selecionados.

Neste exemplo, o site de destino tem páginas com uma estrutura DOM simples, curta e rasa. Ainda assim, obter uma melhoria de velocidade de cerca de 6% com apenas uma pequena alteração no código é um ganho que vale a pena!

👍 Prós:

  • Fácil de implementar no Beautiful Soup

👎 Contras:

  • Pequena vantagem
  • Funciona apenas em páginas com estruturas DOM complexas
  • Analisadores HTML mais rápidos podem ter uma API mais complexa

2. Implementar scraping multiprocessamento

O multiprocessamento é uma abordagem de execução paralela em que um programa gera vários processos. Cada um desses processos opera em paralelo e de forma independente em um núcleo da CPU para executar tarefas simultaneamente, em vez de sequencialmente.

Esse método é particularmente benéfico para operações vinculadas a E/S, como o Scraping de dados. O motivo é que o principal gargalo geralmente é o tempo gasto aguardando respostas dos servidores da web. Ao utilizar vários processos, você pode enviar solicitações para várias páginas ao mesmo tempo, reduzindo o tempo total de scraping.

Para adaptar seu script de scraping para multiprocessamento, você precisa fazer algumas modificações importantes na lógica de execução. Siga as etapas abaixo para transformar seu Scraper Python de uma abordagem sequencial para uma abordagem de multiprocessamento.

Para começar a usar o multiprocessamento em Python, a primeira etapa é importar Pool e cpu_count do módulo multiprocessing:

from multiprocessing import Pool, cpu_count

Pool fornece o que você precisa para gerenciar um conjunto de processos de trabalho. Já cpu_count ajuda a determinar o número de núcleos de CPU disponíveis para processamento paralelo.

Em seguida, isole a lógica para raspar uma única URL dentro de uma função:

def scrape_page(url):
    print(f"Raspando página: '{url}'")

    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")
    quote_html_elements = soup.select(".quote")

    quotes = []
    for quote_html_element in quote_html_elements:
        text = quote_html_element.select_one(".text").get_text()
        author = quote_html_element.select_one(".author").get_text()
        tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
        citações.append({
            "texto": texto,
            "autor": autor,
            "tags": ", ".join(tags)
        })

    imprimir(f"Página '{url}' raspada com sucesso")

    retornar citações

A função acima será chamada por cada processo de trabalho e executada em um núcleo da CPU por vez.

Em seguida, substitua o fluxo de scraping sequencial por uma lógica de multiprocessamento:

def scrape_quotes():
    urls = [
        "http://quotes.toscrape.com/",
        "https://quotes.toscrape.com/page/2/",
        "https://quotes.toscrape.com/page/3/",
        "https://quotes.toscrape.com/page/4/",
        "https://quotes.toscrape.com/page/5/",
        "https://quotes.toscrape.com/page/6/",
        "https://quotes.toscrape.com/page/7/",
        “https://quotes.toscrape.com/page/8/”,
        “https://quotes.toscrape.com/page/9/”,
        “https://quotes.toscrape.com/page/10/”
    ]

    # criar um conjunto de processos
com Pool(processes=cpu_count()) como pool:
resultados = pool.map(scrape_page, urls)

# achatar a lista de resultados
citações = [citação para sublista em resultados para citação em sublista]

imprimir("Exportando dados coletados para CSV")

    com open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") como csvfile:
        fieldnames = ["text", "author", "tags"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(citações)

    imprimir("Citações exportadas para CSVn")

Por fim, execute a função scrape_quotes() enquanto mede o tempo de execução:

if __name__ == "__main__":
    start_time = time.time()
    scrape_quotes()
    end_time = time.time()

    execution_time = end_time - start_time
    print(f"Tempo de execução: {execution_time:.2f} segundos")

Observe que a construção if __name__ == "__main__": é necessária para impedir que certas partes do seu código sejam executadas quando o módulo é importado. Sem essa verificação, o módulo multiprocessing pode tentar gerar novos processos que podem levar a um comportamento inesperado, especialmente no Windows.

Junte tudo e você obterá:

from multiprocessing import Pool, cpu_count
import requests
from bs4 import BeautifulSoup
import csv
import time

def scrape_page(url):
    print(f"Raspando página: '{url}'")

    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")
    quote_html_elements = soup.select(".quote")

    quotes = []
    for quote_html_element in quote_html_elements:
        text = quote_html_element.select_one(".text").get_text()
        author = quote_html_element.select_one(".author").get_text()
        tags = [tag.get_text() para tag em quote_html_element.select(".tag")]
        citações.append({
            "texto": texto,
            "autor": autor,
            "tags": ", ".join(tags)
        })

    imprimir(f"Página '{url}' raspada com sucesso")

    retornar citações

def extrair_citações():
    urls = [
        "http://quotes.toscrape.com/",
        "https://quotes.toscrape.com/page/2/",
        "https://quotes.toscrape.com/page/3/",
        "https://quotes.toscrape.com/page/4/",
        "https://quotes.toscrape.com/page/5/",
        "https://quotes.toscrape.com/page/6/",
        "https://quotes.toscrape.com/page/7/",
        “https://quotes.toscrape.com/page/8/”,
        “https://quotes.toscrape.com/page/9/”,
        “https://quotes.toscrape.com/page/10/”
    ]

    # criar um conjunto de processos
com Pool(processes=cpu_count()) como pool:
resultados = pool.map(scrape_page, urls)

# achatar a lista de resultados
citações = [citação para sublista em resultados para citação em sublista]

imprimir("Exportando dados coletados para CSV")

    com open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") como csvfile:
        fieldnames = ["text", "author", "tags"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(citações)

    imprimir("Citações exportadas para CSVn")

if __name__ == "__main__":
    start_time = time.time()
    scrape_quotes()
    end_time = time.time()

    execution_time = end_time - start_time
    print(f"Tempo de execução: {execution_time:.2f} segundos")

Execute o script novamente. Desta vez, ele produzirá alguns registros como segue:

Raspando a página: 'http://quotes.toscrape.com/'
Raspando a página: 'https://quotes.toscrape.com/page/2/'
Raspando a página: 'https://quotes.toscrape.com/page/3/'
Raspando a página: 'https://quotes.toscrape.com/page/4/'
Raspagem da página: 'https://quotes.toscrape.com/page/5/'
Raspagem da página: 'https://quotes.toscrape.com/page/6/'
Raspagem da página: 'https://quotes.toscrape.com/page/7/'
Raspagem da página: 'https://quotes.toscrape.com/page/8/'
Página 'http://quotes.toscrape.com/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/9/'
Página 'https://quotes.toscrape.com/page/3/' raspada com sucesso

Raspando a página: 'https://quotes.toscrape.com/page/10/'
Página 'https://quotes.toscrape.com/page/4/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/5/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/6/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/7/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/2/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/8/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/9/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/10/' extraída com sucesso

Exportando dados extraídos para CSV
Cotações exportadas para CSV

Tempo de execução: 1,87 segundos

Como você pode ver, a ordem de execução não é mais sequencial. Seu script agora pode extrair várias páginas simultaneamente. Especificamente, ele pode extrair até o número de núcleos disponíveis em sua CPU (8, no nosso caso).

O processamento paralelo resulta em uma melhoria de tempo de cerca de 145%, reduzindo o tempo de execução de 4,61 segundos para 1,87 segundos. Isso é impressionante!

👍 Prós:

  • Ótima melhoria no tempo de execução
  • Suporte nativo pela maioria das linguagens de programação

👎 Contras:

  • Limitado pelo número de núcleos disponíveis em sua máquina
  • Não respeita a ordem das URLs na lista
  • Requer muitas alterações no código

3. Implemente o scraping multithreading

Multithreading é uma técnica de programação para executar várias threads simultaneamente em um único processo. Isso permite que seu script execute várias tarefas simultaneamente, com cada tarefa sendo tratada por uma thread dedicada.

Embora semelhante ao multiprocessamento, o multithreading não requer necessariamente vários núcleos de CPU. Isso ocorre porque um único núcleo de CPU pode executar vários threads simultaneamente, compartilhando o mesmo espaço de memória. Aprofunde-se neste conceito em nosso guia sobre concorrência x paralelismo.

Lembre-se de que transformar um script de scraping de uma abordagem sequencial para uma multithread requer alterações semelhantes às descritas no capítulo anterior.

Nesta implementação, usaremos o ThreadPoolExecutor do módulo concurrent.futures do Python. Você pode importá-lo conforme abaixo:

from concurrent.futures import ThreadPoolExecutor

O ThreadPoolExecutor fornece uma interface de alto nível para gerenciar um conjunto de threads, executando-as simultaneamente para você.

Como antes, comece isolando a lógica para scraping de um único URL em uma função, assim como fizemos no capítulo anterior. A principal diferença é que agora você precisa utilizar o ThreadPoolExecutor para executar a função em várias threads:

quotes = []

# crie um pool de threads com até 10 workers
with ThreadPoolExecutor(max_workers=10) as executor:
    # use map para aplicar a função scrape_page a cada URL
    results = executor.map(scrape_page, urls)

# combine os resultados de todos os threads
for result in results:
    quotes.extend(result)

Por padrão, se max_workers for None ou não for especificado, o padrão será o número de processadores em sua máquina, multiplicado por 5. Neste caso, temos apenas 10 páginas, então definir como 10 será suficiente. Não se esqueça de que abrir muitas threads pode tornar seu sistema mais lento e pode levar a prejuízos no desempenho, em vez de melhorias.

O script de scraping completo conterá o seguinte código:

from concurrent.futures import ThreadPoolExecutor
import requests
from bs4 import BeautifulSoup
import csv
import time

def scrape_page(url):
    print(f"Raspando a página: '{url}'")

    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")
    quote_html_elements = soup.select(".quote")

    quotes = []
    for quote_html_element in quote_html_elements:
        text = quote_html_element.select_one(".text").get_text()
        author = quote_html_element.select_one(".author").get_text()
        tags = [tag.get_text() para tag em quote_html_element.select(".tag")]
        citações.append({
            "texto": texto,
            "autor": autor,
            "tags": ", ".join(tags)
        })

    imprimir(f"Página '{url}' raspada com sucesso")

    retornar citações

def extrair_citações():
    urls = [
        "http://quotes.toscrape.com/",
        "https://quotes.toscrape.com/page/2/",
        "https://quotes.toscrape.com/page/3/",
        "https://quotes.toscrape.com/page/4/",
        "https://quotes.toscrape.com/page/5/",
        "https://quotes.toscrape.com/page/6/",
        "https://quotes.toscrape.com/page/7/",
        “https://quotes.toscrape.com/page/8/”,
        “https://quotes.toscrape.com/page/9/”,
        “https://quotes.toscrape.com/page/10/”
    ]
    
    # onde armazenar os dados coletados
    quotes = []

    # criar um pool de threads com até 10 trabalhadores
    com ThreadPoolExecutor(max_workers=10) como executor:
        # usar map para aplicar a função scrape_page a cada URL
        resultados = executor.map(scrape_page, urls)

    # combinar os resultados de todas as threads
    para resultado em resultados:
        cotações.extend(resultado)

    imprimir("Exportando dados coletados para CSV")

    com abrir("quotes_multiprocessing.csv", "w", nova linha="", codificação="utf-8") como csvfile:
        nomes de campos = ["texto", "autor", "tags"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(quotes)

    print("Citações exportadas para CSVn")

if __name__ == "__main__":
    start_time = time.time()
    scrape_quotes()
    end_time = time.time()

    execution_time = end_time - start_time
    print(f"Tempo de execução: {execution_time:.2f} segundos")

Inicie-o e ele registrará as mensagens abaixo:

Raspando a página: 'http://quotes.toscrape.com/'
Raspando a página: 'https://quotes.toscrape.com/page/2/'
Raspando a página: 'https://quotes.toscrape.com/page/3/'
Raspando a página: 'https://quotes.toscrape.com/page/4/'
Raspando a página: 'https://quotes.toscrape.com/page/5/'
Raspando a página: 'https://quotes.toscrape.com/page/6/'
Raspando a página: 'https://quotes.toscrape.com/page/7/'
Raspagem da página: 'https://quotes.toscrape.com/page/8/'
Raspagem da página: 'https://quotes.toscrape.com/page/9/'
Raspagem da página: 'https://quotes.toscrape.com/page/10/'
Página 'http://quotes.toscrape.com/' raspada com sucesso

Página 'https://quotes.toscrape.com/page/6/' raspada com sucesso

Página 'https://quotes.toscrape.com/page/7/' raspada com sucesso

Página 'https://quotes.toscrape.com/page/10/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/8/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/5/' extraída com sucesso

Página “https://quotes.toscrape.com/page/9/” extraída com sucesso

Página “https://quotes.toscrape.com/page/4/” extraída com sucesso

Página “https://quotes.toscrape.com/page/3/” extraída com sucesso

Página 'https://quotes.toscrape.com/page/2/' extraída com sucesso

Exportando dados extraídos para CSV
Cotações exportadas para CSV

Tempo de execução: 0,52 segundos

Semelhante à extração multiprocessamento, a ordem de execução das páginas não é mais sequencial. Desta vez, a melhoria no desempenho é ainda maior do que com o multiprocessamento. Isso porque o script agora pode executar 10 solicitações simultaneamente, excedendo o limite anterior de 8 solicitações (o número de núcleos da CPU).

A melhoria no tempo é enorme, de 4,61 segundos para 0,52 segundos, resultando em uma redução percentual de cerca de 885%!

👍 Prós:

  • Grande melhoria no tempo de execução
  • Suporte nativo pela maioria das tecnologias

👎 Contras:

  • Não é fácil encontrar o número certo de threads
  • Não respeita a ordem das URLs na lista
  • Requer muitas alterações no código

4. Usando Async/Await Scraping

A programação assíncrona é um paradigma de programação moderno que permite escrever código não bloqueante. A ideia é dar aos desenvolvedores a capacidade de lidar com operações simultâneas sem gerenciar explicitamente multithreading ou multiprocessamento.

Em uma abordagem síncrona tradicional, cada operação deve ser concluída antes que a próxima comece. Isso pode levar a ineficiências, especialmente em tarefas vinculadas a E/S, como o Scraping de dados. Com a programação assíncrona, você pode iniciar várias operações de E/S simultaneamente e aguardar que elas sejam concluídas. Isso mantém seu script responsivo e eficiente.

Em Python, o scraping assíncrono é geralmente implementado usando o módulo asyncio da biblioteca padrão. Esse pacote fornece a infraestrutura para escrever código simultâneo de thread único usando corrotinas, por meio das palavras-chave async e await.

No entanto, bibliotecas HTTP padrão, como requests, não oferecem suporte a operações assíncronas. Portanto, você precisa usar um cliente HTTP assíncrono como o AIOHTTP, que foi projetado especificamente para funcionar perfeitamente com o asyncio. Essa combinação ajuda a enviar várias solicitações HTTP simultaneamente sem bloquear a execução do seu script.

Instale o AIOHTTP usando o seguinte comando:

pip install aiohttp

Em seguida, importe asyncio e aiohttp:

import asyncio
import aiohttp

Assim como nos capítulos anteriores, encapsule a lógica para extrair um único URL em uma função. No entanto, desta vez, a função será assíncrona:

async def scrape_url(session, url):
    async with session.get(url) as response:
        print(f"Raspando página: '{url}'")

        html_content = await response.text()
        soup = BeautifulSoup(html_content, "html.parser")
        # lógica de raspagem...

Observe o uso da função await para recuperar o HTML da página da web.

Para executar a função em paralelo, crie uma sessão AIOHTTP e reúna várias tarefas de raspagem:

# executando as tarefas de raspagem simultaneamente
async com aiohttp.ClientSession() como sessão:
    tarefas = [raspar_url(sessão, url) para url em urls]
    resultados = aguardar asyncio.gather(*tarefas)

# achatar a lista de resultados
citações = [citação para sublista em resultados para citação em sublista]

Por fim, use asyncio.run() para executar sua função principal de scraping assíncrona:

if __name__ == "__main__":
    start_time = time.time()
    asyncio.run(scrape_quotes())
    end_time = time.time()

    execution_time = end_time - start_time
    print(f"Execution time: {execution_time:.2f} seconds")

Seu script de scraping assíncrono em Python conterá estas linhas de código:

import asyncio
import aiohttp
from bs4 import BeautifulSoup
import csv
import time

async def scrape_url(session, url):
    async with session.get(url) as response:
        print(f"Raspando página: '{url}'")

        html_content = await response.text()
        soup = BeautifulSoup(html_content, "html.parser")
        quote_html_elements = soup.select(".quote")

        quotes = []
        para quote_html_element em quote_html_elements:
            text = quote_html_element.select_one(".text").get_text()
            author = quote_html_element.select_one(".author").get_text()
            tags = [tag.get_text() para tag em quote_html_element.select(".tag")]
            citações.append({
                "texto": texto,
                "autor": autor,
                "tags": ", ".join(tags)
            })

        imprimir(f"Página '{url}' raspada com sucesso")

        retornar citações

async def extrair_citações():
    urls = [
        "http://quotes.toscrape.com/",
        "https://quotes.toscrape.com/page/2/",
        "https://quotes.toscrape.com/page/3/",
        "https://quotes.toscrape.com/page/4/",
        "https://quotes.toscrape.com/page/5/",
        "https://quotes.toscrape.com/page/6/",
        "https://quotes.toscrape.com/page/7/",
        “https://quotes.toscrape.com/page/8/”,
        “https://quotes.toscrape.com/page/9/”,
        “https://quotes.toscrape.com/page/10/”
    ]

    # executando as tarefas de scraping simultaneamente
    async com aiohttp.ClientSession() como sessão:
        tarefas = [scrape_url(sessão, url) para url em urls]
        resultados = aguardar asyncio.gather(*tarefas)

    # achatar a lista de resultados
    citações = [citação para sublista em resultados para citação em sublista]

    imprimir("Exportando dados raspados para CSV")

    com abrir("quotes_multiprocessing.csv", "w", nova linha="", codificação="utf-8") como csvfile:
        nomes de campos = ["texto", "autor", "tags"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(quotes)

    print("Citações exportadas para CSVn")

if __name__ == "__main__":
    start_time = time.time()
    asyncio.run(scrape_quotes())
    end_time = time.time()

    execution_time = end_time - start_time
    print(f"Tempo de execução: {execution_time:.2f} segundos")

Execute-o e você obterá um resultado como este:

Raspando a página: 'http://quotes.toscrape.com/'
Página 'http://quotes.toscrape.com/' raspada com sucesso                                                                

Raspando a página: 'https://quotes.toscrape.com/page/3/'
Raspando a página: 'https://quotes.toscrape.com/page/7/'
Raspando a página: 'https://quotes.toscrape.com/page/9/'
Raspando a página: 'https://quotes.toscrape.com/page/6/'
Raspagem da página: 'https://quotes.toscrape.com/page/8/'
Raspagem da página: 'https://quotes.toscrape.com/page/10/'
Página 'https://quotes.toscrape.com/page/3/' raspada com sucesso

Raspagem da página: 'https://quotes.toscrape.com/page/5/'
Raspagem da página: 'https://quotes.toscrape.com/page/4/'
Página 'https://quotes.toscrape.com/page/7/' raspada com sucesso

Página 'https://quotes.toscrape.com/page/9/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/6/' extraída com sucesso

Extraindo página: 'https://quotes.toscrape.com/page/2/'
Página 'https://quotes.toscrape.com/page/10/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/5/' extraída com sucesso

Página 'https://quotes.toscrape.com/page/4/' extraída com sucesso

Página “https://quotes.toscrape.com/page/8/” extraída com sucesso

Página “https://quotes.toscrape.com/page/2/” extraída com sucesso

Exportando dados extraídos para CSV
Cotações exportadas para CSV

Tempo de execução: 0,51 segundos

Observe que o tempo de execução é semelhante ao da abordagem multithreading, mas com a vantagem adicional de não ser necessário gerenciar manualmente os threads.

👍 Prós:

  • Grande ganho de tempo de execução
  • A programação moderna é baseada na lógica assíncrona
  • Não requer gerenciamento manual de threads ou processos

👎 Contras:

  • Não é tão fácil de dominar
  • Não respeita a ordem das URLs na lista
  • Requer bibliotecas assíncronas dedicadas

5. Outras dicas e abordagens para acelerar o scraping

Outras maneiras de tornar o Scraping de dados mais rápido são:

  • Otimização da taxa de solicitações: ajuste os intervalos entre as solicitações para encontrar o equilíbrio ideal entre velocidade e evitar limitações de taxa ou banimento.
  • Proxies rotativos: use proxies rotativos para distribuir as solicitações por vários endereços IP, reduzindo as chances de ser bloqueado e permitindo um scraping mais rápido. Veja os melhores proxies rotativos.
  • Raspagem paralela com sistemas distribuídos: distribua as tarefas de raspagem por várias máquinas online.
  • Reduza a renderização de JavaScript: tente evitar ferramentas de automação de navegador, preferindo ferramentas como clientes HTTP como analisadores HTML. Lembre-se de que os navegadores consomem muitos recursos e são muito mais lentos do que a maioria dos analisadores HTML tradicionais.

Conclusão

Neste guia, vimos como tornar o Scraping de dados da web mais rápido. Descobrimos as principais razões pelas quais um script de Scraping de dados pode ser lento e examinamos várias técnicas para resolver esses problemas usando um script Python de amostra. Com apenas alguns ajustes na lógica de Scraping de dados, conseguimos uma melhoria de 8 vezes no tempo de execução.

Embora a otimização manual da lógica de Scraping de dados seja crucial para acelerar o processo de recuperação de dados, usar as ferramentas certas é igualmente importante. Ao visar sites dinâmicos que exigem soluções de automação de navegador, as coisas podem se tornar mais complicadas, pois os navegadores tendem a ser lentos e consumir muitos recursos.

Para superar esses desafios, experimenteo Navegador de scraping, uma solução totalmente hospedada na nuvem projetada para scraping. Ele se integra perfeitamente ao Puppeteer, Selenium, Playwright e outras ferramentas populares de automação de navegador. Equipado com um solucionador automático de CAPTCHA e apoiado por uma rede proxy de mais de 150 milhões de IPs residencialis, ele oferece escalabilidade ilimitada para cobrir qualquer necessidade de scraping!

Inscreva-se agora e comece seu teste grátis.