d.sunnyone.org
sunnyone.org

ページ

2015-12-20

PowerShellでスクレイピングする (AngleSharp編)

これは、PowerShell Advent Calendar 2015の12/20分の記事です。Invoke-WebRequestとIEエンジンによるDOM処理とは違うやり方について書きます。

---
PowerShellを使うと、Invoke-WebRequestを使うことで、特にライブラリ等を準備せずともWebページをスクレイピングすることができる(参考:PowerShellでスクレイピング 後編 HTMLをパースする) 。

これはこれで便利なのだが、少し複雑なHTMLになると、Where-ObjectやSelect-Objectがだんだん増えてきてしまう。こんな感じである。

$res = Invoke-WebRequest $url
$resultItems = $res.ParsedHtml.getElementsByTagName("div") | Where-Object { $_.getAttributeNode("class").Value -eq "result-item" }
$itemLinks = $resultItems[0].getElementsByTagName("a") | Where-Object { $_.pathname -ilike "list/*" }
view raw iwr-example.ps1 hosted with ❤ by GitHub


やっぱりDOMツリーをたどるなら使い慣れたCSSセレクタの記法が使いたいよね?ということで、今回はCSSセレクタが使える.NET用HTMLパーサライブラリ「AngleSharp」をPowerShellから使ってみるお話。本当はC#で馴染みのあったCsQueryを使おうと思ったのだけど「Not Actively Maintained」ということでAngleSharpにしてみた。

インストール

例によって適当なフォルダに移動してnuget.exeを使ってダウンロードする。
> nuget install AngleSharp

使い方

初期化

まず、AngleSharp.dllをロードし、loaderが設定されたconfigを使って、BrowsingContextを作っておく。
Add-Type -Path .\AngleSharp.0.9.3\lib\net45\AngleSharp.dll
$config = [AngleSharp.ConfigurationExtensions]::WithDefaultLoader([AngleSharp.Configuration]::Default)
$ctx = [AngleSharp.BrowsingContext]::New($config)
view raw anglesharp1.ps1 hosted with ❤ by GitHub

このBrowsingContextを使っていく。

GETリクエスト

シンプルにGETリクエストを出すには、文字列のURLを使ってOpenAsyncを呼び出す。そして返ってきたものに対して、最初に一致したものだけでよければQuerySelector()を、全て欲しい場合はQuerySelectorAll()を呼び出す。

例として、このブログの右側の「ブログ アーカイブ」の一番上のタイトル(画像参照)を取得するにはこんな感じである。

PS > $doc = [AngleSharp.BrowsingContextExtensions]::OpenAsync($ctx, "http://d.sunnyone.org").Result
PS > $elem = $doc.QuerySelector("#ArchiveList ul.posts li a")
PS > $elem.TextContent
Loqui 0.6.4 リリース
view raw angelsharp2.ps1 hosted with ❤ by GitHub


~Asyncは、流儀通りTask<T>が返ってくる。PowerShell世界では扱いに困るのでResultでひたすらブロックしていく(ひどい)

ほぼ同じであるが、もう一つの例として@mutaguchiさんのブログPowerShell Scripting Weblogの右側にあるMVPのタイトルを取得するにはこんな感じである。
PS > $doc = [AngleSharp.BrowsingContextExtensions]::OpenAsync($ctx, "http://winscript.jp/powershell/").Result
PS > $elem = $doc.QuerySelector('#RMENU a[href*="mvp.microsoft.com"]')
PS > $elem.TextContent.Trim()
Microsoft MVP for Cloud and Datacenter Management July 2004-June 2016
view raw anglesharp3.ps1 hosted with ❤ by GitHub


各ブラウザの開発者ツール(F12)のコンソールで、document.querySelector('#ArchiveList')などとして、先に試しておくとよい。

POSTリクエスト

POSTにするには、OpenAsync()の第2引数にリクエストオブジェクトを生成する。POSTリクエストを作る便利メソッドはいくつかあり、例えばPostAsUrlEncodedを使うとこんな感じである。
$qparam = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]'
$qparam.Add("key1", "value1")
$req = [AngleSharp.Network.DocumentRequest]::PostAsUrlencoded("http://www.example.jp/", $qparam)
$doc = [AngleSharp.BrowsingContextExtensions]::OpenAsync($ctx, $req, [System.Threading.CancellationToken]::None).Result
view raw angelsharp4.ps1 hosted with ❤ by GitHub

$docができたら、あとはGETと同様に、QuerySelector()やQuerySelectorAll()を呼び出せばOKである。

感想

これで一応、AngleSharpをPowerShellから呼ぶことはできた。Where-Objectを連発するよりは見通しのよいものが書けると思う。ただし、AngleSharpの呼び出す部分は拡張メソッドだらけになっており、PowerShellから大変に呼びにくいので、うーんという感じ。やはりInvoke-WebRequestが、querySelector / querySelectorAllできれば万事OKだったのに、という感じであった。

0 件のコメント:

コメントを投稿