Daily Archives: 2014/04/01

Spyder介绍

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中包含了基础的采集信息和过滤器以及额外需要抓取的数据。 先来说说基础的

  1. 生成列表链接
  2. 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

  3. 提取列表及获取文章链接
  4. 在你生成列表链接之后,就可以开始抓取了。抓取下来的都是html页面。这个时候你需要配置需要采集的列表区域以及文章链接。这里需要涉及listparent,contentparent。这里的配置很简单,如果你熟悉jquery的话,那么配置起来相当简单。

上面已经简单的描述了如何去获取列表内容。在这里,可能我会囉嗦几句关于配置正则方面的东西。先来说下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,它与其他采集器不一样的地方在于灵活,配置方便。可以用于各种情况。项目在一年前写的,但是集成了相当多的功能,比如图片存储转换链接,存储到数据库,可扩展插件等等。目前我正在所以对代码进行一次整理,这篇文章也算是对原有代码逻辑的一次梳理。

利用Redis开发预约系统

这次来讲讲redis的一些高级使用机制,redis就不在这里过于介绍了,不了解的可以google,可以wiki,也可以去redis官网看看。上面都有它的历史和使用说明。

Redis主要提供了集中类型:字符串,hash,list,有序/无序集合。每个类型都有不同的使用场景,例如字符串可以保存一些固定key的数据。比如用户基础数据等等。Redis是一个单进程单线程的内存数据库,对于并发来说,基本上不会出现重复的问题,同时它所处理的速度也是极快的。因此在门票预约或者发红包的时候,利用Redis这一特性,可以很好的解决并发所带来的问题,同时也提高了运行效率,处理速度。

对于门票预约来说,一般都会事先生成好所有的座位,这里的座位我们称之为ticket。Redis中List是提供了push和pop功能。第一步需要先把所有生成的ticket放入list中,用户在点击预约时,用lpop命令弹出一个。如果想退订或者失败回滚处理用rpush命令插入。另外,门票每个人只能预约一张,你可以创建一个hash表,用userid做为这个hash表的key,在每次获取订单的时候,你可以去这个hash表上查询,此userid时候已经存在,若不存在,可以将预约成功者的userid以及ticket、状态、时间。存储到这个hash表中。

在这套预约系统中,有一个问题,就是我创建了这个预约事件,那么我想调整人数时候就遇到难题了。增加人数还好说,通过差值,在list中rpush那些数量的ticket。而需要减少的时候,可就没办法了。除非你把这个预约事件全部取消并删除。

在并发测试的时候,没有遇到同时会分配到两个相同的ticket。而且redis的并发量,超出了我们的想象,若是只使用Mysql这类关系数据库,对服务器的压力可能会有所增加,同时,mysql因为行锁原因,可能会比Redis低一些。

通过我上面那么多的废话,你设计这套只需要4个:
1. ticketqueue:2 ticket队列表 后面的数字代表预约事件id。 list类型
2. orderedlist:2 成功预约列表。hash表类型
3. userlist 用户数据表,存储用户的联系方式。 hash表
4. eventlist 预约表。 这个存储每个预约的信息。 hash表

除了你用Redis之外,你还可以用mysql。这个Mysql只是用来存储最终的结果,就是当整个预约流程成功跑完获得ticket之后,将状态,预约信息,用户信息存在数据库中,便于后台的检索。