Julia ウェブスクレイピング関連
最終更新:2020/11/16
Juliaを使ったウェブスクレイピング関連のメモ.
使うパッケージは,
HTTP v0.8.19Cascadia v1.0.1Gumbo 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 もわかりやすい.