整理httpd日志中的信息
2019-05-10 Server因为没有使用Google Analytics一类的流量统计服务,所以我并不清楚自己博客的具体访问情况。
但是,通过另一途径也是可以获取到博客访问记录的,那就是分析httpd日志中的信息。
准备
- 从服务器上下载日志。
直接sftp到指定的日志文件夹中把whoisnian.com-access_log
下载下来即可。 - 选择要使用的工具。
想顺便熟悉一下SHELL操作,就直接用Linux的各种命令来处理了。
目标
先看看日志里有什么:
10.10.10.10 - - [08/May/2019:17:17:33 +0800] "GET / HTTP/1.1" 200 9584
我的日志里每一行都是这样的格式,在Apache官方文档中被叫做Common Log Format (CLF)
,具体含义为:
远端主机地址 远端登录名 远程用户名 时间 请求的第一行 状态码 传送的不包含HTTP头的字节数
也就是说,从日志中可以获取到的有用信息包括访问者的IP,访问时间,请求的具体网址,请求用的协议,服务器响应码。所以统计以下几点是可行的:
- 单个IP的访问次数排行
- 具体的网页访问量排行
- 访问者IP分布图
可惜之前服务器的一次滚挂,导致服务器上的日志只有2018年9月份之后的,猜测最终得到的结果会比较寒碜。😭
筛选
直接打开日志文件,约十四万行,但很明显,里面有许多无效请求。例如:
61.176.222.170 - - [20/Sep/2018:04:49:42 +0800] "GET http://47.99.121.32:39169/Ip/Up?Ip=45.77.221.110&Port=80&Check=30&Order=61.176.222.170 HTTP/1.1" 404 1010
193.112.137.18 - - [20/Sep/2018:06:17:47 +0800] "POST /cainiao.php HTTP/1.1" 404 16
221.231.6.240 - - [26/Sep/2018:03:32:52 +0800] "GET /Web/FCKeditor/fckeditor.js HTTP/1.1" 404 1119
122.114.90.3 - - [26/Sep/2018:21:59:09 +0800] "POST //plus/moon.php HTTP/1.1" 301 243
173.212.192.252 - - [27/Sep/2018:10:49:11 +0800] "GET /scripts/setup.php HTTP/1.1" 301 247
明显是各种自动化扫描工具的请求,试了一下cat whoisnian.com-access_log | grep '\.php' | wc -l
,emmmmmm,36398行带.php
的,推测扫描工具的访问就占了日志三分之一以上。
接下来根据我的链接格式,我只保留了对博客文章的访问且服务器返回非404/403状态码的日志,即:
cat whoisnian.com-access_log | grep -oP '.* - - .* \+0800] "GET /[0-9]{4}/[0-9]{2}/[0-9]{2}/.*" (?!404|403).*' > log1
好嘛,原来十多万行的日志就剩7624行了,可太真实了。
但是发现其中部分IP地址会在几十秒的间隔内发送两次内容相同的请求,再次进行合并,即:
cat log1 | awk '{ print $4,$5,$1,$2,$3,$6,$8,$9,$10,$7 }' | sed 's/\/$//g' | uniq -f 2 | awk '{ print $3,$4,$5,$1,$2,$6,$10,$7,$8,$9 }' > log2
先就这样进行一次初步筛选吧,还剩下7430行。理想情况下还可以只保留同时请求了css文件和博客文章的日志记录,应该可以进一步排除一部分爬虫,但是我怕日志那样筛就没多少剩的了。。。
统计
单个IP的访问次数排行:
cat log2 | awk '{ print $1 }' | sort | uniq -idc | sort -k1nr | head -n 15
将文件log2的内容每行只保留IP,排序合并,再按合并得到的次数降序,输出前15行,结果为:
次数 | IP地址 |
---|---|
221 | 5.45.207.10 |
169 | 66.249.70.30 |
161 | 66.249.70.28 |
160 | 66.249.66.81 |
153 | 66.249.70.3 |
121 | 66.249.66.80 |
111 | 66.249.66.79 |
108 | 111.202.100.130 |
103 | 23.100.232.xxx |
83 | 78.46.161.xxx |
72 | 159.65.177.xxx |
61 | 66.249.66.62 |
60 | 5.45.207.44 |
59 | 66.249.66.60 |
59 | 66.249.66.83 |
emmmmmm,nslookup 5.45.207.10
可以看到name = 5-45-207-10.spider.yandex.com.
,同理,66.249.*.*
是google的,111.202.100.130
是sougou的,访问次数最多的果然是爬虫。
具体的网页访问量排行:
cat log2 | awk '{ print $1,$7 }' | sort | uniq | awk '{ print $2 }' | sort | uniq -dc | sort -k1nr | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf "%b" | head -n 15
将文件log2的内容每行只保留IP和访问的链接,排序合并,即一个IP访问一篇文章只计算一次,再将结果只保留文章链接,排序合并,最后按合并得到的次数降序,输出前15行,结果为:
访问者IP分布:
想要像各种流量统计服务那样生成一个访客地图,在网上搜到了好多都是让使用它的统计服务,它给你自动生成的,自己根据收集到的信息制作访客地图的相关内容却很少。
问了一下熟悉前端的同学,同学推荐了ECharts,看了一下官网给出的样例很不错,于是照着改了一个。那么接下来就需要将IP地址转换成地区,再改成{name: 'xxxxx',value: xxx}
的格式。
先尝试了geoiplookup
,发现部分IP地址识别不出来,还有部分“亚太地区”这样指向不明显的结果。查看/usr/share/GeoIP/
目录下的数据库,发现除了geoiplookup
使用的.dat
之外还有另一种.mmdb
,就又在源里找到了mmdblookup
这个工具,试用了一下,感觉效果还可以,于是用了这个。
将文件log2的内容只保留IP地址,并将相同的IP合并:
cat log2 | awk '{ print $1 }' | sort | uniq > log3
然后使用脚本按行处理:./geo.sh log3 > log4
,脚本如下,注意要手动指定mmdb文件的位置。
#!/bin/bash
# geo.sh
while read line; do
country=`mmdblookup -f /usr/share/GeoIP/GeoLite2-Country.mmdb -i "$line" country names en 2>/dev/null`
if [ $? -ne 0 ]; then
country=`mmdblookup -f /usr/share/GeoIP/GeoLite2-Country.mmdb -i "$line" registered_country names en 2>/dev/null`
fi
echo $country | awk -F '"' '{ print $2 }'
done < $1
得到的列表还需要修改,因为ECharts
中使用的地图需要以国家为划分,而mmdblookup
直接得到列表是以地区为划分的,两者之间还有部分名称不一致的情况,修改如下:
sed -i 's/^Hong\ Kong$/China/g;
s/^Macao$/China/g;
s/^Taiwan$/China/g;
s/^South\ Korea$/Korea/g;
s/^Czechia$/Czech\ Rep\./;' log4
将得到的国家列表排序合并,并转换为ECharts
需要的形式:
cat log4 | sort | uniq -c | sort -k1nr | awk '{printf"{name: '\''%s", $2; for(i=3;i<=NF;i++){printf" %s", $i}; printf"'\'',value: %s},\n", $1;}'
得到:
{name: 'China',value: 1397},
{name: 'United States',value: 720},
{name: 'France',value: 77},
{name: 'Japan',value: 35},
{name: 'Russia',value: 28},
{name: 'Czech Rep.',value: 24},
{name: 'Canada',value: 16},
{name: 'Germany',value: 16},
{name: 'Singapore',value: 11},
{name: 'United Kingdom',value: 7},
{name: 'Malaysia',value: 6},
{name: 'Vietnam',value: 6},
{name: 'Ukraine',value: 5},
{name: 'Netherlands',value: 4},
{name: 'Australia',value: 3},
{name: 'India',value: 3},
{name: 'Cambodia',value: 2},
{name: 'Korea',value: 2},
{name: 'Poland',value: 2},
{name: 'Thailand',value: 2},
{name: 'Algeria',value: 1},
{name: 'Hungary',value: 1},
{name: 'Italy',value: 1},
{name: 'Malawi',value: 1},
{name: 'Norway',value: 1},
{name: 'Philippines',value: 1},
{name: 'Slovakia',value: 1},
{name: 'Switzerland',value: 1},
将数据填入改写的ECharts
样例中,得到:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
<script src="https://cdn.bootcss.com/echarts/4.2.1/echarts.min.js"></script>
<script src="https://echarts-maps.github.io/echarts-countries-js/echarts-countries-js/world.js"></script>
</head>
<body>
<div id="main" style="width:900px;height:500px;"></div>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: '访客地图'
},
tooltip: {
trigger: 'item'
},
visualMap: {
min: 0,
max: 300,
left: 'left',
top: 'bottom',
text:['多','少']
},
toolbox: {
show: true,
orient : 'horizontal',
left: 'right',
top: 'bottom',
showTitle: false,
feature : {
restore : {show: true},
saveAsImage : {show: true}
}
},
series : [
{
name: 'visitors',
type: 'map',
mapType: 'world',
roam: true,
scaleLimit: {
min: 1,
max: 10
},
data:[
{name: 'China',value: 1397},
{name: 'United States',value: 720},
{name: 'France',value: 77},
{name: 'Japan',value: 35},
{name: 'Russia',value: 28},
{name: 'Czech Rep.',value: 24},
{name: 'Canada',value: 16},
{name: 'Germany',value: 16},
{name: 'Singapore',value: 11},
{name: 'United Kingdom',value: 7},
{name: 'Malaysia',value: 6},
{name: 'Vietnam',value: 6},
{name: 'Ukraine',value: 5},
{name: 'Netherlands',value: 4},
{name: 'Australia',value: 3},
{name: 'India',value: 3},
{name: 'Cambodia',value: 2},
{name: 'Korea',value: 2},
{name: 'Poland',value: 2},
{name: 'Thailand',value: 2},
{name: 'Algeria',value: 1},
{name: 'Hungary',value: 1},
{name: 'Italy',value: 1},
{name: 'Malawi',value: 1},
{name: 'Norway',value: 1},
{name: 'Philippines',value: 1},
{name: 'Slovakia',value: 1},
{name: 'Switzerland',value: 1}
]
}
]
};
myChart.setOption(option);
</script>
</body>
</html>
用浏览器打开就可以看到访客地图,还可以点击保存按钮直接保存为png。