Sunday, December 13, 2009

[宅男学厨]三明治

今年出去买了鸡蛋和煎锅:)瞅着手边的材料,除了上次还没用完的土司就还有番茄和奶酪等.那么就做个简单的三明治吧.

将土司去边,从对角线切开,奶酪也如此对切。

去鸡蛋一枚,入碗打散,加入少许盐.将之前的土司夹奶酪浸入碗中,三边开口处都要粘满蛋汁哦.

将土司丢入煎锅,先用中火炸四边,待封口后,转用小火炸两边.至两面均酥皇,将剩余的鸡蛋汁全部交上去,片刻后起锅:)




Friday, December 11, 2009

[宅男学厨]煎蛋,培根与吐司

昨天晚上和March Liu交流了下做饭的心得,得其真传.今天决定从最简单的开始,打好基本功.给自己做一顿早餐.

看看手边的材料,似乎鸡蛋为最.在我系统分析了33种鸡蛋相关做法之后,我决定做史上最有名的......煎蛋吧.岂料杯具发生了,家里的鸡蛋被我放置的太久,竟然,竟然坏掉了很多(:

好不容易找到了一个能用的,打蛋装碗.

锅里放油,开火,以油熟且尚未冒烟为宜(否则,你的油就会...)好,就是此刻,将鸡蛋丢入锅中,待蛋白部分发白略黄的时候,在其周围加水若干滴(保证下形状,嘿嘿).加盐.翻转鸡蛋,煎另一面.起锅.



怎么样,第一次煎蛋还算不错吧:)

接着,将培根丢入锅中约2min,起锅,配合我事前准备的奶酪.


战斗结束,历时6min ;)之前在网上查看食谱的时候,见到一种做法:


两片厚片土司去边 一个鸡蛋加少许盐打散 一片或两片奶酪 
平底锅里放一点黄油熔化后 将去边土司加好奶酪片 放在鸡蛋液里沾一下 
四周都要沾到 夹起来沾好蛋的土司 在锅里 用中火煎 先煎四边 
再煎两面 然后转小火 将剩下的鸡蛋均匀的倒上去 如果没剩下鸡蛋 
就免了这一步了 之后用小火慢慢煎到两面金黄松脆 里面的奶酪半融化状
的时候 就能吃了 非常好吃 蛋香奶香麦香稍微有点淡淡的咸味

 
厨艺果然是创意无限.不知为啥我想起了Bitmap来:)

使用coLinux+Debian+Putty+Emacs构建快速开发环境

本博的笔记本比较古董,跑VirtualBox之流甚是吃力,更不用说Vmware这样的超级杀器.本来一直采用的是Msys来进行*nix的模拟的,不过由于某些软件包实在不给面子,害的我每次都得连接至公司的服务集群上进行测试,如此下来,多有不便:(

于是乎,经过一番爆狗,终于找到了coLinux这样的好东西.CoLinux是在Windows上能够运行的linux. 在Windows计算机上安装Linux的时候,可以不用追加新的硬盘,也不用重新做分区等工作。 如果使用coLinux的话,不重新安装Windows,不变更硬盘分区就可以很轻松地构筑Linux环境。

如果说Cygwin是在C库程序阶段模拟UNIX(在源码级别的互换性)的话,则coLinux是在能驱动真的Linux原核程序上,与Linux和应用程序具有互换性。即:Debian和Fedora能够直接运行。换句话说,coLinux就是一个 Linux 内核,它经过修改,以与另一个操作系统协作运行。主机操作系统(Windows 或 Linux)控制操作系统的物理资源,而访客(guest)操作系统(coLinux)获得硬件的虚拟抽象。主机操作系统必须提供以特权级别(ring 0)执行驱动程序的方法,并提供分配内存的方法.

接下来的事情就很容易了,猛击此处下载当前的coLinux的二进制版本,同时本博下载了列表下方的Debian5.0的压缩包.运行coLinux的安装程序,一路Next至Over(安装目录最好不要出现中文或空格).解压前述Debian的压缩文件至coLinux的老巢.接下来,可以使用Debian提供的BAT脚本直接执行了.

不过也许各位已经发现Debian里只有2GB左右的空间,而且貌似不能上网也没有开启sshd.接下来我们一步一步解决这些问题:

首先,再次猛击一下下,我们得到一个已经做好的4GB大小的分区文件(下载文件很小,只有4xKB的样子).解压丢至coLinux的基地去.

接着,在你的Debian里配置下第二块网卡(eth1):
allow-hotplug eth1
iface eth1 inet static
address 192.168.1.6 //根据实际情况,自行改变
gateway 192.168.1.1
netmask 255.255.255.0

这里贴下我的coLinux.conf文件
# The default kernel
kernel=vmlinux

# File contains the root file system.
# Download and extract preconfigured file from SF "Images for 2.6".
cobd0="Debian-5.0r2-lenny.ext3.2gb"

# Swap device, should be an empty file with 128..512MB.
cobd1="fs_root" //扩展的4GB文件

cofs0=d:\ //与Windows交互设置,这是coLinux自己的方式

root=/dev/cobd0

initrd=initrd.gz

# Slirp for internet connection (outgoing)
# Inside running coLinux configure eth0 with this static settings:
# ipaddress 10.0.2.15   broadcast  10.0.2.255   netmask 255.255.255.0
# gateway   10.0.2.2    nameserver 10.0.2.3
eth0=slirp //dhcp
# Tuntap as private network between guest and host on second linux device
eth1=tuntap //这里就是之前在系统里设置的eth1

启动进入系统,进行挂载测试

mkdir -p /mnt/ext
mount -t ext3 /dev/cobd1 /mnt/ext

如果可以访问的话,那么写入你的/etc/fstab.系统启动时会自动挂载

/dev/cobd1 /root ext3 defaults 0 1 //这里我用来扩展了/root,你自然也可以改成/home/xxx 不过记得拷贝文件

类似的,将windows交互目录挂载进来

mkdir -p /mnt/host
mount -t cofs cofs0 /mnt/host

接下来,重启系统,通过apt-get install ssh来打开sshd,选择putty登陆上去.编译Emacs之前记得先安装ncurses (:

展示下效果


这个黑黑的CMD窗口就是coLinux启动的Debian终端,如果觉着不爽,也可以用colinux-daemon把其做成系统服务.最后赞一下,coLinux的速度真的很快

Wednesday, December 2, 2009

基于REST风格构建WEB应用的实践反思

江湖上有着这样一则传闻:
面试官:"请问REST是虾米?"
面试者:"哦,您放心,我是永远不知道休息的..."

我想面试官的这个问题,本文的读者应该大体是有一个概念的.不过为了行文的方便,这里本博还是将REST的核心原则摘录于下:.

  • 为所有“资源”定义标识(URI)
  • 将所有资源链接在一起
  • 使用标准方法
  • 资源多重表述
  • 无状态通信

由于公司战略需要一款SNS产品,本博有幸参与了整个开发活动,并主持了架构设计和实现.因此得以实战REST,并将前后遇到的一些问题和思考记录下来,遂成此文.


那么为啥会考虑采用REST作为系统架构风格呢?

首先,由于是一款自己运营SNS产品,根据产品规划部门制定的需求,在技术上要求我们实现大量的用户行为记录,同时还要为用户提供包含博客、相册、视频在内的web2.0“标准”应用以及支持第三方植入应用的OpenAPI体系.因此系统在安全性,并发性,稳定性,扩展性等方面均有较高的要求.
其次,由于项目团队的人员配备,开发模式,开发周期等因素的影响,敏捷开发也成为一个潜在的要求.
针对这些客观实际情况,本博考察了主流SNS的技术方案,结合自身特定,决定采用REST来实施构建:

  1. 资源URI可以有效描述系统涉及的角色(用户,及其产生的行为结果),无论是在用户群体还是在单一个用户对象.
  2. 系统对外以URL形式(对内则是URI)来与客户端通讯,这也是负载均衡得以实施的前提
  3. URI(Entity,QueryString,Method)封装了编程实体,后端程序可以有效建立Resource-Object映射,配合Key-Value缓存以及ORM等技术手段优化,可以满足对伸缩性的要求.
  4. REST提倡的HTTP操作方法封装了数据操作的CRUD
  5. URI之间的聚合(combination)有效建立起一组可复用的API体系.


实践中采用REST风格来构建项目,较为显著的带来了两个方面的提升:技术层次上,团队内推广和普及了敏捷开发,并在实践中性能成一套符合自身情况的开发流程;在HTTP协议,Web服务器,键值缓存,开发语言等方面开拓了技术视野并形成了相应的技术积累.管理层次上,由于构建REST应用的需要,将人员合理分配成应用开发,API开发等不同的小组,责权分明.但是很显然,这一架构的引入不可能是一帆风顺的,在实践中我们也遇到了很多问题,很多场景下我们也不得不去破坏学术定义(REST Anti-Pattern REST反模式):

  • 什么是资源?
        这样一个基础的命题上,保持怎样一个粒度,对API的构建乃至整个系统至关重要.是按照"语义"级别,将用户定义为/user/uid这样,还是针对系统级别,将数据表结构定义为资源.在这个问题上,内部产生了很多的思考和争议.虽然我们最终采用了类似前者的思路,但是这只是根据当时需求情况的取舍,这样的定义来带的后果是对"简单数据"CRUD的极大便利;资源交叉高复用,原子操作性较强.但是对集合类似资源(用户组等)至少在概念层次上是无力的,往往需要引用大量的SQL模板来进行封装使用,这也成为性能的一个隐患.而实际上后端开发人员80%的事情均用在这里(从整体最优来看,也是值得的).
        反之,如果我们将数据表,列这些定义成为资源的话,那么实际上我们将走到一条Meta-Data(元数据)编程的路子上.显然在逻辑层次上我们将不需要去考虑业务中角色以及相互关联的
问题.这种类似"虚拟机"(提供一套策略而不是机制)的架构风格应对复杂逻辑是绰绰有余的.但是在SNS这样一个毕竟存在大量单一应用以及"简单数据"访问的情况下,这样有似乎有点"过度设计".当然这只决定于需求和性能之间的平衡.

  • 对Method的使用
        几乎任何一份REST实践指南的资料中都指出过DELETE和PUT的模拟问题,使用GET/POST去实现这样一种模拟.
        完全的使用GET方式去实现,这么做的唯一好处,是开发便利:只需要将这样一条URL贴到浏览器的地址栏中,就可以完成测试.但是,这样的系统本身并没有把URI看成是"资源",而仅仅是
一种传递参数的字符串而已.同时这种链接一般不可加入书签,而且有“爬虫”造成非预期副作用的风险(假设你传递了一个?method=delete这样的参数).
        完全的使用POST方式去实现,这种做法被flicker等知名网站广泛使用,但实际上走回了SOAP的老路上.他不但完全忽视了REST的根本原则并且接下来也无法利用"缓存".我们的系统采
用这种方式的原因是设计上的"便捷"(:
  
  • NoSQL?
        SNS为NoSQL的思想贡献了相当大的关注力,这种思想意图使用key-value键值数据库来完全取代现在有的关系型数据库,通过键值缓存技术来提升性能.虽然如此,但NoSQL对系统的设计要求是相当之高,否则很容易就会发现构建出来的系统几乎不能满足良好的扩展性.

  • 工具与方法论
        开发初期,如何能使更多的开发人员理解REST思想,并应用于开发活动,我们提供了一个在线的调试器,当你输入URL的时候,可以查看返回数据以及相关信息.国内的Taobao开放平台也提供
了类似的沙盒环境(sandbox).推广一种工具往往比推广一种思想要容易的多.当我们意识到并不是所有人都需要明白什么是REST的时候,我们提供了一组language binding clinet library.


软件开发世界没有"银弹",试图用一种架构风格/模式去解决遇到的所有问题是不现实的.在实践中遇到的种种问题,探究他们的缘起以及解决之道,有利于加深对REST架构的理解和应用.那么,当我们 意识到这些问题,并尝试解决的时候,不妨跳出原有的思维局限,开拓眼界,引入更加符合实际情况的混合架构风格设计方案,这也是我们下一代技术产品的思路,并且有打算以开放源码的方式展现在 大家眼前,提供一个Resty的"砖头".

Monday, November 30, 2009

Django的HTTPHandler模型图

很早之前的一篇读书笔记,今天有朋友问我这方面的问题,正好就重新贴出来吧.
时间过得好快啊(:


Saturday, November 28, 2009

也谈网页正文提取[下]

上回书到使用行文本密度(Text Density)来解决网页正文提取问题.给出了一个学术界定量的文本行密度阀值.一般来说武功秘籍的下半部总是看起来牛逼些的,就像当年梅超风练的<九阴真经>一样,练了半本书的一路掌法,已经是妇孺闻之变色的"女魔头"了.所以本博的这下半篇也来不得丝毫差池,怎么也得看起来十分有料才行.

前文所述之阈值估计过程本质上是一个全局统计各个密度的过程,并没有像学习算法一样考虑文本行的分布情况,如在页面的中间发生以及文本行可能在一起出现的可能性高等。但阈值估计有助于快速地选择行,而且可以在没有学习样本的情况下获得较好的表现。

但是不同的场景需求对结果的精度要求是不一样的,作为一直致力于追求更快,更准,更优的本博来说,自然是希望抽取的结果最优.这里的"最优"有两个方面的含义:
  • 抽取的结果尽可能包含正确的行
  • 抽取的结果尽量少地包含错误的结果
为了达到这样的效果,这里介绍两个主要的手段,神经网络(ANN)和错误控制(FDR).

首先来看看神经网络,对这个名字觉得耳熟的朋友肯定至少被忽悠过一次数据挖掘或者人工智能.至少本博第一次听说的时候,是被唬的不行,觉着以自己愚钝的天资还是做不明真相的围观群众好了.但是在我们这里,没有必要那么夸张,下面就由有着生物学背景的本博带领去一亲人工神经网络的芳泽.

既然是人工神经网络,从名字就知道这肯定是模仿其他某些神经组织结构机理而形成的模型.实际上,它所模仿的对象您现在也用着呢,那就是我们人类的"神经细胞",专业的说法叫做神经元.


神经元结构上大致都可分成胞体和突起两部分,突起又分树突和轴突两种。轴突开始一段称为始段,离开细胞体若干距离后始获得髓鞘,成为神经纤维。我们日常感受的刺激在神经结构上就由树突将冲动转向细胞体.典型的神经突触是在两个神经元之间形成的单向通信机制。神经信息的流向是从突触前细胞到突触后细胞。突触通常形成在突触前细胞的轴突和突触后细胞的细胞体或树突之间.如下图所示



人工神经网络就是模仿上述神经突触联接的结构进行信息处理的数学模型.由大量的节点(或称“神经元”)和之间相互联接构成。每个节点代表一种特定的输出函数.每两个节点间的连接都代表一个对于通过该连接路径的加权值,也叫权重,这相当于人工神经网络的记忆. 输出则依赖节点间的链接方式,权重值和节点输出函数.神经网络的构建一般用来模拟自然界某种算法或者函数的逼近,或者某种逻辑的推演(例如本文的问题).


一个典型的单层神经元网络由有限个神经元构成,上图.




从左到右分别是:
  • 输入层,众多神经元接受大量非线形输入信息。
  • 隐藏层,简称“隐层”,是输入层和输出层之间众多神经元和链接组成的各个层面,也是实现监督学习算法的主要层面.
  • 输出层,信息在神经元链接中传输、分析、权衡,形成输出结果。
在我们解决网页正文提取这个特定的场景下,我们采用的神经元学习算法可以归类为监督式学习网络,即从问题领域中提供训练范例,包含输入资料与输出资料。 网络从中学习输入资料与输出资料的内在对映规则.很像老师教学生几个例题,告诉答案,然后训练学生做新的题目.

"ANN是不是一种很复杂的东西?"
    "也许以前是,但现在已经不是了"
"为什么"
    "因为名气大的招数,往往也是高手努力去研究的东西,像ANN这样的利器,江湖甚至出了一个FANN"

首先建立一个fann的训练工程,然后丢给他我们预先标记好的训练文本,过程就是这么简单吖.
关于生成训练文本这里,如果完全手工的话,估计也是一件吐血的事情.但是这活儿又不能没有人工干预,所以折中的办法是预先制定一个训练数据生成算法,然后人工标记检查一下.

训练数据文本格式如下:
3 2 1 //训练对总数 每训练对输入数 每训练对输出数
1 1   //训练对 输入
1     //训练对 输出
1 0
1
-1 1
1

生成算法大致如下:
  • 分别计算当前行与前行以及后行的文本密度、文本字节数、总字节数
  • 若当前行文本密度在0.3以上,则标记输出结果为1,反之为0
out = map(lambda x,y,z:[x,y,z],[Para()]+d[0:-1],d,d[1:]+[Para()])
f.write("%s %s %s\n"%(len(d),3,1))
line = 3*"%s "+"\n"
for x,y,z in out:
    f.write(line%(y.density,y.bytes,len(y.text)))
    if (y.density > 0.3):        f.write("1\n")
    else:        f.write("0\n")

使用FANN进行训练的主要过程,在前文的LineWirter中加入一个方法:
def get_density_with_ann(self):
        obj = libfann.fann_create_standard_array(2, (3, 1))
        ann = fann.fann_class(obj)
        patterns = fann.read_train_from_file('train_data.txt')
        ann.train_on_data(patterns, 1000, 1, 0.0)
        return [line[3] for line in map(lambda x:[x.density,x.bytes,len(x.text),x.text] , self.lines) if ann.run(line[0:2])[0] > 0]    

使用ANN处理文本提取,可以根据输入的上下文进行启发式学习,从而使得输出更加适合于实际情况.利用机器学习来处理每一文本行的信息,可以找出有用的模式.从人工的决策过程分析,我们采用了九个因素用于决定如何过滤文本行。这些因素分别是当前行的密度、当前行的HTML字节数、当前行的输出文本长度、前一行的这三个值、后一行的这三个值。

当然神经网络也不是完美的,它需要一定量的网页学习.而且如果网页的结构变化较大,那么感知器反而会带来一些弊病.造成精度缺失的情况.本博的实际实验情况中,由于对多种类型的网页都进行了训练,结果在我们给出的示例网页中,结果稍微逊于另外一个测试用例.

如果对抽取的结果准确要求相对教高,并且又没有那么多的训练数据可供使用,那又该怎么办呢?本博这里隆重推出终极杀器----FDR 也有人称之为检出率算法,因为其只考虑期望部分的错误率.这里简要阐述FDR的意义以及应用过程。

假设问题有N个可能的候选输出,每个输出可以采用一个概率或可能性值表示输出结果的正确性概率,根据输出的概率从N个选择中选择一定数量的结果作为最后的输出。假设挑选了 个输出作为结果,其中有s个是真正正确的,另外有 个是错误的,实践中希望错误比例Q=V/R平均而言不能超过某个预先设定的值(如0.05),在统计学上,这也就等价于控制FDR不能超过5%.

"看起来很复杂的东西,背后往往相当简单"

这个问题可以转化成:设总共有n个候选输出,每个输出对应的P值从小到大排列分别是P(1)..P(m),若想控制FDR不能超过q,则只需找到最大的正整数i,使得P(i)<=(i*q)/m;然后,挑选对应P(1),p(2),⋯ ,P(i)作为输出结果,这样就能从统计学上保证FDR不超过q.

在前文的LineWriter里加入如下代码即可:
def output_fdr(self):
        self.compute_density()
        pvalue = map(lambda x:1.0/x.density ,self.lines)
        pvalue.sort(reverse=True)
        i = len(self.lines)
        m = [j for j in range(i) if pvalue[j] <= (j*5)/i][0]
        density = 1.0/pvalue[m]
        output = StringIO.StringIO()
        output.writelines(''.join([l.text for l in self.lines if l.density > density]))
        return output.getvalue()
   

采用FDR方法统计意义上的学习之后,得到的新的文本密度p为0.59.

最后简单对比下本文设计和实现的三种算法:ANN算法和估值算法总体性能较好,但是FDR在控制错误上具有很好的表现;如果应用需求很高的错误率,那么FDR是一个较好的选择;NN算法的最大问题是需要有学习的样本;估值算法对于简单的网页具有简单高效的特点.

光風(gfn)

基于Subversion的版本管理流程

摘要:本文围绕开源版本控制软件Subverison,结合开发涉及角色描述版本控制管理流程


涉及角色:
  • 程序员
  • 小组责任人
  • 项目经理


代码仓库:
  • 开发目录(以下称trunk),包含各个小组的开发目录
  • 里程碑目录(以下称tags),包含面向集成测试的里程碑版本
  • 生产目录(以下称release),包含用于生产环境的代码


协同流程:



  • 程序员
  1. 根据项目经理、小组责任人的分配的任务从trunk检出对应模块目录,进行功能开发;
  2. 在分配给小组的开发服务器上经行单元测试;
  3. 修正集成测试反馈的代码缺陷,重复步骤2)后交付责任人。


  • 小组责任人
  1. 在各个里程碑期间,保证组内程序员开发的代码通过单元测试;
  2. 与项目经理沟通后,确定当前小组负责模块的稳定版本,提交至项目经理指定的tags版本目录。
  3. 协助项目经理经行集成测试,接受测试反馈,并组织修复,重复步骤2)




  • 项目经理
  1. 召集各小组负责人在里程碑点进行集成测试;
  2. 测试产生的问题反馈至相应模块责任人;
  3. 确认通过集成测试的系统版本,提交至release,并根据实际情况安排部署。




也谈网页正文提取[上]


看到这里,如果有看官不知道啥叫正文提取,那我只能说,大哥我真的没有忽悠您,我既没说"网页去噪",也没说互联网的"自动摘要",更没说海量互联网数据的"文本挖掘"。由此可见本博是个很厚道的人,会手把手教你如何完成这个看起来牛逼实则很简单的一件事情,绝对让你感到物超所值(阅读的时间)。

从字面意思上理解,网页的正文提取嘛,无非就是把网页当中对咱最有价值的那部分文章给取出来撒.有点编程经验的朋友肯定都知道,右键网页源文件,看看html代码,取出来有正则匹配一下也就几分钟而已的事情。更好一点的办法,那自然是用上像DOM或者XPath这样专门对付html的利器,多写几次估计一分钟也不要。如果本博也这么干的话,那还怎么体现您的慧眼如矩呢:)

上面说的方法,实际上在垂直搜索引擎的定向抓取中,给一个目标站点利用DOM建立抽取模板是一个很常用也很准确的办法。但是当问题域变得稍微大那么一点点,比方说吧,我觉着谷歌做的不错,我也想搞一个的话,那咋整呢?再利用上面的办法,机械的给每一个页面建立DOM,是会死人的哟XD

那么问题其实就变成了对于任意篇网页,有没有办法"聪明"点的法子,能识别出正文部分呢?
既然我们人是可以做到这一点的,那么就说明存在利用人工智能去解决这个问题的可能性.当然,我们现在不急,先从简单的做起.

网页里面除了可读文本就是链接,图片,视频,以及其他媒体类型.而后面这些东东在HTML里面都是用专有的标签来显示的,而且还是被标签所"夹住"的。那我们来看看一个文本块里,除了标签还剩下来的东西有多少.

通过Python内置的htmllib模块和formatter的配合,我们可以统计出网页中每一行文本中标签和正文的数量.代码如下:


#coding:utf-8

import htmllib,urllib2
import formatter,StringIO

class TrackParser(htmllib.HTMLParser):

    def __init__(self, writer, *args):
        htmllib.HTMLParser.__init__(self,*args)
        self.writer = writer
  
    def parse_starttag(self,i):
        index = htmllib.HTMLParser.parse_starttag(self,i)
        self.writer.index = index
        return index

    def parse_endtag(self,i):
        self.writer.index = i
        return htmllib.HTMLParser.parse_endtag(self,i)


class Para:

    def __init__(self):
        self.text = ''
        self.bytes = 0
        self.density = 0.0

class LineWirter(formatter.AbstractWriter):
    """
    a Formatter instance to get text in lines
    """

    def __init__(self):
        self.last_index = 0
        self.lines = [Para()]
        formatter.AbstractWriter.__init__(self)

    def send_flowing_data(self, data):
        t = len(data)
        self.index += t
        b = self.index - self.last_index
        self.last_index = self.index
        l = self.lines[-1]
        l.text += data
        l.bytes += b

    def send_paragraph(self,blankline):
        if self.lines[-1].text == '':
            return
        self.lines[-1].text += 'n'*(blankline+1)
        self.lines[-1].bytes += 2*(blankline+1)
        self.lines.append(Para())
      
    def send_literal_data(self,data):
        self.send_flowing_data(data)
  
    def send_line_break(self):
        self.send_paragraph(0)



def extract_text(html):

    writer = LineWirter()
    fmt = formatter.AbstractFormatter(writer)
    parser = TrackParser(writer,fmt)
    parser.feed(html)
    parser.close()
    return writer.lines


htmls = urllib2.urlopen("http://ent.hunantv.com/d/x/20091128/503722.html")
print map(lambda x:[x.bytes,len(x.text)],extract_text(htmls.read()))

看着飞速跑过的列表,你是不是恨不得把他给全部写入一个文件来看看结果?在文件尾部加入
q = open("e.csv","w+").writelines('\n'.join(["%s,%s"%(x[0],x[1]) for x in s]))

现在结果变成了一个csv文件鸟,来上个图看看:



上图清晰的表达了该网页的文本分布,根据与页面的比对,我们发现文本所在的区域与相对应的行域保持了某种关系.这似乎说明我们的思路是正确的.

实际上行文本字节数与行总字节数的比值被称为行文本密度.有了这个概念,我们就可以对网页全文扫描计算相应的文本密度,这里我们不妨做一个假设,文本密度在0.5以上的就是我们需要的文本部分,也就是说我们认定某行的文本至少和该行的标签一样多的话,他就可能是我们需要的文本区域.

修改上述代码的两个地方,我们来初试下身手,
在原有的LineWriter里加入:

    def output(self):
        self.compute_density()
        output = StringIO.StringIO()
        for l in self.lines:
            if l.density > 0.5: //这里就是我们设置的行文本密度
                output.write(l.text)
        return output.getvalue()

修改extract_text函数
def extract_text(html):
        writer = LineWirter()
        fmt = formatter.AbstractFormatter(writer)
        parser = TrackParser(writer,fmt)
        parser.feed(html)
        parser.close()
        return writer.output()

文件末尾改成
htmls = urllib2.urlopen("http://ent.hunantv.com/d/x/20091128/503722.html")
s = open('e.html','w+').write(extract_text(htmls.read()))

先透口气,然后平静的点开e.html,喔,你看见了什么!

激动之后,应该会有这么一个疑问,刚才我们设置的文本密度为0.5,这个数字到底是怎么来的?他具有普适性么?

其实这个文本密度是可以计算出来的:
设y为行文本集合,z为行标签集合,则文本密度p为:



设i代表任意行,分别用yi,zi代表任意行文本/标签的长度,设且均符合正态分布.uy,uz分别代表行文本和行标签的平均长度:




计算方差:





如果选择文本行的概率为p,那么标签行就为1-p.相应的各文本项平均长度为







最后得到p的估值:




将我们选用的网页实际情况带入以后,我们得到真实的文本密度p大约为0.53,和估计值很相近.学术界有针对行的做了大量实验,得出新闻资讯类网站的文本密度大约在0.4-0.6左右,sina和sohu的这个值大约都是0.6;博客类网站的文本密度大约在0.7-0.8之间.

作为这个话题上半部分的结束,谈谈这个文本密度的实际应用,除了本文所涉及到的文本抽取以外,文本密度现在被广泛应用于搜索引擎网页价值分析的预处理,同时我们也大体可以看出网站内容的大致分布.

下半部分,本博将引入ANN(神经网络)和FDR(错误控制)的相关方法继续探讨这个话题.敬请围观.


光風(gfn)

Monday, November 23, 2009

数组过滤之bitmap解法

求一个数组中过滤掉重复的元素,并保证原有的元素均存在。

>>> data
['a', 'a', 'z', 'b', 'a', 'b', 'c']
>>> def foo(f,n=0):
    if f%2 != 0:
        l.append(n)
    if f/2 == 1:
        l.append(n+1)
        return l
    n += 1
    foo(f/2,n)

   
>>> l = []
>>> p = foo(reduce(lambda x,y:x|y,map(lambda x:1<<(128-ord(x)),data)))
>>> r = map(lambda x:chr(128-x),l)
>>> r
['z', 'c', 'b', 'a']

March Liu大法

>>def refoo(d, k):
c = d.get(k, 0)
d[k] = c+1
return d

>>> map(lambda p: p[0], filter(lambda p:p[1]==1, reduce(refoo, data, {}).iteritems()))

Wednesday, November 11, 2009

通过Shell Script解决Subversion分支与主干合并

最近公司内部推行了基于Subversion的开发流程规范.程序员在开发功能模块的时候,基本在各自的trunk活动,当完成单元测试后,方可提交至生产版本库.这样一来就涉及到版本合并的问题.

使用Windows的朋友自然不觉得这是一个多麻烦的事情,毕竟有"小乌龟"嘛.可是,作为拥护Unix&Shell以及崇尚"DRY"原则,还不时热爱捣鼓点新鲜玩意儿的本博来说:岂有不用shell实现的道理;)

#!/bin/bash
export trunk_dir=/root/dp-trunk/
export release_dir=/root/dp-release/dp-0.1.0/

trunk=`svn up $trunk_dir|grep revision|awk -F' ' '{ print $3 }'|awk -F'.' '{print $1}'`
release=`svn info $release_dir | grep "Last Changed Rev"|awk -F: '{print $2}'`

info=`svn merge -r $release:$trunk $trunk_dir $release_dir && svn ci -m '' $release_dir`
echo $info

Thursday, November 5, 2009

Perl实现Subversion更新守护进程

和前一篇使用Subverison同步多台服务器代码类似,前者主要解决Subversion向服务器同步完全相同的环境代码,但是当不同的服务器需要的程序并不一样的时候,前者就很捉襟见肘了.于是,这里给出一个Perl的Deamon实现

#!/usr/bin/perl
exit if fork();
while(1) {system("svn up /root/192.168.8.192");sleep(5)};

Wednesday, November 4, 2009

使用Subverison同步多台服务器代码

在实际生产中,一套分布式的程序可能需要被部署在很多环境相同的服务器上.如何实现程序的自动部署不仅可以降低因版本冲突而导致的服务异常,也可以未将来的持续集成做基础准备.

这里采用的代码版本控制程序为Subversion,由中央版本库向多台服务器同步代码的方法有很多,基本上都是通过使用hook(post-commit)来实现的.

这里记录下我的post-commit:

use Net::SSH::Expect;


#此处添本代码仓库需要同步的服务器组
my @cluster = ('192.168.1.191',
               '192.168.1.190',
               '192.168.1.193'
              );


foreach $svr (@cluster) {
   my $ssh = Net::SSH::Expect->new(
   host =$svr,
   password ='xxx',
   user ='xxx',
   raw_pty =1
   );
   my $logins = $ssh->login();
   my $command = $ssh->exec('svn up /path');
   $ssh->close();
}

Friday, October 30, 2009

程序员十月刊阅读随想

从公司借阅了10月刊的程序员杂志,从08年以后,我已经很久没有看过这本杂志了.越来越多的商业广告和充斥满眼的宣传炒作渐渐感觉背离了这样一本杂志的初衷...

这次翻阅倒也是因为March Liu大侠正在研究的ORM之上的语义网数据库模型,个人很感兴趣。于是找来原文围观下,有空的话发一篇随笔上来吧:)

言归正传,本期程序员的前半部分讲了很多关于云计算的厂家宣传,后半部分又介绍了不少国内的虚拟化实践:综合来看,验证了个人对云计算未来物理基础的认识和判断----虚拟化.除了企业IT总体拥有成本之外的考量,虚拟化可以在现有物理机器之上虚拟出大量的node,在软件层面上实现分布式计算,负载均衡等架构技术从而实现系统的健壮,Scalability等特性。而且工业界目前的商业云计算案例似乎也都是构建在虚拟机堆栈之上的,比如Amazon S3.

说道AWS,那还得说说Microsoft的云计算计划Azure,在RDB方向上的发力以及早期具备的API-Entry访问模式使其具有双重DB性质.这点似乎已经成为了数据库云计算方面的领跑者.同时看到微软近期对PHP的大力支持,是否可以认为是微软加大对互联网方向的投入和关注的信号呢?

哦,今天知道了MTK的OS是nucleus...

最后来谈谈关于开放平台的事情。现在越来越多的厂家开始在自己的路线图里出现API和SDK这样的字眼,那么构建开放平台到底是为了什么呢?

一如Apple的App Store通过开放平台来实现产品销量的增加;另一如saleforce通过平台来卖软件获得收入.

其实,个人以为开放平台的构建说到底还是为了更好的增加产品竞争力,实力强大的通过开放来统一产业链;实力弱小的通过开放来增加生存空间和砝码。

Monday, October 26, 2009

Shell脚本收集

清除当前目录下所有emacs的临时文件
  • find . -iname '*~' | xargs rm -f
添加 当前目录下所有未进入版本库的文件
  • svn st| tr '^\?' ' ' | xargs svn add

虚拟化技术漫谈

虚拟化是一个广义的术语,对于不同的人来说可能意味着不同的东西,这要取决他们所处的环境。在计算机科学领域中,虚拟化代表着对计算资源的抽象,而不仅仅 局限于虚拟机的概念。例如对物理内存的抽象,产生了虚拟内存技术,使得应用程序认为其自身拥有连续可用的地址空间(Address Space),而实际上,应用程序的代码和数据可能是被分隔成多个碎片页或段),甚至被交换到磁盘、闪存等外部存储器上,即使物理内存不足,应用程序也能 顺利执行。

虚拟化的历史:
  • 硬件虚拟化,分时系统 IBM System/360
  • 处理器虚拟化,现在程序语言的虚拟里VM
  • 指令集虚拟化,指令集转换
  • 库级虚拟化,例如PC平台上的某些街机模拟器
VMM 调度虚拟机时,将其部分状态恢复到主机系统中。并非所有的状态都需要恢复,例如主机 CR3 寄存器中存放的是 VMM 设置的页表物理地址,而不是 Guest OS 设置的值。主机处理器直接运行 Guest OS 的机器指令,由于 Guest OS运行在低特权级别,当访问主机系统的特权状态(如写 GDT 寄存器)时,权限不足导致主机处理器产生异常,将运行权自动交还给 VMM。此外,外部中断的到来也会导致 VMM 的运行。VMM 可能需要先将 该虚拟机的当前状态写回到状态数据结构中,分析虚拟机被挂起的原因,然后代表 Guest OS 执行相应的特权操作。最简单的情况,如Guest OS 对 CR3 寄存器的修改,只需要更新虚拟机的状态数据结构即可。一般而言,大部分情况下,VMM 需要经过复杂的流程才能完成原本简单的操作。最后 VMM 将运行权还给 Guest OS,Guest OS 从上次被中断的地方继续执行,或处理 VMM “塞”入的虚拟中断和异常。这种经典的虚拟机运行方式被称为 Trap-And-Emulate

VT-x 为 IA 32 处理器增加了两种操作模式:VMX root operation 和 VMX non-root operation。VMM 自己运行在 VMX root operation 模式,VMX non-root operation 模式则由 Guest OS 使用。两种操作模式都支持 Ring 0 ~ Ring 3 这 4 个特权级,因此 VMM 和 Guest OS 都可以自由选择它们所期望的运行级别。 这两种操作模式可以互相转换。运行在 VMX root operation 模式下的 VMM 通过显式调用 VMLAUNCH 或 VMRESUME 指令切换到 VMX non-root operation 模式,硬件自动加载 Guest OS的上下文,于是 Guest OS 获得运行,这种转换称为 VM entry。Guest OS 运行过程中遇到需要 VMM 处理的事件,例如外部中断或缺页异常,或者主动调用 VMCALL 指令调用 VMM 的服务的时候(与系统调用类似),硬件自动挂起 Guest OS,切换到 VMX root operation 模式,恢复 VMM 的运行,这种转换称为 VM exit。VMX root operation 模式下软件的行为与在没有 VT-x 技术的处理器上的行为基本一致;而VMX non-root operation 模式则有很大不同,最主要的区别是此时运行某些指令或遇到某些事件时,发生 VM exit。

实战虚拟化技术之Xen

Xen是由剑桥大学开发的VMM.这里简要记录下安装过程中的几个问题:
使用egrep '(vmx|svm)' /proc/cpuinfo 检测处理器是否支持虚拟化.
如果不支持的话那么很遗憾,只能使用全虚拟化.
接下来,按照手册的说明文件:
make world
make install
然后修改grub的引导,使xen支持的内核可以启动,重启服务器.下面就可以体验xen了

Thursday, October 22, 2009

实战虚拟化技术之VMware Server

在虚拟化技术方案中,最耳熟能详的莫过于VMware的产品了.其桌面端VMware Workstation让很多人体验了虚拟机的快感.在服务器虚拟化领域,它的对应产品就是VMware Server.
本文主要描述了我在虚拟化方案技术选型中对Vmware Server考察的过程.

测试环境
  •  Machine :Dell PowerEdge R200 Intel(R) Pentium(R) Dual CPU E2180 @ 2.00GHz
  •  HostOS: CentOS 4.5
  •  Kernel: Linux2.6.9-89.0.11.EL
  •  Memory:2GB
  •  Software:Vmware Server 2.0.1
从Vmware官方下载二进制安装包,执行vmware-install.pl.根据提示输入你的实际参数,一般情况下直接默认就可以了.安装的最后会提示你是否运行vmware-config.pl.你可以选择yes,接下来如果遇到内核模块支持错误提示的话,可以放狗去搜对应内核模块的vmware-patch.我使用的CentOS4.5倒是没有这个问题.

一切完毕之后,就从浏览器输入服务器ip:8333进入VMware Infrastructure Web Acess,你会发现这和workstation版本几乎没有什么区别.接着就是新建虚拟机了:

 .
我配置的虚拟机参数Memory 256MB;Core 1;HDD 8G,由于使用场景的不同,我很关注I/O效率.所以特意做了一些实验,结果如下:

avg-cpu:  %user   %nice    %sys %iowait   %idle
           0.00    0.00    2.00   25.00   73.00
Device:    rrqm/s wrqm/s   r/s   w/s  rsec/s  wsec/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util
sda          0.00  86.00  0.00 37.00    0.00 32800.00     0.00 16400.00   886.49     6.08  566.00   6.89  25.50
dm-0         0.00   0.00  0.00 88.00    0.00  704.00     0.00   352.00     8.00   703.14 27351.68   2.89  25.40
dm-1         0.00   0.00  0.00  0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00

总体来看.Vmware Server门槛较低,交互界面相对统一;对服务器几乎没有硬性要求.部署方便容易,磁盘镜像可复用.
对于测试型场景来说,性能完全可以满足需求.

Tuesday, October 20, 2009

Emacs Advanced Guide - Chapter 1

子曰 "学而时习之" 


HotKey for Copy & Paster
  • C-@ C-W  剪切
  • C-Y      粘贴
  • C-@ M-W  复制

HotKey for Delete
  • C-D      删除后一个字符
  • C-BK     删除前一个字符
  • C-K      删除到行尾
  • M-D      删除后一个字
  • M-K      删除到段尾
  • C-@      标记起点
  • C-W      剪切到缓冲区
  • C-X U    Undo


HotKey for Cursor
  • C-F       后进一位
  • C-B       前进一位
  • C-N       下一行
  • C-P       前一行
  • M-F       下一个字
  • M-B       前一个字
  • M-A       段首
  • M-E       段位
  • C-A       行首
  • C-E       行尾
  • C-V       上一页
  • M-V       下一页

Monday, October 19, 2009

linux下使用vpn的方法

公司的开发服务器使用VPN连接,在Windows下面直接使用ppoe就可以了,但是在Liunx下面并不一样.这里将我的配置过程记录一下.

由于我所使用的环境是Fedora,所涉及到的软件这里全部采用yum方式安装.
首先需要保证系统上安装pptp的相关软件:

#yum install NetworkManager-pptp

这样你就可以在Gnome Network Manager里面去添加VPN连接了,注意选择PPTP:



下面进行连接配置:
  • Connection Name:随便填写,方便自己即可
  • Gateway: vpn连接的ip或者域名
  • User Name:用户名
  • Password:连接的密码


在Apply之前,点击Advanced,勾选Use Point-to-Point encryption(MPPE),然后OK



到这里,你可以尝试下选择NetworkManager里面的VPN Connection,如果连接成功的话,那么恭喜了;如果失败的话,没关系,我们继续:
很有可能你的VPN连接默认会使用EAP来做认证,但在NetworkManager不能 disable EAP的认证,需要使用gconf-edit才行,在你的终端中输入gconf-editor.

#gconf-editor
然后选择system->networking->connetions,找到子项有vpn连接的数字,在右边新建key,内容如下:


最后,连接到你的vpn服务器吧.