F . Chorzによるウェブ掻き取り


NOTE: The content of this post is based on this code, check it for the full example.

https://github.com/AngelMunoz/Escalin


Fシャープの単純なもの


おい、ここでは、F - Chornで単純なものの次のエントリです
あなたがウェブサイトから定期的にデータを引っ張りたいならば、あなたはそれからEPlaywright あなたのためのオプションかもしれません.サイプレスやPhantomjsと同様に、PlayWrightは、ウェブサイトとの自動化を自動化することができますライブラリです、あなたも、スクリーンショットやPDFファイルを取ることができます!
PlayWrightは、次のブラウザへのアクセスを提供しています
  • クロム
  • エッジ
  • クロム
  • ファイアフォックス
  • WebKit
  • 通常、これらのツールを念頭に置いてJavascriptで作られています(脚本家は例外です)しかし、脚本家を提供しています.同様にネットライブラリを使用する場合は、F -経典、VBまたはCの残高を使用したい場合は、いくつかのWebスクラップを行うことができます.

    事前の要件


    私たちはここでF - Chorchenに焦点を当てるので、あなたは.NET SDK あなたのマシンにインストールされて、また、あなたがそれをインストールするのを好むならば、PlayWright Global CLIツール
    dotnet tool install --global Microsoft.Playwright.CLI
    
    インストールしたら、次のように新しいコンソールプロジェクトを作成できます.
    # feel free to use VB o C# if you prefer it 
    dotnet new console -lang F# -o Escalin
    
    この場合、私は呼ばれるEscalin , プロジェクトを作成したら、これらの依存関係をインストールします.
    cd Escalin
    dotnet add package Microsoft.Playwright
    dotnet add package Ply
    dotnet build
    # this is required in order to install the browsers playwright uses
    # if you've installed them before (via npm or even the same tool)
    # you can omit this step
    playwright install
    

    SCRIPTING: You can actually use playwright with F# scripts as well but you will need to install the playwright browsers first on that machine either by creating a dummy project and run the dotnet tool or using playwright npm tool to download them


    一度私たちの依存関係を準備すると、我々はVSCodeIonide , Rider or Visual Studio .

    運動


    今日の運動のために、私たちは私自身のブログのウェブスクラップをして、インデックスページのポスト要約のリストを得て、JSONファイルとして保存します
    そのためには、次のようにします.
  • 移動するhttps://blog.tunaxor.me
  • インデックスページのポストエントリのすべてを選択します
  • 各エントリからすべてのテキストを展開する
  • 各テキストブロックから"POST "を生成する
  • JSONファイルを書くposts.json
  • 名前空間といくつかのタイプから始めましょう.
    open Microsoft.Playwright
    // Playwright is very heavy on task methods we'll need this
    open System.Threading.Tasks
    open FSharp.Control.Tasks
    // This one is to write to disk
    open System.IO
    // Json serialization
    open System.Text.Json
    
    // Playwright offers different browsers so let's 
    // declare a Discrimiated union with our choices
    type Browser =
        | Chromium
        | Chrome
        | Edge
        | Firefox
        | Webkit
    
        // let's also define a "pretty" representation of those
        member instance.AsString =
            match instance with
            | Chromium -> "Chromium"
            | Chrome -> "Chrome"
            | Edge -> "Edge"
            | Firefox -> "Firefox"
            | Webkit -> "Webkit"
    
    type Post =
        { title: string
          author: string
          summary: string
          tags: string array
          date: string }
    
    また、メインの目的は以下のようなものです.
    [<EntryPoint>]
    let main _ =
        Playwright.CreateAsync()
        |> getBrowser Firefox
        |> getPage "https://blog.tunaxor.me"
        |> getPostSummaries
        |> writePostsToFile
        |> Async.AwaitTask
        |> Async.RunSynchronously
    
        0
    
    つまり、以下の関数を作成する必要があります
  • getBrowser - これはブラウザとTask 劇作家のインスタンスで
  • getPage - これは文字列Task ブラウザのインスタンスで
  • getPostSummaries - それはTask ページインスタンスで
  • WritePostsToFile - それはTask ポスト配列で
  • の場合Async.AwaitTask and Async.RunSynchronously それは彼らがFSHAREであるので必要ではありません.コア実装、パイプ演算子も使用します|> 最後の関数の結果を次の関数のパラメータとして適用します.

    The pipe operator is very useful in F# it could also make it to javascript at some point

    if we want to visualize that in another way, we can think of it as this:

    64 |> addNumbers 10 is equivalent to addNumbers 10 64


    始めましょうgetBrowser

    NOTE: I changed the parameters here vs the source code be more readable


    let getBrowser (kind: Browser) (getPlaywright: Task<IPlaywright>) =
        task {
            // it's like we wrote
            // let playwright = await getPlaywright
            let! playwright = getPlaywright
    
            printfn $"Browsing with {kind.AsString}"
    
            /// return! is like `return await`
            return!
                match kind with
                | Chromium -> pl.Chromium.LaunchAsync()
                | Chrome ->
                    let opts = BrowserTypeLaunchOptions()
                    opts.Channel <- "chrome"
                    pl.Chromium.LaunchAsync(opts)
                | Edge ->
                    let opts = BrowserTypeLaunchOptions()
                    opts.Channel <- "msedge"
                    pl.Chromium.LaunchAsync(opts)
                | Firefox -> pl.Firefox.LaunchAsync()
                | Webkit -> pl.Webkit.LaunchAsync()
        }
    
    この場合、ブラウザのインスタンスを作成し、それを返すだけではなく、ブラウザのオプションやその他のものを渡すために変更することができる単純なヘルパー関数として考えます.
    また、タスクをパラメータとして使用しているので、pipe オペレーターが簡単にここでダウンサイド私は推測しなければならないということですlet! playwright = getPlaywright しかし、私はそれについてあまり考えません、利益は我々が我々の主な機能をより読みやすくすることができて、我々がどうしたいかについてはっきりした徴候を与えてくれるということです.
    次はgetPage
    let getPage (url: string) (getBrowser: Task<IBrowser>) =
        task {
            let! browser = getBrowser
            printfn $"Navigating to \"{url}\""
    
            // we'll get a new page first
            let! page = browser.NewPageAsync()
            // let's navigate right into the url
            let! res = page.GotoAsync url
            // we will ensure that we navigated successfully
            if not res.Ok then
                // we could use a result here to better handle errors, but
                // for simplicity we'll just fail of we couldn't navigate correctly
                return failwith "We couldn't navigate to that page"
    
            return page
        }
    
    この関数は短いです、我々はちょうど新しいページを開けて、特定のURLを行って、我々が正しくそれをしたことを確実とします
    次の関数はgetPostSummaries それは、我々がちょうど最後の機能で訪問したページのポスト概要のすべてを見つけます.
    let getPostSummaries (getPage: Task<IPage>) =
    
        task {
            let! page = getPage
            //  The first scrapping part, we'll get all of the elements that have
            // the "card-content" class
            let! cards = page.QuerySelectorAllAsync(".card-content")
            printfn $"Getting Cards from the landing page: {cards.Count}"
    
            return!
                cards
                // we'll convert the readonly list to an array
                |> Seq.toArray
                // we'll use the `Parallel` module to precisely process each post
                // in parallel and apply the `convertElementToPost` function
                |> Array.Parallel.map convertElementToPost
                // at this point we have a  Task<Post>[]
                // so we'll pass it to the next function to ensure all of the tasks
                // are resolved
                |> Task.WhenAll // return a Task<Post[]>
        }
    
    次のいずれかに到達する前に、何をチェックする必要がありますconvertElementToPost を行うには、要素の読み取り専用リストからどのようにポスト配列に行くのですか?コードがあまりに外国人に見えないように、ポストを得るために我々がする必要があることのリストを作りましょう
  • 要素の内部、タイトルを検索する
  • 要素の内部で、著者の検索
  • 要素の内部、コンテンツの検索
  • タイトルからテキストを抽出する
  • 内容が配列に分割される...
  • 要約では、配列の最初の要素を取得するか、空の文字列を返します
  • 番目の要素は、我々が持っている場所で分割されます\n 人物
  • その配列の最初の要素に、私たちはそれを分割します# 我々のタグを得るために.
  • 余分なスペースから文字列をトリムし、空の文字列をフィルタアウトする
  • 番目の要素はスペースからもトリミングされ、それが私たちの日付になります
  • このすべては、内容がこのようになるかもしれないということを知っていることに基づいて
    Simple things in F If you come from PHP, Javascript this might help you understand a... #dotnet  #fsharp  #mvc  #saturn \nJul 16, 2021
    
    let convertElementToPost (element: IElementHandle) =
        task {
            // steps 1, 2 y 3
            let! headerContent = element.QuerySelectorAsync(".title")
            let! author = element.QuerySelectorAsync(".subtitle a")
            let! content = element.QuerySelectorAsync(".content")
            // step 4
            let! title = headerContent.InnerTextAsync()
            let! authorText = author.InnerTextAsync()
            let! rawContent = content.InnerTextAsync()
            // step 5
            let summaryParts = rawContent.Split("...")
    
            let summary =
                // step 6
                summaryParts
                |> Array.tryHead
                |> Option.defaultValue ""
    
            // try to split the tags and the date
            let extraParts =
                // step 7
                (summaryParts
                 |> Array.tryLast
                 // we'll default to a single character string to ensure we will have
                 // at least an array with two elements ["", ""]
                 |> Option.defaultValue "\n")
                    .Split '\n'
    
            // split the tags given that each has a '#' and trim it, remove it if it's whitespace
    
            let tags =
                // step 7.1
                (extraParts
                 |> Array.tryHead
                 |> Option.defaultValue "")
                    .Split('#')
                // step 7.2
                |> Array.map (fun s -> s.Trim())
                |> Array.filter (System.String.IsNullOrWhiteSpace >> not)
    
            let date =
                // step 7.3
                extraParts
                |> Array.tryLast
                |> Option.defaultValue ""
    
            printfn $"Parsed: {title} - {authorText}"
            // return el post
            return
                { title = title
                  author = authorText
                  tags = tags
                  summary = $"{summary}..."
                  date = date }
        }
    
    
    フィル!それは激しい権利だった?文字列の扱いは特別に混乱している場合は、それは私の心は何が生産することができますが、それは作品として長く!我々がここでした他のウェブスクラップは、我々がカードの中にいたということを知っていたならば、我々が我々が我々が行く準備ができているテキストを処理したあと、彼らが安全に要素を質問することができて、彼らがそのカードの子供だけであるということを知っていたことができるならば、我々がここでしたウェブサイトです.
    本題の最後のステップに入りましょうwritePostsToFile , これは、最後の関数チェインで返されたポスト配列タスクを受け取ります.
    let writePostsToFile (getPosts: Task<Post array>) =
        task {
            let! posts = getPosts
    
            let opts =
                let opts = JsonSerializerOptions()
                opts.WriteIndented <- true
                opts
    
            let json =
                // serialize the array with the base class library System.Text.Json 
                JsonSerializer.SerializeToUtf8Bytes(posts, opts)
    
            printfn "Saving to \"./posts.json\""
            // write those bytes to dosk
            return! File.WriteAllBytesAsync("./posts.json", json)
        }
    
    一度我々はすべての結果を我々はAsync.AwaitTask F - Channのasync/taskが同じでないなら、

    check Async and Task docs to have a better overview


    F - CHERHUNEは、asyncを本当に持っていませんmain だから、最後のタスクを同期的に実行し、最後に0を返すのです
    結果は次のようになります

    NOTE: that gif contains old code but produces the same output



    ノートと結論


    私がこのコードを得るために行った過程は基本的に私のブログに行って、私のブラウザーでそれを調べて、ウェブサイトの構造を分析し始めるということでした.
    PlayWrightは、多くの多くのオプションを持っていることを念頭に置いて、クリックすると、テキスト入力は、スクリーンショット、PDFファイルを取得することができますマウスイベントを行うと、いずれかのテストを行うか、私がちょうどあなたを示したようにいくつかのウェブスクラップを行うことによってあなたの目標をアーカイブすることができます多くのことを行うことができます.
    Fは、かなり簡潔な言語であり、ほんの少しの場合は、非同期と並列プログラミングのいくつかの場合は、まだ私たちは、両方をしただけでなく、実際に自然に感じたか、少なくとも私はそれがあなたのためにその方法を感じていた方法でそれらを混合した混合することができる複雑ないくつかの可能性がありますか?
    楽しんでください、そして、私は次のエントリで再びあなたに会います!