Dla konkursu Daj Się Poznać potrzebowałem czegoś co mi z listy URLi podanej przez Macieja ściągnie informacje o RSSie. Normalnie bym się tym nie przejmował ale tutaj było tego ponad 200… tego się po prostu ręcznie nie chce robić :)

Stwierdziłem więc, że odświeżę swoje skostniałe umiejętności F# i w ten sposób zautomatyzuje pobieranie tych linków. Oczywiście pewne założenia musiałem mieć:

  • Interesuje mnie tylko head ze strony
  • Interesują mnie tylko tagi link które zawierają atrybut type
  • Interesują mnie tylko tagi link których type jest application/rss+xml
  • Interesują  mnie tylko taki link które nie zawierają słowa comment gdyż przeważnie odnoszą się do rss do komentarzy

Założenia były proste. To co nie spełniało ich, było olewane. Do ich spełnienia potrzebowałem czegoś co mi pozwoli załadować dokument HTML lub coś co pozwoli mi go z parsować. Na szczęście istnieje biblioteka FSharp.Data która udostępnia parser HTML. Dokumentacja może nie jest jakaś porywająca, ale wystarczająca by coś napisać :)

Po kilku próbach udało mi się wypluć z siebie kod, który zwraca mi url do rssów, jednak był i jest on wciąż kodem bez wsparcia na wyjątki które mogą zajść w trakcie pobierania strony czy parsowania HTML. Dzięki pomocy Krzysztofa (jeszcze raz dzięki!) mogę podzielić się poprawioną wersją skryptu wraz z obsługą błędów.

Uwaga, kod zakłada istnieje pliku urls.txt, który zawiera urle – jeden na linię – do blogów, czyli to co dał Maciej.

Jeżeli uważacie że da się to jeszcze lepiej napisać to pls dajcie znać :) z chęcią się pouczę F# :) Tak czy siak wynik skryptu możecie zobaczyć na .NET Blogs PL <3 Daj Się Poznać :)

open System.IO
open FSharp.Data

let readLines filePath = File.ReadAllLines(filePath);;

let downloadWebAsync (links:seq) =
    links
    |> Seq.map (HtmlDocument.AsyncLoad >> Async.Catch)

let castToOption (linkElements:seq) =
    linkElements
    |> Seq.choose (fun x ->
        x.TryGetAttribute("type")
        |> Option.map (fun a -> x.Attribute("href").Value(), a.Value())
    )

let getLinkElements (doc:HtmlDocument) : seq =
    doc.Descendants["link"]

let filterOnlyRss (_:string, t:string) =
    t.Equals("application/rss+xml", System.StringComparison.OrdinalIgnoreCase) || t.Equals("application/atom+xml", System.StringComparison.OrdinalIgnoreCase)

let filterNoComments (uri:string, _:string) =
    uri.ToLower().Contains("comments") = false

let getLinks ()=
    readLines "urls.txt"
    |> Seq.filter (fun f -> f.StartsWith("//") = false)

let loadAll () =
    getLinks ()
    |> downloadWebAsync
    |> Async.Parallel
    |> Async.RunSynchronously

let findLinks =
    getLinkElements
    >> castToOption
    >> Seq.filter (filterOnlyRss)
    >> Seq.filter (filterNoComments)

[<EntryPoint>]
let main argv =

    loadAll ()
    |> Array.collect (fun n ->
        match n with
        | Choice1Of2 d -> findLinks d |> Seq.toArray
        | Choice2Of2 e ->
            printfn "FAIELD WITH: %A" e.Message
            [||]
    )
    |> Seq.distinctBy (fun (x, y) -> if x.Length > 10 then x.Substring(0, if x.IndexOf('.', 10) >= 0 then x.IndexOf('.', 10) - 1 else x.Length - 1) else x)
    |> Seq.iter (fun (x,y) -> printfn "%s" x)

    0

5 KOMENTARZE

  1. Chwilę mi zajęło zanim zrozumiałem, czemu mojego bloga tam nie ma. Istnieje szansa że obsłużysz też feedy w formacie application/atom+xml ? :-)

Comments are closed.