APP自动化爬虫(小红书)仅供学习

3,293 阅读3分钟

环境


1.MacBook

2.小米手机(已root)-红米note4x

3.pycharm

整体思路


  1. 基于Airtest编写自动化脚本,比如自动下拉列表

  2. Airtest代码copy到pycharm运行

  3. 安装Mitmproxy,编写拦截脚本并运行,实现爬取接口返回数据

实践


  • 基于Airtest编写自动化脚本

    Airtest 是一个跨平台的、 **基于图像识别 **的UI自动化测试框架,适用于游戏和App,支持平台有Windows、Android 和 IOS,主要编写语言为 python。

    Airtest基本[Api]使用:

    1. 获取元素:
      • ls = poco(name="com.xingin.xhs:id/aj8") 经过name获取
      • ls = poco(text=item.get_text()) 经过text
      • poco("android.widget.LinearLayout").offspring("com.xingin.xhs:id/ak6") 经过目录树
    2. 点击元素: x.click()
    3. 获取文本: x.get_text()
    4. 滑动屏幕: swipe([0.5, 0.8], [0.5, 0.7]) 从一个点到另外一个点
    5. 是否存在: x.exist()
  • 基于Mitmproxy拦截请求响应体

    mitmproxy是一个支持HTTP和HTTPS的抓包程序,相似Fiddler、Charles的功能,只不过它经过控制台的形式操做;使用 mitmproxy 最主要是使用它的一个组件 mitmdump ,它能够经过python脚本处理响应内容,相似于Fiddler的界面抓包,可是咱们能够更加方便地拿到响应数据

    mitmproxy基本命令

    #安装mitmproxy
    pip3 install mitmproxy
    
    #http.HTTPFlow 实例 flow
    #flow.request.headers #获取所有头信息,包含Host、User-Agent、Content-type等字段
    #flow.request.url #完整的请求地址,包含域名及请求参数,但是不包含放在body里面的请求参数
    #flow.request.pretty_url #同flow.request.url目前没看出什么差别
    #flow.request.host #域名
    #flow.request.method #请求方式。POST、GET等
    #flow.request.scheme #什么请求 ,如https
    #flow.request.path # 请求的路径,url除域名之外的内容
    #flow.request.get_text() #请求中body内容,有一些http会把请求参数放在body里面,那么可通过此方法获取,返回字典类型
    #flow.request.query #返回MultiDictView类型的数据,url直接带的键值参数
    #flow.request.get_content()#bytes,结果如flow.request.get_text()
    #flow.request.raw_content #bytes,结果如flow.request.get_content()
    #flow.request.urlencoded_form #MultiDictView,content-type:application/x-www-form-urlencoded时的请求参数,不包含url直接带的键值参数
    #flow.request.multipart_form #MultiDictView,content-type:multipart/form-data
    
    #以上均为获取request信息的一些常用方法,对于response,同理
    #flow.response.status_code #状态码
    #flow.response.text#返回内容,已解码
    #flow.response.content #返回内容,二进制
    #flow.response.setText()#修改返回内容,不需要转码
    #以上为不完全列举
    

    #爬取小红书列表脚本(xhs.py)

    #xhs.py
    from mitmproxy import ctx
    import json
    import csv
    
    import urllib.parse
    
    try:
        # 所有的请求都会经过request
        def request(flow):
            print("flow.request.url==========", flow.request.url)
    
            #搜索
            if "https://edith.xiaohongshu.com/api/sns/v10/search/notes" in flow.request.url:
                d = {}
                test = urllib.parse.parse_qs(urllib.parse.urlparse(flow.request.url).query)
                print("aaatest=",str(test["keyword"][0]))
    
        # 所有的请求都会经过response
        def response(flow):
            if "https://edith.xiaohongshu.com/api/sns/v10/search/notes" in flow.request.url:
                # d = {"text": str(flow.response.text)}
                # content = json.dumps(d)
                keywordStr = urllib.parse.parse_qs(urllib.parse.urlparse(flow.request.url).query)
                keyword = keywordStr["keyword"][0]
                content = json.loads(flow.response.text)
                f = open('小红书.csv', 'a+', encoding='utf-8')  # a+表示追加
                csv_writer = csv.writer(f)
                csv_writer.writerow(["id", "类型", "标题", "摘要", "点赞", "用户名", "用户头像", "用户id", "发布时间", "搜索关键词"])
                for i in range(len(content["data"]["items"])):
                    id = content["data"]["items"][i]["note"]["id"]
                    type = content["data"]["items"][i]["note"]["type"]
                    desc = content["data"]["items"][i]["note"]["desc"]
                    title = content["data"]["items"][i]["note"]["title"]
                    img_user = content["data"]["items"][i]["note"]["user"]["images"]
                    like = content["data"]["items"][i]["note"]["liked_count"]
                    user = content["data"]["items"][i]["note"]["user"]["nickname"]
                    user_id = content["data"]["items"][i]["note"]["user"]["userid"]
                    time = content["data"]["items"][i]["note"]["timestamp"]
    
                    note_url = "https://www.xiaohongshu.com/discovery/item/" + str(id)
                    # star1 = get_star_comment(note_url)[0]
                    # comment1 = get_star_comment(note_url)[1]
    
                    # t1="id: {},标题:{},喜欢:{},用户名:{},用户头像:{}, 用户id: {}, 发布时间:{}, 收藏数:{}, 评论数: {}".format(id,title,like,user,img_user,user_id,time,star1, comment1)+"\n"
                    # print(t1)
    
                    csv_writer.writerow([id, type, title, desc, like, user, img_user, user_id, time, keyword])
    
                f.close()
    
    except:
        pass
    
  • 在PyCharm运行脚本

    Airtest与Pycharm不能同时运行

    1. 运行run_xhs.py脚本,可方便debug(xhs.py)
    import sys
    import os
    from mitmproxy.tools.main import mitmdump
    
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    print(os.path.dirname(os.path.abspath(__file__)))
    
    mitmdump(['-s', 'xhs.py','-p','8085'])
    
    1. PyCharm安装pocoui
    pip install pocoui
    
    1. 编写自动下拉脚本(auto_swipe.py),先在Airtest写好,然后再copy到pycharm
    # -*- encoding=utf8 -*-
    __author__ = "Nero"
    
    from airtest.core.api import *
    
    import random
    
    auto_setup(__file__)
    import logging
    
    from poco.drivers.android.uiautomation import AndroidUiautomationPoco
    
    poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
    
    logger = logging.getLogger("airtest")
    logger.setLevel(logging.ERROR)
    
    # poco(text="小红书").click()
    # sleep(10.0)
    # 搜索框
    poco(name="com.xingin.xhs:id/c2b").click()
    # 搜索关键字
    poco(name="com.xingin.xhs:id/b6i").set_text("护肤")
    # 点击搜索
    poco(name="com.xingin.xhs:id/b6m").click()
    # biji_list[0].click()
    swipe = True
    total = 0
    while swipe:
        # 滑动的速率,(0,1)
        duration = random.random()
        # 提高容错:判断当前页面是否在loading,措施:向上滑再恢复下滑
        isLoading = poco(name="com.xingin.xhs:id/at_").exists()
        print("isLoading=", isLoading)
        while isLoading:
            sleep(5.0)
            poco.swipe([0.5, 0.2], [0.5, 0.8], duration=duration)
            poco.swipe([0.5, 0.2], [0.5, 0.8], duration=duration)
            isLoading = poco(name="com.xingin.xhs:id/at_").exists()
    
        # 向下滑动
        poco.swipe([0.5, 0.8], [0.5, 0.2], duration=duration)
        sleep(1)
    
        # 判断是否已到底
        isLast = poco(name="com.xingin.xhs:id/aqi").exists()
        if isLast:
            if (poco(name="com.xingin.xhs:id/aqi").get_text() == '无更多内容'):
                swipe = False
    print("total=", total)
    

    4.最后先执行run_xhs.py,再执行auto_swipe.py即可