Concorrência vs Paralelismo: Diferenças significativas para a Web Scraping

Scraping, As diferenças, Jan-17-20225 minutos de leitura

Quando se trata de simultaneidade vs. paralelismo, pode parecer que eles se referem aos mesmos conceitos em execuções de programas de computador em um ambiente com vários threads. Bem, depois de ver suas definições no dicionário Oxford, você pode estar inclinado a pensar assim. No entanto, quando você se aprofunda nessas noções com relação a

Quando se trata de simultaneidade vs. paralelismo, pode parecer que eles se referem aos mesmos conceitos em execuções de programas de computador em um ambiente com vários threads. Bem, depois de ver suas definições no dicionário Oxford, você pode estar inclinado a pensar assim. No entanto, quando você se aprofunda nessas noções com relação a como a CPU executa as instruções do programa, percebe que a simultaneidade e o paralelismo são dois conceitos distintos. 

Este artigo se aprofunda na concorrência e no paralelismo, como eles variam e como trabalham juntos para melhorar a produtividade da execução do programa. Por fim, ele discutirá quais são as duas estratégias mais adequadas para a Web scraping. Então, vamos começar.

O que é execução simultânea?

Primeiro, para simplificar, começaremos com a simultaneidade em um único aplicativo executado em um único processador. O Dictionary.com define simultaneidade como uma ação ou esforço combinado e a ocorrência de eventos simultâneos. No entanto, pode-se dizer o mesmo sobre a execução paralela, pois as execuções coincidem e, portanto, essa definição é um pouco enganosa no mundo da programação de computadores.

No dia a dia, você terá execuções simultâneas no seu computador. Por exemplo, você pode ler um artigo de blog no navegador enquanto ouve música no Windows Media Player. Haveria outro processo em execução: o download de um arquivo PDF de outra página da Web - todos esses exemplos são processos separados.

Antes da invenção dos aplicativos de execução simultânea, as CPUs executavam programas sequencialmente. Isso significava que as instruções de um programa precisavam concluir a execução antes que a CPU passasse para o próximo.

Por outro lado, a execução simultânea alterna um pouco de cada processo até que todos estejam concluídos.

Em um ambiente de execução multithread com um único processador, um programa é executado quando outro está bloqueado para entrada do usuário. Agora você pode se perguntar o que é um ambiente multithread. É uma coleção de threads que são executados independentemente uns dos outros - mais sobre threads na próxima seção.

A simultaneidade não deve ser confundida com a execução paralela

Agora, então, é mais fácil confundir simultaneidade com paralelismo. O que queremos dizer com simultaneidade nos exemplos acima é que os processos não estão sendo executados paralelamente. 

Em vez disso, digamos que um processo exija a conclusão de uma operação de entrada/saída, então o sistema operacional alocaria a CPU para outro processo enquanto ele conclui sua operação de E/S. Esse procedimento continuaria até que todos os processos concluíssem sua execução. Esse procedimento continuaria até que todos os processos concluíssem sua execução.

No entanto, como a alternância das tarefas pelo sistema operacional ocorre em um nano ou microssegundo, para o usuário parece que os processos são executados em paralelo, 

O que é um Thread?

Diferentemente da execução sequencial, a CPU pode não executar todo o processo/programa de uma só vez com as arquiteturas atuais. Em vez disso, a maioria dos computadores pode dividir o processo inteiro em vários componentes leves que são executados independentemente uns dos outros em uma ordem arbitrária. São esses componentes leves que são chamados de threads.

Por exemplo, o Google Docs pode ter vários threads que operam ao mesmo tempo. Enquanto um thread salva automaticamente seu trabalho, outro pode ser executado em segundo plano, verificando a ortografia e a gramática.  

O sistema operacional determina a ordem e quais threads devem ser priorizados, o que depende do sistema.

O que é execução paralela?

Agora você conhece a execução de programas de computador em um ambiente com uma única CPU. Por outro lado, os computadores modernos executam muitos processos simultaneamente em várias CPUs, o que é conhecido como execução paralela. A maioria das arquiteturas atuais tem várias CPUs.

Como você pode ver no diagrama abaixo, a CPU executa cada thread pertencente a um processo paralelamente entre si.  

No paralelismo, o sistema operacional alterna os threads de e para a CPU em intervalos de macro ou microssegundos, dependendo da arquitetura do sistema. Para que o sistema operacional alcance a execução paralela, os programadores de computador usam o conceito conhecido como programação paralela. Na programação paralela, os programadores desenvolvem códigos para fazer o melhor uso das várias CPUs. 

Como a concorrência pode acelerar a Web scraping

Com tantos domínios utilizando a Web scraping para scrape dados de sites, uma desvantagem significativa é o tempo que consome para scrape grandes quantidades de dados. Se você não for um desenvolvedor experiente, poderá acabar perdendo muito tempo experimentando técnicas específicas antes de finalmente executar o código sem erros e com perfeição.

A seção abaixo descreve alguns dos motivos pelos quais a Web scraping é lenta.

Motivos significativos pelos quais a Web scraping está lenta?

Em primeiro lugar, o raspador precisa navegar até o site de destino na Web scraping. Em seguida, ele teria de extrair e recuperar as entidades das tags HTML das quais você deseja fazer scrape . Por fim, na maioria das circunstâncias, você estaria salvando os dados em um arquivo externo, como o formato CSV.  

Portanto, como você pode ver, a maioria das tarefas acima exige uma operação de E/S com limite pesado, como extrair dados de sites e salvá-los em arquivos externos. A navegação para os sites de destino geralmente depende de fatores externos, como velocidade da rede ou espera até que uma rede fique disponível.

Como você pode ver na figura abaixo, esse consumo de tempo extremamente lento pode prejudicar ainda mais o processo de scraping quando você tiver de scrape três ou mais sites. Pressupõe-se que você execute a operação scraping sequencialmente.

Portanto, de uma forma ou de outra, você teria que aplicar simultaneidade ou paralelismo às suas operações em scraping . Analisaremos o paralelismo primeiro na próxima seção.

Concorrência na Web scraping usando Python

Tenho certeza de que você já tem uma visão geral da concorrência e do paralelismo. Esta seção se concentrará na simultaneidade na Web scraping com um exemplo simples de codificação em Python.

Um exemplo simples de demonstração sem execução simultânea

Neste exemplo, vamos scrape o URL dos países por uma lista de capitais com base na população da Wikipedia. O programa salvaria os links e, em seguida, acessaria cada uma das 240 páginas e salvaria o HTML dessas páginas localmente.

 Para demonstrar os efeitos da concorrência, mostraremos dois programas - um com execução sequencial e outro com vários threads.

Aqui está o código:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import time

def get_countries():
    countries = 'https://en.wikipedia.org/wiki/List_of_national_capitals_by_population'
    all_countries = []
    response = requests.get(countries)
    soup = BeautifulSoup(response.text, "html.parser")
    countries_pl = soup.select('th .flagicon+ a')
    for link_pl in countries_pl:
        link = link_pl.get("href")
        link = urljoin(countries, link)
        
        all_countries.append(link)
    return all_countries
  
def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)
  

        
def main():
    clinks = get_countries()
    print(f"Total pages: {len(clinks)}")
    start_time = time.time()
    for link in clinks:
        fetch(link)
 
    duration = time.time() - start_time
    print(f"Downloaded {len(links)} links in {duration} seconds")
main()

Explicação do código

Primeiramente, importamos as bibliotecas, inclusive a BeautifulSoap, para extrair os dados HTML. As outras bibliotecas incluem a solicitação para acessar o site, a urllib para unir os URLs, como você descobrirá, e a biblioteca de tempo para descobrir o tempo total de execução do programa.

importar solicitações
from bs4 import BeautifulSoup
from urllib.parse import urljoin
importar time

O programa começa com o módulo principal, que chama a função get_countries(). Em seguida, a função acessa o URL da Wikipédia especificado na variável countries por meio da instância BeautifulSoup através do analisador de HTML.

Em seguida, ele procura o URL da lista de países na tabela extraindo o valor no atributo href da tag âncora.

Os links que você recupera são links relativos. A função urljoin os converterá em links absolutos. Esses links são então anexados à matriz all_countries, que é retornada à função principal 

Em seguida, a função fetch salva o conteúdo HTML em cada link como um arquivo HTML. É isso que esses trechos de código fazem:

def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)

Por fim, a função principal imprime o tempo necessário para salvar os arquivos no formato HTML. Em nosso PC, foram necessários 131,22 segundos.

Bem, esse tempo certamente poderia ser mais rápido. Descobriremos isso na próxima seção, em que o mesmo programa é executado com vários threads.

O mesmo programa com simultaneidade

Na versão multithread, teríamos que fazer pequenas alterações para que o programa fosse executado mais rapidamente.

Lembre-se de que a simultaneidade consiste em criar vários threads e executar o programa. Há duas maneiras de criar threads: manualmente e usando a classe ThreadPoolExecutor. 

Depois de criar os threads manualmente, você pode usar a função join em todos os threads para o método manual. Ao fazer isso, o método principal aguardaria que todos os threads concluíssem sua execução.

Nesse programa, executaremos o código com a classe ThreadPoolExecutor, que faz parte do módulo concurrent. futures. Portanto, antes de mais nada, você precisa colocar a linha abaixo no programa acima. 

from concurrent.futures import ThreadPoolExecutor

Depois disso, você pode alterar o loop for que salva o conteúdo HTML no formato HTML da seguinte forma:

  com ThreadPoolExecutor(max_workers=32) como executor:
           executor.map(fetch, clinks)

O código acima cria um pool de threads com um máximo de 32 threads. Para cada CPU, o parâmetro max_workers é diferente, e você precisa experimentar valores diferentes. Isso não significa necessariamente que quanto maior o número de threads, mais rápido será o tempo de execução.

Portanto, nosso PC produziu um resultado de 15,14 segundos, o que é muito melhor do que quando o executamos sequencialmente.

Portanto, antes de passarmos para a próxima seção, aqui está o código final do programa com execução simultânea:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor
import time

def get_countries():
    countries = 'https://en.wikipedia.org/wiki/List_of_national_capitals_by_population'
    all_countries = []
    response = requests.get(countries)
    soup = BeautifulSoup(response.text, "html.parser")
    countries_pl = soup.select('th .flagicon+ a')
    for link_pl in countries_pl:
        link = link_pl.get("href")
        link = urljoin(countries, link)
        
        all_countries.append(link)
    return all_countries
  
def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)


def main():
  clinks = get_countries()
  print(f"Total pages: {len(clinks)}")
  start_time = time.time()
  

  with ThreadPoolExecutor(max_workers=32) as executor:
           executor.map(fetch, clinks)
        
 
  duration = time.time()-start_time
  print(f"Downloaded {len(clinks)} links in {duration} seconds")
main()

Como o paralelismo pode acelerar a Web scraping

Esperamos que você tenha adquirido uma compreensão da execução simultânea. Para ajudá-lo a analisar melhor, vamos ver como o mesmo programa funciona em um ambiente de multiprocessador com processos executados paralelamente em várias CPUs.

Primeiro, você precisa importar o módulo necessário:

from multiprocessing import Pool,cpu_count

O Python fornece o método cpu_count(), que conta o número de CPUs em sua máquina. Sem dúvida, ele é útil para determinar o número exato de tarefas que podem ser executadas em paralelo.

Agora você precisa substituir o código com o loop for em execução sequencial por este código:

com Pool (cpu_count()) como p:
 
   p.map(fetch,clinks)

Depois de executar esse código, ele produziu um tempo de execução geral de 20,10 segundos, o que é relativamente mais rápido do que a execução sequencial no primeiro programa.

Conclusão

Neste ponto, esperamos que você tenha uma visão geral abrangente da programação paralela e sequencial - a escolha de usar uma em detrimento da outra depende principalmente do cenário específico com o qual você se deparou.

Para o cenário da Web scraping , recomendamos começar com a execução simultânea e depois passar para uma solução paralela. Esperamos que tenha gostado de ler este artigo. Não se esqueça de ler também outros artigos relevantes para a Web scraping como este em nosso blog.