想知道网站每天的访问量,都有哪些人访问,都是来自什么地方的访客,都访问了哪些端点。
编写 models.py
# 访问网站的 ip 地址、端点和次数
class UserIP(models.Model):
ip = models.CharField(verbose_name='IP 地址', max_length=30)
ip_addr = models.CharField(verbose_name='IP 地理位置', max_length=30)
end_point = models.CharField(verbose_name='访问端点', default='/', max_length=30)
count = models.IntegerField(verbose_name='访问次数', default=0)
class Meta:
verbose_name = '访问用户信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.ip
# 网站总访问次数
class VisitNumber(models.Model):
count = models.IntegerField(verbose_name='网站访问总次数', default=0) # 网站访问总次数
class Meta:
verbose_name = '网站访问总次数'
verbose_name_plural = verbose_name
def __str__(self):
return str(self.count)
# 单日访问量统计
class DayNumber(models.Model):
day = models.DateField(verbose_name='日期', default=timezone.now)
count = models.IntegerField(verbose_name='网站访问次数', default=0) # 网站访问总次数
class Meta:
verbose_name = '网站日访问量统计'
verbose_name_plural = verbose_name
def __str__(self):
return str(self.day)
使用 geoip2 实现 IP 转换成现实地理位置
安装 geoip2 库
pip install geoip2
下载 Maxmind 网站的 IP 信息库
地址:GeoLite2 Free Geolocation Data | MaxMind Developer Portal
下载完之后,解压,将解压包中的 mmdb 文件复制到项目的应用程序中
下载个GeoLite2 City使用。
然后我们的python代码如下:
#!/usr/bin/python
#-*-coding:utf-8-*-
import geoip2.database
reader = geoip2.database.Reader('./GeoLite2-City.mmdb')
ip = raw_input("输入你要查询的IP:\n")
response = reader.city(ip)
# 有多种语言,我们这里主要输出英文和中文
print("你查询的IP的地理位置是:")
print("地区:{}({})".format(response.continent.names["es"],
response.continent.names["zh-CN"]))
print("国家:{}({}) ,简称:{}".format(response.country.name,
response.country.names["zh-CN"],
response.country.iso_code))
print("洲/省:{}({})".format(response.subdivisions.most_specific.name,
response.subdivisions.most_specific.names["zh-CN"]))
print("城市:{}({})".format(response.city.name,
response.city.names["zh-CN"]))
print("经度:{},纬度{}".format(response.location.longitude,
response.location.latitude))
print("时区:{}".format(response.location.time_zone))
print("邮编:{}".format(response.postal.code))
试一试
(venv) allenwoo@~/renren/code$ python test.py
输入你要查询的IP:
112.74.207.96
你查询的IP的地理位置是:
地区:Asia(亚洲)
国家:China(中国) ,简称:CN
洲/省:Zhejiang(浙江省)
城市:Hangzhou(杭州)
经度:120.1614,纬度30.2936
时区:Asia/Shanghai
邮编:None
再来一个:
(venv) allenwoo@~/renren/code$ python test.py
输入你要查询的IP:
223.192.2.165
你查询的IP的地理位置是:
地区:Asia(亚洲)
国家:China(中国) ,简称:CN
洲/省:Beijing(北京市)
城市:Beijing(北京)
经度:116.3883,纬度39.9289
时区:Asia/Shanghai
邮编:None
2.至于在线库的使用是需要一个license_key
只有前面两步不太一样
连接: client = geoip2.webservice.Client(42, <license_key>)
查询 IP: response = client.insights(<IP>)
编写将 IP 转换为现实地理位置的函数
ip_convert_addr.py
import geoip2.database
def ip_to_addr(ip):
"""
IP 转换成现实中的地理位置
country = 国家
province = 省
city = 城市
"""
reader = geoip2.database.Reader('blog/GeoLite2-City.mmdb')
response = reader.city(ip)
#print(response)
# 因为有些IP的省份和城市未知,所以设置默认为空
province = ''
city = ''
try:
# 国家、省份、城市
country = response.country.names["zh-CN"]
province = response.subdivisions.most_specific.names["zh-CN"]
city = response.city.names["zh-CN"]
except:
pass
if country != '中国':
return country
if province and city:
if province == city or city in province:
return province
return '%s%s' %(province, city)
elif province and not city:
return province
else:
return country
编写 visit_info.py
实现更新网站访问量和访问 ip 等信息
from django.utils import timezone
from .models import UserIP, VisitNumber, DayNumber
from .ip_convert_addr import ip_to_addr
# 自定义的函数,不是视图
def change_info(request, end_point):
"""
# 修改网站访问量和访问 ip 等信息
# 每一次访问,网站总访问次数加一
"""
count_nums = VisitNumber.objects.filter(id=1)
if count_nums:
count_nums = count_nums[0]
count_nums.count += 1
else:
count_nums = VisitNumber()
count_nums.count = 1
count_nums.save()
# 记录访问 ip 和每个 ip 的次数
if 'HTTP_X_FORWARDED_FOR' in request.META: # 获取 ip
client_ip = request.META['HTTP_X_FORWARDED_FOR']
client_ip = client_ip.split(",")[0] # 所以这里是真实的 ip
else:
client_ip = request.META['REMOTE_ADDR'] # 这里获得代理 ip
# print(client_ip)
ip_exist = UserIP.objects.filter(ip=str(client_ip), end_point=end_point)
if ip_exist: # 判断是否存在该 ip
uobj = ip_exist[0]
uobj.count += 1
else:
uobj = UserIP()
uobj.ip = client_ip
uobj.end_point = end_point
try:
uobj.ip_addr = ip_to_addr(client_ip)
except:
uobj.ip_addr = 'unknown'
uobj.count = 1
uobj.save()
# 增加今日访问次数
date = timezone.now().date()
today = DayNumber.objects.filter(day=date)
if today:
temp = today[0]
temp.count += 1
else:
temp = DayNumber()
temp.dayTime = date
temp.count = 1
temp.save()
将上面的函数导入到视图中,在需要时直接调用
from .visit_info import change_info
...
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
change_info(request, '/')
# 记得在顶部引入 markdown 模块
post.body = markdown.markdown(post.body, extensions=['extra', 'codehilite', 'toc',
'tables', 'sane_lists'])
return render(request, 'post_detail.html', context={'post': post})
Django 数据库抽象 API
描述了如何创建、检索、更新和删除独立的对象。但是,有时你会需要处理一些有关对象的集合的统计。本文描述如何使用 Django 查询来处理统计。
本文我们将使用以下模型。这些模型用于在线书店图书清单:
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
friends = models.ManyToManyField('self', blank=True)
class Publisher(models.Model):
name = models.CharField(max_length=300)
num_awards = models.IntegerField()
class Book(models.Model):
isbn = models.CharField(max_length=9)
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
rating = models.FloatField()
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
pubdate = models.DateField()
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
生成整个查询集的统计
Django 提供两种方法来产生统计。
第一种方法是产生整个 查询集 的统计。假设我们要统计所有书的平均价格。 Djnago 中查询所有书的语句为:
>>> Book.objects.all()
在这个语句后加上一个 aggregate() 子句就行了:
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
上例中的 all() 是多余的。所以可以简写为:
>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}
aggregate() 子句的参数代表我们要统计的内容,本例中我们要统计 Book 模型中 price 字段的平均值。
aggregate() 是一个 查询集 的未端子句,调用后会返回一个由名称-值配对组成的字典。名称是指统计的名称,值就是统计的值。名称由字段名称配双下划线加上函数名自动组成。如果你想手动指定统计名称,可以象下例在统计子句中定义:
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
如果你想要生成多个统计,那么只要在统计子句后加上另外的统计子句就可以了。例如,如果要计算所有书价中最高价和最低价,可以这样写:
>>> from django.db.models import Avg, Max, Min, Count, Sum
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
生成查询集中每一个项目的统计
第二种方法是为 查询集 中每个独立的对象生成统计。例如,当你检索一个书单时,可能想知道每本书有几个作者。每本书与每个作者之间是一个多对多的关系,我们要为每本书总结这个关系。
要产生每个对象的统计可以使用 annotate() 子句。当定义一个 annotate() 子句后, 查询集 中的每个对象就可以与特定值关联,相当于每个对象有一个 “注释”。
这种注释的语法与 aggregate() 相同。 annotate() 的每个参数代表一个统计。例如,要计算每本书的作者人数:
生成查询集中每一个项目的统计
第二种方法是为 查询集 中每个独立的对象生成统计。例如,当你检索一个书单时,可能想知道每本书有几个作者。每本书与每个作者之间是一个多对多的关系,我们要为每本书总结这个关系。
要产生每个对象的统计可以使用 annotate() 子句。当定义一个 annotate() 子句后, 查询集 中的每个对象就可以与特定值关联,相当于每个对象有一个 “注释”。
这种注释的语法与 aggregate() 相同。 annotate() 的每个参数代表一个统计。例如,要计算每本书的作者人数:
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
联合的深度是无限的。例如,要统计所有书的作者的最小年龄,你可以这样:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
统计和其他查询子句
filter() 和 exclude()
在过滤器中也可以使用统计。任何用于一般模型的 filter() (或 exclude() )也可与统计联用。
当与 annotate() 子句联用时,过滤器作用于被统计的对象上。例如要统计书名以 "Django" 开头的书:
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
当与 aggregate() 子句联用时,过滤器作用于被统计的所有对象上。例如要统计书名以 "Django" 开头的书的平均价格:
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
过滤统计的值
统计出来的值也可以被过滤。和其他模型一样,统计的结果也可以使用 filter() 和 exclude() 子句来过滤。
例如,要产生一个由两个以上作者的书单可以这样:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
上例先进行统计,然后在统计的结果上使用了过滤器。
annotate() 和 filter() 子句的顺序
当使用同时包含 annotate() 和 filter() 子句的复杂查询时,要特别小心两种子句的顺序。
当一个 annotate() 子句作用于查询时,该统计只对子句之前的查询起作用。也就是说 filter() 和 annotate() 顺序不同,查询就不同了。查询:
>>> Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
和查询:
>>> Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
是不同的。两个查询都会返回至少有一本好书(评分大于 3.0 )的出版商。但是,第一个查询中的统计会提供出版商的所有书的数量;第二个查询中的统计只返回好书的数量。第一个查询中统计先于过滤器,所以过滤器对统计没有作用。而第二个查询过滤器先于统计,所以统计的对象是已经过滤过的。
order_by()
统计可以作为排序的基础。当你定义一个 order_by 子句时,可以引用 annotate() 子句中的统计。
例如,要依据书的作者人数进行排序,可以这样:
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
通常,统计会针对 查询集 中每一个对象返回一个结果。但是,当使用 values 子句来约束要统计的列时,返回的结果会有所不同。原先统计结果中,统计字段的值相同的项会分组合并统计。
例如,要统计每个作者各自所写的书的平均评分:
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
返回的结果会包含每一个作者及其所写的书的平均计分。
但是,如果使用 values() 子句,返回的结果会有所不同:
Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
这个例子中会把作者按名字分组统计,返回的结果中不会有重复的作者名字。名字相同的作者在统计中会作为同一个作者来统计,同名作者所写的书的评分会合并为一个作者的书来统计。
annotate() 和 values() 子句的顺序
当使用 filter() 子句时, annotate() 和 values() 子句的顺序是非常重要的。如果 values() 子句先于 annotate() 子句,会按照前文所述的方式统计。
但是,如果 annotate() 子句先于 values() 子句,那么统计会作用于整个查询集,而 values() 子句只约束统计输出的字段。
例如,如果我们把前一个例子中的 values() 和 annotate() 子句调换顺序:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
这个例子会为每一个作者生成唯一的结果。但是在输了的数据中只会包含作者名和 average_rating 的统计。
你可以注意到 average_rating 在例子中显示地定义了。在 annotate() 和 values() 子句的顺序处于这种情况是必须显式定义。
如果 values() 子句先于 annotate() 子句,那么任何统计会自动添加到输出结果中。但是 values() 子句在 annotate() 子句之后,那么必须显式定义统计列。
缺省排序或 order_by() 子句的副作用
一个查询集中 order_by() 子句中的字段(或一个模型中缺省排序字段)会对输了数据产生影响,即使在 values() 中没有这些字段的定义时也同样会影响。这些特殊的字段会影响统计结果,这种情况在计数统计时尤为明显。
假设有一个这样的模型:
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]
Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
这个例子中会把作者按名字分组统计,返回的结果中不会有重复的作者名字。名字相同的作者在统计中会作为同一个作者来统计,同名作者所写的书的评分会合并为一个作者的书来统计。
annotate() 和 values() 子句的顺序
当使用 filter() 子句时, annotate() 和 values() 子句的顺序是非常重要的。如果 values() 子句先于 annotate() 子句,会按照前文所述的方式统计。
但是,如果 annotate() 子句先于 values() 子句,那么统计会作用于整个查询集,而 values() 子句只约束统计输出的字段。
例如,如果我们把前一个例子中的 values() 和 annotate() 子句调换顺序:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
这个例子会为每一个作者生成唯一的结果。但是在输了的数据中只会包含作者名和 average_rating 的统计。
你可以注意到 average_rating 在例子中显示地定义了。在 annotate() 和 values() 子句的顺序处于这种情况是必须显式定义。
如果 values() 子句先于 annotate() 子句,那么任何统计会自动添加到输出结果中。但是 values() 子句在 annotate() 子句之后,那么必须显式定义统计列。
缺省排序或 order_by() 子句的副作用
一个查询集中 order_by() 子句中的字段(或一个模型中缺省排序字段)会对输了数据产生影响,即使在 values() 中没有这些字段的定义时也同样会影响。这些特殊的字段会影响统计结果,这种情况在计数统计时尤为明显。
假设有一个这样的模型:
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]