Sempre fiquei muito curioso quando ouvia sobre as linguagens funcionais e as vantagens que elas trazem ao desenvolvimento. A promessa de dar adeus ao null
e outros erros de runtime que tanto nos incomodam, faz brilhar meus olhos até hoje. Então, como desenvolvedor .NET, decidi começar no mundo funcional através do F#.
Já fazia um tempo que gostaria de estudar melhor a linguagem e escrever um artigo sobre, assim resolvi começar pela prática estudando como utiliza-lá para aplicar Web Scraping. Para quem desconhece o termo, Web Scraping nada mais é do que um método de coletar dados de páginas web e F# é uma ferramenta muito poderosa para isso.
Existem basicamente 3 formas de se aplicar Web Scraping com F#:
- Utilizar alguma biblioteca feita para isso em C#
- Utilizar um wrapper de Selenium para F#
- Ou então, utilizar a biblioteca
FSharp.Data
Essa última é a que irei abordar nessa artigo, trata-se de uma biblioteca que permite trabalhar mais facilmente com os formatos CSV, XML, JSON e até, não se surpreenda, HTML.
Ela também fornece helpers para realizar requisições HTTP, conversão para os tipos já mencionados e acesso ao WorldBank, mas isso não será abordado nesse artigo.
Através do HtmlProvider
é possível definir um tipo para a página que você deseja fazer o scraping. Ele espera receber um HTML de exemplo que pode ser um arquivo ou uma URL, e vai servir de base para a criação do tipo F#, dessa forma:
type DilbertSearch = HtmlProvider<"https://www.pinterest.pt/search/pins/?q=dilbert%20comic%20strip">
Assim é possível, por exemplo, pegar as primeiras imagens disponíveis da pesquisa por "dilbert comic strip" no Pinterest
:
DilbertSearch().Html.CssSelect(".mainContainer img")
|> List.map (fun d -> getUrlOfLargestImage(d.AttributeValue("srcset")))
|> List.iter (printfn "%s")
// → https://i.pinimg.com/736x/90/38/bb/9038bbcabd5b31d6faa6705230df3a78--peanuts-comics-peanuts-gang.jpg
// → https://i.pinimg.com/736x/b4/f5/ba/b4f5bac902a421a8b2eb00f232a227e4--human-resources-online-comics.jpg
// ...
Os tipos gerados a partir do HtmlProvider
indentificam automaticamente as tabelas e listas (literalmente <table>
, <ul>
ou <ol>
) encontradas na página HTML, assim é possível, por exemplo, pegar a lista de personagens da tirinha do Hagar em sua página no Wikipedia
:
type HagarWiki = HtmlProvider<"https://en.wikipedia.org/wiki/H%C3%A4gar_the_Horrible">
HagarWiki().Lists.``Cast of characters``.Values
|> List.ofArray
|> List.map getCharacterName
|> List.iter (printf "%s\n")
// → Hägar the Horrible
// → Helga
// ...
O nome dado a lista ou tabela identificada é retirado dos atributos/tags HTML id
, title
, name
, summary
ou caption
, se nenhum deles é encontrado então o nome dado será TableXX
ou ListXX
, em que o XX
é um número sequencial de onde o elemento foi encontrado na página.
Mas o melhor disso é que essas tabelas e listas ficam disponíveis em tempo de desenvolvimento através do IntelliSense do Visual Studio ou Visual Studio Code:
Nesse outro exemplo, é possível buscar os filmes da franquia Star Wars
e a sua arrecadação em dólares:
type StarWarsWiki = HtmlProvider<"https://en.wikipedia.org/wiki/List_of_Star_Wars_films_and_television_series">
let filmsByRevenue = StarWarsWiki().Tables.``Box office performance``.Rows
|> Seq.filter (fun r -> isReleaseDate r.``Release date``)
|> Seq.sortBy (fun x -> convertReleaseDate x.``Release date``)
|> Seq.map (fun r -> r.Film, convertRevenue r.``Box office revenue - Worldwide``)
|> Seq.toArray
filmsByRevenue
|> Seq.iter (fun elem -> elem ||> printf "%s - %f Billions \n")
// → Star Wars - 0.775398 Billions
// → The Empire Strikes Back - 0.547969 Billions
// ...
E então, é possível também plotar um gráfico utilizando a biblioteca FSharp.Charting
, assim:
Chart.Column filmsByRevenue
|> Chart.WithYAxis(Title = "Billions")
|> Chart.WithXAxis(Title = "Films")
|> Chart.Show
Os exemplos utilizados no artigo estão disponíveis nesse repositório do GitHub.
Nele existem dois projetos de exemplo um utilizando .NET Framework e outro utilizando o .NET Core. As únicas diferenças entre eles, além da versão do .NET, é que nesse último:
- A biblioteca
FSharp.Data
só é compatível com o .NET Core, se utilizada a partir da sua versão3.0.0-beta
que está em beta - E a biblioteca
FSharp.Charting
foi removida, pois tem dependência do framework
Assim, a biblioteca Fsharp.Data
torna o F# uma ferramenta muito poderosa para fazer scraping de páginas web. Entretando, nem tudo são flores e se a página possui conteúdo muito dinâmico (utilização de javascript para renderização), existem dificuldades de se utilizar a biblioteca, mas que podem ser contornadas utilizando-a em conjunto da segunda opção apresentada no início do artigo, um wrapper de Selenium para F#.
Enfim, a primeira impressão com a linguagem e com o paradigma foi muito positiva, trata-se de uma forma diferente de desenvolvimento que torna o código muito mais claro, mas que necessita de certo aprofundamento teorico, pois exige uma mudança de mindset para quem esta acostumado com o mundo orientado a objetos. Como próximo passo, devo continuar me aprofundando na linguagem pra quem sabe trazer mais algum artigo sobre o assunto.
Seguem algumas referências utilizadas: