Julia ウェブスクレイピング関連
最終更新:2020/11/16
Juliaを使ったウェブスクレイピング関連のメモ.
使うパッケージは,
HTTP v0.8.19
Cascadia v1.0.1
Gumbo v0.8.0
Julia Version 1.5.0
パッケージの説明
簡単にそれぞれのパッケージの役割を確認するため, 私のHPのトップページをスクレイピング してみようと思う. ネットワーク関連には疎いので, 用語は適切ではないと思う.
一言でいうと, HTTP.jlでHTMLにアクセスし, Gumbo.jlで解析しやすい階層構造に変換し, Cascadia.jl でターゲットとなる情報を検索・抽出する.
HTTP.jl
ウェブサイトにアクセスして, HTMLをとってくるパッケージだと思う.
using HTTP r = HTTP.get("https://koshiba.sakura.ne.jp/") # 結果 julia> r. body headers request status version julia> r.status 200
HTTP.get
でアクセスすると, いくつかの情報をとってきてくれる.
statusが200というのは, 無事情報をとってこれたという意味だそう.
肝心な情報は, r.body
の中に入っているので見ていく.
Gumbo.jl
HTMLをパース ( 構文解析 ) するパッケージ, とのことですが詳しくはわからない.
役割を見るほうがはやそうなのでみてみる.
先程, r.body
にHTMLが入っていると書いたけど, 以下のように読めたもんでは
ない.
julia> r.body 2174-element Array{UInt8,1}: 0x3c 0x21 0x44 0x4f 0x43 ⋮ 0x6d 0x6c 0x3e 0x0a
ここで, Gumbo.jlのちからを借りる.
parsehtml
すると, 読めるHTMLとなる.
using Gumbo h = parsehtml(String(r.body)) # result julia> h HTML Document: <!DOCTYPE html> HTMLElement{:HTML}:<HTML> <head> <meta charset="UTF-8"/> <script async="" src="https://www.googletagmanager.com/gtag/js?id=G-FHJGK11XRK"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-FHJGK11XRK'); </script> <meta content="width=device-width, user-scalable=yes, maximum-scale=1.0, minimum-scale=1.0" name="viewport"/> <title> Takahiro Koshiba's HP </title> <meta content="小柴孝太のHP TOP" name="description"/> <link href="https://koshiba.sakura.ne.jp/src/style.css" media="screen" rel="stylesheet" type="text/css"/> <script src="https://koshiba.sakura.ne.jp/src/jquery2.js"></script> <script> $(function() {$("#header").load("src/header.html")}); </script> </head> <body> ...
こうすれば, もうすでにh.root
に, htmlの内容が階層的に入っている.
例えば,h.root[1]
にはheadが, h.root[2]
にはbodyが入っている.
そして, h.root[2][1]
には, bodyの中の1つ目の要素
( この場合は, <div id="wrapper">の内容 )
が入っている.
julia> h.root[2][1] HTMLElement{:div}:<div id="wrapper"> <div id="header"></div> <div id="mainPhoto"> <img alt="Top photo" height="600" src="src/top_photos/top1.jpg" width="900"/> <br/> ウズベキスタンで撮影しました. </div> <div id="new"> <h2> 更新履歴 </h2> 2020/10/23 公開開始 <br/> 現在工事中 </div> </div>
さらに, 一番下の階層までいけば, .txt
を使ってテキストをとってこれる.
例えば, body[1][3][1][1]
は”更新履歴”という文字を含むh2要素なので, 以下のようにして
”更新履歴”という文字列を取得できる.
julia> temp = body[1][3][1][1] HTML Text: `更新履歴` julia> temp.text "更新履歴"
Cascadia.jl
Selector
という函数を駆使して, 欲しい情報を検索する.
データの抽出には, juliaに附随のeachmatch
などを利用すればいいよう.
以下で具体的に使ってみる.
気象庁から気温データをとってみる
気象庁の過去の気象データのページから, 2020年1月1日, 京都, 10分毎の気温データを取得し, 可視化 してみる.
対象とするページは ココ まずは, このURLにあるHTMLをとってくる.
r = HTTP.get("http://www.data.jma.go.jp/obd/stats/etrn/view/10min_s1.php?prec_no=61&block_no=47759&year=2020&month=01&day=01&view=p1") r = parsehtml(String(r.body)) r = r.root; r = r[2]; # bodyの取得 ( r[1]はhead ) , 別にやらなくてもいい
次に, 観測データが入っているテーブルをCascadia.jlのSelector
を使ってとってくる.
テーブルには, id="tablefix1"
とclass="data2_s"
が
ついているようなので, セレクタでこれを指定. 両方指定してもいいし, 片方でもいい.
指定の仕方は, Cascadiaのページの一番下にある.
最後に, テーブルの本体が格納されている場所がqs[1][1]
であることを, 試行錯誤的に確認し, 取得した.
qs = eachmatch(Selector("#tablefix1"), r) # idで指定 qs = eachmatch(Selector(".data2_s"), r) # classで指定 qs = qs[1][1]
これで, qs
にテーブル要素が入っている.
具体的にどう中身が入っているかは, qs[1]
, qs[2]
, qs[3]
, ...と実際にアクセスしてみたらわかると思う.
それぞれが, 表の1行となっている.
for文でそれぞれのqs[i]
にアクセスし, 気温データをとってくる.
具体的な数値には”data_0_0”というクラスが使われていたので, これをセレクタにした.
さらに, 気温は4要素目であったので,4番目の要素を指定している.
nodeText
という函数は, 説明がないのでよくわからないが, HTMLの要素からタグの
中身を抽出するよう. 便利なのでこれを用いた. ( 先程は.txt
でアクセスした. これを使う場合
もう一つ下の要素に使用する必要がある. )
ちなみに, 144は6データ/時間✕24. また, for文が3から始まっているのは, 1,2がテーブルのヘッダー であったから.
temp = zeros(144); # preallocation for iTime in 3:146 q = qs[iTime] q = eachmatch(Selector(".data_0_0"), q)[4] temp[iTime-2] = parse(Float32, nodeText(q)); end
最後に, 一応図示してみる.
using Plots pyplot(fmt=:svg, size=(400, 250)) plot(temp) PyPlot.savefig("kion.png", dpi=300)
参考記事
参考としたのは, 基本的にそれぞれのパッケージのチュートリアル. あと, Scraping web pages with Julia and the HTTP and Gumbo packages もわかりやすい.