SSブログ

Python CGI で作るアクセスカウンタ~sqlite3モジュール編~ [プログラム三昧]このエントリーを含むはてなブックマーク#

PythonからSQLiteモジュールが直接使えるように、さくらインターネットに対応してもらいました。 詳しくは、Python 2.5.2 で、 SQLite が使えるはずだったのに。に書きました。 これで、ついに、コマンドラインを介さずにデータベースの操作をすることができます。 そこで、アクセスカウンタをデータベースに対応させてみました。

データベースの構成

アクセスカウンタで記録する情報は、「LOGファイル版」のものと同じです。 これらをテキストファイルではなく、データベースの「行」として記録していきます。

カラム名タイプ内容
timeREAL記録時刻を表す数値です。
urlTEXTアクセス先のURL (document.URL) を記録します。
referrerTEXT参照元のURL (document.referrer) を記録します。参照元が不明な場合には、 "None" が入ります。
r_addrTEXTページの表示を要求したクライアントのIPアドレス (REMOTE_ADDR) です。
r_hostTEXTクライアントが申告したホスト名 (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を使う方法は、これを参考にしました。

Python Cookbook

Python Cookbook

  • 作者:
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2005/05/05
  • メディア: ペーパーバック

翻訳版もあります。

Python クックブック 第2版

Python クックブック 第2版

  • 作者: Alex Martelli
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2007/06/26
  • メディア: 大型本

nice!(0)  コメント(0)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。