2012年10月8日星期一

使用python/casperjs编写终极爬虫-客户端App的抓取

1.缘起

随着移动互联网的发展,现在写web和我三年前刚开始写爬虫的时候已经改变了太多。特别是在node以及javascript/ruby社区的努力下,以往“服务器端”做的事情都慢慢搬到了“浏览器”来实现,最极端的例子可能是meteor了 ,写web程序无需划分前端后端的时代已经到来了。。。

在这一方面,Google一向是最激进的。纵观Google目前的产品线,社交的Google Plus,网站分析的Google Analytics,Google目前赖以生存的Google Adwords等,如果想下载源码,用ElementTree来解析网页,那什么都得不到,因为Google的数据都是通过Ajax调用经过数据混淆处理 的数据,然后用JavaScript进行解析渲染到页面上的。

本来这种事情也不算太多,忍一忍就行了,不过最近因业务需要,经常需要上Google的Keyword Tools来分析特定关键字的搜索量

图为关键字搜索的截图



图为Google经过混淆处理的Ajax返回结果


要把这么费劲的事情自动化倒也不难,因为Google也提供了API来做,Adwords项目的TargetingIdeaService就是来做这个的,问题是Google的API调用需要花钱,而如果能用爬虫技术来爬取这个结果,就能省去不必要的额外开销。

2. Selenium WebDriver

 由于要解析执行复杂的JavaScript,必须有一个Full Stack的浏览器JavaScript环境,这种环境三年前的话,可能只能诉诸于于selenium,selenium是一款多语言的浏览器Driver,它最大的优点在于,提供了从命令行统一操控多种不同浏览器的方法,这极大地方便了web产品的兼容性自动化测试。

2.1 在没有图形界面的服务器上安装和使用Selenium

安装selenium非常简单,pip install selenium 即可,但是要让firefox/chrome工作,没有图形界面的话,还是要费一番功夫的。
推荐的做法是
apt-get install xvfb
Xvfb :99 -ac -screen 0 1024x768x8&
export DISPLAY=:99
Selenium的安装和配置在此就不多说了,值得注意的是,如果是Ubuntu用户,并且要使用Chrome的话,必须额外下载一个 chromedriver,并且把安装的chromium-browser链接到/usr/bin/google-chrome,否则将无法运行。

2.2 爬取Keywords

 先总结一下Adwords的使用方法吧,要能正常使用Adwords,必须要有一个开通Adwords的Google Account,这倒不是很难,只要访问 http://adwords.google.com ,Google会协助创建账号,如果还没有的话,其次就是登陆了。





首先别忘了开一个浏览器先

from selenium import webdriver
driver = webdriver.Firefox()
 
 
driver.find_element_by_id("Email").send_keys(email)
driver.find_element_by_id("Passwd").send_keys(passwd)
driver.find_element_by_id('signIn').submit()
 
登陆后,我们发现需要访问一个类似 https://adwords.google.com/o/Targeting/Explorer 的网页才能跳转到关键字工具,于是我们手动生成一下这个网页




search = re.compile(r'(\?[^#]*)#').search(driver.current_url).group(1)
kwurl='https://adwords.google.com/o/Targeting/Explorer'+search+'&__o=cues&ideaRequestType=KEYWORD_IDEAS'

到了工具主页以后,事情就变得Tricky起来了。因为整个关键字工具都是个客户端App, 在全部文件载入完成以后,页面不会直接渲染完毕,而是要经过复杂的JavaScript运算后页面才会完整显示。然而Selenium WebDriver并不知道这一点,所以我们要让他知道。
在这里,我们要等待Search按钮在浏览器中出现以后,才能确认网页加载完毕,Selenium WebDriver有两种方式可以实现这一点,我偷懒用了全局的默认等待机制:
driver.implicitly_wait(30)
于是Selenium就会在找不到页面元素的时候自动等候不超过30秒
接下来,等待输入框和Search按钮出现后提交搜索iphone关键字的请求
driver.find_element_by_class_name("sEAB").send_keys("iphone")
find_element_by_css_selector("button.gwt-Button").click()
然后我们继续等待class为sLNB的table的出现,并解析结果
result = {}
texts = driver.find_elements_by_xpath('//table[@class="sLNB"]')\
            [0].text.split()
for i in range(1, len(texts)/4):
    result[ texts[i*4] ] = (texts[i*4+2], texts[i*4+3])
这里我们使用了xpath来提取网页特征,这也算是写爬虫的必备吧。
完整的例子见: https://gist.github.com/3798896 替换email和passwd后直接就能用了

3. JavaScript Headless解决方案


随着Node以及随之而来的JavaScript社区的进化,如今的我们就幸福多了。远的我 们有phantomjs, 一个Headless的WebKit Driver,意味着可以无需GUI,完全模拟Chrome/Safari的操作。 近的有casperjs(基于phantomjs的好用封装),zombie(相比phantomjs的优势是可以和node集成)等。
其中非常可惜地是,zombiejs似乎对富JavaScript网站支持得有问题,所以后来我还是只能用casperjs来进行测试。Headless的方案因为不需要渲染GUI,执行速度约为Selenium方案的三倍。
另外由于这是纯JavaScript的方案,于是我们可以直接在例如Chrome的Console模式下写代码控制浏览器,不存在如Selenium那样还需要语义转换,非常简洁直观。例如利用W3C Selectors API Level 1所提供的querySelector来快速选取元素,对表单进行submit,对按钮进行click,甚至可以执行自定义JavaScript脚本以便按一定规律对页面进行操控。
但是casperjs或者说phantomjs的弱点是不支持除了文件读写和浏览器操作以外 的一切*nix IPC惯用伎俩,socket神马的统统不支持,1.4版本以后才加入了一个webserver用于和外界通信,但是用httpserver来和外界通 信?我有点抵触就是了。
废话不说了,casperjs的代码看起来就是这样,登陆:



var casper = require('casper').create({verbose:true,logLevel:"debug"});
 
casper.start('http://adwords.google.com');
 
casper.thenEvaluate(function login(email, passwd) {
    document.querySelector('#Email').setAttribute('value', email);
    document.querySelector('#Passwd').setAttribute('value', passwd);
    document.querySelector('form').submit();
}, {email:email, passwd:passwd});
 
casper.waitForSelector(".aw-cues-item", function() {
    kwurl = this.evaluate(function(){
        var search = document.location.search;
        return 'https://adwords.google.com/o/Targeting/Explorer'+search+'&__o=cues&ideaRequestType=KEYWORD_IDEAS';
    })
});



与Selenium类似,因为页面都是Ajax调用的,我们需要明确地“等待某个元素出现”,即:waitForSelector,casperjs的文档既简洁又漂亮,不妨多逛逛。
值得一提的是,casperjs一定要调用casper.run方法,之前的start, then等方法,只是把步骤封装到了this._steps里面,只有在run的时候才会真正执行,所以casperjs设计流程的时候会很痛 苦,for/each之类的手法有时并不好用。
这个时候需要用JavaScript编程比较常用的递归化的方法,参见https://github.com/n1k0/casperjs/blob/master/samples/dynamic.js 这个例子。我在完整的casperjs代码里面也是这么做的。
具体逻辑的实现和selenium类似,我就不废话了,完整的例子参见: https://gist.github.com/3798922

2012年10月7日星期日

怎样写出能翻墙的爬虫程序?

1、安装goagent(不会安装的google一下)

安装goagnet是为了爬虫翻墙需要

2、在 python中加入代理部分:


# -*- coding: utf-8 -*-
import urllib2
import os,sys
proxies={'http':'127.0.0.1:8087'}
proxy_support=urllib2.ProxyHandler(proxies)
opener=urllib2.build_opener(proxy_support,urllib2.HTTPHandler)
urllib2.install_opener(opener)
a=urllib2.urlopen('http://www.twitter.com/').read()
print a 


通过print语句我们看到了twitter的首页代码,可见这个小爬虫翻墙成功了。

python中使用squid代理服务器

一、ubuntu下安装squid代理服务器

安装:

 1sudo apt-get install squid

 修改:

 2、sudo gedit /etc/squid3/squid.conf

更改/etc/squid3/squid.conf文件.如下几项:

A 、port :3128
B 、visible_hostname
C、 将http_access deny all改为allow all

3、设置好后:

sudo /etc/init.d/squid3 stop

sudo squid3 -z

sudo /etc/init.d/squid3 reload

sudo /etc/init.d/squid3 start

就成功了。

 客户端的配置 :端口要记得是3128

二、在python中使用squid代理服务器

# -*- coding: utf-8 -*-
import urllib2
import os,sys
proxies={'http':'127.0.0.1:3128'}
proxy_support=urllib2.ProxyHandler(proxies)
opener=urllib2.build_opener(proxy_support,urllib2.HTTPHandler)
urllib2.install_opener(opener)
a=urllib2.urlopen('http://123cha.com/').read()

print a
open('whatmyip1','w').write(a)

"""
path=os.path.dirname(os.path.realpath(sys.argv[0]))

print path

print "当前脚本的名字:" + sys.argv[0]

print len(sys.argv[0])

print len(sys.argv)

print "path has", len(sys.path),"members"
print sys.path

"""

2012年10月3日星期三

怎样用python模仿浏览器行为

在信息抓取过程中,经常遇到下面的问题 :


0、你访问网站过于频繁,被拒绝访问;
1、你被网站认为是使用程序访问,被拒绝访问;
2、你同时建立的连接过多,被拒绝访问。





以下的方法可以解决上述问题:


1、你建立的网络连接一定要模拟出浏览器的特征属性;

2、 在访问同一个网站的时候,应该用连接池;
连接池的意思是在本地列表中维护你已经创建出来的连接。当有连接需要的时候,从这个池中任意挑选一个。这样可以减少打开连接的次数和打开的数量

3、 遇到timeout的时候可以尝试多次。 建立连接池和retry:
def retrieve_url(url,retries= 3):
    url_parse = urlparse.urlsplit(url)
    hostname = url_parse[ 1]
    if hostname in http_connection_pool:
        connections = http_connection_pool[hostname]
        if len(connections) < 5:
            opener = urllib2.build_opener(support, urllib2.HTTPHandler )
            connections.append(opener)
            http_connection_pool[hostname]=connections
        else:
            i = random.randint( 0, 4)
            opener = connections[i]
    else:
        opener = urllib2.build_opener( support, urllib2.HTTPHandler )
        http_connection_pool[hostname]=[opener]
    try:
        req = urllib2.Request(
            url = url,
            headers = headers
        )
        return opener.open(req).read()
    except Exception:
        if retries> 0:
                return retrieve_url(url,retries- 1)
        else:
            logger.exception( “Can’t retrieve content from url:”+url)
            return None


2012年9月21日星期五

利用低端VPS开设VPN

VPS是就是虚拟主机,一半都是高级货,不过也有例外,例如rapidxen
————————————–
rapidxen是我见过的vps主机中最适合做VPN的,原因为
1.便宜,6刀就可以租用
2.带宽高,6刀就有600G每月的带宽
3.性能不错,CPU没有限制,pptpd转包时其实还挺耗费CPU的,一般都是CPU先于带宽挂掉
4.据我评估可能可以同时支持50个左右的链接,不过我还是推荐5-10人合用
————————————–

在使用了coupon后(coupon哪里有?随便搜索一下就有了),128M的最低端空间只要6刀,足够了。
这次我写个详细点的攻略吧,对象是ubuntu的初学者,对ubuntu/linux以及vps都有一定的认知
=====================================
一、开通VPN
=====================================
1.登录,http://rapidxen.net/plans
2.点选7.49刀的第一个方案
3.填写资料,在OS中选择Ubuntu 8.04
4.提交、付钱(paypal)、等待
5.在一定时间之后(这个指不定的,一般会在一天以内),就会收到email了
6.用ssh登录: ssh root@IP ,输入它给的密码
=====================================
二、安装PPTPD
=====================================
0.(因为是root也可以不用sudo,推荐新建一个权限低的用户来做后面的事情)
1. sudo apt-get pptpd
2. sudo nano /etc/pptpd.conf,使得除了注释以外如下:
option /etc/ppp/pptpd-options
logwtmp
localip 192.168.0.1
remoteip 192.168.0.234-238,192.168.0.245
3.sudo nano /etc/ppp/chap-secrets,使得除了注释以外如下:
username pptpd password *
4.sudo nano /etc/ppp/pptpd-options,使得除了注释以外如下:(其实什么都不用改就行了)

===================================
三、运行服务
===================================
1.sudo /etc/init.d/pptpd restart
2.设置转发:
# enable IP forwarding in /etc/sysctl.conf:
net.ipv4.ip_forward = 1
# enable IP forwarding now:
echo 1 > /proc/sys/net/ipv4/ip_forward
3.NAT和防火墙端口(记不得了,应该可以省略这步,因为他们端口都是打开的)
不行的话,参照这篇文章吧
http://blog.chinaitlab.com/html/30/104830-166896.html
===================================
四、Windows客户端设置
===================================
省略
===================================
五、性能问题
===================================
1.这么个vpn的带宽大约是每个月600G左右,5个人使用是绰绰有余
2.如果路由一下,把不用VPN的地址用本地访问,那么50个人用都不会有问题
3.路由方法见这个链接:
https://docs.google.com/View?id=dg6xfw7w_0hbbkmwcg
4.提供一个精简版的教程(For Windows):
a.下载这个文件:http://vpnscript.pastebin.com/pastebin.php?dl=f731e6538
b.重命名为vpnroute.bat
c.每次拨号之前执行之
===================================

ps:http://forums.ramhost.org/bbs/viewtopic.php?id=4

利用google搜索网络摄像头



因为能看到实时画面的原因,有的人喜欢在网络上找一些公共的摄像头看看,纯粹是猎奇吧,下面的方法可以利用Google搜索引擎找到一些网络上的在线摄像头:

1、打开 Google .
2、搜索 inurl:/view.index.shtml
3、点开搜索结果中的链接就可以查看在线摄像头了


下面的列出的也可以作为搜索网络摄像头关键词:

inurl:/view.shtml
intitle:”Live View / - AXIS” | inurl:view/view.shtml^
inurl:ViewerFrame?Mode=
inurl:ViewerFrame?Mode=Refresh

python 学习手册不是一本好的python入门书




python程序设计语言当下变得越来越流行,介绍python的书也越来越多,我买的第一本书是《python学习手册》,前前后后读了两遍,最近又从网络上下载了一本python语言的PDF书,看了一些后觉得非常符合自己的思路,和《python学习手册》对比一下,发现《python学习手册》有如下一些问题:

0、文字太多,图片、代码示例太少;

1、理论性太强,不太适合刚刚入门的初学者;

2、面向对象部分介绍的较少,书中介绍的对初学者来说也不易理解;

3、网络编程没有涉及。

PS:以上都是个人理解,我觉得选择第一本程序语言的书要符合自己的思维习惯或风格,这样理解起来会容易点,入门后再选择与自己认知风格不同的书,可以拓展思路。

2012年9月19日星期三

SQL vs NoSQL:数据库并发写入性能比拼

最近听说了很多关于NoSQL的新闻,比如之前Sourceforge改用MongoDB,Digg改用Cassandra等等。再加上之前做数据库比较时有人推荐我mongodb,所以也搜索了一下NoSQL,觉得NoSQL可能真的是未来的趋势。
`

NoSQL vs SQL

传统SQL数据库为了实现ACID(atomicity, consistency, isolation, durability),往往需要频繁应用文件锁,这使得其在现代的web2.0应用中越来越捉襟见肘。现在SNS网站每一个点击都是一条/多条查询,对 数据库写的并发要求非常之高,而传统数据库无法很好地应对这种需求。而仔细想来SNS中大部分需求并不要求ACID,比如Like/Unlike投票等 等。

NoSQL吸取了教训,比如有些NoSQL采用了eventually consistency的概念,在没有Update操作一段时间后,数据库将最终是“consistency”的,显然这样的数据库将能更好的支持高并发读写。
`
SQL数据库是基于schema的,这对时时刻刻更新着的web2.0应用开发者来说是个噩梦:随时随地有新的应用出现,旧的数据库无法适应新的应用,只能不停地更新schema,或者做补丁表,如此一来要么schema越发混乱,要么就是数据库频繁升级而耗时耗力耗钱。
`
NoSQL一般就没有schema这种概念,大部分NoSQL都直接保存json类的Row,比如一个记录可以是
{ ‘id’ = 1, name = ‘Bob’, phone = 38492839 },这样扩展升级非常方便,比如需要地址信息直接加入 address=’blahblah’ 即可。
`
传统SQL很难进行分布式应用,即使可以也往往代价高昂。而NoSQL则很好地解决了这个问题:他们一般都直接从分布式系统中吸取了Map/Reduce方法,从而很容易就可以处理规模急速增加的问题。
`
推荐robbin牛的NoSQL数据库探讨之一 - 为什么要用非关系数据库?一文,介绍了主流的一些NoSQL系统,还有这个站http://nosql-database.org/收集了基本上目前所有的NoSQL系统。
`
总结一下我对NoSQL的看法,NoSQL出现的目的就是为了解决高并发读写的问题,而高并发应用往往需要分布式的数据库来实现高性能和高可靠性,所以NoSQL的关键字就是concurrencyscalability
`

我的瓶颈

我之前主要关注数据库的select性能也就是read性能,在读性能方面SQL数据库并没有明显的劣势,应该说纯粹高并发读的性能的话往往要优于NoSQL数据库,然而一旦涉及写,事情就不一样了。
`
我本来以为自己不会遇到大量写的问题,后来发现即使在simplecd这种简单的应用环境下也会产生大量的并发写:这就是爬VC用户评论的时候。事实上,sqlite3在处理这个问题上非常的力不从心,所以我产生了换个数据库的想法。
`
既然我是要求能高并发读写,干脆就不用SQL了,但是同时我也想测试一下其他SQL的写性能。
我的数据有180万条,总共350M,测试用了10个线程,每个线程做若干次100个数据的bulk写入,然后记录总共耗时。结果如下。
`
innodb: 15.19
myiasm: 14.34
pgsql: 23.41
sqlite3: 锁住了
sqlite3(单线程): 300+
mongodb: 3.82
couchdb: 90
couchdb(单线程):66
`
作为一个MySQL黑,看到这组测试数据我表示压力很大。在SQL数据库中,mysql意外地取得了最佳的成绩,好于pgsql,远好于sqlite。更 令人意外的是myisam居然优于号称insert比较快的innodb。不管如何,对我的应用来说,用mysql保存评论数据是一个更为明智的选择。我 对mysql彻底改观了,我宣布我是mysql半黑。以后select-intensive的应用我还是会选择sqlite,但是 insert/update-intensive的应用我就会改用mysql了。
`
MongoDB和CouchDB同为NoSQL,表现却截然相反,MongoDB性能很高,CouchDB的并发性能我只能ORZ,这种性能实在太抱歉了。
`

NoSQL的碎碎念

其实我本来还打算测试cassandra的,可是cassandra用的是java,这首先让我眉头一皱,内存大户我养不起啊,其次看了 cassandra的文档,立刻崩溃,这简直就是没有文档么。(BTW,CouchDB也好不到哪里去,我都是用python-couchdb然后 help(couchdb.client)看用法的)
`
至于CouchDB,可能是因为采用http方式发送请求,所以并发性能糟糕的一塌糊涂,很怀疑它是否有存在的理由。
`
MongoDB是我用下来最讨人喜欢的一个NoSQL。不但文档丰富,使用简单,性能也非常好,它的Map/Reduce查询(很多NoSQL都有)让我 惊叹,数据库可以非常简单地就扩大规模,完全不用理会什么分区分表之类繁琐的问题,可惜这方面我暂时没有需求。但是MongoDB有两大致命问题。
`
第一是删除锁定问题,当批量删除记录时,数据库还是会锁定不让读写。这意味着进行数据清理时会让网站应用失去响应。见locking problems
`
第二是内存占用问题,MongoDB用了操作系统的内存文件映射,这导致操作系统会把所有空闲内存都分配给MongoDB,当MongoDB有这个需要 时。更可怕的是,MongoDB从来不主动释放已经霸占的内存,它只会滚雪球一样越滚越大,除非重启数据库。这样的上下文环境下,MongoDB只适合一 台主机就一个数据库,而没有其他应用的环境,否则一会儿功夫MongoDB就会吃光内存,然后你都fork不出新进程,彻底悲剧。见memory limit
`
总之NoSQL虽然让我眼前一亮,可是目前尝试的一些产品都让人望而生畏,现在的NoSQL都把目光放在了巨型网站上,而没有一个小型的,可以在VPS里 面应用的高性能NoSQL,令我有点失望。NoSQL尚未成熟,很期待它的将来发展,目前来说MySQL还是更好的选择。

推荐一些相关文章

详细介绍了为何从MySQL转移到MongoDB的一篇文章,还指出了迁移过程中遇到的问题和他们的解决方法:

http://blog.boxedice.com/2009/07/25/choosing-a-non-relational-database-why-we-migrated-from-mysql-to-mongodb/

MySQL,MongoDB,Tokyo Cabinet的性能测试对比,写得很好:

http://jayant7k.blogspot.com/2009/08/document-oriented-data-stores.html

从概念上比较了MongoDB和CouchDB实现的不同:
 http://www.mongodb.org/display/DOCS/Comparing+Mongo+DB+and+Couch+DB

2012年9月18日星期二

OpenVZ主机的省内存绝招

前文有做过OpenVZ和Xen主机的性能比较,个人因为OpenVZ的性能较好而更中意OpenVZ主机。
`
OpenVZ的最大问题在于内存,它把虚拟内存也计入你消耗的内存,无形中吃掉了大量的内存,OpenVZ有大量的公共开销免去了内存消耗,但两相比较下来很可能会比Xen占用更多内存。
`
这种多占内存的现象在多线程应用时尤为明显,这是因为一个线程默认就要占用8M的Stack,比如php-cgi5开5个线程的话,啥都没干就要吃掉40M内存。
`
这个省内存绝招就是修改Stack大小:

修改/etc/security/limits.conf
www-data hard stack 256
`
接下来要确保pam_limits模块的加载,ubuntu下面好像默认加载了
修改/etc/pam.d/common-session
session required pam_limits.so
`
重启VPS,取决于不同的应用,应该能有15%-30%的内存省下来了
`
参考:Reducing memory usage

2012年9月17日星期一

光板VPS最小化资源占用配置

嗯,不仅是因为有人问我这方面的事,也是因为有必要记录下来以后就不用重头再来了。
使用环境假定:
ramhost主机,ubuntu 9.10系统,已用ssh dropper登录到了root
配置目标:
在保证日常功能:ssh,ftp,http的情况下尽量节约系统资源,所以http server就用nginx,mysql换成sqlite,博客程序使用wordpress+PDO插件(以使用sqlite)
内存方面,sshd 2M, proftpd 2M, nginx 2+4M, php-cgi 9*2=18M,其他不占内存,一共是28M内存,其他初始杂七杂八的如init,cron,syslog等等加起来10M左右,那么一共才用了38M的 内存,再苛刻的plan都足够用了,当然配置的时候也可以不用那么抠门,呵呵。

1.为安全起见,创建非root用户,修改密码,修改sudo权限:

useradd observer -m
passwd observer
vi /etc/sudoers
> observer ALL=(ALL) ALL
vi /etc/passwd


> 把observer那行的/bin/sh改为/bin/bash
 

su observer


2.安装和简单配置vim编辑器

sudo apt-get install vim
sudo vi /etc/vim/vimrc
> syntax on
> set ts=4
> set enc=utf-8



3.安装编译环境(总会用得到的)

sudo apt-get install build-essential



4.安装sshd,使得可以通过ssh命令(windows下则是putty)直接进行访问

sudo apt-get install ssh



5.安装ftpd,使得可以通过ftp来上传下载文件

sudo apt-get install proftpd
sudo vi /etc/proftpd/proftpd.conf


#下面是一个修改案例,配置好以后可以用本机的用户登录ftp(例如刚才建的observer账户)
> ServerName “proftp server”
> ServerIdent on “Welcome to proftp server”
> UseReverseDNS off
> IdentLookups off
> DefaultRoot ~
> RequireValidShell off
> Maxclients 30
> MaxClientsPerHost 2 #每个客户端限制的连接个数
> #如果想要让某个账户直接访问http文件所在的位置,那么在/etc/passwd中修改home目录,然后再加上类似下行代码,/var/www改为 目录,wwwuser改为给访问权限的用户,记得不要忘记把/var/www的owner改为wwwuser(sudo chown wwwuser /var/www):
> DefaultRoot /var/www wwwuser


6.nginx+php

sudo apt-get install nginx
sudo apt-get install php5-cgi


新建一个脚本文件php-fastcgi,用来监听php请求

#!/bin/bash
BIND=127.0.0.1:9000
USER=www-data
PHP_FCGI_CHILDREN=2
PHP_FCGI_MAX_REQUESTS=1000
PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0
start() {
echo -n "Starting PHP FastCGI: "
start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
RETVAL=$?
echo "$PHP_CGI_NAME."
}
stop() {
echo -n "Stopping PHP FastCGI: "
killall -q -w -u $USER $PHP_CGI
RETVAL=$?
echo "$PHP_CGI_NAME."
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL


然后挪到/etc/init.d/,并启动之

sudo mv php-fastcgi /etc/init.d/
sudo chmod 755 /etc/init.d/php-fastcgi
sudo /etc/init.d/php-fastcgi start


配置nginx的php转发,模板文件,供参考。

sudo vi /etc/nginx/sites-enabled/default

server {
listen 80;
server_name www.app-base.com;

access_log /var/log/nginx/app-base.access.log;
location / {
root /var/www/app-base.com/htdocs/;
index index.html index.htm;
}
fastcgi_index index.php;
location ~ \.php {
include /etc/nginx/fastcgi_params;
keepalive_timeout 0;
fastcgi_param SCRIPT_FILENAME /var/www/app-base.com/htdocs/$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}


启动nginx:

sudo /etc/init.d/nginx start


7.安装wordpress using sqlite
下载wordpress http://wordpress.org/download/
下载PDO插件 http://wordpress.org/extend/plugins/pdo-for-wordpress/
把插件解压到wp-content
然后:

cd wp-content
mkdir database
chmod 777 database
cd ..
cp wp-config-sample.php wp-config.php
vi wp-config.php
> define('DB_COLLATE', '');
> define('DB_TYPE', 'sqlite');


然后就是正常的安装了,访问wp-admin/install.php即可
=========================

2012年9月16日星期日

如何提高互联网流量

提高流量、做用户不是一劳永逸的,必须是一个机动化、持久化、长期的运作。
为什么要做流量?
首先,做流量本质上是做用户,做产品。这不是雇几个小孩,花钱买广告, 或雇几个商务运营就可以自动解决的事情。本质上需要CEO、产品经理自己去想,而这里面其实误区非常多。
一个错误是,简单化冲指标。当年的互联网公司,无论是视频网站还是财经网站,经常在网盟里放一些丰胸、裸女这种火爆的标题,把流量导到自己的网站。他们投 入很多来提升自己网站近期的运营指标。但是,这真的会带来实质性的效果吗?道理听起来很简单,一到执行层面是非常容易忘记的。实际上互联网的推广,包括前 段时间团购网站的推广,80%甚至90%花出去的钱没有意义。
另一个错误是忽视产品。产品、用户获取和运营实际上是三位一体的。不少产品出身的创始人,相信只要产品本身做好了,接下来推广就很简单了。作流量实际上就是产品如何到达用户,这和产品本身是一样重要的,也是个产品问题。比如Zynga, 他的早期游戏本身其实和之前的没有区别,但它是第一批很好的利用了Facebook把游戏从核心玩家到达了蓝海用户. 新的用户到达方法本身就能成为巨大的创新。
第二个基本问题,是什么时候开始花钱获得用户。大部分做流量的方法其实不用花钱。靠自己产品的性能和运营获得用户,本来是获取用户的正当方法,但是为什么要花钱做用户?答案可能有两个:一,加速增长。二,需要种子用户。
这两个都是很有道理的答案。实际操作过程中有两种情况,一是当你获取的用户能够挣钱,回报多于付出的话,你是可以无限制地花钱获得用户,这是毋庸置疑的。很多网游都是这样。
但是如果你赔钱获取用户,你的目标应该是达到自增长的一个点。按照互联网的习惯,当一个品牌或用户群达到一定密度之后,接下来就能实现一定的自增长。如果 不是特别乱的情况下,真实的用户、活跃的用户达到百万级别,或者口碑达到一定的级别,加上真实的美誉度,真实的搜索指数能过万,产品本身又好,就能获得自 增长。而去做用户增长,目的就是为了尽快达到这个自增长点。
如果是这样的话,其实目的就很明确。你要搞清楚那个点是什么?达到那个点需要的真实指标是什么?如果你做的所有推广是有助于达到那个点的话,就是有效的; 如果无助于你达到那个点,比如当年某财经门户找了一些裸女图片放在网络上吸引点击,虽然指标是达到了,但实际上对你积累真实指标达到那个点没有任何帮助, 这就属于完全白费。
做流量的三个“相对真理”
最基本的理念梳理完毕之后,接下来就是我自己这么多年总结的几个相对真理吧。
先说一次传播。一次传播的第一个绝对真理是,在任何一个特定的方法和特定的渠道内你能获取的总流量是有限的,而且你想获取的越多,它的单价越贵,成本越高。
无论是门户网站、搜索引擎还是其他,符合你目标的总用户就是这么多。以搜索引擎为例,里面有最适合你的和最便宜的关键字,比如,你投资一个关键字,搜索流 量一天就五千个,这些获取过来是最便宜的。但是如果你想一天获取一万个流量,那第二个五千流量就去找跟你重合度更低的关键字,但是跟你的用户匹配比例更 低,可能拍卖价格更高,效果更差。总之,如果你想再获得五千个流量的话,就更糟糕。
这就导致两个现象,A,你对于获取流量必须要有非常清晰的认识;B,你获取流量的渠道必须是多元化的。
第二个相对真理是,所有的流量有通用的和专用的区别。你从各种各样的用户入口获得所需要的流量,可以是免费的,可以是收费的,但是都要付出代价。这些资源 交换的代价里,凡是只有你才能用、而别人用起来不方便或者对别人没有用的流量,反而是越便宜的流量。凡是对所有人都有用、人人拿出来都可以给自己引导用户 的,则是非常贵的流量。
举一个例子,谁放到hao123首页,对它都有用,这样的位置就很贵。但是早年的小说网站非常不值钱,五千块就可以把千万级别流量的小说网站的关键位置包月。因为在那个年代,那个流量无论放什么广告,做什么推广都没有用。但是那个流量后来在推广页游却非常有用。
第三个相对真理是,任何流量的做法都不是长期有效,都有时间不长的窗口期,衰退期,而最早发现某个流量的最好挖掘方法的人受益最多。这种方法很快就会达到一个很高的价格,然后逐渐到了衰退期。
比如早年我在谷歌的时候,2006年获取网吧流量是非常便宜的。一是没有人获取,二是没有人知道怎么从网吧把流量给装回来。很多人不但不知道价格,也不知道方法。包括2003年的时候,最早一波知道怎么做搜索引擎优化的站长,用得最好的人占很大的优势。
但是,用户的入口在不断的变化,用户入口本身的规则和方法也在不断变化。所以做流量、做用户不是一劳永逸的,必须是一个机动化、持久化、长期的运作。
每个月都会有一些方法从非常划算变成一般,甚至有些会被取代,然后一些新的渠道和方法会萌芽。比如腾讯开放微信接口了,苹果发布ios6了,任何变化都要想一想和我有什么关系。
具体细节和做法
1、有效用户是谁?
做流量第一个要考虑的是什么问题?用户群。这个阶段想要什么人来用你的产品,往往是很多人做流量之前没想清楚的一件事,这个问题很重要的原因有三个:
1)首先跟你的产品阶段有关。Pinterest在发展早期相对来说比较平稳,所以在早期发展用户的时候没有去发展中国所谓的丝用户;后来的主力用户群 ——美国的中年妇女也没有发展。他首先重点发展的用户是摄影师。因为它是把美丽的照片跟大家分享,如果想吸引真正的主力用户,首先得有非常好的内容。假如 一开始进入一大堆中年妇女,没有好的摄影师作为核心的话,产品目的就毁了。每一个阶段的流量推广,首先得有一个产品目的和用户目的。
2)用户达到引爆点和自增长点是需要密度的。如果花钱买了1000个用户,假设其中有100个用户群是不一样的圈子,那每个用户群就只有10个。这在任何 用户群上都做不到引爆点和自增长点。而如果这1000个人是处在同一个用户群里,比如说某个圈子或者某个学校组织,起码这次推广在这个圈子里能达到自增 长。1000个人过一段时间流失到800个,但形成一个核之后,这800人过段时间会变成1000个,甚至1200个。否则的话,这1000个人的推广费 花出去之后,因为人群密度太小,过几天什么都保留不下来。同样是1000个的量,就会有很大的区别。
3)还有一个很重要的原因是媒体选择。每个媒体的属性千差万别,价格也是千差万别。你按什么标准选择媒体呢?是按媒体的单价?还是按媒体的总流量?这都没法比。这些东西都不重要,最重要的是所谓的有效流量,或者是有效的性价比,包括有效的用户行为。
4) 你想要用户来干嘛
你要想清楚,你的用户是谁以及你想要用户干吗?比如你是一个做高端奢侈品消费的网站,你把广告打在贴吧上,打在hao123上,不是说没有用户,而是你花 钱买一万个流量可能只有10个是跟你相关的人群,而这10个人里可能只有一个在上这个网站的时候想着跟你相关的事情。因为那9个人就算是相关的人群,可能 在这个网站上做的事情导致他根本就不会想你所宣传的东西。
2、对产品的要求
如果产品要做推广,先得保证做好哪几件事情?产品和质量是非常正确的答案。你一旦要准备做推广,或者想明白了这些东西,第一个问题就是产品是不是已经好到可以向你设定的这些人群目标推广的地步。这里面有好几个步骤要做。
1)产品本身值得向这些用户推广。你在已有的用户里做过测试,保证你要推广的那些目标用户在你产品里各方面的表现参数指标都是足够好的。
2)你的产品必须是可被推广的。推广是指你花了钱在别人那里买了位置,或者买了用户到达的机会。用户只会给你10秒钟的时间,而你的产品是不是已经做得清 晰、简明、明确,能在10秒钟内让用户产生兴趣进入探索你的产品,而且在接下来的3分钟内愿意再次使用你的产品。而你要有办法在一周之内继续联系到这个用 户,想办法让用户回头。这是一个基本的概念,就是十秒钟、三分钟和一个礼拜。
3)你要有基本的一些跟踪代码,无论是软件或者网站,从不同渠道来的用户你能给这些用户做精确的定位,并且知道不同渠道的用户之间的差别是什么,效果差别是什么,跟其他的用户区别在哪里。如果你没有这点意识,做推广等于浪费钱。
3、产品推广步骤
一个产品的推广要划分很多步骤,从用户第一次使用到真正把用户维护和固定下来,加在一起是一个完整的链条。
先说前端吧,第一个事情是媒体的选择。这些做法非常多,而且具体到每个媒体、每个方法,这些前端的操作都不一样。比如针对搜索引擎,花钱你买什么关键字,不花钱你如何做SEO?如何让自己的页面出现在更多的搜索结果里,这是一个大原则。
第二个landing page,对于网页来说相对简单,就是点击广告之后终端页面要跳转成功。这要保证你的落地页面足够快,有非常高的兼容性。无论通过什么样的浏览器和什么样速度的网络,都必须在几秒时间里落地成功。
以网站为例,哪怕公司所有其他的服务器都租不起最好的,这个服务器要放在最好的机房里。如果其他页面都用了很多动态超大的图片,这个落地页面就把它做成静态页面或者非常小的页面,让它能够非常快的打开。
如果是客户端,首先跳到一个下载处,让用户下载成功。如果用户没有打开允许从第三来源下载这个网站,这个下载就有可能失败。所以下载的过程中还得有明确的 提示帮助他解决问题。就算下载成功了,用户可能都忘了这件事。尤其是用户是从豌豆荚这样的地方一次批量下载了10个软件,可能都忘了这回事。怎么样把下载 成功率提高,怎么样让用户激活打开它,这都是非常重要的步骤
再举一个例子,很多软件,哪怕不是很大的软件都先给你下载一个200K的下载器,然后再下载。第一,200K很容易。第二,下载20兆的时候有可能下载软 件停了或者用户重新启动机器。第三,就算你重新启动机器,下载器也可以继续下载,而且下载完了之后可以确保让你的软件运行。手机上有很多这样的做法,尤其 是个头比较大的。比如在安卓上,尤其是针对早期机型,如果你的尺寸超过两三兆就会比较麻烦,如果是几十兆,在中国标配的手机SD卡的容量都很小,用户下载 完了根本装不进去。
第三步是用户的一次转换。这里面牵扯到非常多的问题,比如,10秒钟之内你的产品要给用户留下什么印象。首先能不能用一句不超过10个字的话描述清楚你的 产品是干什么的?或者你的产品有很多功能,但是在这个推广阶段非常明确地知道产品想吸引哪个用户群,我想用户来做什么。YY本质上能干很多事情,但在一个 具体的推广阶段是非常特定的。比如YY针对百度贴吧某个私群做推广时,产品的特点就弱化到“这边有无数能听歌的地方”。最好的一点是,能在一句话里面想明 白、看明白这个产品整体是干什么的。如果做不到这一点,起码要想明白针对这个客户群,产品是用来做什么的。如果这点没想明白很难有好的效果。
还有一个根本原因,通过推广捞回来的用户,理论上都是对你没概念的用户,不知道你的产品。而且有很大的可能是,他是用过很多产品的用户,甚至可能也用过你 的竞争对手的产品,那他凭什么要切换到你这边?所以,这十个字里面不但要说出你的产品给他带来的好处,还要明确的告诉他,你能帮助他完成这件事的同时,在 哪一点上比别人牛逼很多。除非你第一个实现这个功能,而且这个功能有很强的需求。比如,如果talkbox刚做推广的时候,只需要说“我是做免费对讲”, 因为当时世界上没有其他任何一个手机软件有这个功能。但是前段时间后出的Voxer,他要做这件事就必须强调“我是多方都能同时说话”,因为你已经不是第 一个做这件事情的人。所以在10个字的描述里,你要想明白这些人在这里面干什么,有什么突出的优点。
还有,这个页面本质的作用是在十秒钟之内让用户明白你是做什么的,并且了解你有什么优点。如果你运气很好,产品本身就有一个现成的页面可以满足用户的需求,但是99%的情况是这个页面必须单做。
一是,在10秒钟之内给用户留下印象,二,跳转到用户对产品有更深层次的了解和探索,或者用户愿意做更深层次的交互,你就要设计好所有的入口。用户看了 10秒钟愿意继续看下去,就要给用户做一个导览。这个时间希望值不要太高,可以把它设定为3分钟。在这3分钟里,你想用户干什么?这种可能性非常多,因为 大家的目的不一样,做法也就不一样。有的可能想进一步加深用户的印象,有的想给用户看一些网站的内容。除了让一部分用户对产品产生印象,还要让一小部分用 户留下点什么,能让你以后主动找到他。
这也是很多网站采取分阶段的注册方法的原因,用户第一次使用只需要留下邮箱地址或者手机号等等。如果要让用户提供更多的信息,一种方法是分阶段,另一种方 法是给用户强有力的理由。比如,没有用户会真的提供生日,但是前段时间我们在一个化妆品网站,要给用户做一个简单的肤质测试,用户不知不觉地就把自己的年 龄告诉我们。这方面要有非常多的技巧,当年我在谷歌的很多产品针对不同的渠道和不同的用户群写的页面都是不一样的,就是精细到这种地步。
再下一步是更重要的过程,可以把一个用户初次访问和之后两周里面,把它当成一个网游的RPG升级游戏。用户对你有了初步的了解,你也知道该怎么去联系他, 如果用户在两周内第二次、第三次到达网站,就应该逐步地让他加深印象和展示更多的功能,逐步提高用户对你的好感,保证在两个礼拜之后用户还能再次访问。用 户导入过程是非常细致的。
像社交游戏甚至传统的客户端网游,在这点往往狠下功夫。因为这对他们来说是直接的生死之线,是马上能见到钱的。但对于不挣钱的产品来说,没有一个硬指标在 后边,往往很忽视这个过程。那些游戏很少会在用户第一次玩或者第一级的时候就把所有的功能展示给用户,也不会在第一级的时候就强迫用户去交友。他们把用户 设定成一个成长曲线,在几天或者到什么阶段给用户什么功能,这是非常明确的事情。
以游戏或者社交网站为例,不同的做法在3天留存率、一周留存率和两周留存率就能差好几倍。所有的这些转换工作,媒体选择好和不好就能有几倍的差 距;Landing page配比每个步骤不同也可以有10%的差距,最后一次转换、二次转换提高很多实际上是很容易的。而二次转换到三次转换,到三天留存,再到七天留存,甚 至到两周留存,每一个环节只要差百分之十几,最后的结果就会差好几倍。完全的留存不但需要你对产品、用户认识清晰,还需要细致的运营、策划、产品开发团队 的介入。
社会化
再简单说一下产品的社会化。无论是在美国、中国,无论是在传统的应用领域、社交领域, 还是电子商务领域,都出现了一个现象:几十人、甚至十几人,在非常短的时间内就做到1000万用户,并且能做到非常高的增长,给用户提供很高的价值。这种 例子特别多。这里面牵扯到几个问题,什么是社会化?产品的内部社会化运营和社会化设计,产品外部的社会化推广。如果要达到这个境界,内容和服务就是由用户 产生的,这些产品的推广和二次传播也是通过社会化的方法来实现的。就是让你的用户成为你的作者,成为你的交易监管者,成为你的营销人员,才能只用十几二十 人以很高的速度发展起来。
就算你不能做到这一点,总有某个环节是可以做到这一点的,比如产品本身不是社会化的,但是推广手段是社会化的。社会化并不是什么神秘的概念,早期没有社交 网站,大家都利用社会化营销,如果没有社会化营销,facebook之前并没有其他的社会化网站,facebook怎么推广自己? 这个话题非常大,我们可以下次再找机会单独讨论这个问题。

2012年9月15日星期六

用Twill工具来做网站压力测试

今天发现了个不错的东东,叫twill,是用python写的主页在这里http://twill.idyll.org/
 这东西好就好在可以用命令行登录,然后模拟用户执行操作,并且可以开无数个这样的进程,测试压力。
我最早只是苦于不知道如何用python处理登录 和cookie的东东(其实我哪个语言都不知道该怎么做,呵呵,这可能要看些html的书,我是静不下心来看,有现成的包就用现成的包呗)

show一段吧:自动搜索google的代码:


setlocal query "twill Python"
 
go http://www.google.com/
 
fv 1 q $query
submit btnI     # use the "I'm feeling lucky" button
 
show

2012年9月14日星期五

用Python指定HTTPConnection的出口IP(specify outgoing ip)

描述:
客户端设置了多IP,想让不同的python脚本使用不同的IP访问远程服务器,怎么办?或者说如何用python指定出口IP(specify outgoing ip)?
解决:
真是花了我不少力气,被折腾死之后,发现python的socket是支持ip绑定的,用socket.bind就行了

再参考了这贴 http://bugs.python.org/issue3972

我写了个HTTPConnection_with_ip_binding的类,继承了httplib.HTTPConnection,修改了初始化,和conn ect函数,加了个参数bindip,试验成功,代码如下:


import httplib
import socket
class HTTPConnection_with_ip_binding(httplib.HTTPConnection):
    def __init__(self, host, port=None, strict=None,
                timeout=socket._GLOBAL_DEFAULT_TIMEOUT, bindip=None):
        httplib.HTTPConnection.__init__(self,host,port,strict,timeout)
        self.bindip = bindip
    def connect(self):
        msg = "getaddrinfo returns an empty list"
        for res in socket.getaddrinfo(self.host, self.port, 0,
                                      socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            try:
               self.sock = socket.socket(af, socktype, proto)
               if self.debuglevel > 0:
                 print "connect: (%s, %s)" % (self.host, self.port)
               if self.bindip is not None:
                 self.sock.bind((self.bindip,0))
               self.sock.connect(sa)
            except socket.error, msg:
                if self.debuglevel > 0:
                    print 'connect fail:', (self.host, self.port)
                if self.sock:
                    self.sock.close()
                self.sock = None
                continue
            break
        if not self.sock:
            raise socket.error, msg
def testip():
    conn = HTTPConnection_with_ip_binding
(host="darwin.bio.uci.edu",bindip="64.13.223.117")
    conn.request("GET", "/cgi-bin/ipecho.pl")
    r1 = conn.getresponse()
    print r1.status, r1.reason
    data1 = r1.read()
    print data1
    conn.close()
if __name__=='__main__':
    testip()
执行发现返回了正确的ip
200 OK
<html><head>
<title>IP Echo</title>
</head>
<body><h2>Your Computer's IP Number</h2><dl>
<dt><b>REMOTE_ADDR</b>
<dd><i>64.13.223.117</i>
<dt><b>REMOTE_HOST</b>
<dd><i></i>
<dt><b>REMOTE_PORT</b>
<dd><i>36112</i>
</dl>
</body></html>

2012年9月13日星期四

使用非官方Google API

最近一直有往论坛灌水的行为,一般都是从google reader上转载
久而久之就有点腻,心想,如果能直接抓取我想要的内容该多好啊
于是就转而搜索起这方面的文章了,本文是找到的第一篇好文,呵呵
业余时间一直在做RSS新闻的Filter,不过这个也是进行的拖拖拉拉有很长时间了。之前很长一段时间有很大的功夫花费在写爬虫、写 Parser,RSS的协议虽然不是很多,不过也有点五花八门,Atom两个,RSS3个还有RDF的。后来有一天注意到Google Reader也提供RSS feed,当时很奇怪,他再提供feeds能干吗呢?查了查Google Reader API,官方始终没有正式版本,不过已经有很多Google Reader API的分析文档,很全,很强大。

于是,爬虫简单了,parser简单了,我不用去管那么多种类的rss协议了,我只要去抓Google Reader就好了,保证数据源及时量又足而且爬来的速度很快。以前一个有个问题就是假如你有200个feed要去爬,五个线程,桌面程序去爬,没有个 30分钟基本是爬不完,现在好了~~推荐写Feed Aggregator实验或者应用的同学使用,呵呵。


具体的API分析可以参考这里:http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI
Google Reader是个非常有意思的项目,同样一个逆向分析API的 文章中写道,他们是先有的Google Feed API,才在这套API上搭建的Google Reader的,该文章下面有GR的工程师确认正确,并表示会很快发布正式API,可惜这篇文章是2005年底的,至今也未见GR有正式的动作。不过据说 变动不大,而没有能发布的主要问题是认证问题。

现在使用Google Service的认证开发上很简单,每个用户会有一个20位的ID,而这个ID跟你的Profile等信息ID没有关系。经常使用GR的人可能会发现地址 栏中经常出现一大串数字,那个就可能是你的User ID了。登录很简单,认证过程也很简单,必须使用POST请求发送用户名、密码和要使用的服务到https://www.google.com /accounts/ClientLogin,请求一个Session ID,这个Session ID代表着你这次登录状态一直有效,直到你退出。之后使用API的命令基本都要使用这个Session ID,Google的服务会检查请求中是否有这个cookie。

所以,为了使用GR API,确定你使用的语言或者库支持HTTP客户端、支持GET和POST、支持Cookie、支持HTTPS连接。但并不是有了这个Session ID就够了,某些操作还需要单独请求一个Token,而这个Token会定时失效的,即便是在登录状态,失效了之后可以再请求新的Token。其实大部分 的认证基本上都是这个过程,不过有的服务还多了一步授权,比如Flickr或者豆瓣(OAuth是不是也需要授权)。

GR的数据格式基本上是兼容Atom的,应该是已经兼容Atom 1.0的了。不过还加上了GR自己的namespace,有挺多有意思的数据在协议里面。比如现在你可以在GR上分享文章,协议类似于<link rel=”via” …>,而对于最近在文章上加Note的,使用的是<gr:annotation>,这些都是新加上去的,业余时间里分析这些报文,查找 新feature对应数据上的变化是非常有趣的。
我现在想做的是基于GR的API提取数据实验自己想做的一些Filter,目的是看看能否对于减轻阅读压力有所帮助。本着分享很COOL的精神,把 自己做这些的细节记录下来应该能够帮助自己坚持做下去,:)。目前GR API读取部分已经做完了,后面还需要做设置不同状态(比如标志为已读,Share)等primitive操作。

PS: 有一个使用GR API的Firefox扩展很cool,叫Feedly, 貌似网站被。。。了。有很多Feature很cool,借助于Firefox和现代计算机的强大,后台技术很少,据说有万(或者十万)行的JS代码,用来 加载Google Reader、FriendFeed、Twitter,把新闻阅读同好友评论推荐融合在一起,并改变新闻阅读的体验,一上来就好像是杂志或者报纸一样……

PS2:跟Aza和Dan简单的问了一下关于如何设计RSS的问题,基本上他们的意见是NO Rss才能解决rss的问题,就好像我们使用铁锹,我们都过于关注铁锹,注意他使用什么金属,使用什么样的把手,但其实根本问题是,用户需要的是一个 hole。

而Dan说了一点也让我感觉挺深刻的,为什么要把RSS reader做成stream形式的,他不是stream,而是data,他更喜欢像报纸那样的阅读体验,我今天没看完,明天我还可以看今天的报纸,我还 可以接着看……Mozilla Labs里面有几个项目利用了rss而且就是这种no rss的理念设计的,有Ambient News,还有Firefox 3.1中全新的New Tab操作(这个还是概念),不过Aza说很快就应该有prototype出来了。

2012年9月12日星期三

服务器间同步/镜像/备份配置备忘录


因 为上了独服,就不能依靠VPS供应商的备份了,其实大部分经济型VPS都没备份的,但是人家硬盘起码是RAID1+0,就算物理故障了只要不太严重不是电 脑爆炸硬盘全毁之类的还是能修复的。独立服务器就不一样了,为了省钱不上RAID,不租备份,所有备份都得自己折腾了,这些配置折腾起来实在烦人,还是得 记下来省下以后再搜索的功夫。

一、同步服务器的选择

备份服务器不需要好的CPU或者内存,只要求硬盘大,网络好就行了,对我来说100G以上的备份空间是必须的,然而100G以上的VPS压根就没几家会提供,便宜的更是难找,好在一不小心发现fdcservers在搞特价,256M内存300G硬盘10Mout100Min无限带宽的VPS只要9刀,没有比这个更合适做备份服务器的了。

二、文件同步

1.rsync
文件同步一般都用rsync。
rsync -avz -e ssh /path backupuser@backuphost:/backuppath
a是全部,v是verbose输出,z是传输时压缩选项,-e ssh是通过ssh来传输,于是会让你输入密码,如果要加入cron的计划任务让它定时运行,那就要配置一下ssh的authorized_keys了。
在源主机当前用户下运行

ssh-keygen -t dsa
 
加若干回车什么都不填,就能在 ~/.ssh/id_dsa_pub 中生成公匙,然后把这个公匙字符串添加到目标主机用户的 ~/.ssh/authorized_keys 文件(无则新建)即可。
这个是基础,我都是用rsync先同步一遍然后在开始试着实时同步的。
2、inotify-tools + rsync
rsync+cron确实方便,但是我需要备份的小文件实在太多了,可能多达2百万,rsync性能成问题;再加上我很贪心地想要实时同步,所以就展开了google之旅。最先尝试的工具叫inotify-tools。

inotify-tools是基于linux 2.6.11内核以后加入的inotify机制的一套工具,http://inotify-tools.sourceforge.net/,debian/ubuntu官方就有源,直接 apt-get install inotify-tools 就能安装。
使用起来也很简单,直接根据说明文件写个脚本就ok了,然后就nohup一下让它在后台运行即可,只要一有“close_write”事件,就会同步过去。
#!/bin/sh
# get the current path
CURPATH=`pwd`
inotifywait -mr --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' \
 -e close_write /监控路径 | while read date time dir file; do
 FILECHANGE=${dir}${file}
 # convert absolute path to relative
 FILECHANGEREL=`echo "$FILECHANGE" | sed 's_'$CURPATH'/__'`
 rsync --progress --relative -vrae 'ssh -p 22'  $FILECHANGEREL 用户名@备份主机:/备份路径 && \
 echo "At ${time} on ${date}, file $FILECHANGE was backed up via rsync"
done 
如果想要modify,create,move,attrib等事件发生时都同步,那么就要加上参数: -e close_write,modify,create,move,attrib 。如果要剔除文件/路径,可以用 –exclude 参数,后跟一个POSIX格式的正则表达式即可。
话虽如此,不知为何我按照manpage的说明这样做时同步就不太灵光了,日志文件非常混乱,根本不知道发生了什么orz。而且因为很多操作可能同 时有多个inotify事件,还有类似vi等生成的临时文件也会试着同步,而exclude语法的POSIX的正则不知为何似乎是没有识别,总之因为日志 不正常我也不明白毛病出在哪里。好在除了inotify-tools之外也有其他运用inotify机制的同步工具,所以再次转移阵地。
3. sersync
sersync是国人用C++写的开源的东东,在StackOverflow上看到有人推荐点进去一看还有中文,好亲切啊。
题外话,sersync居然是用NetBeans开发的,这奇葩的东东我记得超级让人讨厌的,远不如Eclipse,不知道现在好点了没。
sersync相对于inotify-tools的优势是整合了failover机制,自动识别临时文件(不知道实现得如何)不用写一堆 exclude。其他的自称的优势我都不认为是优势,例如多线程机制似乎完全没有必要,增加系统开销,增加出错隐患,但是完全看不出有什么作用。服务器之 间的网速单线程与多线程会有多少区别?
sersync文档很乱,只有基于web页面的;源码也没按GNU的格式来;也没有弄–help;总之让人感觉没用心做的样子(虽然我自己用的东西也经常这样,但是发出来的东西文档还是会好好写的-.-),然而使用下来却意外地顺手。
先修改备份服务器的 /etc/rsyncd.conf,修改path,host allow等值
uid=root gid=root max connections=36000 use chroot=no log file=/var/log/rsyncd.log pid file=/var/run/rsyncd.pid lock file=/var/run/rsyncd.lock [tongbu1] path=备份路径 comment = 注释 ignore errors = yes read only = no hosts allow = 192.168.0.100/24 hosts deny = * 配置好以后,以root运行 rsync –daemon
然后切到主服务器,把binary解压到主服务器,并编辑confxml.xml
 
 
 
 
   
   
 
 
   
   
   
   
   
   
   
   
 
 
   
     
   
   
     
     
     
     
     
   
   
   
     
       
     
   
   
 



其中
  • filter中可用正则表达式定义例外
  • inotify中可选择监听事件的true/false:
  • auth那一段users写目标主机的用户名,start=false说明使用的是key而不是密码
这配置文件定义得有点古怪,不说那个古怪的start标签,为毛要把remote放在localpath里面,而rsync里面居然只有用户信息没有host信息,这种设计实在让我百思不得其解。
不管怎么说,就这样一个简单的同步服务配置好了,然后执行
./sersync2 -d -n 1
就可以自动监控文件的改动并同步了。-n 1是为了让它单线程执行。
sersync最大的缺点是文档极烂和不生成日志(有没有搞错!),文档也就算了,凑合着能用就行,没有日志就有点过分了,脚本语言没有日志还能看 解释器的出错信息,C++没有日志就是找死流啊。目标服务器上倒是有一堆意义不明的日志,类似file received,server connected,之类的日志,不过这貌似是rsync日志,解读不能。
4. inotify-tools ++

用了sersync一天后,发现严重的问题,也许就是多线程惹的祸,如果监听modify事件,则在大文件持续写入的时候只会同步一部分;另外,sersync其超高的内存和CPU占用也是个大问题。
无奈重新研究inotify-tools,这次被我找到症结所在了。
inotify的–exclude参数不太好用,但是过滤文件其实并不一定要用–exclude,也可以读取文件名后用shell脚本来过滤,所以改进了2的方案,新脚本如下:
# get the current path
CURPATH=`pwd`
SRCPATH=/blahblah
DSTPATH=/blahblah
DSTUSER=blahblah
DSTHOST=blah.blah.blah.blah
 
inotifywait -mr --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' \
-e close_write,move,modify $SRCPATH | while read date time dir file; do
    # filter sqlite3 files & logs & hidden files(startswith ".")
    echo $file | egrep -q "(db$|db3$|journal$|log$|^\.)"
    if [ $? -eq 0 ]; then
        continue
    fi
 
    FILECHANGE=${dir}${file}
    # convert absolute path to relative
    FILECHANGEREL=`echo "$FILECHANGE" | sed 's_'$CURPATH'/__'`
 
    rsync --progress --relative -vrae 'ssh -p 22'  $FILECHANGEREL $DSTUSER@$DSTHOST:$DSTPATH && \
    echo "At ${time} on ${date}, file $FILECHANGE was backed up via rsync"
done 


这次效果就很不错了,到底是加入到各个linux发行版软件源的工具,比sersync稳定,内存和CPU也能让我满意了。

三、数据库同步

数据库就不能用rsync来同步了,一是因为尺码太大就算完全一样的文件rsync要用checksum之类的工具判断他们完全一样都得花不少功夫;二是因为它们会随时修改,rsync到一半内容改了那备份服务器的数据库就直接损坏了,备份就没有意义了。

1.mysql

mysql可用replication,虽然这个是用来同步数据库,减轻单数据库访问压力的,实际上完全可以用来当实时备份用。我是参照了这篇攻略。写得非常好,我就纯引用吧,略微修改了一下顺序,简化了一下操作。
1. master机授权slave用户

> GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'%' IDENTIFIED BY 'your_password';
> FLUSH PRIVILEGES;



2. master机修改my.cnf
log-bin = /home/mysql/logs/mysql-bin.log
binlog-do-db=my_database
server-id=1
重启mysql
3. master机锁定数据库,并导出数据库复制到slave机



> FLUSH TABLES WITH READ LOCK;
# mysqldump my_database -u root -p > /home/my_home_dir/database.sql;
# scp -C root@128.0.0.1:/home/my_home_dir/database.sql /home/my_home_dir/



4. master机记录log的status
# mysql -u root -p
> SHOW MASTER STATUS;
+---------------------+----------+-------------------------------+------------------+
| File                | Position | Binlog_Do_DB                  | Binlog_Ignore_DB |
+---------------------+----------+-------------------------------+------------------+
| mysql-bin.000001    | 21197930 | my_database,my_database       |                  |
+---------------------+----------+-------------------------------+------------------+

5. slave机修改my.cnf配置

server-id=2
master-host=128.0.0.1
master-connect-retry=60
master-user=slave_user
master-password=slave_password
replicate-do-db=my_database
 
relay-log = /var/lib/mysql/slave-relay.log
relay-log-index = /var/lib/mysql/slave-relay-log.index

重启数据库

6. slave机建立数据库并导入数据
> CREATE DATABASE my_database;
# mysql -u root -p my_database  &lt;/home/my_home_dir/database.sql
7. slave机根据master机的status设置replication点
> slave stop;
> CHANGE MASTER TO MASTER_HOST='128.0.0.1', MASTER_USER='slave_user', MASTER_PASSWORD='slave_password', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=21197930;
> slave start; 
8. master机解锁数据库
> unlock tables;
大功告成。
Tips1: 值得一提的是我在replication的时候经常碰到duplicate entry错误导致replication被终止,那篇攻略没有提及,其实可以通过slave-skip-errors=1062,来忽略这个错误,另外 1053也可以考虑忽略,这样就避免了网络问题引起的错误。在my.cnf相应位置加入:
slave-skip-errors=1062,1053
Tips2: 一旦出现挂掉或者换hostip,要再同步重复3,4,6,7步骤即可,不行则必须清理日志:
slave:
rm *relay*
rm master.info
master:
cd /home/mysql/logs/; rm -f *;
多master对单slave
搞定了单对单的replication,又有新的问题了:有些其他的东东为了分散风险起见放在了其他的服务器,而上述配置只支持一对一或者一对多的master-slave,但是我却想实现多对一的mysql replication。
这也是有办法的,开N个mysql instance就行了,然而不必那么麻烦,mysql配置文件自动支持多服务器,可以省事不少。
在多mysql起来以后,无非就是重复上述过程而已,偷懒我就不写了。

2.sqlite3

sqlite3不支持replication,想要同步非常得困难。好在这数据库就是我写的,所以只要我在数据库修改的同时也往同步服务器的数据库做出同样的修改即可。
不过sqlite3不支持远程访问,所以同步起来要更费一番周折:用消息队列通知同步服务器更新数据库。
消息队列就有点像加强无数倍的pipe;pipe已经是强大得不得了的东东了,MQ是强化版理所当然地也是强大得不得了。原来我都不知道有这东东,一直很苦手进程间通信应该怎么处理,后来一google发现这玩意儿流行得不得了,真是居家旅行必备的大杀器。
我用的是RabbitMQ,超喜欢他们的文档,在我看来是好的文档的典范啊,所以也就不罗嗦了,以后我要是忘记怎么用了当然也会直接去看他们文档而不是看自己的博客。
与之成反面教材的就是mysql的文档,详细是详细了,可也太琐碎,一点条理也没有,也没有范例可以参考,看完文档经常需要看看其他人写的tutorial来理清一下思路。

四、总结

文件的同步还算简单,几个方法中我还是比较推荐inotify-tools ++方法,sersync虽然有很多缺点,但是在只有小文件的情况下似乎没什么问题,还有中文的配置攻略和扣扣群(orz),e文苦手的可以一试。
而数据库的同步异常麻烦,可能因为每次配置都要锁正在运行的数据库让我压力很大的缘故吧:)定期备份才是更省心省力的方法,否则真是折腾:)
如此一来所有的文件都同步好了,虽然累了个半死,不过在遇上服务器故障的时候就完全不用慌了。:)

2012年9月11日星期二

使用python爬虫抓站的一些技巧总结:进阶篇

以前写过一篇使用python爬虫抓站的一些技巧总结,总结了诸多爬虫使用的方法;那篇东东现在看来还是挺有用的,但是当时很菜(现在也菜,但是比那时进步了不少),很多东西都不是很优,属于”只是能用”这么个层次。这篇进阶篇打算把“能用”提升到“用得省事省心”这个层次。

一、gzip/deflate支持

现在的网页普遍支持gzip压缩,这往往可以解决大量传输时间,以VeryCD的主页为例,未压缩版本247K,压缩了以后45K,为原来的1/5。这就意味着抓取速度会快5倍。
然而python的urllib/urllib2默认都不支持压缩,要返回压缩格式,必须在request的header里面写明’accept- encoding’,然后读取response后更要检查header查看是否有’content-encoding’一项来判断是否需要解码,很繁琐琐 碎。如何让urllib2自动支持gzip, defalte呢?
其实可以继承BaseHanlder类,然后build_opener的方式来处理:
import urllib2
from gzip import GzipFile
from StringIO import StringIO
class ContentEncodingProcessor(urllib2.BaseHandler):
  """A handler to add gzip capabilities to urllib2 requests """
 
  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req
 
  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
        gz = GzipFile(
                    fileobj=StringIO(resp.read()),
                    mode="r"
                  )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
        resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
        gz = StringIO( deflate(resp.read()) )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and
        resp.msg = old_resp.msg
    return resp
 
# deflate support

import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)


然后就简单了:
encoding_support = ContentEncodingProcessor
opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
 
#直接用opener打开网页,如果服务器支持gzip/defalte则自动解压缩
content = opener.open(url).read() 
 

二、更方便地多线程

总结一文的确提及了一个简单的多线程模板,但是那个东东真正应用到程序里面去只会让程序变得支离破碎,不堪入目。在怎么更方便地进行多线程方面我也动了一番脑筋。先想想怎么进行多线程调用最方便呢?

1、用twisted进行异步I/O抓取

事实上更高效的抓取并非一定要用多线程,也可以使用异步I/O法:直接用twisted的getPage方法,然后分别加上异步I/O结束时的callback和errback方法即可。例如可以这么干:


from twisted.web.client import getPage
from twisted.internet import reactor
 
links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
 
def parse_page(data,url):
    print len(data),url
 
def fetch_error(error,url):
    print error.getErrorMessage(),url
 
# 批量抓取链接
for url in links:
    getPage(url,timeout=5) \
        .addCallback(parse_page,url) \ #成功则调用parse_page方法
        .addErrback(fetch_error,url)     #失败则调用fetch_error方法
 
reactor.callLater(5, reactor.stop) #5秒钟后通知reactor结束程序
reactor.run() 
 
twisted人如其名,写的代码实在是太扭曲了,非正常人所能接受,虽然这个简单的例子看上去还好;每次写twisted的程序整个人都扭曲了,累得不得了,文档等于没有,必须得看源码才知道怎么整,唉不提了。
如果要支持gzip/deflate,甚至做一些登陆的扩展,就得为twisted写个新的HTTPClientFactory类诸如此类,我这眉头真是大皱,遂放弃。有毅力者请自行尝试。
这篇讲怎么用twisted来进行批量网址处理的文章不错,由浅入深,深入浅出,可以一看。

2、设计一个简单的多线程抓取类

还是觉得在urllib之类python“本土”的东东里面折腾起来更舒服。试想一下,如果有个Fetcher类,你可以这么调用


f = Fetcher(threads=10) #设定下载线程数为10
for url in urls:
    f.push(url)  #把所有url推入下载队列
while f.taskleft(): #若还有未完成下载的线程
    content = f.pop()  #从下载完成队列中取出结果
    do_with(content) # 处理content内容 


这么个多线程调用简单明了,那么就这么设计吧,首先要有两个队列,用Queue搞定,多线程的基本架构也和“技巧总结”一文类似,push方法和pop方法都比较好处理,都是直接用Queue的方法,taskleft则是如果有“正在运行的任务”或者”队列中的任务”则为是,也好办,于是代码如下:
import urllib2
from threading import Thread,Lock
from Queue import Queue
import time
 
class Fetcher:
    def __init__(self,threads):
        self.opener = urllib2.build_opener(urllib2.HTTPHandler)
        self.lock = Lock() #线程锁
        self.q_req = Queue() #任务队列
        self.q_ans = Queue() #完成队列
        self.threads = threads
        for i in range(threads):
            t = Thread(target=self.threadget)
            t.setDaemon(True)
            t.start()
        self.running = 0
 
    def __del__(self): #解构时需等待两个队列完成
        time.sleep(0.5)
        self.q_req.join()
        self.q_ans.join()
 
    def taskleft(self):
        return self.q_req.qsize()+self.q_ans.qsize()+self.running
 
    def push(self,req):
        self.q_req.put(req)
 
    def pop(self):
        return self.q_ans.get()
 
    def threadget(self):
        while True:
            req = self.q_req.get()
            with self.lock: #要保证该操作的原子性,进入critical area
                self.running += 1
            try:
                ans = self.opener.open(req).read()
            except Exception, what:
                ans = ''
                print what
            self.q_ans.put((req,ans))
            with self.lock:
                self.running -= 1
            self.q_req.task_done()
            time.sleep(0.1) # don't spam
 
if __name__ == "__main__":
    links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
    f = Fetcher(threads=10)
    for url in links:
        f.push(url)
    while f.taskleft():
        url,content = f.pop()
        print url,len(content)



三、一些琐碎的经验

1、连接池:

opener.open和urllib2.urlopen一样,都会新建一个http请求。通常情况下这不是什么问题,因为线性环境下,一秒钟可能 也就新生成一个请求;然而在多线程环境下,每秒钟可以是几十上百个请求,这么干只要几分钟,正常的有理智的服务器一定会封禁你的。
然而在正常的html请求时,保持同时和服务器几十个连接又是很正常的一件事,所以完全可以手动维护一个HttpConnection的池,然后每次抓取时从连接池里面选连接进行连接即可。
这里有一个取巧的方法,就是利用squid做代理服务器来进行抓取,则squid会自动为你维护连接池,还附带数据缓存功能,而且squid本来就是我每个服务器上面必装的东东,何必再自找麻烦写连接池呢。

2、设定线程的栈大小

栈大小的设定将非常显著地影响python的内存占用,python多线程不设置这个值会导致程序占用大量内存,这对openvz的vps来说非常致命。stack_size必须大于32768,实际上应该总要32768*2以上
from threading import stack_size
stack_size(32768*16) 

3、设置失败后自动重试

    def get(self,req,retries=3):
        try:
            response = self.opener.open(req)
            data = response.read()
        except Exception , what:
            print what,req
            if retries>0:
                return self.get(req,retries-1)
            else:
                print 'GET Failed',req
                return ''
        return data 

4、设置超时

 

import socket
socket.setdefaulttimeout(10) #设置10秒后连接超时

5、登陆

登陆更加简化了,首先build_opener中要加入cookie支持,参考“总结”一文;如要登陆VeryCD,给Fetcher新增一个空方法login,并在__init__()中调用,然后继承Fetcher类并override login方法:


def login(self,username,password):
    import urllib
    data=urllib.urlencode({'username':username,
                           'password':password,
                           'continue':'http://www.verycd.com/',
                           'login_submit':u'登录'.encode('utf-8'),
                           'save_cookie':1,})
    url = 'http://www.verycd.com/signin'
    self.opener.open(url,data).read() 
于是在Fetcher初始化时便会自动登录VeryCD网站。

四、总结

如此,把上述所有小技巧都糅合起来就和我目前的私藏最终版的Fetcher类相差不远了,它支持多线程,gzip/deflate压缩,超时设置,自动重试,设置栈大小,自动登录等功能;代码简单,使用方便,性能也不俗,可谓居家旅行,杀人放火,咳咳,之必备工具。
之所以说和最终版差得不远,是因为最终版还有一个保留功能“马甲术”:多代理自动选择。看起来好像仅仅是一个random.choice的区别,其实包含了代理获取,代理验证,代理测速等诸多环节,这就是另一个故事了。

2012年9月10日星期一

备份VeryCD的进一步讨论(一): 爬虫的使用

上文尝试了用wget和httrack为VeryCD做整站镜像,但是这个方法有弱点如下:
=================================
1.备份文件太大,平均一个资源要占用500K左右的硬盘空间,那么整站备份可能要80G(或许还不止)
2.数据是保存了,但是搜索引擎等代码无法保存,所以镜像虽大,却也残废,无法搜索
3.备份目标无法随意指定,无论wget还是httrack,只能被动地跟随链接来备份,比如我想只备份2004年的资源便无法做到。
4.慢网速问题,现在VeryCD主站的速度极慢,用”wget http://www.verycd.com”,平均速度一般都在5K左右,如果用wget或者httrack,由于它们会下载所有有关/无关文件,因此效率极低。
=================================

这些毛病可以说是离线浏览软件的通病。并非更换软件可以解决。
一种解决方案是直接问VeryCD要数据库,那就一了百了,但是傻瓜也知道这是不可能的,商业机密,这个都给你了那还了得?
对于VeryCD来说,什么光盘图标啊,介绍图片或者视频啊,又或者资源后的评论啊,这些东西虽然重要,但是相对于ed2k链接来说就微不足道了,可不可以只备份这些关键信息,而摒弃与资源无关的东东呢?
答案是可以,我们可以写一个爬虫脚本,模仿用户行为从VeryCD上依次浏览资源,取其精华,去其“糟粕”,那么空间问题将顺利解决。其次,因为有 了关键数据,就可以在其上构架自己的搜索引擎,那么问题2也解决了。因为是爬虫脚本,自定义方便,所以问题3问题4也将一并解决。
=====================================
1.最适合当爬虫的语言无疑是python了,用python爬网页只要几行代码
import urllib # 载入urllib模块
r = urllib.urlopen('http://www.verycd.com').read() # 下载到r
print r # 打印保存的内容


2.下载完以后要提取信息,这里要用到正则表达式,正则表达式是啥?
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是*和?。如果你想查找某个目录下的所有的Word文档的话,你 会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需 求——当然,代价就是更复杂——比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数 字的字符串(像010-12345678或0376-7654321)。
这篇文章不错,建议看看:正则表达式30分钟入门

---------------------------------------------------------------------
 
import urllib
import re # 载入正则表达式模块
r = urllib.urlopen('http://www.verycd.com/topics/5420/').read()
 
#提取网页中<h1>到visit之间的内容,这部分内容即为VeryCD的摘要所在区域
abstract = re.compile(r'<h1>.*?visit',re.DOTALL).search(r).group()





---------------------------------------------------------------------------------------------------------

更具体一点的,提取标题
- ----------------------------------------------------------------

title = re.compile(r'<h1>(.*?)</h1>',re.DOTALL).findall(abstract)[0]

- ----------------------------------------------------------------
本文最后会给出提取信息的完整代码
3.提取完该页内容后,就要保存了,考虑到我们要自建搜索引擎,所以我们要把数据存入数据库
数据库都差不多,因为python2.6以后版本都自带了sqlite3,所以我们就偷懒使用sqlite3吧
- ----------------------------------------------------------------
import sqlite3
conn = sqlite3.connect('verycd.sqlite3')
conn.text_factory = str
 
------------------------------------------------------------- 
之后如果要使用sql语句进行数据库操作,只需要
- ----------------------------------------------------------------

    c = conn.cursor()
 
    #执行sql语句
    c.execute('create table a(id integer,dd text)')
 
    conn.commit()
    c.close()
- ---------------------------------------------------------------- 
首先考虑数据库字段,我们要求很简单,保存这个资源是什么以及链接地址的信息,其他无关的我们一概不要。

所以在保存到数据库之前,我们先来设计数据库吧

因为不需要设计用户互动,也不需要设计统计,所以我们只要一张表就够了,很爽,保存啥呢,我保存了,标题,状态,摘要,发布时间,更新时间,类别,ed2k链接,以及链接后一般会有的简介。 ======================================================================== 附1.fetch函数,参数为topic id,提取该topic的关键信息:

   def fetch(id,debug=False):
    urlbase = 'http://www.verycd.com/topics/'
    url = urlbase + str(id) + '/'
    res = urllib.urlopen(url).read()
 
    abstract = re.compile(r'<h1>.*?visit',re.DOTALL).findall
 (res)[0]
 
    title = re.compile(r'<h1>(.*?)</h1>',re.DOTALL).findall 
(abstract)[0]
    status = re.compile(r'"requestWords">(.*?)<',re.DOTALL).
search(abstract).group(1)
    brief = re.compile(r'"font-weight:normal"><span>(.*?)</td>', 
re.DOTALL).search(abstract).group(1)
    brief = re.compile(r'<.*?>',re.DOTALL).sub('',brief).strip()
    pubtime = re.compile(r'"date-time">(.*?)</span>.*?"date-time">
(.*?)</span>',re.DOTALL).findall(abstract)[0]
    category1 = re.compile(r'分类.*?<td>(.*?)&nbsp;&nbsp;(.*?)
&nbsp;&nbsp;',re.DOTALL).findall(abstract)[0]
    category = ['','']
    category[0] = re.compile(r'<.*?>',re.DOTALL).sub('',category1 
[0]).strip()
    category[1] = re.compile(r'<.*?>',re.DOTALL).sub('',category1 
[1]).strip()
 
    res2 = re.compile(r'iptcomED2K"><!--eMule.*?<!--eMule end-->',
re.DOTALL).findall(res)[0]
    sub = re.compile(r'title=\'字幕下载\'/></a>',re.DOTALL).findall 
(res2)
 
    if sub:
        ed2k = re.compile(r'ed2k="(.*?)" subtitle_.*?="(.*?)">
(.*?)</a>',re.DOTALL).findall(res2)
    else:
        ed2k = re.compile(r'ed2k="(.*?)">(.*?)</a>',re.DOTALL). 
findall(res2)
 
    content = re.compile(r'<!--eMule end-->(.*?)
<!--Wrap-tail end-->',re.DOTALL).findall(res)[0]
 
    content = re.compile(r'<br />',re.DOTALL).sub('\n',content)
    content = re.compile(r'<.*?>',re.DOTALL).sub('',content)
    content = re.compile(r'&.*?;',re.DOTALL).sub(' ',content)
    content = re.compile(r'\n\s+',re.DOTALL).sub('\n',content)
    content = content.strip()
 
    if debug:
        print title
        print status
        print brief
        print pubtime[0],pubtime[1]
        print category[0],category[1]
        for x in ed2k:
            print x
        print content

调用fetch(5420,debug=True)将会输出:

冰封王座英文光盘版
精华资源
无
2003/09/16 14:17:29 2003/09/16 14:17:29
游戏 光盘版游戏
('ed2k://|file|Warcraft.III.-.Frozen.Throne.KeyGen.exe|24064|e73d5ed877490f59882b8eb537bda31c|/', 'Warcraft.III.-.Frozen.Throne.KeyGen.exe')
('ed2k://|file|Warcraft_III_Frozen_Throne.DEViANCE.X-Gamers.bin|563946096|f7fb1dce076dc1db448441b937a4c6ac|/', 'Warcraft_III_Frozen_Throne.DEViANCE.X-Gamers.bin')
简介:
冰封王座英文光盘版
不必介绍了吧……