使用pandas进行SEO日志分析

虽然网络上有比较多的SEO日志分析工具,比如爱站,光年,但那都是固定维度的,不如自己写的灵活,想怎么拆分就怎么拆分,加上最近在学习[《利用python进行数据分析》][pandas-book]这本书,正好可以用来练习练习,顺便熟悉一下pandas库。不得不说,pandas这个库真的强大的不要不要的,对数据的加载、存储、清理、转换、合并样样兼顾,虽然目前只看到第9章,但是感觉对数据的处理能力有了很大的进步!
对于SEO日志分析,比较常用拆分维度是:

  • 统计总抓取量
  • 统计不重复抓取量
  • 统计状态码数量
  • 统计目录的抓取量
  • Top N抓取量

此外,我参考了光年日志分析工具,也加入了停留时间和访问时间,尽量把维度拆分的细一点多一点(练手嘛),但是,如果按我想的那样,拆分的很细的话,那肯定要写很多行代码(事实上,没用pandas写之前,就用了400多行代码实现过一个demo),所以取巧了一下,拆分成需要的数据之后,用 pivot_table 的方法建立透视表。
日志的格式如下:
220.181.108.81 - - [05/Mar/2016:20:41:23 +0800] "GET /images/im_qq.gif.pagespeed.ce.8GQAmIPgsj.gif HTTP/1.1" 200 4581 "http://\w+.\w+.com/\d+.html" "Mozilla/5.0 (Windows NT 6.2; Trident/7.0; MANM; ED; rv:11.0) like Gecko"
日志分析脚本demo如下:
# -- coding: utf-8 --
'''爬虫日志分析demo1.0'''

from time import strptime,mktime,time
from pandas import Series,DataFrame
import pandas as pd
import sys,socket
reload(sys) 
sys.setdefaultencoding('utf-8')
start = time()
format = "%d/%b/%Y:%H:%M:%S"  #写出日志的日期格式

def file_open(file):
    col_names=['ip','datetime','resource','status','bytes','refer','ua','path','level1','data']
    reader = pd.read_csv(file,sep='\s+',header=None,chunksize=1000000,names=range(10))
    data = DataFrame(columns=col_names)
    for piece in reader:
        data[['ip','status','bytes','refer','ua']] = piece[[0,6,7,8,9]]
        data['refer'] = data['refer'].fillna('abc')
        data['datetime'] = piece[3].str.replace('[','')
        data['resource'] = piece[5].str.split(' ').str[1]
        data['data'] = piece[3].str.match('\[(\S+):\d+:\d+:\d+').str[0]
        data['path'] = piece[5].str.match('\w+\ ((?:/[A-Za-z0-9-_/\.]*/)|(?:/)\W)').str[0].str.strip()
        data['level1'] = piece[5].str.match('\w+\ ((?:/[\w\d_-]*/)|(?:/) )').str[0].str.strip()
    return data
  
'''判断爬虫真假'''
def check_spider(df):
    start = time()
    ip = list(df['ip'].unique())
    print u'正在检查spider真伪,共%s个待查询iP,耗时较长,请稍等' % len(ip)
    true_spider = []
    fake_spider = []
    
    for x,y in enumerate(ip):
        try:
            result = socket.gethostbyaddr(y)[2][0]
            true_spider.append(result)
            print 'NO.%s %s is spider and now appending in true spider list' % (x,y)
        except socket.herror, e:
            print 'NO.%s %s is facke spider' % (x,y)
            fake_spider.append(y)
            
    log = df[df['ip'].isin(true_spider)]
    print u'执行完成,共耗时%s秒' % (time()-start)
    print 'True Spider IP %s,False Spider IP %s' % (len(true_spider),len(fake_spider))
    return log
    
'''提取某个爬虫访问记录'''
def get_spider_log(df,spider):
    spiders = df['ua'].str.contains(spider)
    spider_log = df[spiders]
    spider_log['datetime'] = spider_log['datetime'].apply(lambda x:int(mktime(strptime(x,format))))
    return spider_log

'''统计停留时间和访问次数'''
def get_x(x):
    a = []
    a.append(x)
    return a
    
def get_list(list_name):
    '''后一个减去前一个,计算时间差'''
    t_list = list(list_name) #传入的是一个Serice对象,所以要转化成list
    t_list.sort()
    values = []
    weight = len(t_list)

    for value in xrange(weight):
        if weight == 1:  #只有一个时间的情况,停留时间算成1秒
            values.append(1)
        elif value > 0:  #前面是升序,所以要 >0 才能用后一个减去前一个
            values.append((t_list[value]-t_list[value-1]))
            
    ''' size 是大于1799的个数,因为间隔1800秒以上访问次数+1,大于1799是包含了1800'''
    size = len(filter(lambda x:x >1799,values))

    '''如果有一个大于1800秒,表示共访问了2次,有 N 个,表示访问了 N+1 次'''
    visits = size + 1  
    
    '''过滤大于1800的值,因为过滤前可能是[1811,2],有两个停留时间,过滤后变成[2]只剩一个时间,因此加上size修正'''
    _values = filter(lambda x:x <1800,values)  #访问时间差的列表
    '''存在过滤后为空列表的情况,但只要访问过,就有停留时间'''
    if _values == []:
        _values.append(1)
        
    stay_time = reduce(lambda x,y:x+y,_values) + size

    result = [visits,stay_time]

    return result
   
if __name__ == '__main__':
    
    script,file,output = sys.argv
    
    log = file_open(file)
    
    spider_log = get_spider_log(log,'Baiduspider')  #默认百度爬虫
    
    spider_log = check_spider(spider_log)  #检查爬虫真伪    

    drop_dup = spider_log.drop_duplicates(['data','resource']) #去除'resource'列中的重复项,算不重复抓取量用
    
    '''不重复抓取量'''
    No_rep = drop_dup.pivot_table(values=['resource'],index=['data','level1','path'],aggfunc='count')      
    No_rep.columns = ['No_rep']
    
    '''次级目录总抓取数,数数的时候会按层次化索引来数,所以数'ua'列也可以'''
    All_crawl = spider_log.pivot_table(values=['ua'],index=['data','level1','path'],aggfunc='count') 
    All_crawl.columns = ['All_crawl']
    
    '''次级目录总字节数'''
    All_bytes = spider_log.pivot_table(values=['bytes'],index=['data','level1','path'],aggfunc='sum') 
    All_bytes.columns=['All_bytes']
    
    '''平均抓取字节(总抓取字节/抓取量)'''
    Avg_bytes = pd.Series(All_bytes['All_bytes']/All_crawl['All_crawl'],index=All_crawl.index,name='avg_bytes')
   
    '''次级状态码数量'''
    All_status = spider_log.pivot_table('resource',index=['data','level1','path'],columns=['status'],fill_value=0,aggfunc='count')  
    
     '''算出停留时间和访问次数'''
    t = spider_log['datetime'].groupby([spider_log['data'],spider_log['ip'],spider_log['level1'],spider_log['path']]).apply(lambda x:get_x(x))
    
    t = t.apply(lambda x:get_list(x[0])).reset_index()
    
    t['visits'] = t[0].str.get(0)
    
    t['saty_time'] = t[0].str.get(1)

    '''停留时间和访问次数'''
    visits_stay = t.pivot_table(index=['data','level1','path'],aggfunc='sum') 

    ''' 平均停留时间(总停留时间/总抓取量)''
    Avg_saty = pd.Series(visits_stay['saty_time']/All_crawl['All_crawl'],index=All_crawl.index,name='Avg_saty')
    
    '''次级目录IP访问数量'''
    Ip_count = t.pivot_table('ip',index=['data','level1','path'],aggfunc='count')

    '''精确到次级目录'''
    last = No_rep.join([Ip_count,All_crawl,All_bytes,Avg_bytes,Avg_saty,visits_stay,All_status]).reset_index()

    '''精确到一级目录'''
    no_path = last.pivot_table(index=['data','level1'],aggfunc='sum')
    no_path['avg_bytes'] = pd.Series(no_path['All_bytes']/no_path['All_crawl'],index=no_path.index)
    no_path['Avg_saty'] = pd.Series(no_path['saty_time']/no_path['All_crawl'],index=no_path.index)
    
#    last.to_csv('ulast.csv',index=False)
    no_path.to_csv(output)     #默认输出

    over = time()

    print '***Analysis OK!Take %s seconds***' % (over-start)

导出的结果如下:
![SEO日志分析结果][result]
这里说一下页面停留时间计算原理,因为日志的记录里只记录了进入页面的时间,并没有记录停留时间,所以只能间接地算出来。比如:A IP 访问了1和2两个页面,那么1页面的停留时间=2页面的进入时间-1页面的进入时间。由于A IP 没有访问第3个页面,所以算不到第2个页面的停留时间,我这里人为的当成停留了1秒。

总停留时间的计算思路是按 IP 和目录分组,得到每个目录的时间列表,然后列表里的后一个数减去前一个数得出时间差,过滤时间差>1799的数,然后累加,算出每个目录的总停留时间。而访问数量就是>1799的数量+1。

另外,脚本算出来的总停留时间和光年日志分析工具的会不一样,不过那并不碍事,本来总停留时间就是一个参考,同一个标准算出来的数据对最后的结论影响不大。

最后,如果是用 shell 进行SEO日志分析的话,可与阅读这篇文章:
[使用shell分析日志][shell]——老狼
[shell]:http://www.itseo.net/direction/show-145.html
[result]:http://upload-images.jianshu.io/upload_images/1464422-375da4f3a91693d7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
[pandas-book]:https://book.douban.com/subject/25779298/ "《利用python进行数据分析》"
[pandas]:http://pandas.pydata.org/pandas-docs/version/0.18.0/ "Pandas-0.18.0-documentation"

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,400评论 25 707
  • 最近在看SEO方面的知识,很是有趣,能学些新东西的感觉总是好的,随着经历增多心境较之前也少了些浮躁,当下的年纪也正...
    斯瓦西里阅读 2,846评论 2 24
  • 此生无事/ 只为花侍/ 嗜事如是/ 所谓何事/ 读万卷书所谓何事/ 是为闲适/ 是为无事/ 事事如此/
    苍声阅读 201评论 0 1
  • 莱斯, 9岁时,圣诞节到了,别家孩子都得到家长赠送的礼 物 , 有跑车,有新衣服,有家电…… 而莱斯的父亲却给...
    旖旎i阅读 285评论 0 2
  • 再看那片海,又思那些人。 慌慌中日两年已,些许无奈,些许放纵,些许不干。但一切都是那么的无可奈何,不经意间花已落去...
    九思往阅读 397评论 0 1