使用pandas进行SEO日志分析

来源:互联网 发布:java递归算法详解 编辑:程序博客网 时间:2024/06/05 01:03

http://www.jianshu.com/p/84d4f9c6e83f

虽然网络上有比较多的SEO日志分析工具,比如爱站,光年,但那都是固定维度的,不如自己写的灵活,想怎么拆分就怎么拆分,加上最近在学习《利用python进行数据分析》这本书,正好可以用来练习练习,顺便熟悉一下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,timefrom pandas import Series,DataFrameimport pandas as pdimport sys,socketreload(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 adef 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 resultif __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日志分析结果


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

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

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

最后,如果是用 shell 进行SEO日志分析的话,可与阅读这篇文章:
使用shell分析日志——老狼



作者:担得一身好酱油
链接:http://www.jianshu.com/p/84d4f9c6e83f
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原创粉丝点击