还记得这篇文章吗?一年前给博客做优化时使用到了 TpCache 这个插件,作者是 Phpgao;想来当时为了避免缓存会影响到文章阅读数统计,故而关掉了 缓存 页面/文章 的选项;

photo_2020-12-12_17-05-42.jpg

via 春潮频道;

TpCache for typecho & Redis

I. 主理人序

本文的好处及麻烦,让 MySQL/PHP 不再受伤,让缓存好好扮演缓存;麻烦就是以下三个脚本写的不是很好,但逻辑上而言,还是很自洽的;

II. 思路

页面缓存的意义在于减少后端的读写,从而减轻服务器压力;这就意味着,页面被缓存后你再怎么刷新页面,都不会再有新的数据写入后端(如MySQL的数据库);即只会有 GET 请求,不会再有 POST 请求(参阅 GET 与 POST 的区别);

所以,此时我们应该将视线转移到 nginx 上来,access.log;lnmp,MySQL/PHP都唔得救了,那就从 nginx 上找方法吧,是的 还是会有日志的,用户(简单来说,一个用户就是一个IP地址,计为一个UV,刷次5次页面则计为5个PV)每一次浏览都会留下证据,从哪里来,访问了哪些页面等等;

我们要通过 access.log 统计一定时间范围内(泛指一天),访问的页面、对应的UV数,并将UV数与之前的页面浏览量加在一起,然后写入 MySQL后端数据(怎么写、都有包括哪些字段在后面会详细说明);

P.S. 博主的 TPCache 设置的 24小时清除一次缓存;另外,分析、抓取符合一定条件的日志时会使用到正则表达式(grep),希望大家对正则表达式有一定的了解;还有就是 MySQL 的一些命令,如查询、更新;嗯,以及 Crontab 定时脚本的使用;

不过大家可以放心,本文将会非常通俗易懂,操作起来逻辑性很强;

III. access.log 日志

鉴于过去一直潜在的DDoS/CC攻击,nginx 一直是有开启日志功能的,以便使用脚本统计一定时间范围内可能异常的IP访问及访问次数、访问页面等(他们啊刷我搜索,我就把搜索换成了谷歌自定义搜索),打了个寂寞;

Cloudlfare 下 nginx 获取用户真实IP地址

你们套了 Cloudflare CDN 吗?套了的话 参考下 Cloudflare 下 Nginx 获取用户真实IP 地址 这篇文章;

acess.log 日志切割

博主采用的是 lnmp.org 提供的日志切割脚本,参考:nginx日志切割脚本使用方法

#!/bin/bash
#function:cut nginx log files for lnmp v0.5 and v0.6
#author: http://lnmp.org

#set the path to nginx log files
log_files_path="/home/wwwlogs/limbopro.xyz/"
log_files_dir=${log_files_path}$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")
#set nginx log files you want to cut
log_files_name=(access)
#set the path to nginx.
nginx_sbin="/usr/local/nginx/sbin/nginx"
#Set how long you want to save
save_days=30

############################################
#Please do not modify the following script #
############################################
mkdir -p $log_files_dir

log_files_num=${#log_files_name[@]}

#cut nginx log files
for((i=0;i<$log_files_num;i++));do
mv ${log_files_path}${log_files_name[i]}.log ${log_files_dir}/${log_files_name[i]}_$(date -d "yesterday" +"%Y%m%d").log
done

#delete 30 days ago nginx log files
find $log_files_path -mtime +$save_days -exec rm -rf {} \; 
$nginx_sbin -s reload

IV. 脚本概括及可能会遇到的挫折

一共会使用到三个脚本:分别依次执行,archives.count.sh(分析 access.log、统计数据;计为新增,add)、archives.slug.sh(导出原始数据库数据,计为原始,origin)、archives.count.all.sh(原始与新增进行加和,计为全部,all,并写入MySQL);

脚本中涉及的路径请根据需求修改为自己服务器下的路径正则表达式未必适用于你的 access.log 格式(默认的话一般都适用);

V. 分析、抓取日志以获取文章新增阅读数

抓取当天符合一定条件下的日志记录;

1.机器人访问的日志丢弃;
2.同页面同IP多次访问去重只取一次访问的值;
3.排除特定的请求方法如head/post等非get请求;
4.IPv4/IPv6地址的过滤;
5.正确的使用到正则表达式(grep)点此学习

slug.png

以下脚本命名为:archives.count.sh;

#!/bin/bash 
#清除上次执行脚本时产生的数据;
rm /home/wwwlogs/limbopro.xyz/log/*; #清除 浏览某篇文章的IP;
> /home/wwwlogs/limbopro.xyz/access.archives; #清除 去重的slug字段值;
> /home/wwwlogs/limbopro.xyz/access.archives.filter; #清除 去重的slug字段值;
> /home/wwwlogs/limbopro.xyz/archives.id; #清除 去重的slug字段值;
> /home/wwwlogs/limbopro.xyz/archives.id.count.origin; #清除 从MySQL 读取的文章原始阅读数;
> /home/wwwlogs/limbopro.xyz/archives.id.count.add; #清除 当日文章新增的阅读数数据;
> /home/wwwlogs/limbopro.xyz/archives.id.count.all; #清除 原始阅读数与新增阅读数之和;

#过滤出文章slug字段并存入 access.archives 文本下;
grep -o -P "(?:(?<=/)|(?<=archives/))(\w{1,50}|(\w{1,50}-){1,10}\w{1,50})(?=\.html\s.*?200)" /home/wwwlogs/limbopro.xyz/access.log >> /home/wwwlogs/limbopro.xyz/access.archives

#对 access.archives 内slug字段进行去重;
cat /home/wwwlogs/limbopro.xyz/access.archives | sort | uniq >> /home/wwwlogs/limbopro.xyz/access.archives.filter

#根据slug字段对IP数进行统计;
IPADDR=$(</home/wwwlogs/limbopro.xyz/access.archives.filter)
for IPADDR in ${IPADDR[@]}; do

grep -o -P "((\w{1,3}\.){3}\w{1,3}|((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))))(?=\b.*?GET.*?/$IPADDR\.html.*?200)(?!(.*?Googlebot|.*?bot|.*?Bot|.*?Wget))" $logpath > $slugpath$IPADDR; #IPv4/IPv6 过滤出对应文章slug下的ip数 根据日志实际情况变换正则表达式

ipcounts=$(awk '{print $1}' /home/wwwlogs/limbopro.xyz/log/$IPADDR | sort -n | uniq | wc -l); #去重并计算IP数量
echo $IPADDR >> /home/wwwlogs/limbopro.xyz/archives.id #输出文章 slug 到 archives.id 文本内;
echo $IPADDR $ipcounts >> /home/wwwlogs/limbopro.xyz/archives.id.count.add #输出文章slug及本日IP访问数量
done
/home/wwwlogs/limbopro.xyz/archives.slug.sh >> /home/wwwlogs/limbopro.xyz/archives.id.count.origin; #获取并统计原始阅读数;
/home/wwwlogs/limbopro.xyz/archives.count.all.sh #统计最终数量并更新 MySQL 数据表;

VI. 读取MySQL views 字段值以获取原始阅读数

获取并统计原始阅读数;根据 slug 字段值,读取 views 字段值(统计文章原始阅读数);

以下脚本命名为archives.slug.sh;

#!/bin/bash  

passwd=填写你的数据库密码;
tpcontents=填写你的数据表名称; #例如我的是 typecho.typecho_contents 前面的 typecho 是数据库名称,typecho_contents 是数据表名称,数据库与数据表用.符号连接,表示从该数据库下某数据表读取数据;
IPADDR=$(</home/wwwlogs/limbopro.xyz/archives.id) # 从上一个脚本拿到的 文章 slug 值
for IPADDR in ${IPADDR[@]}; do
MYSQL="mysql -hlocalhost -uroot -p$passwd --default-character-set=utf8 -A -N"
sql="select views from '$tpcontents' where slug = '$IPADDR';"
result="$($MYSQL -e "$sql")"
echo $IPADDR $result;
done

VII. 统计新增+原始阅读数

以下脚本命名为archives.count.all.sh;

#!/bin/bash 
> /home/wwwlogs/limbopro.xyz/archives.id.count.all;
passwd=填写你的数据库登录密码;
tpcontents=填写你的数据表名称; #例如我的是 typecho.typecho_contents 前面的 typecho 是数据库名称,typecho_contents 是数据表名称,数据库与数据表用.符号连接,表示从该数据库下某数据表读取数据;
IPADDR=$(</home/wwwlogs/limbopro.xyz/archives.id)
for IPADDR in ${IPADDR[@]}; do
orgin=$(grep -o -P "(?<=^$IPADDR\s).*" /home/wwwlogs/limbopro.xyz/archives.id.count.origin);
add=$(grep -o -P "(?<=^$IPADDR\s).*" /home/wwwlogs/limbopro.xyz/archives.id.count.add);
new=`expr $orgin + $add`
echo $IPADDR $new >> /home/wwwlogs/limbopro.xyz/archives.id.count.all
MYSQL="mysql -hlocalhost -uroot -p'$passwd' --default-character-set=utf8 -A -N"
sql="UPDATE '$tpcontents' SET views='$new' WHERE slug='$IPADDR';"
result="$($MYSQL -e "$sql")"
done

VIII. 设置定时执行命令(crontab)

crontab 命令操作指引:Linux crontab 命令

[email protected]:~#crontab -e
#新增一条定时执行命令
55 23 * * * /home/wwwlogs/limbopro.xyz/archives.count.sh; #每晚11点55分更新阅读数;

好了,以上;

最后修改:2021 年 01 月 18 日 11 : 02 PM