玩式草子─ソフトウェアとたわむれる日々

第71回 レシピデータベースを自炊する[その1]

この記事を読むのに必要な時間:およそ 5.5 分

データベースとWebアプリの作成

システムは以前紹介した「お家通信カラオケ」同様,データベースにSQLite3を用い,Webアプリ側はPHPデータを登録する作業はPythonで組むことにしました。

SQLite3の場合,テキスト型のデータはUTF形式のコーディング(UTF-8やUTF-16)しか使えないものの,格納する文字数をあらかじめ決めておく必要がないので設計は簡単です。まずはデータファイルの構造をなぞって,1レコードにページ名とそのページに関するキーワードをテキスト形式で収める,単純な構造のデータベースを作ってみることにしました。

$ sqlite3 knrdb.sql
SQLite version 3.8.5 2014-06-04 14:06:34
Enter ".help" for usage hints.
sqlite> create table pages(page text, keyword text);
sqlite> .q
$ 

先に作ったデータファイルをこのデータベースに登録する処理は,以前に書いたコードを切り貼りしてPythonで組みました。データベースに登録する際のページ名は,複数冊の中から一意にページを区別できるように,データファイルから与えられる年,月のデータを各ページ番号に追加してlong_pageという名前にしています(27行目⁠⁠。なおデータファイルはEUC-JPな文字コードと想定しています(29行目⁠⁠。

 1 #!/usr/bin/python
 2 # -*- coding: euc-jp -*-
 3 
 4 import sys,os,sqlite3
 5 
 6 def insert_db(cursor, t):
 7     try:
 8         print("inserting {}".format(t))
 9         cursor.execute('insert into pages values(?, ?)', t)
10     except sqlite3.Error, e:
11         print("An error occurred:{}".format(e.args[0]))
12 
13 def main():
14     dbname = 'knrdb.sql'
15 
16     connection = sqlite3.connect(dbname)
17     cursor = connection.cursor()
18 
19     file = sys.argv[1]
20     (year, month) = file.replace('.dat','').split('-')
21 
22     with open(file, 'r') as f:
23         lines = f.readlines()
24 
25     for l in lines:
26         (short_page, conts) = l.strip().split(':')
27         long_page = year + '-' + month + '-' + short_page
28         
29         conts_utf = conts.decode('euc-jp')
30         insert_db(cursor, (long_page, conts_utf))
31         connection.commit()
32 
33 if __name__ == "__main__":
34     main()

このコードをinsert.pyという名前にして,先に作ったデータファイルを流しこんでみます。

$ python insert.py 2009-01.dat 
inserting ('2009-01-page_0001.jpg', u' \u76ee\u6b21 \u30ab\u30ec\u30f3\u30c0\u30fc')
inserting ('2009-01-page_0002.jpg', u' \u76ee\u6b211')
inserting ('2009-01-page_0003.jpg', u' \u76ee\u6b212')
inserting ('2009-01-page_0004.jpg', u' CM \u30bf\u30ab\u30e9\u672c\u307f\u308a\u3093')
...

データが正しく登録されたかsqlite3コマンドで確認します。この際,使用する端末の文字コードはUTF-8にしておかないとsqlite3の出力を正しく表示できません。

$ sqlite3 knrdb.sql 
SQLite version 3.8.5 2014-06-04 14:06:34
Enter ".help" for usage hints.
sqlite> select * from pages;
2009-01-page_0001.jpg| 目次 カレンダー
2009-01-page_0002.jpg| 目次1
2009-01-page_0003.jpg| 目次2
2009-01-page_0004.jpg| CM タカラ本みりん
...
sqlite> select * from pages where keyword like '%ベーコン%';
2009-01-page_0012.jpg| 冬のお手軽洋風おかず 塩田ノア タルティフレット じゃがいも たまねぎ カリフラワー ベーコン カマンベールチーズ
2009-01-page_0017.jpg| 冬のお手軽洋風おかず 城川朝 レンジミネストローネ キャベツ にんじん たまねぎ にんにく じゃがいも ベーコン 赤いんげん豆 ミックスハーブ トマトの水煮
2009-01-page_0019.jpg| 冬のお手軽洋風おかず 城川朝 たらのベーコン巻き 生だら ベーコン
  ...

データベースはきちんと作成できているようなので,このデータベースを検索するためのコードをPHPで書いてみます。11~21行がデータベースと接続,23~34行がデータベースを検索する関数で,FORM経由で送られてきたキーワードを後者の関数を使ってデータベースから引き,その結果を一覧表示するだけのコードです。このコードはsearch.phpという名前にしました。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta http-equiv="Content-Language" content="ja">
 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 6 <title>KNR keyword search</title>
 7 </head>
 8 <body>
 9 
10 <?php
11 function DBconnect($name) {
12    $dbname = sprintf("sqlite:%s", $name);
13    try {
14        $db = new PDO($dbname);
15        echo"DB connected<br>\n";
16    }
17    catch (PDOException $e) {
18        exit("cannot connect to DB".$e->getMessage());
19    }
20    return $db;
21 }
22   
23 function query_db($db, $key) {
24     $sql = sprintf("select * from pages where keyword like '%%%s%%' order by page", $key);
25     echo "sql:$sql<br>\n";
26     foreach ($db->query($sql) as $line) {
27          $item = array(
28                  "page" => trim($line['page']),
29                  "keyword" => trim($line['keyword'])
30                  );
31          $results[] = $item;
32     }
33     return $results;
34 }
35 
36 $dbname = 'knrdb.sql';
37 
38 $key = $_GET['key'];
39 echo "key:$key <br>";
40 $db = DBconnect($dbname);
41 $res = query_db($db,  $key);
42 foreach ($res as $line) {
43     printf("<a href=\"show.php?id=%s\">%s : %s </a> <br>\n", $line['page'], $line['page'], $line['keyword']);
44 }
45 printf("<br><a href=\"index.html\">検索ページへ戻る </a><br>\n");
46 ?>
47 
48 </body>
49 </html>

search.phpを呼び出す側はindex.htmlという名前にしました。このコードはキーワード入力を受け付けて,FORM経由でsearch.phpを呼び出すだけです。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta http-equiv="Content-Language" content="ja">
 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 6 
 7 <title>KNR search</title>
 8 </head>
 9 <body>
10 <H2>キーワード入力</H2>
11 <form action="search.php">
12 <p>
13 キーワード<input type="text" name="key">
14 </p>
15 </form>
16 
17 </body>
18 </html>

search.phpでは43行目でキーワードに該当するページの画像を表示するためのリンクを出力します。そのリンクを辿った際,実際に画像を表示するコードはshow.phpとしました。show.phpでは,search.phpから送られた"2009-01-page_0100.jpg"といったページ情報を"Pages/2009/01/page_0100.jpg"のような実際のファイル名に変換し,そのファイルを読み込んで<img>タグで表示します。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta http-equiv="Content-Language" content="ja">
 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 6 <title>KNR page show</title>
 7 </head>
 8 <body>
 9 <?php
10    $id = $_GET['id'];
11    list($year, $month, $page) = explode('-', $id);
12    printf("<img src=\"Pages/%s/%s/%s\" width=\"800\">\n", $year, $month, $page);
13 ?>
14 </body>
15 </html>

先に作ったSQLite3のデータベースファイルとこれら3つのファイルは,httpd経由で利用できるように~/public_html/KNR_Test/に用意しました。

一方,⁠きょうの料理」をスキャンした画像ファイルはファイルサーバ上の/Document/Scans/KNR/<年>/<月>/ というディレクトリに保存しています。show.phpからこれら画像ファイルにアクセスするため~/public_html/KNR_Test/Pages/ というディレクトリを作って,そこに/Document/Scans/KNR/以下を--bindオプションを指定してマウントしておきます。--bindオプションを指定したマウントは,既存のディレクトリを別のディレクトリ以下に見せる,いわばディレクトリへのシンボリックリンクのような機能を果します。

$ mkdir ./public_html/KNR_Test/Pages
$ sudo mount --bind /Document/Scans/KNR ./public_html/KNR_Test/Pages

動作テスト

以上の環境を整えて,実際にキーワードから該当するページが表示できるかを試してみました。まず,index.htmlを開いて,⁠卵」というキーワードを指定します。

index.htmlにキーワードを入力

index.htmlにキーワードを入力

「卵」というキーワードはsearch.phpに送られ,データベースを検索して該当するページの一覧が表示されました。

データベースの実行結果

データベースの実行結果

適当なリンクを開いてやると,show.phpがスキャンしたページを表示しました。

該当するページの表示例

該当するページの表示例

今回のコードは必要最低限の機能しか実装していないので,ページ表示から戻るにはブラウザの「戻る」機能を使わなければならない等,さまざまな制約があります。しかしながら,わずか100行ほどのコードでキーワードからレシピを検索するシステムのプロトタイプを作れたのは大きな収穫です。次回は,このコードにさまざまな機能を追加して,より実用的なシステムにしてみる予定です。

著者プロフィール

こじまみつひろ

Plamo Linuxとりまとめ役。もともとは人類学的にハッカー文化を研究しようとしていたものの,いつの間にかミイラ取りがミイラになってOSSの世界にどっぷりと漬かってしまいました。最近は田舎に隠棲して半農半自営な生活をしながらソフトウェアと戯れています。

URLhttp://www.linet.gr.jp/~kojima/Plamo/index.html