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órychtype
jestapplication/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
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 ? :-)
@karste
dzięki, zaktualizuje skrypt by obsłużyć atom jak nie ma rss.
@karste
Ok, juz wylapuje Twoj blog, dzieki
tym razem to Ty mnie uprzedziłeś:) do mojego projektu będzie jak znalazł
Wow, to było szybkie. Dzięki!
Comments are closed.