本文来源于i春秋学院,未经允许严禁转载。

最近打算更新微信机器人,发现机器人的作者将代码改进了很多,但去掉了sqlite数据库,需要自己根据需求设计数据库,跟作者沟通得到的建议是为了防止消息并发导致数据库死锁,建议另开一个进程读写数据库,将消息加入一个队列中,因为对Python了解有限,队列和多线程更不是我擅长的内容,于是最近疯狂Google、百度,探索着实现了此功能。写此文记录下基本概念和实现方法

0x00 Python队列
队列是线程中交换数据的形式。
创建一个队列对象

import Queue

q = Queue.Queue(maxsize = ) #maxsize是队列长度,不限制长度可以不不赋值

将一个值放入队列

q.put()

将一个值从队列取出

q.get()

三种队列及构造函数

  • FIFO队列先进先出: class Queue.Queue(maxsize)
  • LIFO类似于堆,即先进后出: class Queue.LifoQueue(maxsize)
  • 优先级队列: class Queue.PriorityQueue(maxsize)

队列的常用方法

q.qsize() #返回队列的大小

q.empty() #如果队列为空,返回True,反之False

q.full() #如果队列满了,返回True,反之False

q.full #与 maxsize 大小对应

q.get([block[, timeout]]) #获取队列,block:是否阻塞等待,timeout等待时间

q.get_nowait() #相当q.get(False)

q.put(item[, block[, timeout]) #非阻塞写入队列,block:是否阻塞等待,timeout等待时间

q.put_nowait(item) #相当q.put(item, False)

q.task_done() #在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号

q.join() #等到队列为空,再执行别的操作

几个例子

一个线程往队列里写入随机数,另一个线程从队列里取数字(阻塞等待)

#!/usr/bin/env python

#coding:utf8

import random,threading,time

from Queue import Queue

#Producer thread

class Producer(threading.Thread):

    def __init__(self, t_name, queue):

        threading.Thread.__init__(self,name=t_name)

        self.data=queue

    def run(self):

        for i in range():  #随机产生10个数字 ,可以修改为任意大小

            randomnum=random.randint(,)

            print "%s: %s 生成了一个数字 %d 并把它扔进了队列!" % (time.ctime(), self.getName(), randomnum)

            self.data.put(randomnum)  #将数据依次存入队列

            time.sleep()

        print "%s: %s finished!" %(time.ctime(), self.getName())

#Consumer thread

class Consumer_all(threading.Thread):

    def __init__(self, t_name, queue):

        threading.Thread.__init__(self, name=t_name)

        self.data = queue

    def run(self):

        while :

            try:

                # print self.data

                val_even = self.data.get() 

                print "%s: %s 从队列里取出了 %d !" % (time.ctime(), self.getName(), val_even)

                time.sleep()

            except  Exception, e: 

                print '取数据失败'

                continue

#Main thread

def main():

    queue = Queue()

    producer = Producer('Pro.', queue)

    consumer_all = Consumer_all('Con_all.', queue)

    producer.start()

    consumer_all.start()

if __name__ == '__main__':

    main()
运行结果 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! Wed Aug :: : Pro. finished!

如果将入队的时间间隔修改,出队的程序将阻塞运行,将“ time.sleep(1)”改为“ time.sleep(10)”,再次运行

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. 生成了一个数字  并把它扔进了队列!

Wed Aug   :: : Con_all. 从队列里取出了  !

Wed Aug   :: : Pro. finished!

会发现当队列没有值的时候程序会阻塞等待队列有值才继续运行。
稍微修改程序,一个线程往队列里写入随机数,另一个线程从队列里取数字(非阻塞等待)

#!/usr/bin/env python

#coding:utf8

import random,threading,time

from Queue import Queue

#Producer thread

class Producer(threading.Thread):

    def __init__(self, t_name, queue):

        threading.Thread.__init__(self,name=t_name)

        self.data=queue

    def run(self):

        for i in range():  #随机产生10个数字 ,可以修改为任意大小

            randomnum=random.randint(,)

            print "%s: %s 生成了一个数字 %d 并把它扔进了队列!" % (time.ctime(), self.getName(), randomnum)

            self.data.put(randomnum)  #将数据依次存入队列

            time.sleep()

        print "%s: %s finished!" %(time.ctime(), self.getName())

#Consumer thread

class Consumer_all(threading.Thread):

    def __init__(self, t_name, queue):

        threading.Thread.__init__(self, name=t_name)

        self.data = queue

    def run(self):

        while :

            try:

                # print self.data

                val_even = self.data.get(,)  # get(self, block=True, timeout=None) ,1就是阻塞等待,5是超时5秒

                print "%s: %s 从队列里取出了 %d !" % (time.ctime(), self.getName(), val_even)

                time.sleep()

            except  Exception, e:  # 等待输入,超过5秒  就报异常

                print '取数据失败'

                continue

#Main thread

def main():

    queue = Queue()

    producer = Producer('Pro.', queue)

    consumer_all = Consumer_all('Con_all.', queue)

    producer.start()

    consumer_all.start()

if __name__ == '__main__':

    main()
Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. 生成了一个数字 并把它扔进了队列! Wed Aug :: : Con_all. 从队列里取出了 ! 取数据失败 Wed Aug :: : Pro. finished! 取数据失败 取数据失败 取数据失败

可以看出,取数据的线程在超过设定的等待时间后会抛出异常并继续往下执行。

0x01 使用队列将微信机器人消息存入MongoDB

使用上面的例子,稍作修改,将接收到的消息扔进队列,开启另一个线程取数据,取到后将消息格式化并存入数据库

#!/usr/bin/env python

#coding:utf8

import threading,time,pymongo

from pymongo import MongoClient

from Queue import Queue

#消息入队

class MsgInQueue():

    def __init__(self, queue):

        threading.Thread.__init__(self)

        self.data=queue

    def putmsgqueue(self,msg):

        self.data.put(msg)

        print 'put msg in queue success:'+msg['Content']

###消息出队并存入数据库
class MsgOutQueue2db(threading.Thread):

    def __init__(self, queue):

        threading.Thread.__init__(self)

        self.data = queue

        #建立MongoDB连接

        self.conn = MongoClient()

        #数据库

        self.db = self.conn.wechatRobot

        #数据表

        self.messages = self.db.messages

    def run(self):

        while :

            try:

                # print self.data

                #从队列里取消息

                msg = self.data.get(, )  # get(self, block=True, timeout=None) ,1就是阻塞等待,5是超时5秒

                print "%s: %s get %s from queue !" % (time.ctime(), self.getName(), msg['Content'].encode('utf-8'))

                try:

                    #格式化消息数据

                    m = dict(groupname=msg['FromUserName'].encode('utf-8'),

                             time=msg['CreateTime'],

                             username=msg['ActualUserName'],

                             usernickname=msg['ActualNickName'].encode('utf-8'),

                             message=msg['Content'].encode('utf-8'),

                             messagetype=msg['MsgType']

                             )

                    print m

                    #存入数据库

                    self.db.messages.insert(m)

                    time.sleep()

                except  Exception, e:

                    print e

                    continue

            except  Exception, e:

                continue
 

主线程

def complex_reply():

    queue = Queue()

    outqueue = MsgOutQueue2db(queue)#实例化出队入库类

    outqueue.start()#开启线程

    @itchat.msg_register('Text', isGroupChat = True)

    def text_reply(msg):

        # print itchat.__client.storageClass.groupDict

        print itchat.__client.storageClass.chatroomList

        print msg

        inqueue=MsgInQueue(queue)#实例化入队类

        inqueue.putmsgqueue(msg)#消息入队

        if msg['isAt']:

            print msg

            itchat.send(u'@%s\u2005I received: %s'%(msg['ActualNickName'], msg['Content']), msg['FromUserName'])

消息存入数据库:

通过多线程和MongoDB的结合,有效防止消息过多导致数据库死锁的问题,也更加模块化,可以根据真实需求更换其他数据库。后面我将结合MongoDB插入、更新数据快的特点写一下我如何设计群聊统计功能,在Python方面和MongoDB方面我都是小白,如有更好的建议请多指教,我们共同学习有关Python多线程的课程请参考《python安全编程入门》

Python队列及在微信机器人中的应用的更多相关文章

  1. [bigdata] 使用Redis队列来实现与机器无关的Job提交与执行 (python实现)

    用例场景: 定时从远程多台机器上下载文件存入HDFS中.一开始采用shell 一对一的方式实现,但对于由于网络或者其他原因造成下载失败的任务无法进行重试,且如果某台agent机器down机,将导致它对 ...

  2. Python队列服务 Python RQ Functions from the __main__ module cannot be processed by workers.

    在使用Python队列服务 Python RQ 时候的报错: Functions from the __main__ module cannot be processed by workers. 原因 ...

  3. 【python】10分钟教你用python下载和拼接微信好友头像图片

    前言 相信微信大家是用得再多也不过了.那么,对于python+微信,又能玩出什么新的花样呢?下面小编就给大家带来一个好玩的东西.用python下载所有的微信好友的头像,然后拼接成一张大图.这样,大家就 ...

  4. python与shell通过微信企业号发送消息

    python与shell通过微信企业号发送信息,脚本来源于网络,做好搬运工,哈哈,相应的参考链接放在末位 shell版本: #!/bin/bash # CropID="xxxx" ...

  5. Python使用JsAPI发起微信支付 Demo

    Python使用JsAPI发起微信支付 Demo 这个是基于Django框架. 了解更多,可以关注公众号"轻松学编程" 1.公众号设置.微信商户号设置 这些都可以在官网查得到, 公 ...

  6. Python + Appium 自动化操作微信入门看这一篇就够了

    简介 Appium 是一个开源的自动化测试工具,支持 Android.iOS 平台上的原生应用,支持 Java.Python.PHP 等多种语言. Appium 封装了 Selenium,能够为用户提 ...

  7. pycharm上的python虚拟环境移到离线机器上

    Pycharm的Terminal 中执行: 查看现有的包到requirements.txt中 pip freeze > requirements.txt 生成依赖包 D:\machangwei\ ...

  8. 网上售卖几百一月的微信机器,Python几十行代码就能搞定

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 故事胶片 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...

  9. python队列、线程、进程、协程

    目录: 一.queue 二.线程 基本使用 线程锁 自定义线程池 生产者消费者模型(队列) 三.进程 基本使用 进程锁 进程数据共享 默认数据不共享 queues array Manager.dict ...

随机推荐

  1. mysql const与eq_ref的区别

    简单地说是const是直接按主键或唯一键读取,eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询. 下面的内容翻译自官方方档: const该表最多有一个匹配行, 在查询开始时读取.由于只有一 ...

  2. mysql实用函数

    1.  group_concat(); 可以将选择的字段列数据,分组以逗号分隔成一串.实用方便.select id,group_concat(distinct name) from ttt group ...

  3. [Ting's笔记Day8]活用套件carrierwave gem:(3)Deploy图片上传功能到Heroku网站

    前情提要: 身为Ruby新手村民,创造稳定且持续的学习步调很重要,我用的方法就是一周在IT邦写三篇笔记,希望藉由把笔记和遇到的bug记录下来的过程,能帮助到未来想用Ruby on Rails架站的新手 ...

  4. 在ASP.NET中使用KindEditor富文本编辑器

    以前一直用百度的UEditor.这次客户提了一个需求要在编辑器中插入Flash动画,但是不知道怎么用UEditor实现,于是选用了KindEditor. 更重要的一点是,客户的网站使用Framewor ...

  5. Curl 基本命令

    下载单个文件,默认将输出打印到标准输出中(STDOUT)中 curl http://www.centos.org 通过-o/-O选项保存下载的文件到指定的文件中:-o:将文件保存为命令行中指定的文件名 ...

  6. python中assert详解

    assert基础 官方解释:"Assert statements are a convenient way to insert debugging assertions into a pro ...

  7. Power designer 的使用

    1.Powere Designer 逆向 工程 首先 逆向工程 就是将数据库表 导入到模型, 首先新建个模型, 此处就省略 ... 工具栏,数据库(database) 下的 update model ...

  8. <jsp:forward page='/index' />

  9. PHP导出Excel表

    <?php/** * Created by PhpStorm. * User: admin * Date: 2019/3/16 * Time: 9:41 *///利用excel导出插件PHPEx ...

  10. JAVA初学者的JDB 尝试

    使用JDB调试简单递归程序 跟着娄老师的博客学习, 首先在终端使用Ctrl+Shift+T打开三个标签,方便操作. 使用Vim编辑自己的程序,练习程序如下 1 public class Factori ...