Пишем свою статистику посещения на Python
Categories: Python on Sep.04, 2008
И так, продолжаем играться с питоном и попробуем написать статистику посещений для сайта. Для реализации воспользуемся связкой python и sqlite.
Я постараюсь рассказать про мой подход к написанию статистики для одного своего сайта написанного на питоне.
Создаем базу для статистики с одной единственной таблицей.
setup.py
1 2 3 4 5 6 7 8 9 10 11 | # -*- coding: utf-8 -*- import sqlite3 connection = sqlite3.connect('statistics.db') cursor = connection.cursor() cursor.execute('CREATE TABLE daily (id INTEGER PRIMARY KEY, time, uri, referer, user_agent, ip)') cursor.close() connection.close() |
Для сбора статистики будем помещать в базу информацию о каждом запросе. У меня все запросы проходят через файл core.py так что в начале этого файла пишем код такого вида
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | core.py # -*- coding: utf-8 -*- import sqlite3 import time connection = sqlite3.connect('statistics.db') cursor = connection.cursor() cursor.execute('INSERT INTO daily (time, uri, referer, user_agent, ip) VALUES (%s, %s, %s, %s, %s)' % (time.time(), uri, refer, user_agent, ip)) cursor.close() connection.close() |
Отвлечемся немного на sqlite. Модуль sqlite3 реализует работу с базой через DB-API 2.0. При таком подходе стандартная работа с базой выглядит так
connection = sqlite3.connect(‘файл БД’) — соединяемся с базой
cursor = connection.cursor() — получаем курсор, через него в дальнейшем выполняем все запросы
cursor.execute(запрос) — выполняем запрос
cursor.fetchone() — получить одну строку запроса
cursor.fetchall() — получить все строки запроса
connection.close() — закрываем соединение
В sqlite3 есть одна магическая команда, позволяющая обращаться к результатам выборки не только по индексу, но и по имени
1 | connection.row_factory = sqlite3.Row |
Теперь займемся самым легким, выбором из базы необходимых данных (мой подход возможно не самый оптимальный, так как вся нагрузка ложится на выборки, но все же)
Создадим класс для статистики.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | # -*- coding: utf-8 -*- import sqlite3 from parser import Parser class Stats(object): def __init__(self, db): self.__connection = sqlite3.connect(db) self.__connection.row_factory = sqlite3.Row self.__cursor = self.__connection.cursor() self.__parser = Parser() self.__connection.create_function('truncate_time', 1, self.__parser.truncate_time) self.__connection.create_function('parse_os_type', 1, self.__parser.parse_os_type) self.__connection.create_function('parse_os_version', 1, self.__parser.parse_os_version) self.__connection.create_function('parse_browser_type', 1, self.__parser.parse_browser_type) self.__connection.create_function('parse_browser_version', 1, self.__parser.parse_browser_version) def __del__(self): self.__connection.close() def get_rows(self, query): self.__cursor.execute(query) return self.__cursor.fetchall() def get_row(self, query): self.__cursor.execute(query) return self.__cursor.fetchone() def get_row_count(self, query): self.__cursor.execute(query) count = 0 for row in self.__cursor.fetchall(): count = count + 1 return count def os_type(self): return self.get_rows('SELECT COUNT(id) count, parse_os_type(user_agent) os FROM daily GROUP BY os ORDER BY count DESC') def os_version(self): return self.get_rows('SELECT COUNT(id) count, parse_os_type(user_agent) os, parse_os_version(user_agent) version FROM daily GROUP BY os, version ORDER BY count DESC') def browser_type(self): return self.get_rows('SELECT COUNT(id) count, parse_browser_type(user_agent) browser FROM daily GROUP BY browser ORDER BY count DESC') def browser_version(self): return self.get_rows('SELECT COUNT(id) count, parse_browser_type(user_agent) browser, parse_browser_version(user_agent) version FROM daily GROUP BY browser, version ORDER BY count DESC') def daily_stats(self): return self.get_rows('SELECT COUNT(id) count, truncate_time(time) date FROM daily GROUP BY date ORDER BY date DESC') def page_visited(self): return self.get_rows('SELECT COUNT(id) count, uri FROM daily GROUP BY uri ORDER BY uri') def all_unique(self): return self.get_row_count('SELECT ip, truncate_time(time) date FROM daily GROUP BY ip, date') def unique_for_date(self, date): return self.get_row_count('SELECT ip, truncate_time(time) date FROM daily WHERE date = «%s» GROUP BY ip, date' % date) def unique_for_period(self, begin, end): return self.get_row_count('SELECT ip, truncate_time(time) date FROM daily WHERE date BETWEEN «%s» AND «%s» GROUP BY ip, date' % (begin, end)) def count(self): result = self.get_row('SELECT COUNT(id) count FROM daily') return result[0] def get_all(self): return self.get_rows('SELECT * FROM daily') |
Класс Parser мы рассмотрим поздней, в нем реализованы методы для обработки запросов.
В примере я реализовал следующие функции:
truncate_time — переводим тип timestamp к формату dd.mm.yyyy
parse_os_type — выбираем из поля user_agent типы ОС
parse_os_version — выбираем из поля user_agent версии ОС
parse_browser_type — выбираем из поля user_agent типы браузеров
parse_browser_version — выбираем из поля user_agent версии браузеров
Функция create_function делает доступными методы питона в запросах sqlite, для выборки статистики будем активно пользоваться этой возможностью.
Нам надо зарегистрировать методы класса как функции в sqlite
1 2 3 4 5 | create_function('truncate_time', 1, self.__parser.truncate_time) create_function('parse_os_type', 1, self.__parser.parse_os_type) create_function('parse_os_version', 1, self.__parser.parse_os_version) create_function('parse_browser_type', 1, self.__parser.parse_browser_type) create_function('parse_browser_version', 1, self.__parser.parse_browser_version) |
Методы класса
get_rows, get_row, get_row_count — спомогательные методы
os_type — получаем типы ОС
os_version — типы ОС вместе с версиями
browser_type — типы браузеров
browser_version — типы и версии браузеров
daily_stats — количество хитов по дням
page_visited — посещенные страницы и количество хитов
all_unique, unique_for_date, unique_for_period — все уникальные посетители, на дату и за период
count — общее количество хитов
get_all — все записи
Ну а теперь сам парсер. Приведу сразу полный код класса, думаю там ничего сложного нет. При вызове метода в него передается поле из базы и обрабатывается.
# -*- coding: utf-8 -*-
import datetime
import string
class Parser(object):
def parse_os_type(self, user_agent):
user_agent = user_agent.lower()
if 'windows' in user_agent:
return 'windows'
if 'linux' in user_agent:
return 'linux'
if 'macintosh' in user_agent:
return 'mac'
if 'freebsd' in user_agent:
return 'bsd'
return 'unknown'
def parse_os_version(self, user_agent):
user_agent = user_agent.lower()
if 'windows' in user_agent:
if 'windows nt 6.0' in user_agent:
return 'vista'
if 'windows nt 5.2' in user_agent:
return '2003'
if 'windows nt 5.1' in user_agent:
return 'xp'
if 'windows nt 5.0' in user_agent:
return '2000'
if 'windows nt 4.0' in user_agent:
return 'nt'
if 'windows 98' in user_agent:
return '98'
if 'windows 95' in user_agent:
return '95'
if 'macintosh' in user_agent:
start = user_agent.find('mac os x') + 8
return string.replace(user_agent[start: start + 7], '_', '.')
return 'unknown'
def parse_browser_type(self, user_agent):
user_agent = user_agent.lower()
if 'firefox' in user_agent:
return 'firefox'
if 'opera' in user_agent:
return 'opera'
if 'safari' in user_agent:
return 'safari'
if 'msie' in user_agent:
return 'ie'
return 'unknown'
def parse_browser_version(self, user_agent):
user_agent = user_agent.lower()
if 'firefox' in user_agent:
start = user_agent.find('firefox/') + 8
return user_agent[start: start + 3]
if 'opera' in user_agent:
start = user_agent.find('opera') + 5
return user_agent[start: start + 4]
if 'safari' in user_agent:
start = user_agent.find('version/') + 8
return user_agent[start: start + 4]
if 'msie' in user_agent:
start = user_agent.find('msie') + 4
return user_agent[start: start + 4]
return 'unknown'
def truncate_time(self, time):
date = datetime.datetime
return date.fromtimestamp(time).strftime('%d.%m.%Y')
Пример использования
1 | statex = Stats(<span style="color: #a31515;">'statistics.db'</span>) |
for row in statex.browser_type():
print '%s %s' % (row['browser'], row['count'])
for row in statex.os_type():
print '%s %s' % (row['os'], row['count'])
У меня в проекте это выглядит примерно так.
Выполняю запрос и передаю его в шаблон:
statex = Stats('statistics.db')
total = statex.count()
if (re.match(r'^(.*)/os(\/*)$', req.uri)):
os_types = []
for row in statex.os_type():
os_types.append({'name': row['os'], 'count': row['count'], 'percent': calc_percent(total, row['count'])})
os_versions = []
for row in statex.os_version():
os_versions.append({'name': row['os'], 'version': row['version'], 'count': row['count'], 'percent': calc_percent(total, row['count'])})
template = Template(filename=path + '/templates/os.tpl', format_exceptions=True)
req.content_type = 'text/html'
req.write(template.render(os_types=os_types, os_versions=os_versions, fetch_time=round(time.time() — start, 3)))
В шаблоне рисую табличку
<h1>OS</h1>
</p>
% for row in os_types:
${row['name']} ${row['count']} ${row['percent']}</br>
% endfor
<h1>OS Versions</h1>
% for row in os_versions:
${row['name']} ${row['version']} ${row['count']} ${row['percent']}</br>
% endfor
</p>
Fetch time: ${fetch_time}
Все. Дорабатывайте парсер и запросы под свои нужды.
P.S. Да бы меня не уличие в велосипедостроении, прошу считать данный пост примером к использованию sqlite3 и python :)
Similar posts:


Оставить отзыв