1、起因

最近一直想学习一下vim或着emacs这两大神器,可惜一直没有成功过。前段时间学了一下emacs,对于emacs的elisp语法很不习惯。用着用着,有些地方配置不起来就放弃了。后来一直在用Ulipad,用一段时间后,发现其实有一些高级功能不用也没什么太大关系,想着想着就想去试试VIM。发现VIM可以用python写插件,有python的接口。马上下载过来试试,但vim7.2用的是python2.4,vim7.3用的是python2.7,而我自己用的是2.6的。所幸开源的东西都是可以重新编译的。

2、编译准备工作

ftp://ftp.vim.org/pub/vim/pc/ 把vim73rt.zip和vim73src.zip下载过来,把vim73rt.zip里的vim73文件夹下面的文件解压到D:\makevim\runtime目录下,vim73src.zip里的vim73文件夹下面的文件解压到D:\makevim目录下。

然后装个gcc吧,我用的是dev c++里面带的。

3、开始编译

在src目录里面的Make_ming.mak文件里的最前面加入:

PYTHON=c:\Python26
PYTHON_VER=26

然后执行make编译:

make -f Make_ming.mak GUI=yes OLE=yes  USERNAME=Chronos USERDOMAIN=TK

成功后会在src目录下面生成一个gvim.exe文件,把这个复制到官方的windows安装版目录下就可以了。

很久以前就听说这tokyo cabinet非常牛叉,一直想找个机会试试。这个key-value数据库不支持windows所以就在ubuntu上面安装。

1、下载安装包

可以去作者的官网 http://1978th.net 上面就下载:

wget http://1978th.net/tokyocabinet/tokyocabinet-1.4.47.tar.gz

然后解压出来编译安装:

tar xvzf tokyocabinet-1.4.47.tar.gz
cd tokyocabinet-1.4.47
./configure

结果提示没有zlib.h

2、安装zlib1g-dev

1
sudo apt-get install zlib1g-dev

提示没有bzlib.h

3、安装libbz2-dev

1
sudo apt-get install libbz2-dev

这回终于提示完成了,没有再说缺什么了。

4、编译安装

1
2
make
make install

终于完工了,再来就是试试这个数据库的性能,以及资源占用情况。

测试Flask-Cache插件的时候想看看效果,就给Flask的输出页面底部添加了页面执行时间的东东。原理其实很简单,就是在页面生成之前记录一下时间点,然后再生成后再相减一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import g
from timeit import default_timer

@app.before_request
def before_request():
#保存初始时间
g.starttime=default_timer()

@app.after_request
def after_request(response):
#输出页面执行时间
response.data = response.data.replace("<|pageruntime|>",str(default_timer()-g.starttime))
return response

通过替换模板中的”<|pageruntime|>”这个字符串,将执行时间输出到页面的底部。

昨天写的那个采集网站的代码太次了,太容易出问题了。不过就这么几行代码也就不追求这些健壮性了。现在最头痛的写代码经常都是一次性的,过期做废,几乎不可重用。重复造轮子的代码太多了,所以把采集的这个东西整理了一下,重新写了一遍,把一些重要的东西封装了起来。

对于采集这一块的代码参考了 http://obmem.info/?p=753 里面的多线程抓取,对里面的Fetcher类进行了一下改写,把urllib2换成了pycurl。据说pycurl的性能比urllib2要好,反正我也没测试过。用起来的感觉还不错。

一、封装pycurl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#coding:utf-8
import pycurl,StringIO

class Tpycurl():
low_speed_time = 100
connection_timeout = 100
timeout = 100
def __init__(self,url=None):
self.content = StringIO.StringIO()
self.curl_handle = pycurl.Curl()
if url != None:
self.curl_handle.setopt(pycurl.URL,url)
self.curl_handle.setopt(pycurl.FOLLOWLOCATION, 1)
self.curl_handle.setopt(pycurl.MAXREDIRS, 5)
self.curl_handle.setopt(pycurl.CONNECTTIMEOUT, self.connection_timeout)
self.curl_handle.setopt(pycurl.TIMEOUT, self.timeout)
self.curl_handle.setopt(pycurl.NOSIGNAL, 1)
self.curl_handle.setopt(pycurl.LOW_SPEED_LIMIT, 100)
self.curl_handle.setopt(pycurl.LOW_SPEED_TIME, self.low_speed_time)
self.curl_handle.setopt(pycurl.HTTPHEADER, ["User-Agent: %s"%"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)"])
#self.curl_handle.setopt(pycurl.MAXFILESIZE, max_size)
self.curl_handle.setopt(pycurl.COOKIEFILE, 'cookies.txt')
self.curl_handle.setopt(pycurl.COOKIEJAR, 'cookies.txt')
self.curl_handle.setopt(pycurl.WRITEFUNCTION, self.content.write)

def open(self,url):
self.curl_handle.setopt(pycurl.URL,url)

def read(self):
try:
self.curl_handle.perform()
return (self.curl_handle.getinfo(pycurl.HTTP_CODE),self.content.getvalue(), self.curl_handle.getinfo(pycurl.CONTENT_TYPE))
except pycurl.error,e:
print e
return (-1,e,0)

目前就是简单的封装一下,方便调用而已,没详细研究过其中的参数。

二、实现多线程采集

修改了一下observer大神的Fetcher类,改成pycurl的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#coding:utf-8
from Tpycurl import Tpycurl
from threading import Thread,Lock
from Queue import Queue
import time

class Fetcher:
retries = 3
def __init__(self,threads):
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):
self.opener = Tpycurl()
while True:
req = self.q_req.get()
with self.lock: #要保证该操作的原子性,进入critical area
self.running += 1
retries = self.retries
while retries&gt;0:
ans = Tpycurl(req).read()
if ans[0] == 200:
ans = ans[1]
break
elif retries <= 0:
ans = ''
print 'GET FAILED',req
retries+=-1

self.q_ans.put((req,ans))
with self.lock:
self.running -= 1
self.q_req.task_done()
time.sleep(0.1) # don't spam

有了这个类代码写起来舒服多了,早就应该这么干了。以后多研究研究写轮子,搞成通用性的。省得以后再写这么多乱七八糟的代码。

三、最终重写的采集网站信息的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#coding:utf-8

from lxml import html
from utils.TFetcher import Fetcher
import re,sys


links=[]#存放下载的页面地址
for i in range(132):
if i==0:
links.append('http://www.hltm.cc/list/18.html')
else:
links.append('http://www.hltm.cc/list/18_%s.html'%(i+1))

f = Fetcher(10)#开启10线程

for url in links:
f.push(url)#将地址放入队列


def GetWorks(content):
dom = html.document_fromstring(content)#生成html的xml对象
works = []
for work in dom.xpath('//div[@class="listInfo"]'):
title = work.xpath('./h3/a')[0].text#取出标题
downloads = int(re.findall('.*?(\d+)',work.xpath('./p')[0].text)[0])#取出下载量,并转化成数字
works.append((title,downloads))#添加到works
return works

works = []
while f.taskleft():
url,content = f.pop()
content = content.decode('gbk')
works += GetWorks(content)#获取信息并添加到列表
print u"下载:%s成功"%url

def mcmp(x,y):
if x[1]&gt;y[1]:
return 1
elif x[1]==y[1]:
return 0
else:
return -1

works.sort(mcmp,reverse=True)
worksort = open('worksort.txt',"w+")
sys.stdout = worksort
for work in works:
print (u'作品名:%s 下载次数:%s'%(work[0],work[1])).encode('utf8')
worksort.close()

代码比昨天写的要清楚一点,以前太少用到类的特性,封装成类后,代码调用还真方便,扩展也方便。而且重写的时候发现,原来list是可以直接用加号的,上次还用循环来做,真是太傻了。

今天晚上闲来无事,想找部动漫看看,上次下载的《魔法禁书目录2》的720P版本已经看完了,手头上暂时没什么想看的存货,以前遗留下来的到是有一些,但是放太久了,现在没什么心情看。断的时间长了,就没当初那么好的兴致了。

我看动漫作品喜欢看完结的,没完结的要等死。前段时间收藏的一个叫红旅动漫 http://www.hltm.cc 的网站蛮不错的。里面经常可以下载到720P的片子。当然下载源还是要借助于像115之类的网盘。但是网站里面的完结动漫是没有按下载量排序的,这样就不容易找热门的下载。于是就萌生了采集网站信息自己排序的想法。以前下漫画的时候也没少干这事。

简易的代码如下:

GetList.py : 采集网站信息通过cPickle存入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#coding:utf-8
import urllib2,cPickle,re

pages=[]#存放下载的页面地址
for i in range(132):
if i==0:
pages.append('http://www.hltm.cc/list/18.html')
else:
pages.append('http://www.hltm.cc/list/18_%s.html'%(i+1))

def GetInfo(content):
'''
根据信息片段提取想要关片名和下载量信息
'''
rs=[]
rs.append(re.findall('title\=\&quot;(.*?)\&quot;',content)[0])
rs.append(re.findall('\xcf\xc2\xd4\xd8\xb4\xce\xca\xfd\xa3\xba(\d*)\&lt;\/p\&gt;',content)[0])
return rs

def GetWorks(content):
'''
取得当前面的信息列表
'''
tmp=content
rs = []
#通过循环把网页源代码进行分割
while tmp.find('&lt;div class=&quot;listInfo&quot;&gt;')!=-1:
start=tmp.find('&lt;div class=&quot;listInfo&quot;&gt;')
tmp=tmp[start:]#截取后半段
stop=tmp.find('&lt;/div&gt;')
rs.append(tmp[:stop])
tmp = tmp[stop:]
rs1=[]
#生成关键信息列表
for i in rs:
rs1.append(GetInfo(i))
return rs1

works = []#作品列表
error = []#错误页面列表

for i in pages:
try:
print u'正在获取%s'%(i)
tmp = GetWorks(urllib2.urlopen(i).read())
for j in tmp:
works.append([j[0],int(j[1])])
print len(works)
except :
error.append(i)

f=open('works','w+')
cPickle.dump(works,f)
f.close()

f=open('error','w+')
cPickle.dump(error,f)
f.close()

sort.py : 排序并输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#coding:utf-8
import cPickle,sys

works = cPickle.load(open('works','rb'))

def mcmp(x,y):
if x[1]&gt;y[1]:
return 1
elif x[1]==y[1]:
return 0
else:
return -1

works.sort(mcmp,reverse=True)

for i in range(len(works)):
sys.stdout.write((u'作品名:%s 下载次数:%s\r\n'%(works[i][0].decode('gbk'),works[i][1])).encode('gbk'))

本来想把error的处理给写了,后来发现网络比较给力没有出错直接采集完了,所以就没写这东西。

tip

输出结果到文本:

python sort.py>list.txt

python写这种东西确实很方便,等有空了把以往写过的代码都整理一下,以后写个比较通用的采集模块。

因为使用SyntaxHighlighter的时候会使用到多种语言,一次必载入全部的语言配置文件下载的东西会比较多,所以SyntaxHighlighter提供了一个shAutoloader.js脚本。

官方给出的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function path()
{
var args = arguments,
result = []
;

for(var i = 0; i < args.length; i++)
result.push(args[i].replace('@', '/pub/sh/current/scripts/'));

return result
};

SyntaxHighlighter.autoloader.apply(null, path(
'applescript @shBrushAppleScript.js',
'actionscript3 as3 @shBrushAS3.js',
'bash shell @shBrushBash.js',
'coldfusion cf @shBrushColdFusion.js',
'cpp c @shBrushCpp.js',
'c# c-sharp csharp @shBrushCSharp.js',
'css @shBrushCss.js',
'delphi pascal @shBrushDelphi.js',
'diff patch pas @shBrushDiff.js',
'erl erlang @shBrushErlang.js',
'groovy @shBrushGroovy.js',
'java @shBrushJava.js',
'jfx javafx @shBrushJavaFX.js',
'js jscript javascript @shBrushJScript.js',
'perl pl @shBrushPerl.js',
'php @shBrushPhp.js',
'text plain @shBrushPlain.js',
'py python @shBrushPython.js',
'ruby rails ror rb @shBrushRuby.js',
'sass scss @shBrushSass.js',
'scala @shBrushScala.js',
'sql @shBrushSql.js',
'vb vbnet @shBrushVb.js',
'xml xhtml xslt html @shBrushXml.js'
));
SyntaxHighlighter.all();

但按上面的配置死活不成功,通过chrome的审查元素里面也没看到变化,服务端里更没有读取相应语言的js包的访问信息。

后来在 http://www.leadnt.com/Javascript/510.html 这篇文章里查到,原来这个脚本应该在页面读取完成后再运行。

知道这个就好办了,可以把这段脚本放到页尾使用。如果有jquery就放到$(document).ready()里面。

配合jquery,修改后的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function path()
{
var args = arguments,
result = []
;

for(var i = 0; i < args.length; i++)
result.push(args[i].replace('@', '/static/js/SyntaxHighlighter/'));

return result
};

$(document).ready(function(){
SyntaxHighlighter.autoloader.apply(null, path(
'applescript @shBrushAppleScript.js',
'actionscript3 as3 @shBrushAS3.js',
'bash shell @shBrushBash.js',
'coldfusion cf @shBrushColdFusion.js',
'cpp c @shBrushCpp.js',
'c# c-sharp csharp @shBrushCSharp.js',
'css @shBrushCss.js',
'delphi pascal @shBrushDelphi.js',
'diff patch pas @shBrushDiff.js',
'erl erlang @shBrushErlang.js',
'groovy @shBrushGroovy.js',
'java @shBrushJava.js',
'jfx javafx @shBrushJavaFX.js',
'js jscript javascript @shBrushJScript.js',
'perl pl @shBrushPerl.js',
'php @shBrushPhp.js',
'text plain @shBrushPlain.js',
'py python @shBrushPython.js',
'ruby rails ror rb @shBrushRuby.js',
'sass scss @shBrushSass.js',
'scala @shBrushScala.js',
'sql @shBrushSql.js',
'vb vbnet @shBrushVb.js',
'xml xhtml xslt html @shBrushXml.js'
));
SyntaxHighlighter.all();
});

今天在编写博客程序的时候遇到模板里面自带的filter不够用的情况,遂想起自定义一个。参考了jinja2的文档,再经过百度大神的帮助。最终发现,其实给flask的jinja2模板加filter很简单。

代码如下:

1
2
3
4
5
6
def my_join(value,d='',what=''):
'''用于生成分隔字符串'''
return d.join([i[what] for i in value])

#注册filters
app.jinja_env.filters['my_join']=my_join

然后在模板里面就可以使用这个filter了,使用方法如下:

1
{{i.categories|my_join(d=', ',what='title')}}