Prolog
PrologDay 12

PrologでHTMLスクレイピング

More than 1 year has passed since last update.

はじめに

HTMLスクレイピングの話題があったのでPrologでやってみようってつぶやいてたら尾崎さんに解かれてしまいました。

% Prolog load_html/3の実行例です

?- shell('cat >foo.html').
<html>
<body><pre class="text">
ABC
DEF
</pre></body>  
</html>
true.

?- load_html('foo.html',L,[]).
L = [element(html,[],[element(body,[],[element(pre,[class=text],['ABC\nDEF'])])])].

foo.html を作り、 load_html/3 述語を使ってロードできて、要素(element)のリストが帰ってくるので、それをあとは好きに使ったら良いようです。

shell使の入力終了は、ctrl+dを入力するとよいようです。
せっかくなのでもうちょっと掘り下げて使ってみましょう。

xpath を使ってみよう

xpath 使ってみてみたい。

?- load_html('foo.html',L,[]),xpath(L,//(html)/body/pre(text),R).
L = [element(html, [], [element(body, [], [element(pre, [class=text], ['ABC\nDEF'])])]), '\n\n;'],
R = 'ABC\nDEF' .

テキストの中身が取れてます。

?- load_html('foo.html',L,[]),xpath(L,//(pre(text)),R).
L = [element(html, [], [element(body, [], [element(pre, [class=text], ['ABC\nDEF'])])])],
R = 'ABC\nDEF' .

属性は

?- load_html('foo.html',L,[]),xpath(L,//(pre(@class)),R).
L = [element(html, [], [element(body, [], [element(pre, [class=text], ['ABC\nDEF'])])])],
R = text .

どこかのURLからとって来たい

?- use_module(library(http/http_client)).
?- http_get('http://google.com',R,[]).
R = '<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head>......

取れてます。しかし文字列からの読み込みはよくわかりません。ストリームかファイル名を指定する必要があるので、http_open/3 でストリームを開いて読み込みます:

?- http_open('http://google.com',R,[]),load_html(R,L,[]).
Correct to: "http_open:http_open('http://google.com',R,[])"? 
Please answer 'y' or 'n'? yes
R = <stream>(0x55a92d58acf0,0x55a92da698d0),
L = [element(html, [itemscope='', itemtype='http://schema.org/WebPage', lang=ja], [element(head, [], ....

xpathで //div[@style] を全部取り出すと:

?- http_open('http://google.com',R,[]),
   load_html(R,L,[]),
   findall(LL,xpath(L,//(div(@style)),LL),L2).
Correct to: "http_open:http_open('http://google.com',R,[])"? yes
R = <stream>(0x56305b056df0,0x56305b37da10),
L = [element(html, [itemscope='', itemtype='http://schema.org/WebPage', lang=ja], [element(head, [], [element(meta, [content='\220\¢\212\E\222\\206\\202\̂\240\\202\炤\202\鏮\225\񂰌\237\\215\����邽\202\߂̃c\201\[\203\\213\\202\𒱋\237\\202\µ\202\Ă¢\202\܂·\201\B\202\³\202\܂´\202\܂Ȍ\237\\215\����\\\202\ꈗp\202\µ\202\āA\202\¨\222\T\202\µ\202\̏\202\쩂\202\¯\202\Ă­\202\¾\202\³\202\¢\201\B', name=description], []), element(meta, [content=noodp, ... = ...], []), element(meta, [... = ...|...], []), element(meta, [...|...], []), element(..., ..., ...)|...]), element(body, [bgcolor='#fff'], [element(script, [], ['(function(){var src=\'/images/nav_logo229.png\';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}\nif (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}\n}\n})();']), element(div, [... = ...], [' '|...]), element(center, [], [...|...]), element(..., ..., ...)|...])])],
L2 = ['left:0', 'right:0', 'padding:28px 0 3px', 'font-size:83%;min-height:3.5em', 'height:110px;width:276px;background:url(/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png) no-repeat', 'color:#777;font-size:16px;font-weight:bold;position:relative;top:70px;left:218px', 'height:32px;margin:4px 0', 'font-size:10pt', 'margin:19px auto;text-align:center'].

L2 に全部取れてます。
お行儀よく、ファイルを閉じるにはsetup_call_cleanup/3を使います。

?- setup_call_cleanup(
  http_open('http://google.com',R,[]),
  (load_html(R,L,[]),findall(LL,xpath(L,//(div(@style)),LL),L2)),
  close(R)).

こう書けばちゃんと閉じますけど、アクセスするかどうかは聞かました。

a.pl
:- use_module(library(http/http_open)).

:- setup_call_cleanup(
     http_open('http://google.com',R,[]),
     (load_html(R,L,[]),findall(LL,xpath(L,//(div(@style)),LL),L2)),
     close(R)),
   writeln(L2).
:- halt.

ファイルから読んでみるとこうなります。http_open/3を使う時はlibrary(http/http_open))use_moduleする必要があります。

HTMLの出力

出力もしたいですよね。html_write/3で出力できます。

output.pl
:- html_write(user_output,[element(html,[],[element(body,[],['test'])])],[]),nl.
:- halt.

実行は:

$ swipl output.pl
<html>
  <body>test</body>
</html>

user_output は標準出力です。 第一引数に標準出力を指定して出力しています。

参考URL

http://www.swi-prolog.org/pldoc/doc_for?object=section('packages/sgml.html')