Showing posts with label 子曾经曰过. Show all posts
Showing posts with label 子曾经曰过. Show all posts

Friday, May 14, 2010

并行计算框架 ParallelPython

如何在有限的硬件条件下,使程序耗时更少,或者处理数据量更大?

这个问题在进入多核时代以来,似乎多了一种解决途径,那就是充分利用多处理器(核)来并行的完成计算任务.

这里的多处理环境可以是指一台拥有多核处理器甚至多颗处理器的服务器,也可以是指一个拥有多节点的计算机群。


针对开篇提出的那个问题,解决办法其实都是"分而治之"。从数据分割入手,我们可以得到类似MapReduce的解决方案,类似的Python实现也有不少;从程序角度去看,目前主要是以下几种途径:
  • 大规模并行处理系统(MPP,Figure 1)
  • 对称多处理(SMP,Figure 2)
  • 分布式计算(集群/网格计算,Figure 3)



 Figure 1 MPP
Figure 2 SMP
 
 Figure 3 Cluster

本文主要介绍的ParallelPython(简称pp)框架可以有效支持SMP和集群方式进行并行计算。

根据官方介绍,pp提供了在SMP(多CPU或多核)和集群(通过网络连接的多台计算机)上并行执行Python代码的机制,具有以下特性:
  • 在SMP和集群上并行执行Python代码
  • 易于理解和实现的基于工作的并行机制,便于把穿行应用转换成并行的
  • 自动构造最佳配置(默认时工作进程数量等同于系统处理器数量)
  • 动态处理器分配(允许运行时改变工作处理器数量)
  • 函数的工作缓存(透明的缓存机制确保后续调用降低负载)
  • 动态负载均衡(任务被动态的分配到各个处理器上)
  • 基于SHA的连接加密认证
  • 跨平台移植(Windows/Linux/Unix)
和传统的线程模型不同的是,thread和threading模块无法在字节码一级实现并行。因为Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行。这个GIL限制你在SMP机器上同一时间也 只能执行一条字节码指令。而pp在内部使用进程和进程间通信来组织并行计算。并隐藏了所有内部的细节和复杂性,应用程序只需要提交工作任务并取回结果就可以了。

代码说话:
import pp
nodes = ('10.0.0.1',)
jober = pp.Server(ppservers=nodes)
f = jober.submit(func,args,depfunc,module)

submit函数接受worker具体执行的函数func,参数args以及func内部调用的函数depfunc和涉及到的模块module

相应的节点机器上执行:
./ppservers.py

不过节点机器上可能会报错"Socket connection is broken".解决办法如下:

class Close(Exception):
    pass

def send(self, data):
        bufsz = self.bufsz
        t_size = len(data)
        size = struct.pack('!Q', t_size)
        p_size = self.socket.send(size)
        if p_size == 0:
            raise Close('end connection')

        s_size = 0L
        while s_size < t_size:
            nd_sz = min(bufsz, t_size - s_size)
            p_size = self.socket.send(data[s_size:s_size+nd_sz])
            if p_size == 0:
                raise Close('end connection')
            s_size += p_size

Thursday, May 13, 2010

并发编程利器Eventlet

Eventlet是由第二人生(Secondlife)开源的高度伸缩性的Python网络编程库.


根据官方介绍大致特性如下:
  • 非阻塞I/O模型
  • 协程(Coroutines)使得开发者可以采用阻塞式的开发风格,却能够实现非阻塞I/O的效果
  • 隐式事件调度,使得可以在Python解释器或者应用程序的某一部分去使用Eventlet


关于协程,大致可以理解成允许子程序可以多次暂停和恢复执行,是实现多任务的一种有效手段,具体见这里


在Python的世界里,实现了nonblocking I/O的产品并不算少.比如内置的Asyncore和著名的Twisted.相比之下,Eventlet是更容易上手和使用的。


举个例子

import eventlet
pool = eventlet.GreenPool()
while True:    pool.spawn(func,args)

上面这段代码,几乎就是使用eventlet的范式:
  • GreenPool 用来实现协程,保证并行;
  • Spawn     用来调用相应的函数,完成具体业务.
每个func之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪,当出现阻塞时,就显式切换到另一 段没有被阻塞的代码段执行,直到原先的阻塞状况消失以后,再人工切换回原来的代码段继续处理.


Eventlet内置提供了一个基于上述原理实现的数据库连接池,目前仅支持MySQL和PostgreSQL.为了测试其性能如何,我参考了gashero的这篇文章,并简化了测试方案.


测试对象分别是MySQLdb(MySQL驱动的Python封装),Eventlet.db_pool,DBUtils


测试代码如下:
import time
import random
import MySQLdb
import eventlet.db_pool as db_pool
from DBUtils.PooledDB import PooledDB

conn_kwargs={'host':'192.168.8.84','user':'root','passwd':'','db':'logs'}
sql="""SELECT * FROM test WHERE id=%d"""
pooled=db_pool.ConnectionPool(MySQLdb,**conn_kwargs)
pooldb=PooledDB(MySQLdb,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    return cur

def print_now():
    print time.strftime("%H:%M:%S")
    return

def test1(times):
    print_now()
    for i in range(0,times):
        conn=MySQLdb.connect(**conn_kwargs)
        r = query(conn)
        r.close()
        conn.close()
    print_now()
    return

def test2(times):
    print_now()
    for i in range(0,times):
        conn=pooled.get()
        try:
            query(conn)
        finally:
            pooled.put(conn)
    print_now()
    return

def test3(times):
    print_now()
    for i in range(0,times):
        conn=pooldb.connection()
        r=query(conn)
        r.close()
        conn.close()
    print_now()
    return


然后进入Python解释器交互环境
Python -i db-pool-test.py
>>> test1(10000) //MySQLdb
16:04:34
16:11:25
>>> test2(10000) //Event
16:12:35
16:15:22
>>> test3(10000) //DBUtils
16:15:28
16:18:09

总体来看,和传统的MySQLdb相比,性能有了很大的提升,和DBUtils差别并不是很明显.


协程凶猛啊!

Thursday, April 15, 2010

芒果圈SNS用户社交分析图谱[上]

SNS(Social Networking Services,传说中的社会化网络服务)是当今互联网上很潮的一个应用.专指旨在帮助人们建立社会性网络的互联网应用服务.

几乎各大互联网公司都推出了类似的产品,公司的芒果圈就是其中之一.上线运营小半年以来,各项指标都还不错,基本保持平稳增长趋势. 某个月黑风高的夜晚,我突然心血来潮,想看看社区当中用户的交往情况如何,也可以为下一阶段若干产品开发提供相关思路.

从用户评论关系入手,通过数据库将数据做了格式化(csv),通过对偶来表示用户之间的交互:

3,14822   //3号用户对14822号用户评论了一次哦
4,14822
14822,4
5,14880

这个简单的数据表示的关系图是这样的


最终得到的数据大约有20万个左右这样的点,将所有的节点和边的关系映射出来之后得到:

中间那个蓝色的点是我们的客服,围绕在周围的几乎就是社区的意见领袖了,大量的3-4节点自组织结构说明社区存在很多彼此熟悉的小圈子.看来初步验证了有人的地方就有江湖啊

Tuesday, March 16, 2010

I don't wanna miss you

兜兜去了广州.


I don't want to miss a thing

I could stay awake just to hear you breathing

Watch you smile while you are sleeping

While you're far away and dreaming

I could spend my life in this sweet surrender

I could stay lost in this moment forever

Every moment spent with you is a moment I treasure



I don't wanna close my eyes

I don't wanna fall asleep

Cause I'd miss you, baby

And I don't wanna miss a thing

Cause even when I dream of you

The sweetest dream will never do

I'd still miss you, baby

And I don't wanna miss a thing



Lying close to you feeling your heart beating

And I'm wondering what you're dreaming

Wondering if it's me you're seeing

Then I kiss your eyes and thank God we're together

And I just wanna stay with you

In this moment forever, forever and ever



I don't wanna close my eyes

I don't wanna fall asleep

Cause I'd miss you, baby

And I don't wanna miss a thing

Cause even when I dream of you

The sweetest dream will never do

I'd still miss you, baby

And I don't wanna miss a thing



I don't wanna miss one smile

I don't wanna miss one kiss

Well, I just wanna be with you

Right here with you, just like this

I just wanna hold you close

Feel your heart so close to mine

And stay here in this moment

For all the rest of time



Don't wanna close my eyes

Don't wanna fall asleep

Cause I'd miss you, baby

And I don't wanna miss a thing

Cause even when I dream of you

The sweetest dream will never do

Cause I'd still miss you, baby

And I don't wanna miss a thing



I don't wanna close my eyes

I don't wanna fall asleep

Cause I'd miss you, baby

And I don't wanna miss a thing

Cause even when I dream of you

The sweetest dream will never do

I'd still miss you, baby

And I don't wanna miss a thing



Don't wanna close my eyes

Don't wanna fall asleep, yeah

I don't wanna miss a thing



''世界末日''主題曲中文翻譯

I don't want to miss a thing(我不願錯過這一切)

為了聽見你的呼吸,我可以不睡

在你沉睡時,注視著你的笑容

當你夢見遠方

我願用一輩子甜蜜的臣服於你

永遠迷失在這片刻間

和你在一起的每一刻都是我所珍愛的時刻



不願閉上眼睛

不願入睡

因為我可能會錯過你,寶貝

我不願錯過這一切

因為即使我夢見你

最美的夢也無法取代

我依然想念你,寶貝

我不願錯過任何事



躺在你身旁,感覺著你的心跳

我想知道你夢見了什麼

你是否在夢中遇見了我

於是,我吻了你的雙眼

感謝上帝讓我倆在一起

我要永遠和你停留在這個時刻

生生世世



我不願錯過任何一個笑容

我不願錯過任何一個吻

我就是要和你在一起

像現在一樣

我要緊緊的抱著你

感受你的心貼近我的心

在此時此地

和我倆的餘生

Monday, February 1, 2010

22岁,人生第一个十万....

05年大一,看着宿舍楼下"IT培训成就十万年薪"的商业广告,我嘴角的微笑告诉别人,我的不屑.作为ACM教练的我,觉得这个数字很容易得到...

07年大三,退学的我,为了生计每日奔波,做着一个普通的埃踢民工,会因为每月10号多出来的2k而欢呼不已,会在周末的时候带着兜兜出去"吃大餐",那年我看到一套房子的首付也不需要十万...

09年正式工作刚好一年,出来混社会差不多两年,我每日在忙碌工作之余,常常在想啥时候能年薪十万丫,兜兜告诉我不急,生活的意义不在于此...另一方面我开始留恋数码新品,开始讲究生活质量,却没有为兜兜做过什么让她可以放下担心的事情....这一年,我的东西多了,兜兜的没变多少

10年2月1日,财务的变动让我开始关心今年自己的总收入到底有多少,在网银的总计一栏我赫然发现那个数字超过了十万,而我似乎开始麻木了...

再过几个小时就是我23岁的生日,十万,一个曾经的梦想对我来说承载了太多的回忆和承诺.比这些更重要的是 感谢兜兜从那个时候开始就一直在我身边,无论是几平米的小屋,抑或是下雪天去雪地里刨食,不论是我赋闲待业,也不论我是连续加班,不离不弃.而我欠她的太多太多...
如果说男人天生需要奋斗,那么世俗的金钱和权利即是一座座里程碑,那么这样一个开始或许意味着前方将有更难走的路和更难爬的山,但是这些之后的风光是否更加美丽,更加绚丽?

默默许下人生第二个心愿,希望在这个路标之后,到达下一个真正意义上的里程碑的时候,我可以看到兜兜在我身边幸福的微笑.

Friday, December 11, 2009

使用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

基于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()))

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

虚拟化技术漫谈

虚拟化是一个广义的术语,对于不同的人来说可能意味着不同的东西,这要取决他们所处的环境。在计算机科学领域中,虚拟化代表着对计算资源的抽象,而不仅仅 局限于虚拟机的概念。例如对物理内存的抽象,产生了虚拟内存技术,使得应用程序认为其自身拥有连续可用的地址空间(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。

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       下一页