正则,不一定每个程序员都会使用,正则中的语法也不是每一个人都能非常的了解,每个语言对正则的支持度又是一样的,一般来说正则表达式有POSIX类,PERL类等等。在这里不会把正则的初始教程也写下来,主要为已经会使用正则,需要知道一些技巧的朋友提供一些帮助。在这里主要以python为主,正则大致上都是相同的,所以如果用不了,可以看看所用语言的文档。
首先来说一下正则的一些基础知识:
- 正则中的一些特殊字符
正则,不一定每个程序员都会使用,正则中的语法也不是每一个人都能非常的了解,每个语言对正则的支持度又是一样的,一般来说正则表达式有POSIX类,PERL类等等。在这里不会把正则的初始教程也写下来,主要为已经会使用正则,需要知道一些技巧的朋友提供一些帮助。在这里主要以python为主,正则大致上都是相同的,所以如果用不了,可以看看所用语言的文档。
首先来说一下正则的一些基础知识:
Spyder这个项目本来也没打算要写,但是最近因为一些业务上的需求,需要重新使用,就顺手写一点介绍。Spyder是一个用python2写出来的页面抓取工具,代码托管在Github上。原先Spyder是需要mysql才行的,这次改版我把管理界面和采集器本身进行了一次分离。因此这一次只来说说采集器的本身,Web管理界面等到下一次再说吧。
在src目录里spyder可以采集器,web为web管理界面,libs放了一些通用的函数在里面。用Spyder前,你需要安装lxml。这个是一个非常有用的库,可以对采集回来的html数据进行dom操作。
Spyder中最基础的单元为Seed,也就是所谓的种子。一个种子他包含了你需要采集的需求。看下这例子:
import os, sys parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); if parentdir not in sys.path: sys.path.insert(0, parentdir) from spyder.seed import Seed config = { 'listtype': u'html', 'tries': 5, 'frequency': 7200, 'lang': u'zhCN', 'seed_name': u'抓取在线人数', 'enabled': 1, 'rule': { 'urlformat': 'http://www.douyu.tv/directory/all?offset=$page&limit=30', 'pageparent': '', 'maxpage': 25, 'step': 30, 'startpage': 0, 'contenturl': '', 'listparent': 'div[id="item_data"] ul li', 'urltype': 'createLink',#链接模式 'contentparent': 'a[class="list"]', 'zero': 1, 'entryparent': '', 'filters': [ #filterid, value, fetch_all, type(content/list) ], 'extrarules':[ ('title', 'h1[class="title"].text()', 0, 'list'), ('view', 'span[class="view"].text()', 0, "list"), ('name', 'span[class="nnt"].text()', 0, "list"), ('game', 'span[class="zbName"].text()', 0, "list"), ] }, 'timeout': 5, 'sid': 1000L } seed = Seed(config);
上面就是一个抓取某网站直播在线人数的配置文本。这里包含了listtype(列表页面类型:html, json, feed)、tries(尝试次数,默认为5次)、seed_name(种子名称)、sid(种子ID,必须要分配一个)、enable(是否启用)、rule(采集配置)。
rule中包含了基础的采集信息和过滤器以及额外需要抓取的数据。 先来说说基础的
urlformat 列表页面模板,这个需要和下面的urltype结合在一起使用。一般来说需要抓取的列表页面都是有规则的。所以我在urltype中设定了三种模式inputLink(自定义模式)、createLink(根据设定的step, maxpage, startpage,来生成列表链接)、dateLink(根据设定的日期格式)。 在这些列表中可选参数是$page。这个用于指定需要填充的位置。比如上面例子中的,就是createLink类型。根据所指定的规则,他会生成为:
http://www.douyu.tv/directory/all?offset=0&limit=30
http://www.douyu.tv/directory/all?offset=30&limit=30
上面已经简单的描述了如何去获取列表内容。在这里,可能我会囉嗦几句关于配置正则方面的东西。先来说下extrarules。这里面包含了你需要采集的其他信息。rule本身只是去抓取页面和获取文章链接,其余信息是都没有的。所以你需要配置extrarules让Spyder知道你需要从页面上获取哪里信息。
('title', 'h1[class="title"].text()', 0, 'list'),
上面这个就是一个简单的extrarules中的一条。 分别对应name,parent, fetchall, type。 name就是在你后期获取数据时候的一个key。第二个为抓取正则,这个正则如同上面一样,都是和jquery差不多。获取文字用text(),如果你要获取html就用html()。这个就可以在浏览器先试成功之后,再复制粘帖进来。第三个为是否全部抓取,第四个为类型(list列表, content 文章)。
除了extrarules还有一个叫做filters,过滤器功能可以用做替换,可以改文本内容。这个下次时候我在述说。
通过上面的配置,一个采集的种子已经配置完成了。
第二步就是数据的采集过程了。
import os, sys parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); if parentdir not in sys.path: sys.path.insert(0, parentdir) from spyder.seed import Seed from spyder.document import Grab seed = Seed(config); data = Grab(seed);
你只要引用document中的Grab就可以了。他会采集完成后的数据全部放在items中。你可以只需要进行数据遍历即可。
以上简单介绍了一个Spyder,它与其他采集器不一样的地方在于灵活,配置方便。可以用于各种情况。项目在一年前写的,但是集成了相当多的功能,比如图片存储转换链接,存储到数据库,可扩展插件等等。目前我正在所以对代码进行一次整理,这篇文章也算是对原有代码逻辑的一次梳理。
在python中, 有时候你需要捕捉print的输出保存到一个指定的文件下面. 一般的情况下, print实际上是调用的sys.stdout. 如果你需要需要对print进行捕捉. 你只要需要hook sys.stdout即可.
例如我们将所有的print都保存到StringIO下, 只需要写成
from StringIO import StringIO import sys old_stdout = sys.stdout sys.stdout = mystdout = StringIO() #获取内部值 sys.stdout = old_stdout print mystdout.getvalue() mystdout.close()
但是当你的print中有unicode字符的时, 使用StringIO会报TyperError或者UnicodeError之类的错误.这个时候我们需要进行一下改写.
from StringIO import StringIO import sys, itertools class Logger(object): def __init__(self): self.log = StringIO(); self.terminal = sys.stdout def write(self, message): self.log.write(self.safestr(message)) def safestr(self, obj, encoding='utf-8'): #包含能正确转换 if isinstance(obj, unicode): return obj.encode(encoding) elif isinstance(obj, str): return obj elif hasattr(obj, 'next'): return itertools.imap(self.safestr, obj) else: return str(obj) def close(self): self.log.close() def output(self): self.terminal.write(self.log.getvalue()) old_stdout = sys.stdout sys.stdout = Logger() print "Hello" print u"测试" #将所有catch的输出到terminal sys.stdout.output() """ Hello 测试 """ sys.stdout.close()
在print调用write使用, 我们将message进行一次转码. 在通过调用StringIO().getvalue方法的时候, 就不会报错了.
如果你想将写入文件, 只需要在self.log这部分进行一下改动即可.
self.log = open("debug.log", "a")
有时候, 我们写完一个项目需要对其进行打包. 打完成包非常简单, 我们可以使用git archive这个命令. 但是有时候我们需要对项目的一些特殊文件进行过滤. 这个时候, 可能会说这些文件可以放在.gitingore中. 我需要告诉的时, 在开发项目的时候, 有些文件也需要在git仓中, 以方便其他小组的人使用. 另一种情况, 我们需要打一个差异包出来, 如果没有任何特殊要求, 可以直接使用tar zxvf xxx-patch.tar.gz `git diff –name-only`进行打包. 但是你这个仓中含有很多个submodule时候, 使用这种方法, 会遇到一个很不幸的事, 不管这个子模块修改了一个, 还是多个文件, 他都会将这个子模块全部打包进去. 因此, 我们需要一个好的打包工具, 解决以上问题. 目前我是采用python + pygit2的方式. 同时你系统环境中要有git.
pygit2有一个已知的bug, 你给你的项目打tag, 如果打tag时候没有加上message. pygit2是无法读取到的.
读取git仓中的tag非常的简单. 代码如下
def get_tags(self): self.tags = []; print "=== Parsing package version"; data = self.repo.listall_references(); for item in data: ref = self.repo.lookup_reference(item); if (ref.type == pygit2.GIT_OBJ_COMMIT): oid = ref.oid; ref_obj = self.repo[oid]; if isinstance(ref_obj, pygit2.Tag): self.tags.append(ref_obj); else: continue; self.tags.sort(lambda x,y: cmp(y.tagger.time, x.tagger.time));
获取tag之后, 我们可以使用tag下属性timer, 根据时间先后次序进行排序. 这样你想取最后一个tag作为目标进行打包 只要self.tags[0](倒序)就能直接获取到. 同样你需要获取两个版本的差异. 只要git diff tag[0]..tag[1]
Read more »
在python的mysql中, fetch_row 默认是不传递参数的, 只返回一条且没有column的数据tuple.
但是在仔细看了API文档之后, 发现有两个参数: num_results和display_column
num_result: 默认只显示一条, 当传入0时, 将显示全部数据. 否则根据你的数字显示数据条数
display_column: 默认不显示
1. 只显示column名
2. 显示格式为 table.column
文本头部设置了
#coding: utf-8
但是当你处理这些utf8文本的时候, 还是依然会出现”acsii balabala”之类的问题. 这个时候你需要在文件顶部在加入三行代码
import sys reload(sys) sys.setdefaultencoding("utf-8")
这个时候 就能正确处理了. 原因很简单python2.7及以下版本(3.0不知道), 如果不那么申明, 内部依赖还是以ascii码处理参数的.
通过使用Python Image Library创建魔兽世界地图
代码如下
#coding: utf-8
import _mysql as mysql
import MySQLdb
import os, io, re, math;
import Image
class WorldMaps(object):
def __init__(self, base, output):
self._basedir = base
self._output = output
self.connectMysql()
self.cacheMaps()
def connectMysql(self):
self.db = mysql.connect(host=”127.0.0.1″, user=”root”, passwd=””, db=”wowdb_ctm”)
self.db.query(“SET NAMES UTF8”)
#cache maps metadata from WorldMapArea.dbc
def cacheMaps(self):
self.MAPS_INFO = {}
sql = “SELECT ID, Map, AreaTable, Icon FROM worldmapareadbc”
self.db.query(sql)
results = self.db.store_result()
data = results.fetch_row()
while (data):
data, = data;
self.MAPS_INFO[int(data[0])] = {
“mapid” : data[1],
“areaid” : data[2],
“mapfilename” : data[3]
}
#fetch now
data = results.fetch_row()
def GetNumberOfDetailTiles(self, dirname, mapfilename):
numOnDetailTiles = 0;
for filename in os.listdir(dirname):
if (re.match(mapfilename+”(\d+)”, filename)):
numOnDetailTiles = numOnDetailTiles + 1;
return numOnDetailTiles;
def GetMapInfo(self, mapid):
mapid = int(mapid)
mapinfo = self.MAPS_INFO[mapid]
mapfilename = mapinfo[“mapfilename”]
return mapfilename, self._basedir + “/” + mapfilename + “/”
def GetMapOverlays(self, mapid):
overlays = []
sql = “SELECT Path, Width, Height, `Left`, `Top` FROM worldmapoverlaydbc WHERE ZoneId = ” + str(mapid);
self.db.query(sql);
results = self.db.store_result();
data = results.fetch_row();
while data:
data, = data
overlays.append({
“path” : data[0],
“width” : int(data[1]),
“height”: int(data[2]),
“left” : int(data[3]),
“top” : int(data[4])
});
data = results.fetch_row();
return overlays
def createWorldMap(self, mapid):
mapfilename, dirname = self.GetMapInfo(mapid)
numOnDetailTiles = self.GetNumberOfDetailTiles(dirname, mapfilename);
texs = {};
#map standard size: 1002 * 668
new_image = Image.new(“RGB”, (1002, 668));
x = 0;
y = 1;
for i in range (1, numOnDetailTiles+1):
texName = dirname + “/” + mapfilename + str(i) + “.png”
# x x x x
# x x x x
# x x x x
texs[i] = Image.open(texName);
x = x + 1;
#print x, y
new_image.paste(texs[i], (x * 256 – 256, y * 256 – 256));
if x == 4:
y = y + 1
x = 0;
#sec, get overlays, and paste
overlays = self.GetMapOverlays(mapid)
textureCount = 0
for overlayinfo in overlays:
textureName = overlayinfo[“path”];
textureWidth = overlayinfo[“width”];
textureHeight = overlayinfo[“height”];
offsetX = overlayinfo[“left”];
offsetY = overlayinfo[“top”];
#print textureWidth, textureHeight, textureName
if (textureName and textureName != “”):
numTexturesWide = math.ceil(float(textureWidth) / float(256))
numTexturesTall = math.ceil(float(textureHeight) / float(256))
#print textureWidth, textureHeight, textureName, numTexturesWide, numTexturesTall
neededTextures = int(textureCount + (numTexturesWide * numTexturesTall))
texturePixelWidth = 0
texturePixelHeight = 0
textureFileWidth = 0
textureFileHeight = 0
for j in range(1, int(numTexturesTall) + 1):
if (j < numTexturesTall):
texturePixelHeight = 256
textureFileHeight = 256
else:
texturePixelHeight = textureHeight % 256;
if (texturePixelHeight == 0):
texturePixelHeight = 256
textureFileHeight = 16
while textureFileHeight < texturePixelHeight:
textureFileHeight = textureFileHeight * 2for k in range(1, int(numTexturesWide) + 1):
textureCount = textureCount + 1
if (k < numTexturesWide):
texturePixelWidth = 256
textureFileWidth = 256
else:
texturePixelWidth = textureWidth % 256
if (texturePixelWidth == 0):
texturePixelWidth = 256
textureFileWidth = 256
while textureFileWidth < texturePixelWidth:
textureFileWidth = textureFileWidth * 2_textureName = textureName + str(int((( j - 1) * numTexturesWide) + k)) + ".png";
texture = Image.open(dirname + "/" + _textureName);
new_image.paste(texture, ( (offsetX + (256 * (k - 1))) , ((offsetY + (256 * (j - 1)))) ), texture );
new_image.save( self._output + "/" + str(mapid) + ".png");
[/python]生成的效果图: