Python CGI で作るアクセスカウンタ~sqlite3モジュール編~ [プログラム三昧]
PythonからSQLiteモジュールが直接使えるように、さくらインターネットに対応してもらいました。 詳しくは、Python 2.5.2 で、 SQLite が使えるはずだったのに。に書きました。 これで、ついに、コマンドラインを介さずにデータベースの操作をすることができます。 そこで、アクセスカウンタをデータベースに対応させてみました。
データベースの構成
アクセスカウンタで記録する情報は、「LOGファイル版」のものと同じです。 これらをテキストファイルではなく、データベースの「行」として記録していきます。
カラム名 | タイプ | 内容 |
---|---|---|
time | REAL | 記録時刻を表す数値です。 |
url | TEXT | アクセス先のURL (document.URL) を記録します。 |
referrer | TEXT | 参照元のURL (document.referrer) を記録します。参照元が不明な場合には、 "None" が入ります。 |
r_addr | TEXT | ページの表示を要求したクライアントのIPアドレス (REMOTE_ADDR) です。 |
r_host | TEXT | クライアントが申告したホスト名 (REMOTE_HOST) です。クライアントが申告しなかった場合には、 "None" が入ります。 |
このテーブルに "visitor" という名前をつけて、 "acount1.sqlite" というレンタル・サーバ上のファイルに格納します。
データベース初期化CGI : acounter1_init.cgi
データベース版アクセスカウンタをBLOGに実装するため、いくつかのプログラムを作成しました。 最初は、データベースを初期化するためのCGIプログラムです。
#!/usr/local/bin/python # $Id: acounter1_init.cgi,v 1.2 2009/07/21 07:49:36 noritan Exp $ # Create a table import sys import cgi import sqlite3 import cgitb # Parameters db_file = "acount1.sqlite" # Enable debug output cgitb.enable() # Issue SQL con = sqlite3.connect(db_file) cur = con.cursor() cur.execute("CREATE TABLE visitor (time REAL, url TEXT, referrer TEXT, r_addr TEXT, r_host TEXT)") con.commit() cur.close() con.close() # Executed successfully print """Content-Type: text/plain OK"""
仕様に従って、 "acount1.sqlite" ファイルに "visitor" テーブルを作成するだけの CGI です。 "sqlite3" モジュールが使えるおかげで、従来の方法に比べてこんなにシンプルになりました。
アクセス記録 JavaScript : acounter1.js
アクセスを記録するための構成は、Python CGI で作るアクセスカウンタ~LOGファイル版~と同じです。 "JavaScript" のプログラムから "Python" の CGI を呼び出します。
// // Access counter in a SQLite file // // $Id: acounter1.js,v 1.1 2009/07/05 06:01:41 noritan Exp $ // // Construct a javascript statement // to invoke a CGI with parameters as text/javascript. // document.writeln('<script type="text/javascript" charset="utf-8"' + ' src="http://noritan.org/cgi/acounter1.cgi' + '?URL=' + escape(document.URL) + "&HTTP_REFERER=" + escape(document.referrer) + '"></script>');
単に呼び出される CGI の URL が変更されただけです。 呼び出された CGI の出力は、 "JavaScript" プログラムとして扱われます。
アクセス記録 CGI : acounter1.cgi
実際にデータベースを操作するための CGI プログラムです。 "sqlite3" モジュールが使えるおかげで、ずいぶんとすっきりしました。
#!/usr/local/bin/python # # Access counter in a SQLite file # # $Id: acounter1.cgi,v 1.1 2009/07/05 06:58:55 noritan Exp $ import os import cgi import time import sqlite3 import cgitb # Parameters db_file = 'acount1.sqlite' # Show error as a page description. cgitb.enable() # Get a POST data. form = cgi.FieldStorage() # Collect VALUEs of a record. now = time.time() url = form.getvalue('URL', 'None') referrer = form.getvalue('HTTP_REFERER', 'None') r_addr = os.getenv('REMOTE_ADDR', 'None') r_host = os.getenv('REMOTE_HOST', 'None') # Insert a new record con = sqlite3.connect(db_file) cur = con.cursor() cur.execute( 'INSERT INTO visitor VALUES (?,?,?,?,?)', (now, url, referrer, r_addr, r_host) ) con.commit() cur.close() con.close() # Show HTTP response print """Content-type: text/javascript; charset="utf-8" document.writeln('<div>OK</div>') """
ご覧のように、 "Python" で生成した文字列を何ら変換することなく格納することが出来るので、文字をエスケープしたりする手間がかかりません。 また、SQL文が生成されるときに適切なエスケープ処理が行われるので、安全にデータベースを操作することができます。
データベースの操作は、 "Cursor.execute" メソッドによる操作指示と "Connection.commit" メソッドによる操作確定から構成されています。 この操作を確定させる機能が備わっているために、データベースの一貫性が保たれます。
実行の結果、 "OK" を表示する "JavaScript" プログラムが生成されます。
BLOGにアクセスカウンタを仕掛ける
このアクセスカウンタを仕掛ける方法も従来と同じです。 以下のような記述をHTML文書の中に仕込みます。
<script type='text/javascript' charset='utf-8' src='http://noritan.org/cgi/acounter1.js'></script>
これで、アクセス記録を作成する部分は完了です。
お試しCGI : acounter1_show_me.cgi
LOGファイル版では、テキストファイルに全てのアクセス記録が見えてしまうため、LOGファイルの場所は隠蔽してありました。 今回は、データベースを使用しているので、必要なアクセス記録だけを表示させることが出来ます。 そこで、結果を表示しようとしているクライアントと同じIPアドレスの記録だけを表示する CGI プログラムも作成しました。
#!/usr/local/bin/python # $Id: acounter1_show_me.cgi,v 1.1 2009/07/21 07:56:30 noritan Exp $ # Show my records import os import sys import cgi import urllib import time import sqlite3 import cgitb # Show error as a page description. cgitb.enable() # Database releated information db_file = "acount1.sqlite" # Get Current time now = time.time() # Get current IP address r_addr = os.getenv('REMOTE_ADDR', 'None') # Compose a SQL con = sqlite3.connect(db_file) cur = con.cursor() cur.execute( 'SELECT * FROM visitor WHERE r_addr=? ORDER BY time', (r_addr, ) ) # Show HTML header print """Content-type: text/html; charset="utf-8" <html> <body> <pre> """ # Access to the DATABASE for field in cur.fetchall(): print "DATE: %s" % time.strftime( "%Y-%m-%d (%A) %H:%M:%S", time.localtime(float(field[0])) ) print "URL : %s" % cgi.escape(field[1]) if (field[2] != 'None') : print 'HTTP_REFERER : <a href="%s">%s</a>' % ( cgi.escape(field[2]), cgi.escape(field[2]) ) print "REMOTE_ADDR : %s" % cgi.escape(field[3]) if (field[4] != 'None') : print "REMOTE_HOST : %s" % cgi.escape(field[4]) print "" # Show footer print """ </pre> </body> </html> """ cur.close() con.close()
WHERE句を使うことによって、データベースの "visitor" テーブルの中から、アクセス中の "REMOTE_ADDR" と同じ r_addr を持つ行だけを表示します。 この CGI によって、例えば、以下のような結果が得られます。
DATE: 2009-07-21 (Tuesday) 16:40:06 URL : http://noritan-micon.blog.so-net.ne.jp/ HTTP_REFERER : http://noritan.org/ REMOTE_ADDR : XXX.XXX.XXX.XXX REMOTE_HOST : XXXXXX.XXXXXX.net DATE: 2009-07-21 (Tuesday) 16:55:21 URL : http://noritan-micon.blog.so-net.ne.jp/2009-06-27 HTTP_REFERER : http://noritan-micon.blog.so-net.ne.jp/ REMOTE_ADDR : XXX.XXX.XXX.XXX REMOTE_HOST : XXXXXX.XXXXXX.net
アドレスとホスト名は、伏字にしてあります。
データベース版のプログラムを試しに仕込んでみたところ、5781行を記録したファイルは、1,421,312 バイトになりました。 一行あたり246バイトです。 データベースの構成を考えると、もっと小さく出来るはずです。 それは、今後の課題とします。
参考文献
PythonでSQLiteを使う方法は、これを参考にしました。
翻訳版もあります。
コメント 0