Raspagem da Web no R, Parte 2. Acelerando o processo com computação paralela e usando o pacote Rcrawler


Em um artigo anterior , usando a análise de scrapbooking, coletei classificações de filmes dos sites IMDB e Kinopoisk e as comparei. Repositório no Github .


O código fez um bom trabalho, mas a raspagem costuma ser usada para "raspar" não algumas páginas, mas algumas três mil, e o código do artigo anterior não é adequado para uma raspagem "tão grande". Mais precisamente, não será o ideal. Em princípio, praticamente nada o impede de usá-lo para rastrear milhares de páginas. Praticamente, porque você não tem muito tempo



Quando decidi usar scraping_imdb.R para rastrear 1000 páginas


Otimização de código. Um único uso da função read_html

Neste artigo, 100 links para as páginas da livraria Labyrinth serão usados ​​para verificar a operação e a velocidade do código.


Uma mudança explícita que pode acelerar o processo é o uso único da função de código "mais lenta" - read_html . Deixe-me lembrá-lo de que ela "lê" a página HTML. Na primeira versão do código para sites de filmes, executei read_html toda vez que precisava obter algum valor (nome do filme, ano, gênero, classificação). Agora, os traços dessa "vergonha" foram apagados do GitHuba, mas é. Não há sentido nisso, porque a variável criada usando read_html contém informações sobre a página inteira e, para obter dados diferentes, basta html_nodes essa mesma variável com a função html_nodes e não começar a ler HTML todas as vezes. Assim, você pode economizar tempo na proporção do número de valores que deseja obter. No Labirinto, recebo sete valores, respectivamente, o código que usa apenas uma única leitura de uma página HTML funcionará cerca de sete vezes mais rápido. Nada mal! Porém, antes que eu acelere novamente, discordo e falo sobre pontos interessantes que surgem ao acessar o site Labyrinth.


Recursos de raspagem de página no labirinto

Nesta parte, não abordarei o procedimento para obter e limpar os dados mencionados no artigo anterior. Mencionarei apenas aqueles momentos que encontrei pela primeira vez ao escrever código para scrapbooking em uma livraria.


Em primeiro lugar, vale a pena mencionar a estrutura. Ela não está muito confortável. Por outro lado, por exemplo, no site Read-Cities, seções do gênero com "filtros vazios" fornecem apenas 17 páginas. É claro que todos os 8011 livros do gênero "Prosa estrangeira contemporânea" não se encaixam neles.


Portanto, não criei nada melhor do que pesquisar os links https://www.labirint.ru/books/ **** com um simples busto. Francamente falando, o método não é o melhor (mesmo que a maioria dos livros "antigos" não tenha informações, exceto o nome e, portanto, seja praticamente inútil), portanto, se alguém oferecer uma solução mais elegante, ficarei feliz. Mas eu descobri que, sob o primeiro número orgulhoso no site do Labirinto, há um livro intitulado "Como Fazer Moonshine" . Infelizmente, já é impossível comprar este armazém de conhecimento.


Todos os endereços durante a enumeração podem ser divididos em dois tipos:


  • Páginas que existem
  • Páginas que não existem

As páginas existentes, por sua vez, podem ser divididas em duas partes:


  • Páginas que contêm todas as informações necessárias
  • Páginas que não contêm todas as informações necessárias

Termino com uma tabela de dados com sete colunas:


  1. ISBN - Número do livro do ISBN
  2. PRICE - preço do livro
  3. NAME - título do livro
  4. AUTOR - autor do livro
  5. PUBLISHER - editora
  6. ANO - ano de publicação
  7. PAGE - número de páginas

Tudo fica claro nas páginas com informações completas, elas não exigem alterações em comparação com o código dos sites de filmes.


Quanto às páginas nas quais alguns dados não estão disponíveis, não é tão simples com eles. Uma pesquisa na página retornará apenas os valores que encontrar e o comprimento da saída diminuirá pelo número de elementos que não será encontrado. Isso quebrará toda a estrutura. Para evitar isso, uma construção if ... else foi adicionada a cada argumento, que estima o comprimento do vetor obtido após o uso da função html_nodes e, se for zero, retorna NA para evitar valores de polarização.


  PUBLISHER <- unlist(lapply(list_html, function(n){ publishing <- if(n != "NA") { publishing_html <- html_nodes(n, ".publisher a") publishing <- if(length(publishing_html) == 0){ NA } else { publishing <- html_text(publishing_html) } } else { NA } })) 

Mas como você pode notar aqui até dois ifs e até dois outros. Somente o "interno" se .. é relevante para a solução do problema descrito acima. O Exterior resolve o problema com páginas inexistentes.


Páginas que simplesmente não têm mais problemas. Se os valores forem alterados nas páginas com dados ausentes, ao enviar uma entrada read_html que não existe, a função gerará read_html erro e o código interromperá a execução. Porque de alguma forma, não é possível detectar essas páginas antecipadamente, é necessário garantir que o erro não pare todo o processo.


A função possibly do pacote possibly nos ajudará com isso. O significado das funções de possibly (além de possibly quietly e safely ) é substituir a saída impressa de efeitos colaterais (por exemplo, erros) por um valor que nos convém. possibly possui uma estrutura possibly(.f, otherwise) e possibly(.f, otherwise) se ocorrer um erro no código, em vez de interromper sua execução, ele usará o valor padrão (caso contrário). No nosso caso, fica assim:


 book_html <- possibly(read_html, "NA")(n) 

n é uma lista de endereços das páginas do site que raspamos. Na saída, obtemos uma lista de comprimento n, na qual os elementos das páginas existentes estarão no formato "normal" para executar a função read_html , e os elementos das páginas inexistentes consistirão no vetor de caracteres "NA". Observe que o valor padrão deve ser um vetor de caractere, porque no futuro nos referiremos a ele. Se escrevermos apenas NA , como na parte do código PUBLISHER, isso não será possível. Para evitar confusão, você pode alterar o valor de NA para outro.


E agora, de volta ao código para obter o nome do editor. Externo se ... mais é necessário para os mesmos fins que internos, mas com relação a páginas inexistentes. Se a variável book_html for igual a "NA", cada um dos valores "raspados" também será igual a NA (aqui você já pode usar o NA "real", em vez de um impostor simbólico). Portanto, no final, obtemos uma tabela da seguinte forma:


ISBNPREÇONAMEAUTHOREDITORAnoPage
46653057703221488Definir arte de cordas "Filhote de cachorro fofo" (30 * 30 cm) (DH6021)NAGato ruivo2019NA
NANANANANANANA
9785171160814273Arkady Averchenko: Histórias divertidas para criançasAutor: Averchenko Arkady Timofeevich, Artista: Vlasova Anna YulievnaKid2019288

Agora, de volta com a aceleração do processo de raspagem.


Computação paralela na R. Comparação de velocidade e armadilhas ao usar a função read_html

Por padrão, todos os cálculos em R são executados no mesmo núcleo do processador. E enquanto esse núcleo infeliz está trabalhando na face, “raspando” dados de milhares de páginas para nós, o resto de nossos camaradas está “esfriando”, executando outras tarefas. O uso da computação paralela ajuda a atrair todos os núcleos do processador para o processamento / recebimento de dados, o que acelera o processo.


Não vou aprofundar o design da computação paralela no R; você pode ler mais sobre eles, por exemplo, aqui . A maneira como entendi o paralelismo em R é criar cópias de R em grupos separados, de acordo com o número de núcleos indicados que interagem entre si por soquetes .


Vou falar sobre o erro que cometi ao usar a computação paralela. Inicialmente, meu plano era o seguinte: usando computação paralela, recebo uma lista de 100 páginas read_html "lidas" e, em modo normal, apenas read_html os dados necessários. No começo, tudo correu bem: recebi uma lista, gastando muito menos tempo nela do que no modo normal R. Mas somente quando tentei interagir com essa lista, recebi um erro:


 Error: external pointer is not valid 

Como resultado, percebi qual era o problema, procurando exemplos na Internet e, depois disso, de acordo com a lei da maldade, encontrei a explicação de Henrik Bengtsson na vinheta do pacote futuro . O fato é que as funções XML do pacote xml2 são objetos não exportáveis.
) Esses objetos estão "vinculados" a esta sessão R e não podem ser transferidos para outro processo, o que tentei fazer. Portanto, a função lançada na computação paralela deve conter um “ciclo completo” de operações: ler uma página HTML, receber e limpar os dados necessários.


Criar a computação paralela em si não leva muito tempo e linhas de código. A primeira coisa que você precisa é fazer o download das bibliotecas. O repositório do Github indica quais pacotes são necessários para quais métodos. Aqui mostrarei a computação paralela usando a função parLapply do pacote parallel . Para fazer isso, basta executar doParallel (o parallel será iniciado automaticamente neste caso). Se de repente você não souber ou esquecer o número de núcleos do seu processador, detecte quantos deles o detectCores ajudará detectCores


 # detectCores - ,     number_cl <- detectCores() 

Em seguida, crie cópias paralelas do R:


  # makePSOCKcluster -    R,    cluster <- makePSOCKcluster(number_cl) registerDoParallel(cluster) 

Agora estamos escrevendo uma função que fará todos os procedimentos que precisamos. Noto que desde novas sessões são criadas.Os pacotes R cujas funções são usadas em nossa própria função devem ser escritos no corpo da função. No spider_parallel.R, isso faz com que o pacote stringr executado duas vezes: primeiro para obter os endereços da página e depois limpar os dados.


E então o procedimento quase não é diferente de usar a função lapply usual. No parLapply , fornecemos uma lista de endereços, nossa própria função e, a única adição, uma variável com os clusters que criamos.


 # parLapply -  lapply     big_list <- parLapply(cluster, list_url, scraping_parellel_func) #    stopCluster(cluster) 

Isso é tudo, agora resta comparar o tempo gasto.


Comparação da velocidade de computação serial e paralela

Este será o ponto mais curto. A computação paralela foi 5 vezes mais rápida que o normal:


Velocidade de raspagem sem usar computação paralela


o usuárioo sistemapassou
13,570,40112,84

Velocidade de raspagem usando computação paralela


o usuárioo sistemapassou
0,140,0521/12

O que dizer? A computação paralela pode economizar muito tempo sem criar dificuldades na criação do código. Com o aumento do número de núcleos, a velocidade aumentará quase na proporção do seu número. Assim, com algumas alterações, aceleramos o código 7 vezes primeiro (pare de calcular o read_html a cada passo) e depois outros 5, usando cálculos paralelos. Os scripts Spider sem computação paralela, usando os pacotes parallel e foreach , estão no repositório do Github.


Uma pequena visão geral do pacote Rcrawler . Comparação de velocidade.

Existem várias outras maneiras de descartar páginas HTML no R, mas vou me concentrar no pacote Rcrawler . Seu recurso distintivo de outras ferramentas na linguagem R é a capacidade de rastrear sites. Você pode definir a função Rcrawler com o mesmo nome para o endereço do Rcrawler e, metodicamente, página por página, desviará o site inteiro. Rcrawler possui muitos argumentos para configurar a pesquisa (por exemplo, você pode pesquisar por palavras-chave, setores do site (útil quando o site é composto por um grande número de páginas), profundidade da pesquisa, ignorando parâmetros de URL que criam páginas duplicadas e muito mais. funções já foram estabelecidas cálculos paralelos, que são especificados pelos argumentos no_cores (o número de núcleos de processador envolvidos) e no_conn (o número de solicitações paralelas).


Para o nosso caso, raspando os endereços especificados, existe uma função ContentScraper . Como não usa computação paralela por padrão, você precisará repetir todas as manipulações descritas acima. Gostei da função em si - ela oferece muitas opções para configurar a raspagem e é bem compreendida em um nível intuitivo. Também aqui você não pode usar if..else para páginas ausentes ou valores ausentes, como a execução da função não para.


 #   ContentScraper: # CssPatterns -    CSS    . # ExcludeCSSPat -    CSS ,    . # ,   CSS     CSS ,    . # ManyPerPattern -  FALSE,       , #  .  TRUE,     ,   . # PatternsName -      .   #   c  ,      t_func <- function(n){ library(Rcrawler) t <- ContentScraper(n, CssPatterns = c("#product-title", ".authors", ".buying-price-val-number", ".buying-pricenew-val-number", ".publisher", ".isbn", ".pages2"), ExcludeCSSPat = c(".prodtitle-availibility", ".js-open-block-page_count"), ManyPerPattern = FALSE, PatternsName = c("title", "author", "price1", "price2", "publisher", "isbn", "page")) return(t) } 

Mas com todas as qualidades positivas, a função ContentScraper tem um ContentScraper muito menos - a velocidade do trabalho.


Rcrawler ContentScraper Rcrawler ContentScraper sem computação paralela


o usuárioo sistemapassou
47,470,29212,24

Rcrawler ContentScraper ContentScraper Rcrawler usando computação paralela


o usuárioo sistemapassou
0,010,0067,97

Portanto, o Rcrawler deve ser usado se você precisar ignorar o site sem especificar primeiro os endereços de URL, bem como com um pequeno número de páginas. Em outros casos, a velocidade lenta superará todas as vantagens possíveis de usar este pacote.


Ficaria muito grato por quaisquer comentários, sugestões, reclamações
Link do repositório do Github
Perfil do meu círculo

Source: https://habr.com/ru/post/pt464399/


All Articles