网站的搜索关键词和访问地图
用网络日志分析软件AWStats对网站的log进行分析时发现几个问题:
- 无法同时分析出utf-8和gb2312等多种编码的搜索关键词, 关键词表中总会出现一些乱码;
- 不知道如何按照访问日期对关键词排序;
- 不知道如何制作过滤搜索引擎的访问地图;
因为Perl语言看起来的确很烦,所以与其搜索AWStats的解决方案,不如自己用Python写一个程序来获取这些信息。
制作关键词表
关键词在log中的Referer中寻找如下格式:
KeyTable = {"google":("q","utf-8"), "baidu":("wd","gb2312"),
"yahoo":("p","utf-8")}
"google":("q","utf-8")表示对于包含google的referer,寻找参数q=...,并按照utf-8编码进行解码。如果解码失败会依次尝试别的编码。这样就解决了关键词编码的问题。
制作访问地图
下图是使用GoogleMap制作访问地图的截屏。为了过滤搜索引擎的IP,先统计出log中所有的User-Agent,然后人工找到疑似搜索引擎的Agent。

下面是对本网站的统计结果,包含如下字符串的Uer-Agent的IP均不在统计之列:
SearchEng = ["Googlebot", "Baiduspider", "NaverBot", "Exabot",
"woriobot", "Doubanbot", "Feedfetcher-Google",
"Yahoo! Slurp", "Twiceler", "msnbot", "e-SocietyRobot", "iearthworm",
"YodaoBot-Reader", "help.naver.com", "Jakarta"]
地图中的地点标示的大小按照页面的访问次数设置,只对如下的文件类型进行访问计数:
PageType = [".html", ".htm", ".py"]
地点标示的直径与访问次数的平方根成正比,并且设置最小直径为4个像素。每个地点标示都记录了其最后访问日期、IP地址数、页面访问次数以及IP地址列表。
IP地址到地点坐标的转换是用下面的程序从http://whatismyipaddress.com自动抓取而得。它先在缓存文件IpLocation中查找,如果没有找到就用urllib2库抓取网页,并对结果进行正则表达式搜索。
01import urllib2
02import urllib
03import re
04import pickle
05
06try:
07 ipdict = pickle.load(file("IpLocation","rb"))
08except:
09 ipdict = {}
10
11def getIpLocation(IP):
12 if IP in ipdict:
13 return ipdict[IP]
14 url = "http://whatismyipaddress.com/staticpages/index.php/lookup-results"
15 req = urllib2.Request(url)
16 req.add_header("User-Agent",
17"Mozilla/5.0 (Windows NT 5.1; U; zh-cn) Opera 9.00")
18 data = {'LOOKUPADDRESS': IP, 'Lookup IP Address': 'Lookup IP Address'}
19 req.add_data(urllib.urlencode(data))
20 f = urllib2.urlopen(req)
21 data = f.read()
22 f.close()
23 result = re.compile("GLatLng\((.+?)\)").search(data)
24 if result:
25 location = tuple((x.strip() for x in result.group(1).split(",")))
26 ipdict[IP] = location
27 print location
28 return location
29 else:
30 return None
31
32def saveIpLocation():
33 pickle.dump(ipdict, file("IpLocation", "wb"))
Python解S先生与P先生谜题
这道题目来自美国斯坦福大学的麦卡锡教授----S先生与P先生谜题。
题目:S先生与P先生谜题
设有两个自然数X、Y,2<=X<=Y<=99,S先生知道这两个数的和S,P先生知道这两个数的积P,他们二人进行了如下对话:
- S:我确信你不知道这两个数是什么,但我也不知道。
- P: 一听你说这句话,我就知道这两个数是什么了。
- S: 我也是,现在我也知道了。
现在你能通过他们的会话推断出这两个数是什么吗?(当然,S和P先生都是非常聪明的)
关于这道题目的解题思路可以参考: Prolog教程S先生与P先生
01from math import sqrt
02
03MIN, MAX = 2, 99
04#只需要判断y的范围, 程序确保x的取值范围
05def BaseCondition(x, y):
06 if y >= MIN and y <= MAX and y >= x: return True
07 return False
08
09#S先生的S分析
10def S(x, y):
11 s = x + y
12 return [(x,s-x) for x in xrange(MIN, s/2+1)]
13
14#P先生的P分析
15def P(x, y):
16 p = x * y
17 return [(x,p/x) for x in xrange(MIN, int(sqrt(p)+1))
18 if p%x==0 and BaseCondition(x, p/x)]
19
20#所有待检查的数据
21def ToCheck():
22 return ((x,y) for x in xrange(MIN, MAX+1) for y in xrange(x, MAX+1))
23
24#条件1 - S:我确信你不知道这两个数是什么,但我也不知道。
25def Condition1(x, y):
26 s_sep = S(x, y)
27 if len(s_sep) == 1: return False # S分析唯一
28 for x1, y1 in s_sep:
29 if len(P(x1, y1)) == 1: return False #P分析唯一
30 return True
31
32#条件2 - P: 一听你说这句话,我就知道这两个数是什么了。
33def Condition2(x, y):
34 p_sep = [c for c in P(x,y) if c!=(x,y)] #除(x,y)之外的P分析
35 for item in p_sep:
36 if Condition1(*item): return False #若还有满足条件1的解则失败
37 return True
38
39#条件3 - S: 我也是,现在我也知道了。
40def Condition3(x, y):
41 s_sep = [c for c in S(x, y) if c!=(x,y)] #除(x,y)之外的S分析
42 for item in s_sep:
43 if Condition2(*item): return False #若还有满足条件2的解则失败
44 return True
45
46#需要同时满足上面3个条件
47def Condition(*c):
48 return Condition1(*c) and Condition2(*c) and Condition3(*c)
49
50result = (c for c in ToCheck() if Condition(*c))
51print result.next()
海月挨打
我还没吃呢,哪有吃的给你。
和小猫对视
人家都说小孩子三岁之前是一个养成良好习惯的重要阶段,一想到这我有的时候就会对海月很严厉。今天中午海月似乎不是很饿,没有吃多少饭。于是我就把饭放在桌子上让她自己想吃就吃。结果她看我不管她,就故意把饭撒在地上。当时我一方面非常生气,一方面想告诉她这样做非常不好,就顺手打了她的屁股一下,还做出很生气的表情。这样一来她就大哭起来,喊着要睡觉要睡觉,我想给她水喝,也完全喂不进去了。有的时候真的搞不清楚到底怎么样做才是教育孩子的最佳方式。
C55x的Boot Table问题
在Run-time和load-time初始化 一文中,我曾经介绍过C55x系列的Boot Table的格式,以及设置为load-time格式时hex55工具对.cinit段的展开工作。可是在实际的开发中我发现hex55工具(v3.2.2)对.cinit的处理竟然是错误的。下面我就来详细分析一下这个错误。
上图是hex5x产生的boot table的格式。根据此图,编写如下Python程序显示Boot Table中的每个项目。注意图中的Section Byte Count可能为奇数,而其下面的Data Byte的个数必须为偶数,因此当Section Byte Count为奇数的时候,会多出一个填充字节。
01import os, sys, struct
02
03def list_boottable(filename):
04 f = file(filename, "rb")
05 data = f.read(4)
06 # entry point
07 entry_point_address = struct.unpack("!L", data)[0]
08 print "entry point:", hex(entry_point_address)
09 # register
10 data = f.read(4)
11 register_configuration_count = struct.unpack("!L", data)[0]
12 print "register count:", register_configuration_count/2
13 for i in xrange(register_configuration_count/2):
14 data = [hex(struct.unpack("!H", f.read(2))[0]) for i in range(4)]
15 print data[0], "=", data[1]
16 print "delay", data[2] , ",", data[3]
17 # copy table
18 while 1:
19 data = f.read(4)
20 section_byte_count = struct.unpack("!L", data)[0]
21 if section_byte_count == 0: break
22 data = f.read(4)
23 section_start_address = struct.unpack("!L", data)[0]
24 print hex(section_start_address), "<-", hex(section_byte_count), "bytes"
25 data = f.read(section_byte_count)
26 # if byte count is odd, read a padding byte
27 if section_byte_count%2==1:
28 data = f.read(1)
29 f.close()
30
31if __name__ == "__main__":
32 list_boottable("dsp_app.b00")
这段程序对一个设置为load-time的Boot Table的分析结果如下:
entry point: 0x2DFA6L 入口地址 register count: 1 寄存器设置个数 0x1c00 = 0x2210 寄存器设置 delay 0xffff , 0x100 延迟 0x7A20L <- 0x1ECCL bytes 复制0x1ecc个字节到地址0x7a20 0xF3C8L <- 0x98L bytes 0xF69CL <- 0xCL bytes 0xB0ECL <- 0x1696L bytes 0xF68CL <- 0x10L bytes 0xE688L <- 0x400L bytes 0x28430L <- 0x522CL bytes 0x2DD98L <- 0x27CL bytes 0x2E056L <- 0x6L bytes 0x2D65CL <- 0x73CL bytes 0x2E014L <- 0x42L bytes 0x3FF00L <- 0x100L bytes 0x1D9A4L <- 0x1FE2L bytes 0x5A20L <- 0x2000L bytes 0x17720L <- 0x6283L bytes 注意复制字节数为奇数 0xC782L <- 0x1503L bytes 0x2CFA00L <- 0x4L bytes 以下为对.cinit的展开 0x2CF800L <- 0x4L bytes 注意目标地址有问题 0x2D0A00L <- 0x4L bytes 0x766800L <- 0x4L bytes ....
从上面的结果可以看出,虽然Boot Table对.cinit段进行了展开,但是目标地址竟然是错误的。那么实际的地址应该是什么呢,用Coff Reader 打开out文件,查看.cinit段的数据,发现0x2CFA00所对应的地址应该是0x002CFA。那么只要修正这个目标地址的话,hex55所产生的那个Boot Table就可以正常使用了。
.cinit ------------------------------------------------ 00 02 00 2c fa 00 44 7a 00 00 00 02 00 2c f8 00 44 7a 00 00 00 02 00 2d 0a 00 00 00 fa 00 00 02 00 76 68 00 00 00 75 44 00 02 00 76 6a 00 00 00
还有一点需要注意:Boot Table中的目标地址是Byte地址,而.cinit中的地址是Word地址,因此必须把0x2CFA00转换为0x0059F4才对,也就是对有问题的地址(上面的0x2CFA00L, 0x2CF800L, 0x2D0A00L, 0x766800L等)右移7位就正确了。这个只需要稍微修改一下上面的那个Python程序就可以自动完成了。hex55居然有这么一个大BUG,有点令人难以置信,也许有什么参数可以让它产生正确的地址,不过自己动手解决这样的问题也是一种乐趣。
又去医院了
我摆的,怎么样?
这花可真香呀。
昨天晚上发现海月身上有红点点,他爸爸说是湿疹,今天上午去医院一看,果真是湿疹。小孩子很容易得这个皮肤病,主要是对某种食物过敏。但也无需回避,要让她慢慢适应。今天在医院海月大哭起来,说什么也不肯到医生身边去。我说今天只是看看皮肤,才稍稍好了些。边看还边说,不看嗓子,不看嗓子。因为每次到医院都要看嗓子,这让她非常讨厌。
Karrigell的网络日志
最近想了解一下本网站的访问情况,google了一下,发现了一个不错的网络日志分析软件AWStats,它是用Perl编写的,先安装一个Perl的解释器就可以运行了。
下载下来运行一看,才发现Kerrigell的网络日志是简单版,没有User-Agent和Referer的记录。User-Agent提供很多客户端的信息,而Referer则记录页面的访问来源。通过分析这个访问来源,可以知道用户是通过搜索什么关键字找到本网站的,或者找到那些链接到本网站的网页。没有这两个信息的网络日志没有什么意思,而Karrigell似乎没有什么设置能够开启这两项的记录。于是只好修改它的代码了,分析了大半天,才发现只需要修改KarrigellRequestHandler.py文件中的以下函数的定义及即可。
01def log_message(self, format, *args):
02 try:
03 args = args[:2] + (self.RESPONSE.get("Content-Length", "-"),)
04 except:
05 pass
06 try:
07 referer = self.HEADERS.get("referer","-")
08 agent = self.HEADERS.get("user-agent","-")
09 except:
10 referer = "-"
11 agent = "-"
12 msg = ('%s - - [%s] %s "%s" "%s"' %
13 (self.address_string(),
14 self.log_date_time_string(),
15 format%args, referer, agent))
16 if not k_config.silent:
17 BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, format, *args)
18
19 if k_config.loggingFile:
20 logging.info(msg)
修改了网络日志之后,分析了一下这两天的访问情况,发现除搜索引擎之外,有如下网站对本站进行了链接:
- pazu是经常访问本站的一位搞嵌入式开发的朋友,他的博客有很多有趣的东西,发现了朋友的网站,真是一个不小的收获
- 61ic则是盗贴了我所有的DSP文章的一个技术网站,他们没有把图片的链接修改过来,所以当用户阅读文章的时候,会从我的网站下载图片
- imp3, applefans, aplbbs等网站都是链接的本站的rockbox的字库,看来这个字库还是挺受欢迎的
|
见到老朋友了
猜我后面是什么?
上星期天我们的一个日本朋友来家里做客。海月出生后没回国的时候曾和他见过几次面,应该算是海月的老朋友了。这次海月来日本是第一次见他,两个人真的是一见如故。海月一下子就和他熟悉起来,他们一起唱歌,一起吃苹果。以至于海月坐在这位朋友的身上,叫他伯伯。我们看了都觉得非常惊奇。我的其它日本朋友来了,海月基本上只是打个照面就躲起来了。就是中国朋友来了也是远远地看着,不肯到跟前去。跟这位朋友来时所表现的反差太大了。就连我们回去接海月的时候也没有一下子就这么熟悉。我们都一致认为,海月一定还记得这位老朋友。
星期一正好有时间,我就把对门的母子三人请过来和海月玩,因为海月不肯去她们家了。海月和小男孩基本上是各玩各的,你说你的,我说我的。不过海月已经把他的名字记下来了,而且日语的请也说了多次,因为刚到我家时,小男孩不肯玩,海月极力推荐了各种玩具,总算渐渐活动起来。他们走后我问海月还让不让他们来玩,海月一会说让一会说不让,处在一种心理上的矛盾中。
超声波探伤系统
前两个月一直在忙超声波探伤系统的开发。它是为某个圆柱型钢材加工工厂的质量检测部门开发的系统。软件部分需要控制各种机械部件、电机、传感器,以完成自动超声波探伤。整个系统的构成相当复杂,程序需要从近30多个传感器获取数据,控制6个电机的运动,并且实时地从两块高速数据采集卡PCI-9812获取超声波数据,判断钢材中是否有缺陷,并绘制缺陷示意图。
在研究室里写完整个程序的基本结构,花了近一个月的时间。然后就出差到工厂里面实地调试了两个星期。在实际调试的时候才发现有许多东西并不是按照想像的那样运行,例如传感器可能出现误报;电机的Encoder信号可能出现噪声,以至于无法正确判断电机的位置;激光测距仪可能因为材料表面的反射条件而无法正确工作等等。而最令人头疼的是,工厂环境下的电气噪声和接触媒质(水)的不稳定会影响超声波探头的信号,使得缺陷反射的超声波信号和噪声混杂在一起,无法进行正确判断。看着搞装置的同事们想尽各种办法解决这些问题,我觉得我的实践经验值也突飞猛涨。将来有时间要好好写写这个系统开发过程中的经验教训。
客户对缺陷示意图的要求只要是2D的平面图就可以了。不过由于钢材是圆柱型的,平面图很难直观地表现缺陷的位置。于是我利用空闲时间用Python写了一个3D表现缺陷的小程序。下面是这个程序的界面截图,上方显示的是缺陷的平面图。可以看出平面图中蓝色区域标示了许多缺陷(红色的点),它们的实际位置在钢材的圆心部分,实际上是钢材中心的孔。
这个程序采用wxPython做界面,pyOpengl绘制3D图形,用wxPython的Plot模块绘制AScope(超声波的反射信号),用户可以用鼠标任意旋转、移动、放大缺陷,选中某个缺陷之后,可以详细查看其AScope波形,应该能为缺陷的人工检定带来不少的便利。
吃饭的乐趣
有的时候真的好想回家,呆在这里没有亲威没有朋友总是有一种慕名的孤独感。海月的教育问题也一直困扰着我们。可怎样才能回去呢,海月的爸爸回去要找什么样的工作,目前还完全没有头绪。我的日语也不足以让我以它来谋生。问题永远都会存在的,海月的成长却是无法阻当的。看着海月一天天象小树苗一样长大,还有什么比这更有成就感呢。
吃饭的问题
我要把泡泡吹到你的衣服上,把你的衣服搞脏。。。
海月的吃饭问题其实一直困扰着我。上星期海月的爸爸提议要训练海月自己吃饭,至少不能再以中国式的填鸭方式喂海月了。其实多少次我都在和自己说,她不饿的时候不要喂饭,但做起来真的不容易,一到吃饭时间就想尽可能地让她多吃点,这无疑是一种错误的做法。我在对门的小男孩家看到:他只吃了几根面条后,他跟他的妈妈要,他的妈妈却一脸诧议地问:还要吃?由此可见我和他的妈妈之间的差距有多大了。
昨天开始我就采取了措施,不想吃坚决不喂饭。所以昨天中午基本上是海月自己吃的饭。晚上因为她一直不饿,但如果太晚了还不吃,那可能直接喝牛奶,所以我硬喂了几个饺子。今天中午喂了几口她就不想吃了,我也没有再喂,一直到现在海月还在睡觉,只有醒后让她自己吃了。如果吃饭的问题可以让她这样顺其自然地自己想吃就吃,那我真的可以轻松很多。