社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

为了追到小姐姐,我用 Python 制作了一个机器人

编程派 • 3 年前 • 388 次点击  

点击上方“编程派”,选择设为“设为星标

优质文章,第一时间送达!



阅读文本大概需要 15 分钟。


1

目 标 场 景


最近发现有一个微信好友,我的每一条朋友圈动态,无论什么时候发布,发布的什么内容,点赞列表总有它的身影。


这不禁让我陷入一种沉思,是否我也能做一个机器人,第一个时间给暗恋的小姐姐朋友圈点赞,是不是也能拉动我们之间的距离。


作为技术人,肯定首先想的是如何实现的,实现这个功能的主流方案就下面 3 种,分别是:自动化、无障碍服务、Xposed 插件。


本篇文章带大家利用 Python 自动化实现这一骚操作。

2

编 写 代 码


在开始编写代码之前,需要做下面的准备工作

  •  Android 开发环境

  • 本机安装 Node.js

  • npm 命令安装 Appium Server

  • 安装 Python 依赖包

  • 百度情感分析 API

  • 开启 Appium 服务


# 1、安装 Node.js

# 2、安装 Appium
npm install -g appium

# 3、打开appium服务,并开启服务便于调试
appium -g /appium.log

# 4、百度情感分析API依赖
pip3 install baidu-aip

# 5、安装Python依赖
pip3 install Appium-Python-Client


下面通过 7 步完成这个功能,分别是:打开微信、进入朋友圈入口、

首次滑动处理、获取每条动态的内容、操作点赞、可变数据参数化、异常处理。


第 1 步,打开微信


我们利用 adb 命令获取微信应用的包名及入口 Activity,通过数据线连接电脑,获取到设备 id,编写 Appium 配置文件。


# 配置文件
caps = {
    "platformName""Android",
    "deviceName""ca2b3455",     # 设备id
    "appPackage"'com.tencent.mm',   # 微信包名
    "appActivity"'com.tencent.mm.ui.LauncherUI',  # 微信入口Activity
    "autoGrantPermissions"True,    
    "noReset"True   # 不重置应用
}


然后,WebDriver 就能通过上面的配置文件打开微信 App 了。


# 根据配置文件,驱动应用打开
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)

# 隐式等待微信主页完全加载
self.driver.implicitly_wait(10)


第 2 步,进入朋友圈入口


只需要找到首页的发现」Tab,执行点击操作,接着点击朋友圈」文本控件,即能进入到朋友圈主界面。


由于从点击到朋友圈页面完全加载需要一个不确定的时间,这里使用一个显式等待,直到朋友圈「动态列表元素」加载可见。


def __open_friend_circle(self):
     """
     打开朋友圈
     :return:
     """

     # 点击发现Tab
     find_element_by_id_and_text(self.driver, self.tag_id["id_page_main_discover"],
                                    self.tag_text["discover"]).click()

     # 进入朋友圈
     find_element_by_text(self.driver, self.tag_text["friend_circle"]).click()

def __wait_for_appear(self, id):
     """
     等待某个元素出现
     :param id:
     :return:
     """

     # 显式等待 30s,直到元素出现
     WebDriverWait(self.driver, 30).until(
            EC.visibility_of_element_located((By.ID, id))
     )

self.__wait_for_appear(self.tag_id['id_page_friend_circle_listview'])


第 3 步,首次滑动处理


由于屏幕分辨率的差异,部分小屏手机可能第一条动态在界面上可能展示不全,直接处理会产生异常,为了保证处理的完整性,需要做一次滑动预处理。


比如:下图的第一条动态只有发布者和发布内容可见,发布时间不可见。



我们只需要拿到「第一条动态元素」的 y 轴坐标,向上对应的距离,这样第一条动态就完全展示出来了。





    
def swipe_first(self, id_listview):
    """
    首次滑动
    :param param:
    :return:
    """

    element_listview = self.driver.find_element_by_id(id_listview)

    # 由于动态Item从ListView的第二子元素开始,获取到第一个子元素的高度
    element_content = element_listview.find_element_by_class_name("android.widget.LinearLayout")

    # 获取元素的属性
    size = element_content.size

    # 滑动一次
    # 由于滑动因为滑动速度存在误差,这里滑动距离需要做一下处理
    swipe_up_with_distance(self.driver, size.get("height") - 501000)

    time.sleep(2)


需要注意的是,由于滑动过快时,滑动距离会存在误差,这里对滑动距离稍微做了一下处理。


第 4 步,获取每条动态的内容


动态的内容分为纯文本、其他(图片、视频、链接、音乐等)、文本+其他三种形式。


我们获取到:动态的发布者、发布时间、发布文本内容。


def __get_dynamic_content(self, element):
    """
    获取动态的类型
    :param element:
    :return:
    """

    # 文字的id:
    # 注意:不确定是否存在的元素,要使用find_elements_**,否则会抛出异常
    element_titles = element.find_elements_by_id(self.tag_id['id_page_friend_circle_item_title'])

    # 好友名
    element_author = element.find_element_by_id(self.tag_id['id_page_friend_circle_item_friend_name'])

    # 发布时间
    # 注意:可能没法找到,导致异常
    element_publish_time = element.find_element_by_id(self.tag_id['id_page_friend_circle_item_publish_time'])

    author_name = element_author.get_attribute("text")
    publish_time = element_publish_time.get_attribute("text")
    content = None

    if len(element_titles) > 0:
        content = element_titles[0].get_attribute('text')

    # 返回发布者、发布时间、发布内容
    return author_name, publish_time, content


第 5 步,操作点赞


根据上面获取的内容,去判断这条动态是否值得我们去点赞。


如果本条动态的发布内容不为空,我们就采用百度的情感分析 API 去分析内容的积极性。


from aip import AipNlp

def get_word_nlp(word):
    """
    判断内容是否为消极的
    :param word:
    :return:
    """

    """ 你的 APPID AK SK """
    APP_ID = 'xx'
    API_KEY = 'xxx'
    SECRET_KEY = 'xxxx'

    client = AipNlp(APP_ID, API_KEY, SECRET_KEY)

    """ 调用情感倾向分析 """
    result = client.sentimentClassify(word)

    # 该情感搭配的极性(0表示消极,1表示中性,2表示积极)
    sentiment = result.get("items")[0].get("sentiment")

    return sentiment == 0


过滤掉消极内容和已经点过赞的动态,其他每一条动态都执行点赞操作。


# 如果文本存在,并且是消极的,就不处理
if dynamic_contents[2and get_word_nlp(dynamic_contents[2]):
    print('消极的内容,不点赞!')
    continue

    # 点击,弹出点赞按钮
    element_perform_click(element, self.tag_id['id_page_friend_circle_approve_button_pre'])

    # 不点赞的情况:已经点过赞、有文字内容并且为消极
    # 未点赞:赞;已赞:取消
    if approve_text == '取消':
         # 关闭点赞弹框
         print('已经点赞过,不点赞')
         element_perform_click(element, self.tag_id['id_page_friend_circle_approve_button_pre'])
         continue

     # 注意,点赞按钮没法执行点击操作,需要往上找父类元素执行点击操作
     element_perform_click(self.driver, self.tag_id['id_page_friend_circle_approve_button'])


处理完一页动态之后,接着可以循环滑动页面去查找动态列表,继续上面的操作。


while True:
     elements = self.driver.find_elements_by_id(id_item)
     # .... 循环操作

     # 滑动一次
     swipe_up(self.driver, 500)
     time.sleep(2)


第 6 步,可变数据参数化


为了保证后期的可维护性,对文中查询的 id、文本等元素写入到 yaml 配置文件中。


tag:
  id:
    id_page_main_discover: 'com.tencent.mm:id/cw2'   # 主页:发现按钮
    id_page_friend_circle_listview: 'com.tencent.mm:id/e2p'   # 朋友圈页面:动态列表
    id_page_friend_circle_item: 'com.tencent.mm:id/e6t'   # 朋友圈页面:每一项动态
    id_page_friend_circle_item_title: 'com.tencent.mm:id/e6x'  # 朋友圈页面:动态标题文本
    id_page_friend_circle_item_friend_name: 'com.tencent.mm:id/azl'  # 朋友圈页面:动态的发布者
    id_page_friend_circle_item_publish_time: 'com.tencent.mm:id/e25'   # 朋友圈页面:动态发布时间
    id_page_friend_circle_approve_button_pre: 'com.tencent.mm:id/e2c'  # 朋友圈页面:动态点赞入口按钮
    id_page_friend_circle_approve_status: 'com.tencent.mm:id/e1l'  # 朋友圈页面:动态点赞状态文本(赞或者取消)
    id_page_friend_circle_approve_button: 'com.tencent.mm:id/e1k'  # 朋友圈页面:每一个动态的点赞按钮
  text:
    discover: '发现'
    friend_circle: '朋友圈'


后期一旦微信版本升级迭代,只需要更改此处代码即可。


第 7 步, 异常处理


上面的代码如果不做异常处理,直接运行很有可能会出现各类异常,下面逐一进行说明。


首尾动态处理:当前界面第一条动态和最后一条动态中的部分元素不可见。


针对这个问题,需要考虑是在顶部还是尾部。如果在顶部,继续处理下一条动态;如果在尾部,直接跳出本次循环。


for index, element in enumerate(elements):
    try:
          dynamic_contents = self.__get_dynamic_content(element)
    except Exception as e:
          err_tag = "头部元素" if index == 0 else "尾部元素"
          err = "**********%s产生一个异常**********" % err_tag

          print(err)
          logging.error(err)
          logging.error(traceback.format_exc())

          # 判断是页面的第一个元素还是最后一个元素
          if index == 0:
                continue
          else:
                break


元素不可点击:可以往上查找父级元素,直到找到一个可以点击的元素,直接点击操作。


def element_perform_click(parentElement, id):
    """
    某个元素执行点击操作
    :param parentElement:WebDriver或者WebElement
    :param id:待查找的元素id
    :return:
    """

    element = parentElement.find_element_by_id(id)

    # 判断是否可以点击
    element_clickable = element.get_attribute("clickable")

    if element_clickable:
        element.click()
        return

    # 如果当前元素不可以点击,一直向上找可以点击的父类元素,执行点击操作
    while True:
        element = element.parent
        if element.get_attribute("clickable"):
            element.click()
            break


元素不可见:有些元素在执行点击操作的时候,不可见。


这个问题只需要捕获异常,滑动小距离之后,再次执行点击操作即可。


def fb_id(driver: WebDriver, parentElement, element_id):
    """
    通过id查找元素
    :param driver:
    :param parentElement 父元素中查找
    :param element_id:
    :return:
    """

    while True:
        try:
            # 注意:查找单个元素经常容许产生异常,这里进行捕获后,然后滑动一次,继续查找
            element = parentElement.find_element_by_id(element_id)
            return element
        except:
            print('查找元素:【%s】产生异常,滑动一次,再进行查找!' % element_id)
            swipe_up_small(driver, 500)


3

结 果 结 论


通过上面的 7 步操作,就能完成了一个有感情的朋友圈点赞机器人。



我已经将全部源码上传到后台,关注公众号后回复「 点赞 」即可获得全部源码。

如果你觉得文章还不错,请大家点赞分享下。你的肯定是我最大的鼓励和支持。

回复下方「关键词」,获取优质资源


回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版

回复关键词「入门资料」,立即获取主页君整理的 10 本 Python 入门书的电子版

回复关键词「m」,立即获取Python精选优质文章合集

回复关键词「book 数字」,将数字替换成 0 及以上数字,有惊喜好礼哦~


题图:pexels,CC0 授权。

好文章,我在看

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/63894
 
388 次点击