爬虫:4. 消息队列

消息队列

在构建一个松耦合或是异步的系统时,消息队列是最常用的方法。在爬虫中使用消息队列有哪些好处呢?

  • 通过消息队列实现线程安全的去重
  • 多进程消费爬虫任务队列
  • 确保每一条任务都执行

消息队列的选择

在最初我选用的事redis,但是在使用过程中,阻塞获取任务经常发生获取不到任务的情况,redis更多的是作为缓存数据库使用,根据需求最后选择了RabbitMQ,他没有kafka那么重量级,但是功能全面。RabbitMQ有可靠性,灵活的路由,集群模式,高可用队列,多协议,广泛支持的客户端等等特点,详细的教程可以参考官方文档。github上有人翻译了,地址如下:http://rabbitmq.mr-ping.com/

RabbitMQ在爬虫中的应用

爬虫任务队列,生产者消费者模式

爬虫结合消息队列的好处在于:

  • 松耦合,异步,灵活,任意扩展消费者
  • 爬虫中间失败了可以在失败处开始爬取
  • 消息确认模式保证每一个爬虫任务都执行
  • 多线程/进程共享数据
一般情况我们只需要使用消息持久化和消费者消息确认应对意外,可酌情考虑发送消息确认

下面是一个类接口,并有example,包含爬虫任务队列的消息发送(参数可配置投递确认模式),消息接收,消息持久化,消息确认

注意:heartbeat_interval=0,设置心跳时间为0表示心跳不影响客户端的连接。如果不设置,一旦处理时间超长,服务端会断开连接。

配置文件
[RabbitMQ]
host=10.0.0.100
username=admin
passwd=111111
# -*- coding:utf-8 -*-

"""
File Name : 'message_queue'.py
Description:
Author: 'chengwei'
Date: '2016/6/2' '15:48'
"""
import sys
import pika
import codecs
import ConfigParser
import os

reload(sys)
sys.setdefaultencoding('utf-8')

def message(queue_name, handle_data=None, confirm_delivery=0):
    """
    类的实例化,作为接口
    :param queue_name:对列名
    :param handle_data:接收消息时的消息内容处理函数, 默认为None,在发送消息时可不设置
    :param confirm_delivery 是否开启消息投递确认模式, 1为开启,默认为0
    :return:
    """
    message = message_queue(queue_name, handle_data, confirm_delivery)
    return message

class message_queue(object):
    def __init__(self, queue_name, handle_data=None, confirm_delivery=0):
        """
        初始化
        :param queue_name: 对列名
        :param handle_data: 接收消息时的消息内容处理函数
        :param confirm_delivery: 是否开启投递确认模式
        :return:
        """
        self.parasecname = "RabbitMQ"
        self.queue_name = queue_name
        self.confirm_delivery = confirm_delivery
        self.connection, self.channel = self.message_queue_init()
        self.handle_data = handle_data

    def message_queue_init(self):
        """
        消息队列初始化, 默认开启持久化, 使用实例化的参数
        """
        cur_script_dir = os.path.split(os.path.realpath(__file__))[0]
        cfg_path = os.path.join(cur_script_dir, "db.conf")

        cfg_reder = ConfigParser.ConfigParser()
        secname = self.parasecname
        cfg_reder.readfp(codecs.open(cfg_path, "r", "utf_8"))
        rabbitmq_host = cfg_reder.get(secname, "host")
        rabbit_username = cfg_reder.get(secname, "username")
        rabbitmq_pass = cfg_reder.get(secname, "passwd")

        credentials = pika.PlainCredentials(rabbit_username, rabbitmq_pass)
        connection = pika.BlockingConnection(pika.ConnectionParameters(rabbitmq_host, 5672, '/', credentials, heartbeat_interval=0))
        channel = connection.channel()
        # durable 表示是否持久化,exclusive是否排他,如果为True则只允许创建这个队列的消费者使用, auto_delete 表示消费完是否删除队列
        channel.queue_declare(queue=self.queue_name, durable=True, exclusive=False, auto_delete=False)

        if self.confirm_delivery == 1:
            channel.confirm_delivery()
        return connection, channel

    def send_message(self, message):
        """
        发送消息到队列
        # delivery_mode=2 make message persistent
        :param message: 要投递的消息,字符串格式
        confirm_delivery: 如果开启消息投递确认模式,那么可以返回True或False,未开启则只是发送消息,默认不开启
        :return:
        """
        if self.confirm_delivery == 1:
            return self.channel.basic_publish(exchange='', routing_key=self.queue_name,
                                              body=message, properties=pika.BasicProperties(delivery_mode=2))
        else:
            self.channel.basic_publish(exchange='', routing_key=self.queue_name,
                                       body=message, properties=pika.BasicProperties(delivery_mode=2))

    def message_queue_close(self):
        self.channel.close()
        self.connection.close()
        print "stoped!"

    def callback(self, ch, method, properties, body):
        """
        回调函数,其中handle_data为处理接收到的消息,处理正确返回1,如果返回1,那么发送消息确认
        :param ch:和rabbitmq通信的信道
        :param method:一个方法帧对象
        :param properties:表示消息头对象
        :param body:消息内容
        :return:
        """
        print " [x] Received %r" % (body,)
        result = self.handle_data(body)
        if result == 1:
            print " [x] Done"
            ch.basic_ack(delivery_tag=method.delivery_tag)
        else:
            print " [x] handle data error"
            ch.basic_reject(delivery_tag=method.delivery_tag)


    def receive_message(self):
        """
        接收消息队列中的消息, 并调用回调函数处理
        """
        # 同一时刻,不要发送超过一个消息到消费者,直到它已经处理完了上一条消息并作出了回应
        self.channel.basic_qos(prefetch_count=1)
        self.channel.basic_consume(self.callback, queue=self.queue_name)
        print(' [*] Waiting for messages. To exit press CTRL+C')
        try:
            self.channel.start_consuming()
        except KeyboardInterrupt:
            self.message_queue_close()

if __name__ == '__main__':
    # example
    def get_info(body):
        """
        注意在处理接收到得消息时,请自行进行错误处理,如果返回0,那么消息将回到队列,如果此时只有一个消费者,那么将进入死循环
        """
        print body
        return 1
    # 生产者
    test_1 = message('test')
    test_1.send_message("test")

    # 消费者
    test_2 = message('test', handle_data=get_info)
    test_2.receive_message()



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • “ 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列...
    落羽成霜丶阅读 3,929评论 1 41
  • 水莲从供排水专业毕业后,分配到了一个大型企业的自备水厂工作,而她的不如意也正是从这里开始的。 工作后不久,跟她...
    沉默的窗台阅读 637评论 0 2
  • 忙里偷闲,在假日带着两个小孩去观赏了即将落幕的菊花展。 走近大门,就看到两只神气活现的麒麟,大张着嘴巴仿佛在说:“...
    欣然亦君阅读 171评论 0 1
  • 【Rainbow按】 前天晚上看了一场著名的《Sleep No More不眠之夜》,据说这是目前上海,乃至中国最高...
    RainbowPeng阅读 1,696评论 17 50