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__ ) )
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__ ) )
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__ ) )
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
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
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
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
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 )