初めに
ここでは、Juliaを使ってスクレイピングを行う方法を紹介します。
スクレイピング対象は、ZennのJuliaトピックページです。
このページから、記事見出しと記事へのリンクを抜き出します。
ページネーション(ページ割り)は考慮せず、このページに記載されている情報だけを対象とします。
使用するパッケージ
- HTTP
- HTTPを操作する(WebAPIを呼び出す)
- https://segakuin.com/julia/http/
- https://github.com/JuliaWeb/HTTP.jl
- Gumbo
- HTMLを解析する
- https://segakuin.com/julia/gumbo/
- Cascadia
- CSSセレクタを扱う
- https://segakuin.com/julia/cascadia/
- URIs
- url操作を行う。パッケージ名に注意。
- https://github.com/JuliaWeb/URIs.jl
- Dates
- 日時操作(標準で組み込まれています)
- https://docs.julialang.org/en/v1/stdlib/Dates/
パッケージの追加
必要なパッケージを追加します。
julia> using Pkg
julia> Pkg.add("HTTP")
julia> Pkg.add("Gumbo")
julia> Pkg.add("Cascadia")
julia> Pkg.add("URIs")
HTMLの取得
指定URLからHTMLファイルを取得します。
julia> using HTTP
julia> zenn_url = "https://zenn.dev/topics/julia?order=latest"
julia> response = HTTP.request("GET", zenn_url)
julia> code = String(response.body) # Byte列を文字列に変換
基本的には上記のコードで取得できますが、サイトの構成が変更された場合を考慮して、HTTPステータスコードのチェックを入れておきます。ステータスコードが200のときに取得に成功したとみなします。
実用のためには、そのほかの各種エラーのチェックも必要ですがここでは割愛します。
julia> response.status
200
HTMLファイルの解析
取得したHTMLを解析します。
文字列を解析した結果は HTMLDocument型で格納されます。
julia> using Gumbo
julia> doc = Gumbo.parsehtml(code)
doc変数にHTMLDocument型が格納されていますがこのままでは要素を取り出せません。
doc.rootで要素の配列を取り出します。
一般なHTMLでは「head」タグと「body」タグで構成されています。
julia> head = doc.root[1]
julia> body = doc.root[2]
CSSセレクターを用いた検索
bodyタグの内部から必要な内容を取り出します。HTMLの構造を一つ一つたどって取得することも不可能ではありませんが、あまり効率的な方法ではありません。
ここでは、CSSセレクターを使います。タグ、クラス、IDを指定することで、該当する情報をまとめて取得し、結果はリストに格納されます。
リンクを取得するので「a」タグの指定で検索できそうですが、本来必要としない情報まで含まれてしまいます。対象ページのHTMLソースを見ると「class=”ArticleList_link__vf_6E”」の設定されているタグで、見出しとURLを取得できそうです。(タグのクラス指定などは、将来変更になる場合があります)
julia> using Cascadia
julia> qs = eachmatch(Selector(".ArticleList_link__vf_6E"), body)
変数qsに指定されたクラスのaタグの一覧が格納されています。この時点では、タグのすべての記述を含んでいるので、必要な情報のみを抜き出します。
リストに格納されているので、for文で1件ずつ処理しています。1件のデータは次のようになっています。
julia> s = qs[1]
HTMLElement{:a}:<a class="ArticleList_link__vf_6E" href="/eiel/articles/96ce34fa7e16e9">
<h2 class="ArticleList_title__P6X2G">
Juliaでn週間後の日付を確認したい
</h2>
</a>
まず、URLを抜き出します。これは、aタグのhref属性の値を取得することに該当します。万が一、href属性がなかった場合に備え、ここではnothingを設定するように指定しておきます。(特定のURL、例えば404ページのURLなどを指定することもありそうです。)
julia> url = getattr(s, "href", nothing)
"/eiel/articles/96ce34fa7e16e9"
次に見出しを抜き出します。「nodeText()」を使って、タグ中のコンテンツとしての文字列を取得します。したがって上記のaタグでは内側にh2タグがありますが無視されます。
julia> title = nodeText(s)
"Juliaでn週間後の日付を確認したい"
URLの補正
サイトによって異なりますが、対象ページのHTMLには、URLとしていわゆる相対パスが記載されています。このままでは、サイト外で使用することができないので、URLを合成して、絶対パスを作成します。
julia> using URIs
julia> zenn_url = "https://zenn.dev/topics/julia?order=latest"
julia> new_url = resolvereference(zenn_url, url)
URI("https://zenn.dev/eiel/articles/96ce34fa7e16e9")
作成されたnew_urlはURI型になっています。URI型はprintln()などで文字列が正しく出力されるので、このままでもいいのですが、各種処理をするのに文字列型に変換したほうが都合がいい場合は、変数埋め込みを使って文字列に変換します
julia> url_str = "$new_url"
"https://zenn.dev/eiel/articles/96ce34fa7e16e9"
まとめ
上記をまとめたコードを下記に公開しています。
実行すると、日付付きのファイルにTSV形式で見出し+URLを出力します。
julialangjp/ScrapingZennTopicsByJulia (Github)
参考記事
- Julia ウェブスクレイピング関連(Takahiro Koshiba’s HP)
- Juliaで楽しくWebスクレイピング!(@nezuq, Qiita)
- ガルパのスクレイピングをはじめよう~【Julia言語】(@rensF82, Qiita)
- ジュリアでウェブスクレイピングを行うにはどうすればよいですか?(CODE Q&A)
- Juliaでスクレイピング 1/m(眠気.jl)
- Scraping web pages with Julia and the HTTP and Gumbo packages(JULIA.SCHOOL)
- Pythonのseleniumライブラリを使ってJuliaLangでwebスクレイピング(snovaのブログ)
コメント