#:g1: ウェブページのスクレイピングにプロダクションシステムを使ってみる

Posted 2021-12-04 10:09:33 GMT

Lisp一人 Advent Calendar 2021 4日目の記事です。

以前、ウェブページのスクレイピングをProlog(後ろ向き推論)で試してみましたが、今回はプロダクションシステム(前向き推論)で試してみます。

プロダクションシステムは、基本的な考え方としては、所謂IF-THENのルールが並んでいるもので、一般的な会話での『ルールベース』というとこういうものを想像していることが多いようです。
実際の前向き推論のシステムは、IF-THENの列挙というわけではなく、もっと効率の良いアルゴリズムが使われています。 今回試すlisaは、プロダクションシステムとして有名なOPS5系でReteアルゴリズムをベースにしています。

しまむらのページをスクレイピングしてみる

今回試すのは、

  1. 適当なカテゴリの商品一覧ページから商品画像を取り出す
  2. 商品検索ページから商品名で検索して該当した商品画像を取り出す

です。

こんな風に書けました。

(ql:quickload '(clss plump dexador lisa drakma))

(defpackage "40219287-1ccc-5a4a-967b-8efb315e9701" (:use cl lisa) (:shadowing-import-from lisa assert))

(in-package "40219287-1ccc-5a4a-967b-8efb315e9701")

(defmethod initialize-instance :after ((inst plump::node) &rest args &key) (assert-instance inst))

(defcontext :site-query) (defcontext :page-query)

(defclass shimamura-fundamental () ())

(defclass site-query (shimamura-fundamental) ((word :initform nil :initarg :word) (page :initform nil :initarg :page)))

(defrule query-by-word (:context :site-query) (?site-query (site-query (word ?word) (page ?page))) (test (null ?page)) => (modify ?site-query (page (plump:parse (dex:get (fstring "https://www.shop-shimamura.com/disp/itemlist/?q=~A" (quri:url-encode ?word)))))))

(defclass page (shimamura-fundamental) ((is :initform nil)))

(defrule get-page (:context :page-query) (?page (page (is ?html))) (test (null ?html)) => (let ((?html (plump:parse (dex:get "https://www.shop-shimamura.com/disp/itemlist/001002001/")))) (modify ?page (is ?html))))

(defclass card__thumb (shimamura-fundamental) ((is :initarg :is)))

(defrule collect-card__thumb () (plump:element (:object ?e)) (test (equal (plump:attribute ?e "class") "card__thumb")) => (assert (card__thumb (is ?e))))

(defclass img (shimamura-fundamental) ((is :initform nil :initarg :is)))

(defrule collect-img () (card__thumb (is ?e)) => (let ((win (first (plump:get-elements-by-tag-name ?e "img")))) (when win (let ((?img (make-instance 'img :is win))) (assert (?img))))))

(defrule print-result () (img (is ?img)) => (format T "~A: ~A~%" (plump:attribute ?img "alt") (plump:attribute ?img "src")))

OPS5系の作法ではルール発火の火口を別途定義することが多いようなので、startupとして定義し、それをrunに与えてそれぞれ実行してみます。

(defrule startup (:context :page-query)
  =>
  (assert (page)))

(defrule startup (:context :site-query) => (lisa::with-context :site-query (assert ((make-instance 'site-query :word "メンズ ボクサーブリーフ(しまむらロゴ)")))))

実行

(progn
  (reset)
  (run '(:site-query)))
▻ メンズ ボクサーブリーフ(しまむらロゴ): https://img.shop-shimamura.com/items/images/01/0140200003750/01_0140200003750_201_l.jpg
▻ メンズ ボクサーブリーフ(しまむらロゴ): https://img.shop-shimamura.com/items/images/01/0140200003749/01_0140200003749_201_l.jpg

(progn (reset) (run '(:page-query))) ▻ メンズ キャラクターパーカ(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0128200005473/01_0128200005473_312_l.jpg ▻ メンズ キャラクターパーカ(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0128200005472/01_0128200005472_309_l.jpg ▻ メンズ キャラクターパーカ(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0128200005471/01_0128200005471_213_l.jpg ▻ メンズ キャラクターパーカ(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0128200005470/01_0128200005470_107_l.jpg ▻ メンズ キャラクタートレーナー(はぴだんぶい): https://img.shop-shimamura.com/items/images/01/0123200007192/01_0123200007192_213_l.jpg ▻ メンズ キャラクタートレーナー(スーパーシロ×サンリオキャラクターズ ): https://img.shop-shimamura.com/items/images/01/0123200007191/01_0123200007191_307_l.jpg ▻ メンズ キャラクタートレーナー(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0123200007166/01_0123200007166_312_l.jpg ▻ メンズ キャラクタートレーナー(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0123200007165/01_0123200007165_107_l.jpg ▻ メンズ キャラクタートレーナー(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0123200007164/01_0123200007164_213_l.jpg ▻ メンズ キャラクタートレーナー(TVアニメ「ヴィジュアルプリズン」): https://img.shop-shimamura.com/items/images/01/0123200007163/01_0123200007163_309_l.jpg ▻ メンズ ビッグシルエットパーカ(U.S.POLO ASSN.): https://img.shop-shimamura.com/items/images/01/0123200007169/01_0123200007169_312_l.jpg ▻ メンズ ビッグシルエットパーカ(U.S.POLO ASSN): https://img.shop-shimamura.com/items/images/01/0123200007168/01_0123200007168_213_l.jpg ▻ メンズ ビッグシルエット裏起毛トレーナー(U.S.POLO ASSN.): https://img.shop-shimamura.com/items/images/01/0123200007167/01_0123200007167_312_l.jpg ▻ メンズ ビッグシルエット裏起毛トレーナー(U.S.POLO ASSN.): https://img.shop-shimamura.com/items/images/01/0123200007167/01_0123200007167_105_l.jpg ▻ メンズトレーナー(天下一品): https://img.shop-shimamura.com/items/images/01/0128200005491/01_0128200005491_213_l.jpg ▻ メンズトレーナー(天下一品): https://img.shop-shimamura.com/items/images/01/0128200005490/01_0128200005490_211_l.jpg ▻ メンズ トレーナー(天下一品): https://img.shop-shimamura.com/items/images/01/0123200007162/01_0123200007162_213_l.jpg ▻ メンズ トレーナー(天下一品): https://img.shop-shimamura.com/items/images/01/0123200007161/01_0123200007161_211_l.jpg ▻ メンズ フリースジャケット(LATOK): https://img.shop-shimamura.com/items/images/01/0123700000728/01_0123700000728_213_l.jpg ▻ メンズ フリースジャケット(LATOK): https://img.shop-shimamura.com/items/images/01/0123700000728/01_0123700000728_105_l.jpg ▻ メンズ 裏起毛刺しゅうトレーナー(LATOK): https://img.shop-shimamura.com/items/images/01/0123200007126/01_0123200007126_211_l.jpg ▻ メンズ 裏起毛トレーナー: https://img.shop-shimamura.com/items/images/01/0123200007125/01_0123200007125_307_l.jpg ▻ メンズ キャラクター裏ボアトレーナー(Disney): https://img.shop-shimamura.com/items/images/01/0123200006894/01_0123200006894_106_l.jpg ▻ メンズ ビッグシルエットブルゾン: https://img.shop-shimamura.com/items/images/01/0123600000729/01_0123600000729_208_l.jpg ▻ メンズ ビッグシルエット裏起毛パーカ: https://img.shop-shimamura.com/items/images/01/0123200006854/01_0123200006854_211_l.jpg ▻ メンズ中綿アウター(風早ゆうた): https://img.shop-shimamura.com/items/images/01/0123600000736/01_0123600000736_112_l.jpg ▻ メンズ中綿アウター(風早ゆうた): https://img.shop-shimamura.com/items/images/01/0123600000736/01_0123600000736_314_l.jpg ▻ メンズフリーストレーナー(風早ゆうた): https://img.shop-shimamura.com/items/images/01/0123200007100/01_0123200007100_111_l.jpg ▻ メンズフリーストレーナー(風早ゆうた): https://img.shop-shimamura.com/items/images/01/0123200007100/01_0123200007100_314_l.jpg ▻ メンズ レイヤード風裏起毛トレーナー: https://img.shop-shimamura.com/items/images/01/0123200007027/01_0123200007027_214_l.jpg ▻ メンズ レイヤード風裏起毛トレーナー: https://img.shop-shimamura.com/items/images/01/0123200007027/01_0123200007027_107_l.jpg ▻ メンズ ファーブルゾン(中島健): https://img.shop-shimamura.com/items/images/01/0123600000755/01_0123600000755_211_l.jpg ▻ メンズ ファーブルゾン(中島健): https://img.shop-shimamura.com/items/images/01/0123600000752/01_0123600000752_213_l.jpg ▻ メンズ シャギーベスト(LOGOS DAYS): https://img.shop-shimamura.com/items/images/01/0121100000530/01_0121100000530_315_l.jpg ▻ メンズ シャギーベスト(LOGOS DAYS): https://img.shop-shimamura.com/items/images/01/0121100000530/01_0121100000530_105_l.jpg ▻ メンズ 裏起毛プルパーカ(新日本プロレス): https://img.shop-shimamura.com/items/images/01/0120800000748/01_0120800000748_111_l.jpg ▻ メンズ 裏起毛トレーナー(新日本プロレス): https://img.shop-shimamura.com/items/images/01/0120800000747/01_0120800000747_312_l.jpg ▻ メンズ 裏起毛トレーナー(新日本プロレス): https://img.shop-shimamura.com/items/images/01/0120800000746/01_0120800000746_213_l.jpg ▻ メンズ キャラクター裏起毛パーカ(Peko&Poko): https://img.shop-shimamura.com/items/images/01/0123200006941/01_0123200006941_213_l.jpg ▻ メンズ トレーナー(GERRY cosby): https://img.shop-shimamura.com/items/images/01/0123200006905/01_0123200006905_212_l.jpg ▻ メンズ トレーナー(GERRY cosby): https://img.shop-shimamura.com/items/images/01/0123200006904/01_0123200006904_306_l.jpg ▻ メンズ 裏起毛トレーナー(ecko unltd.): https://img.shop-shimamura.com/items/images/01/0123200006885/01_0123200006885_213_l.jpg ▻ メンズ 裏起毛トレーナー(ecko unltd.): https://img.shop-shimamura.com/items/images/01/0123200006885/01_0123200006885_315_l.jpg

ちなみに、lisaではtraceに相当するwatchが使えて、ルールがどのように発火したかを確認できます。

(watch :activations)
(watch :facts)
(watch :rules)

まとめ

IF-THENレベルで記述できるような簡単なルールだと、記述が面倒なだけですが、そこそこ複雑になれば、プロダクションシステムのようなものも活躍できるのかなと思います。
とりあえず、プロダクションシステムのプログラミングの作法に馴染みがないと全然思ったように記述ができないですね。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus