Пример #1
0
    def task( self ):
        """
        自动抓取微信群消息
        """
        app.logger.info( 'executing %s' % ( __name__ ) )

        try:
            app.logger.info( "正在建立Appium会话..." )
            self.driver = webdriver.Remote( self.appium_server, self.desired_capability )  # Appium会话配置
            self.wait=WebDriverWait(self.driver, self.timeout)        

            app.logger.info( "正在点击微信首页消息列表第一条,进入群消息对话框..." )
            self.wait.until( EC.presence_of_element_located( ( By.ID,'com.tencent.mm:id/as4' ) ) ).click()

            # 获取群聊名称
            group_name = self.wait.until( EC.presence_of_element_located( ( By.ID,'com.tencent.mm:id/hm' ) ) ).get_attribute( 'text' )
            if not group_name or not re.compile( self.group_tag_pattern ).findall( group_name ):
                app.logger.info( "当前会话不是群聊,即将关闭..." )
                return
            group_name = re.sub( self.group_tag_pattern, "", group_name )
            app.logger.info( "当前群聊名称为:" + group_name )

            current_simulator_window_size = get_simulator_window_size( self.driver )
            app.logger.info( "当前屏幕尺寸为:{0}".format( current_simulator_window_size ) )

            time.sleep( 1 )  # 强制等待1秒

            # 消息发布时间
            send_time = get_current_date()
            app.logger.info( '消息发布时间:%s' %( send_time )  )   

            # 不断向下滑屏,获取当前屏消息
            while True:
                self.get_current_page_msgs( group_name=group_name, send_time=send_time )
                try:
                    self.driver.find_element_by_id( 'com.tencent.mm:id/abj' )
                    app.logger.info( "发现新消息提示框,正在继续向下滑屏..." )
                    driver_swipe_down( current_simulator_window_size, self.driver )
                except NoSuchElementException:
                    app.logger.info( "没有找到新消息提示框,只向下滑屏3次..." )
                    for i in range(0, 3):
                        driver_swipe_down( current_simulator_window_size, self.driver )
                        self.get_current_page_msgs( group_name=group_name, send_time=send_time )
                    break

            # 一次过滤,first_filter_keys表, 并保存到数据库(messages表),调用公共方法
            app.logger.info( "正在根据关键词过滤原始消息记录表..." )
            MessagesService.filter_by_keys()

            # 二次过滤,second_filter_qqnumber表、second_filter_groupnumber表, 并保存到数据库(messages表),调用公共方法
            app.logger.info( "根据给定的QQ/微信群号码或者QQ/微信号过滤消息记录表" )
            MessagesService.filter_by_numbers()                    

        except:
            app.logger.info( traceback.format_exc() )
        finally:
            app.logger.info( "正在关闭当前Appium会话..." )  # 关闭当前会话
            self.driver.quit()

        app.logger.info( "finished %s" % ( __name__ ) )
Пример #2
0
    def task( self ):
        """
        执行任务
        """
        app.logger.info( "launching %s ..." % ( __name__ ) )

        # 一次过滤,first_filter_keys表, 并保存到数据库(messages表),调用公共方法
        app.logger.info( "正在根据关键词过滤原始消息记录表..." )
        MessagesService.filter_by_keys()

        # 二次过滤,second_filter_qqnumber表、second_filter_groupnumber表, 并保存到数据库(messages表),调用公共方法
        app.logger.info( "根据给定的QQ群号码或者QQ号过滤消息记录表" )
        MessagesService.filter_by_numbers()            

        app.logger.info( "finished %s" % ( __name__ ) )
Пример #3
0
    def task( self ):
        """
        导出QQ群消息记录并写入数据库
        """
        app.logger.info( "launching %s ..." % ( __name__ ) )
        app.logger.info( "当前屏幕宽为:" + str( self.screen_width ) + "," + "当前屏幕高为:" + str( self.screen_height ) )

        for qq_account in self.qq_account_list:  # 依次使用不同的账号登录QQ,导出QQ群消息记录
            qq_number = qq_account["qq_number"]
            qq_password = qq_account["qq_password"]

            # 打开qq
            app.logger.info( "正在打开QQ..." )
            qq = Application( backend='uia' ).start( self.qq_path )

            # qq.QQ.print_control_identifiers()

            # 输入账号            
            app.logger.info( "正在输入账号..." )
            qq.QQ.Pane16.child_window(title="QQ号码", control_type="ComboBox").click_input()
            qq.QQ.Pane16.child_window(title="QQ号码", control_type="ComboBox").type_keys( qq_number )
            
            # 输入密码
            app.logger.info( "正在输入密码..." )
            qq.QQ.Pane16.child_window(title="密码", control_type="Pane").click_input()
            qq.QQ.Pane16.child_window(title="密码", control_type="Pane").type_keys( qq_password )
             
            # 点击登录按钮
            app.logger.info( "正在登录..." )
            qq.QQ.child_window(title="登   录", control_type="Button").click_input()

            app.logger.info( '强制等待10秒,若需要滑块验证,请迅速手动完成' )
            time.sleep( 10 )  # 强制等待10秒,若需要滑块验证,请迅速手动完成

            # 打开QQ消息管理器
            app.logger.info( "正在连接新的QQ进程..." )
            qq = None
            qq = Application( backend='uia' ).connect( path=self.qq_path )

            # 点击第三个tab选项卡,即群聊
            app.logger.info( "正在打开群聊选项卡..." )
            qq.QQ.TabControl.TabItem3.click_input()

            # 单击"我的QQ群"
            # 默认所有QQ账号的“我的QQ群”栏处于关闭状态
            app.logger.info( "正在打开我的QQ群..." )
            qq.QQ.child_window(title="我的QQ群", control_type="ListItem").click_input()

            # 利用pyautogui获取鼠标当前的坐标,然后在第一个QQ群的位置,点击鼠标右键
            x, y = pyautogui.position()
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )
            # 将鼠标向下移动35个像素
            y = y + 35
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )
            pyautogui.moveTo( x, y )
            # 点击鼠标右键
            pyautogui.rightClick()
            # 将鼠标向右移动40个像素,向下移动90个像素    
            x = x + 40
            y = y + 90
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )
            pyautogui.moveTo( x, y )            
            # 点击“查看消息记录”按钮,打开消息管理器
            app.logger.info( "正在打开消息管理器..." )
            pyautogui.click()

            app.logger.info( "等待消息加载完毕" )
            time.sleep( 3 )  # 强制等待3秒
            
            # 导出全部消息记录
            app.logger.info( "正在导出消息记录..." )
            x = self.export_x_coor
            y = self.export_y_coor
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )
            pyautogui.moveTo( x, y ) 
            pyautogui.click(x, y)
            
            # 点击导出按钮
            app.logger.info( "正在点击导出按钮..." )
            x = x + 88
            y = y + 49
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )
            pyautogui.moveTo( x, y ) 
            pyautogui.click( x, y )

            # 选择消息文件保存类型
            app.logger.info( "正在选择文件保存类型..." )
            x = self.msg_file_x_coor
            y = self.msg_file_y_coor
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )     
            pyautogui.moveTo( x, y )
            pyautogui.click( x, y )
            y = y + 39
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )  
            pyautogui.moveTo( x, y )
            pyautogui.click( x, y )
            
            # 向上移动鼠标,防止失去焦点
            y = y - 39
            app.logger.info( "鼠标当前的坐标 X:" + str( x ).rjust( 4 ) + " Y:" + str( y ).rjust( 4 ) )  
            pyautogui.moveTo( x, y )

            # 发送快捷键"Alt+S",保存消息记录文件
            app.logger.info( "正在保存..." )
            pyautogui.keyDown('altleft')
            pyautogui.keyDown('s')
            pyautogui.keyUp('altleft')
            pyautogui.keyUp('s')
            # 如果文件已经存在,则覆盖掉
            pyautogui.keyDown('altleft')
            pyautogui.keyDown('y')
            pyautogui.keyUp('altleft')
            pyautogui.keyUp('y')           

            app.logger.info( '等待消息保存完成' )
            time.sleep( 10 )  # 强制等待10秒

            # 关闭消息管理器
            app.logger.info( "正在关闭消息管理器..." )
            pyautogui.keyDown( 'altleft' )
            pyautogui.keyDown( 'f4' )
            pyautogui.keyUp( 'altleft' )
            pyautogui.keyUp( 'f4' )          
            # qq.QQ.ScrollBar.print_control_identifiers()

            # 关闭QQ
            app.logger.info( "正在关闭QQ..." )
            qq.QQ.child_window(title="我的QQ群", control_type="ListItem").click_input() # 将已经打开的“我的QQ群”栏收起
            pyautogui.keyDown( 'altleft' )
            pyautogui.keyDown( 'f4' )
            pyautogui.keyUp( 'altleft' )
            pyautogui.keyUp( 'f4' ) 

            msgs = open( self.msg_path + r"\全部消息记录.txt","rb" ).read().decode("utf8")

            # 对消息记录进行数据格式化,并增量保存到数据库(original_messages表)   
            app.logger.info( "正在将消息写入数据库..." )
            MessagesService.save_export_group_msgs( msgs=msgs )               
            
            # 一次过滤,first_filter_keys表, 并保存到数据库(messages表),调用公共方法
            app.logger.info( "正在根据关键词过滤原始消息记录表..." )
            MessagesService.filter_by_keys()

            # 二次过滤,second_filter_qqnumber表、second_filter_groupnumber表, 并保存到数据库(messages表),调用公共方法
            app.logger.info( "根据给定的QQ群号码或者QQ号过滤消息记录表" )
            MessagesService.filter_by_numbers()

        app.logger.info( "finished %s" % ( __name__ ) )
Пример #4
0
def patch_delete_qq_numbers(number_id):
    """
    patch /filter/qq_numbers/number_id
        更新一个QQ号码
    delete /filter/qq_numbers/number_id
        删除一个QQ号码
    """
    # 获取所有请求参数
    req_dict = request.values

    # 封装响应字段
    resp_data = {"code": 0, "count": 0, "msg": "操作成功~", "data": []}

    if not number_id:
        resp_data["msg"] = "缺少参数"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 404)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    try:
        number_id = int(number_id)
    except:
        resp_data["msg"] = "参数错误"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 406)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    number_model = SecondFilterQqnumber.query.filter_by(id=number_id).first()
    if not number_model:
        resp_data["msg"] = "资源不存在"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 404)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    if "PATCH" == request.method:
        qq_number = req_dict.get("qq_number", None)
        if not qq_number:
            req_dict = request.get_json()
            qq_number = req_dict.get("qq_number", None)
        if not qq_number:
            resp_data["msg"] = "缺少参数"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 404)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response

        number_model.qq_number = qq_number
        db.session.add(number_model)
        db.session.commit()
        resp_data["data"].append({
            "id": number_model.id,
            "qq_number": number_model.qq_number
        })
        resp_data["count"] = 1
        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"

        # 更新消息记录表
        MessagesService.update_messages()

        return response

    if "DELETE" == request.method:
        db.session.delete(number_model)
        db.session.commit()
        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"

        # 更新消息记录表
        MessagesService.update_messages()

        return response
Пример #5
0
def get_post_qq_numbers():
    """
    get /message/filter/qq_numbers
        获取过滤QQ号码列表
    post /message/filter/qq_numbers
        新增一个QQ号码       
    """
    # 获取所有请求参数
    req_dict = request.values

    # 封装响应字段
    resp_data = {"code": 0, "count": 0, "msg": "操作成功~", "data": []}

    if "GET" == request.method:
        # 实现分页加载
        page = req_dict.get("page", 1)
        try:
            page = int(page)
        except:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        if page < 1:
            page = 1
        page_size = req_dict.get("limit", 10)
        try:
            page_size = int(page_size)
        except:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
        offset = (page - 1) * page_size

        number_model_list = SecondFilterQqnumber.query.order_by(
            SecondFilterQqnumber.id.desc()).offset(offset).limit(
                page_size).all()

        number_data_list = []

        if number_model_list:
            for model in number_model_list:
                number_data_list.append({
                    "id": model.id,
                    "qq_number": model.qq_number
                })

        resp_data["data"] = number_data_list
        resp_data["count"] = db.session.query(
            func.count(SecondFilterQqnumber.id)).scalar()

        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    if "POST" == request.method:
        qq_number = req_dict.get("qq_number", None)
        if not qq_number:
            req_dict = request.get_json()
            qq_number = req_dict.get("qq_number", None)
        if not qq_number:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 404)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        number_model = SecondFilterQqnumber.query.filter_by(
            qq_number=qq_number).first()
        if number_model:
            resp_data["msg"] = "QQ号码已经存在"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        else:
            number_model = SecondFilterQqnumber()
            number_model.qq_number = qq_number
            db.session.add(number_model)
            db.session.commit()
            resp_data["data"].append({
                "id": number_model.id,
                "qq_number": number_model.qq_number
            })
            resp_data["count"] = 1
            response = make_response(json.dumps(resp_data), 200)
            response.headers["Content-Type"] = "application/json;charset=utf-8"

            # 更新消息记录表
            MessagesService.update_messages()

            return response
Пример #6
0
def patch_delete_keys(key_id):
    """
    patch /message/filter/keys/key_id
        更新一个关键词
    delete /message/filter/keys/key_id
        删除一个关键词         
    """
    # 获取所有请求参数
    req_dict = request.values

    # 封装响应字段
    resp_data = {"code": 0, "count": 0, "msg": "操作成功~", "data": []}

    if not key_id:
        resp_data["msg"] = "缺少参数"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 404)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    try:
        key_id = int(key_id)
    except:
        resp_data["msg"] = "参数错误"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 406)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    key_model = FirstFilterKey.query.filter_by(id=key_id).first()
    if not key_model:
        resp_data["msg"] = "资源不存在"
        resp_data["code"] = -1
        response = make_response(json.dumps(resp_data), 404)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    if "PATCH" == request.method:
        key_name = req_dict.get("key_name", None)
        if not key_name:
            req_dict = request.get_json()
            key_name = req_dict.get("key_name", None)
        if not key_name:
            resp_data["msg"] = "缺少参数"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 404)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response

        key_model.key_name = key_name
        db.session.add(key_model)
        db.session.commit()
        resp_data["data"].append({
            "id": key_model.id,
            "key_name": key_model.key_name
        })
        resp_data["count"] = 1
        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"

        # 更新消息记录表
        MessagesService.update_messages()

        return response

    if "DELETE" == request.method:
        db.session.delete(key_model)
        db.session.commit()
        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"

        # 更新消息记录表
        MessagesService.update_messages()

        return response
Пример #7
0
def get_post_keys():
    """
    get /message/filter/keys
        获取过滤关键词列表
    post /message/filter/keys
        新增一个关键词       
    """

    # 获取所有请求参数
    req_dict = request.values

    # 封装响应字段
    resp_data = {"code": 0, "count": 0, "msg": "操作成功~", "data": []}

    if "GET" == request.method:
        # 实现分页加载
        page = req_dict.get("page", 1)
        try:
            page = int(page)
        except:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        if page < 1:
            page = 1
        page_size = req_dict.get("limit", 10)
        try:
            page_size = int(page_size)
        except:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
        offset = (page - 1) * page_size

        key_model_list = FirstFilterKey.query.order_by(
            FirstFilterKey.id.desc()).offset(offset).limit(page_size).all()

        key_data_list = []

        if key_model_list:
            for model in key_model_list:
                key_data_list.append({
                    "id": model.id,
                    "key_name": model.key_name
                })

        resp_data["data"] = key_data_list
        resp_data["count"] = db.session.query(func.count(
            FirstFilterKey.id)).scalar()

        response = make_response(json.dumps(resp_data), 200)
        response.headers["Content-Type"] = "application/json;charset=utf-8"
        return response

    if "POST" == request.method:
        req_dict = request.get_json()
        key_name = req_dict["key_name"] if "key_name" in req_dict else None
        if not key_name:
            resp_data["msg"] = "参数错误"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 404)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        key_model = FirstFilterKey.query.filter_by(key_name=key_name).first()
        if key_model:
            resp_data["msg"] = "关键字已经存在"
            resp_data["code"] = -1
            response = make_response(json.dumps(resp_data), 406)
            response.headers["Content-Type"] = "application/json;charset=utf-8"
            return response
        else:
            key_model = FirstFilterKey()
            key_model.key_name = key_name
            db.session.add(key_model)
            db.session.commit()
            resp_data["data"].append({
                "id": key_model.id,
                "key_name": key_model.key_name
            })
            resp_data["count"] = 1
            response = make_response(json.dumps(resp_data), 200)
            response.headers["Content-Type"] = "application/json;charset=utf-8"

            # 更新消息记录表
            MessagesService.update_messages()

            return response
Пример #8
0
    def get_current_page_msgs( self, group_name=None, group_number=None, send_time=None ):
        """
        提取当前屏/页消息
        """
        app.logger.info( '提取当前屏消息...' )  

        # 当前获取到的消息块列表
        message_list = self.wait.until( EC.presence_of_all_elements_located(
            ( By.ID, 'com.tencent.mm:id/y' )
        ) )
        app.logger.info( '当前页共有%d条消息' %( len( message_list ) ) )

        # 遍历消息块列表,处理每条消息
        app.logger.info( '遍历消息...' )
        for message in message_list:            
            try:  
                # 点击用户头像,进入用户详情页,获取用户信息(如昵称等)
                message.find_element_by_id( 'com.tencent.mm:id/kf' ).click()       
            except NoSuchElementException:
                app.logger.info( '当前消息信息不完全, 跳过当前消息' )
                continue

            # 用户昵称
            sender_name = self.wait.until( EC.presence_of_element_located(
                ( By.ID, 'com.tencent.mm:id/qj' )
            ) ).get_attribute( 'text' )
            app.logger.info( '当前消息用户昵称为:%s' %( sender_name ) )

            app.logger.info( '正在点击返回按钮,返回消息对话框界面...' )
            self.driver.find_element_by_id( 'com.tencent.mm:id/hs' ).click()
            self.wait.until( EC.presence_of_element_located( ( By.ID,'com.tencent.mm:id/hm' ) ) )                   
  
            # 消息内容   
            content = ''
            try:
                content += message.find_element_by_id( 'com.tencent.mm:id/af2' ).get_attribute( 'text' )
                content += message.find_element_by_id( 'com.tencent.mm:id/af5' ).get_attribute( 'text' )
                app.logger.info( '匹配到超链接消息:%s' %( content ) )
            except NoSuchElementException:
                try:
                    content_box = message.find_element_by_id( 'com.tencent.mm:id/kh' )  # 文字消息box                   
                except NoSuchElementException:
                    app.logger.info( '当前消息信息不完全, 跳过当前消息' )
                    continue
                try:
                    app.logger.info( '长按"消息正文"2秒钟' )
                    TouchAction( self.driver ).long_press(  # 长按"消息正文"2秒钟
                        x=content_box.location.get('x'), y=content_box.location.get('y'), duration=2000
                    ).wait( 200 ).release().perform()

                    app.logger.info( '复制当前文字消息' )
                    self.driver.find_element_by_android_uiautomator( # 复制当前文字消息
                        'new UiSelector().text("复制")'
                    ).click()    
                    
                    app.logger.info( '获取剪贴板的文字消息' )
                    content = get_clipboard_text()
                    app.logger.info( '匹配到文字消息:%s' %( content ) )                
                except:
                    app.logger.info( traceback.format_exc() )
                    app.logger.info( '当前消息信息不完全, 跳过当前消息' )
                    continue           
                    
            # 对消息记录进行数据格式化,并增量保存到数据库(original_messages表)     
            app.logger.info( "正在将消息写入数据库..." )                    
            MessagesService.save_wechat_group_msg( sender_name=sender_name, content=content, group_name=group_name, group_number=group_number, send_time=send_time )