웹 캡처com.NET Core 3.1

35855 단어 webscrapingdotnet
최근의 필요한 데이터는 buscar algumas informa çes em sites que n ão possu í amuma API para recuperar os dados necess á rios에서 나왔다.Então,decidi que iria utilizar algumas técnicas de web scraping para buscar as informaões que precissava.Jáhavia construído alguns web scrapers com Python e PHP.Porém,decidi me desafiar e criar um com.NET Core 3.1 e C#.Para isso utilizei um pacote,disponível no gerenciador de pacotes Nuget,chamado AngleSharp.Este pacote pode ser encontradoaqui.
Para examplicar aqui,vamos acessar o sitepensador.com,e recurparar as citaões que ficam disponíveis Para visualizaço.Nosso objetivo entãoérecurparar as citaões de um determinado autor ou tema e armazenarmos em Nosso dispositivo.

Cria ão do projeto 프로젝트


Minha necessidade era construir esse banco de citaçes uma vez,sem a necessidade ficar atualizando ou ter algum acesso remoto.Por este motivo decidi criar uma aplicaão de linha de comando,para isso utilizei o próprio template disponibilizado pelo Visual Studio.

Após nomer o projeto e realizar sua criaão,énecesário installar o pacote AngleSharp como uma dependencia do projeto.Para issoénecessário clicar com o botão direito do mouse em“Dependencies”,e selecionar a opão“Manage NuGet Packages”.Vocêdeve visualizar uma tela semelhante com a imagem abaixo:

Após clicar em instalar,nosso projeto estápronto para começarmos a realizar chamadas ao site.

Acessando 장소


Poderíamos realizar as chamadas diretamente no método Main da aplicaão,mas eu prefiro separar em classes differentes algumas operaçes.Sendo assim,decidi criar uma pasta Services e criar uma classeScrapperService.csdentro.Essa seráa classe responsável por acessar o site que desejamos e trazer nossos dados jámodelados.
Nessa classeScrapperService.csvamos criar uma propriedade do tipoIBrowsingContext,queéuma interface do pacote AngleSharp,que seráresponsável por fazer a requisiçoás páginas web que desejamos acessar.A classe junto com o seu construtor ficaráassim:
using AngleSharp;

namespace PensadorScrapper.Services
{
    class ScrapperService
    {
        private IBrowsingContext context { get; set; }

        public ScrapperService()
        {
            var config = Configuration.Default.WithDefaultLoader();
            context = BrowsingContext.New(config);
        }
    }
}
Nessa etapa jápodemos escrever o método que irárealizar a leitura dos nossos dados de intereste,mas anteséimportant abrirmos a página em nosso navegador para vermos como o DOM estáorganizado,para então organizarmos nossa estrate gia de obtenãdos dados.ao abrir a página inicial de algum tema de intereste podemos perceber a organization ao da página.Vou usar como Examplo cita es com o tema de filosofia, página pode ser visualizada abaixo:

Ao analisarmos dessa forma,conseguimos visualizar onde estão os elementos de interestse para nossa aplicaão,que são:as citaões e número total de citaçes.필요성 검증 o n úmero total de cita es para podermos calcular o n úmero de páginas estimado que cada tema ou autor terá.아심,conseguiremosextrair todas as cita es de cadaum.
Após a análise visualénecesário verificiar a organizaão do documento HTML,como jásabemos nossos elementos de intereste,podemos clicar com o botão direito e inspeciator o elemento para irmos direto a ele no código.Verificando o elemento,podemos então clicar com o botão direito novate e selecionar a opão de copiar seletor.Esse caminhoéque utilizaremos para encontrar Esse elemento em nossa aplicaão com o AngleSharp,então guarde ele em algum lugar.Abaixo uma imagem examplificando o que foi realizado.

Realizando a mesma etapa para o bloco das citaçes,temos dois seletores copiados:
  • 정보 총계: 내용>div.top>div.total
  • Todas citaões da página:#content>div.phrases-list
  • Com esses dados em mãos podemos criar o método para buscar as citaões na classeScrapperService.cs,o método fica assim:
    public async IAsyncEnumerable<Quote> GetQuotes(string assunto, int pagina)
            {
                var url = $"https://www.pensador.com/{assunto}/{pagina}";
                Console.WriteLine(url);
                var document = await context.OpenAsync(url);
    
                var quotesHtml = document.QuerySelectorAll("#content > div.phrases-list > .thought-card");
    
                foreach (var item in quotesHtml)
                {
                    var autor = item.QuerySelector(".autor").Text();
    
                    int id = int.Parse(item.GetAttribute("data-id"));
    
                    var texto = item.QuerySelector(".frase").Text();
    
                    if(item.QuerySelector(".insert-onlist") != null)
                    {
                        continue;
                    }
    
                    yield return new Quote(id, autor.Trim(), texto.Trim());
                }
            }
    
    설명 o c ó digo acima:
  • Criamos um método assíncrono para buscar as citaões,pois assim nossa aplicaçonão fica bloqueada enquanto realization uma chamada HTTP para o servidor,e também pois o pacote do AngleSharp realizaessa leitura da página de forma ass incrona.
  • Como a lógica do método pode ser aplicada pra qualquer página ou tema,vamos passar esses dois par–metros,tornando método mais adaptável.

  • Montamos então a url de acesso concatenando os textos conforme o padrão que o site utiliza,queéalgum desses dois:
  • 펜사도.일반 도메인 이름 형식입니다.br*/tema/pagina*
  • 펜사도.일반 도메인 이름 형식입니다.br*/autor/nome\u autor/pagina*
  • Na linha com o códigovar document = await context.OpenAsync(url);éque realizamos a leitura do documento,com auxilio do pacote AngleSharp.Esse método nos returna um objeto com a página mapeada ondeépossível navegar através dos seus nós.

  • Como jáhavíamos anotado nossos seletores,podemos utilizar a funão queryselectoral para returnar os nós de interest nese documento.Para isso então passamos o nosso seletor de todas as citaçes como par–metro,adicionado da classe que todas citaçes comparitilham em comum,como pode ser observado abaixo:
  • var quotesHtml = document.QuerySelectorAll("#content > div.phrases-list > .thought-card");
  • Após termos todos os nós das citaçes,podemos iterar sobre eles e buscar as informaçes internas de cada nó.Para isso utilizamos Novatemente o QuerySelector,porém agora passando os seletores de cada informaço que queremos.Que nesse caso são:autor,frase e ID.
  • Como vocêpode perceber,esse método returnaum objeto Quote,e esse objeto deve ser criado por nós,para isso crei uma 파스타 모델,e então crei a classe modeloQuote.cs,que pode ser visualizada abaixo:
  • namespace PensadorScrapper.Models
    {
        class Quote
        {
            public int Id { get; set; }
            public string Autor { get; set; }
            public string Texto { get; set; }
    
            public Quote(int id, string autor, string texto)
            {
                Id = id;
                Autor = autor;
                Texto = texto;
            }
        }
    }
    
  • Dentro do laço então retronamos esse objeto Quote,com auxilio da palavra chaveyield,que farácom que as citaçes sejam retronadas assim que forem sendo recuperadas do site.
  • E como descobrimos quantas páginas existem?


    Se nós chamarmos o método construído acima,nossa aplicaão jávai Returnar todas citaçes de cada página que passarmos como par–metro.páginas deum tema ou autor로서 당신은 누구입니까?Para isso que salvamos o seletor daquela parte da página que nos informava o número total de posts.Porém,se analisarmos outra página,de algum autor Escífico,Por Examplo,veremos que algumas informaçes estão Differentes:

    O site pensador,quando tem um artista famoso,muda O texto de total de citaçes,e também muda a estrutura do HTML,para isso entãO precisamos mapear esse seletor também.Logo,temos dois seletores distintos:
  • 유명 연예인: #content>div.autorotal
  • Tema geral:#content>div.top>div.total
  • Precisamos construir entãum método que returna o número total de páginas que cada autor ou tema terá.Uma forma simples de fazer issoédividir o número total de citaçes pela quantidade queéexibida na primeira página,para termos um valor aproximado.Poderíamos também sempre analisarmos os elementos de paginaão no final da página,aténão existir mais nenhum.Mas nessa implementaço vou somente dividir o número total de citaçes pelas exibidas na tela.Mas sintam se livres para alterar o código no github e implementar essa functionalidade.Segue abaixo como o método ficou:
    public async Task<int> GetTotalPages(string assunto)
            {
    
                var document = await context.OpenAsync($"https://www.pensador.com/{assunto}");
    
                var quotesHtml = document.QuerySelectorAll("#content > div.phrases-list > .thought-card");
                var textoTotal = "";
    
                int quantidadePorPagina;
                int total;
    
                if (document.QuerySelector("#content > div.top > div.total") != null)
                {
                    textoTotal = document.QuerySelector("#content > div.top > div.total").Text();
                    if (int.TryParse(Regex.Match(textoTotal, @"(?<=de )(.*)(?= pensamentos )").Value, out total))
                    {
                        quantidadePorPagina = int.Parse(Regex.Match(textoTotal, @"(?<=-\n)(.\d)(?=)").Value);
                        total = int.Parse(Regex.Match(textoTotal, @"(?<=de )(.*)(?= pensamentos )").Value);
                    }
                    else
                    {
                        total = int.Parse(Regex.Replace(textoTotal, @"[^\d]", ""));
                        quantidadePorPagina = quotesHtml.Count();
                    }
                }
                else
                {
                    total = int.Parse(document.QuerySelector("#content > div.autorTotal > strong:nth-child(2)").Text());
                    var textoPagina = document.QuerySelector("#content > div.autorTotal > strong:nth-child(1)").Text();
                    quantidadePorPagina = int.Parse(Regex.Match(textoPagina, @"(?<= )(.\d)(?=)").Value);
                }
    
                int paginas = total / quantidadePorPagina;
    
                Console.WriteLine("Total: " + total);
                Console.WriteLine("Paginas: " + paginas);
    
                return paginas;
            }
    
    Esse método ficou um pouco extenso,e poderia ser otimizado,mas deixaremos para fazer isso em uma segunda versão.para extrair as informaões de total de citaões e paginaço utilizei Regex.Explicando de uma forma geral,o método acessa a primeira página do autor ou tema e verifica se estáno padrão de totais de citaão tema ou autor,então utiliza as técnicas de QuerySelector para realizar a captura dos nós.Após isso,o valor total de citaõesédividido pela quantidade citaões por página e armazenado em um inteiro para arredonddarmos para baixo o total de páginas.

    Acessando todas páginas de um tema Escífico ou autor


    Agora que jápossuímos um método para estimar o número total de páginas e outro método para buscar as citaões dessa página,sónecessamos realizar um laço de repetião alterando a página atéo total de página daquele autor ou tema.Para isso,podemos adicionar em nosso método Main da classeProgram.cs.1 레벨 fica ASIM:
    using PensadorScrapper.Services;
    using System;
    
    namespace PensadorScrapper
    {
        class Program
        {
            static async System.Threading.Tasks.Task Main(string[] args)
            {
                ScrapperService scrapper = new ScrapperService();
    
                string assunto = "autor/william_shakespeare";
    
                Console.WriteLine($"\n\n\n\n\n\nAUTOR: william_shakespeare \n\n\n");
    
                var totalPaginas = await scrapper.GetTotalPages(assunto);
                Console.WriteLine("N de paginas: " + totalPaginas);
    
                for (var i = 1; i <= totalPaginas; i++)
                {
                    await foreach (var quote in scrapper.GetQuotes(assunto, i))
                    {
                        Console.WriteLine($"ID: {quote.Id} | Autor: {quote.Autor.Trim()} | Frase: {quote.Texto.Trim()}");
                    }
                    Console.WriteLine($"\n --------Página {i}--------- \n\n");
                }
            }
        }
    }
    
    Ao executarmos o código podemos ver como estáfuncionando nossa aplicaão:

    Ainda,se desejarmos buscar dados de mais de um autor ou tema poderíamos criar uma Lista de autores e iterar sobre ela chamando os nossos mesmos métodos,dessa maneira:
    using PensadorScrapper.Models;
    using PensadorScrapper.Services;
    using System;
    using System.Collections.Generic;
    
    namespace PensadorScrapper
    {
        class Program
        {
            static async System.Threading.Tasks.Task Main(string[] args)
            {
                ScrapperService scrapper = new ScrapperService();
    
                List<string> autores = new List<string>{
                    "jean_jacques_rousseau",
                    "rene_descartes",
                    "immanuel_kant",
                    "john_locke",
                    "blaise_pascal",
                    "galileu_galilei",
                    "michel_de_montaigne",
                    "mary_wollstonecraft",
                    "angela_davis",
                    "hipatia_de_alexandria",
                    "maquiavel",
                    "adam_smith",
                    "zygmunt_bauman",
                    "baruch_espinosa",
                    "friedrich_engels",
                    "georg_wilhelm_friedrich_hegel",
                    "soren_kierkegaard",
                    "epicteto",
                    "martin_heidegger",
                    "michel_foucault",
                    "martin_heidegger",
                    "hannah_arendt"
                };
    
                foreach (var autor in autores)
                {
                    string assunto = "autor/"+autor;
    
                    Console.WriteLine($"\n\n\n\n\n\nAUTOR: {autor} \n\n\n");
    
                    var totalPaginas = await scrapper.GetTotalPages(assunto);
                    Console.WriteLine("N de paginas: " + totalPaginas);
    
                    for (var i = 1; i <= totalPaginas; i++)
                    {
                        await foreach (var quote in scrapper.GetQuotes(assunto, i))
                        {
                            Console.WriteLine($"ID: {quote.Id} | Autor: {quote.Autor.Trim()} | Frase: {quote.Texto.Trim()}");
                            QuotesRepository.SaveQuote(quote);
                        }
                        Console.WriteLine($"\n --------Página {i}--------- \n\n");
                    }
                }
    
    
            }
        }
    }
    

    결론


    당장!Temos uma aplicaão que realiza um Web Scraping no site pensador.com e returna todas as citaçes disponíveis de um determinado autor ou tema.Claro que normalmente nãoésóisso que desejamos.Muitas vezes essidamos armazenar essas informaçes.Por esse motivo eu járealizei também a integraço dessa aplicaço com um banco de dados,onde essas informaçes ficam armazenadas.Mas como o post jáficou extenso,vou tratar desta etapa em um outro post aqui.Utilizaremos SQLite para armazenar e recurper esses dados.O código completo desse post,com a versãO jáutilizando banco de dados pode ser encontrado no meuGithub.Abaixo temos um GIF da aplicaão funcionando:

    좋은 웹페이지 즐겨찾기