• 【项目整理】安卓应用商店评论监控平台


    目标:对常见手机应用商店评论进行爬取,对第三方数据网上的评论数据进行爬取

    1. 爬虫

    爬虫过程:获取评论数据→写入数据库

    1.1 静态网页爬虫:requests库
    以某应用商店的outlook页面为例:
    1. 在打开的应用页面右键点击检查,然后刷新一下页面,选中network,再选中Fetch/XHR,发现有一个网页评论数据url(下图3)
      在这里插入图片描述
    2. 复制该url在新的页面打开,发现就是我们需要的评论数据,只是被编码过(没关系)
      在这里插入图片描述
    3. 继续分析该url:http://app.so.com/message/index?page=1&requestType=ajax&_t=1661237476289&name=Outlook+Android_com.microsoft.office.outlook,经过测试发现:
      &_t=1661237476289是时间戳,去掉后不影响返回结果;
      page=1是我们请求的评论页数,可以自行更换(后面爬虫就是对page进行遍历)
      &name=Outlook+Android_com.microsoft.office.outlook是应用的名字。
    4. 然后我们就可以使用requests.get(url)方法,获取该url返回的评论数据,利用json.loads(res.text)方法读取返回的文字内容。
      import requests
      import json
      
      headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
      }
      res = requests.get('http://app.so.com/message/index?page=1&requestType=ajax&_t=1661237476289&name=Outlook+Android_com.microsoft.office.outlook', headers=headers)
      # 加载评论内容
      json_str = json.loads(res.text) 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    完整代码如下

    import requests
    import json
    import pymysql
    
    headers = {
      'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
    }
    # 要爬取的应用url
    appname = {
        'Outlook':'Outlook+Android_com.microsoft.office.outlook',
        'Office':'Office+Mobile+for+Office+365+Android_com.microsoft.office.officehub',
    }
    
     # 连接数据库
     db = pymysql.connect(
     host="127.0.0.1", # 本机地址
     port=3306, # 默认端口
     user='root',    #在这里输入用户名
     password='root',     #在这里输入密码
     database='comments', # 数据库名
     charset='utf8mb4'
     )
     cursor = db.cursor() #创建游标对象,用于数据库操作
    
     for k,v in appname.items():# 遍历应用列表
         for i in range(100): # 爬取前100页
             # 获取请求网页内容
             res = requests.get('http://app.so.com/message/index?page='+str(i)+'&requestType=ajax&name='+str(v), headers=headers)
             # 如果获取页面为[],表明无内容
             if res.content == b'\n\n\n[]':
                 break
             # 加载评论内容
             json_str = json.loads(res.text) 
             for item in json_str:
                 # 通过msgid判断是否在数据库已存在此条数据
                 querysql = "select msgid from database_comments_360 where msgid="+pymysql.converters.escape_string(str(item.get('msgid')))
                 if(cursor.execute(querysql)==0):# execute(querysql)返回的是操作影响的行数
                     sql = 'insert into database_comments_360(date,user,content,score,version,name, msgid) values(%s,%s,%s,%s,%s,%s,%s);'
    
                     date = pymysql.converters.escape_string(item.get('create_time')) # 获取数据
                     user = pymysql.converters.escape_string(item.get('username'))
                     content = pymysql.converters.escape_string(item.get('content'))
                     score = pymysql.converters.escape_string(str(item.get('score')))
                     version = pymysql.converters.escape_string(str(item.get('version_name')))
                     msgid = pymysql.converters.escape_string(str(item.get('msgid')))
                     name = pymysql.converters.escape_string(str(k))
    
                     data = [date,user,content,score,version,name,msgid]
                     cursor.execute(sql,data)     # 插入数据
                     db.commit() # 提交命令 (如果不提交,不会进行数据库操作)
    
         print('爬取'+str(k)+'应用评论'+str(i)+'页')
     cursor.close() 
     db.close()  #关闭数据库连接
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    1.2 动态网页爬虫:selenium库

    selenium是一个web测试工具,可以模拟浏览器操作,如打开网页,点击元素等。

    在使用selenium前需要安装浏览器版本对应的chromedriver,解压后把chromedriver.exe的路径加入用户变量Path中

    1. 对某第三方网站的评论页面url进行分析:
      某发烧厂商店edge评论:https://app.diandian.com/app/6njqcmu6xj0rip4/android-review?system=4&market=3&summer=
      某蓝厂商店edge评论:https://app.diandian.com/app/rn6vtpukqjzvhw4/android-review?system=4&market=4&summer=
      发现只有两部分不一样,分别是应用名字和market,而&summer=经过测试可以删掉,不影响返回结果。这样我们就获得了需要的url。
    2. 在页面点击右键,选中检查,发现该网站使用js动态加载数据,无法像静态网页一样通过url获取评论数据。
    3. 点击element,用鼠标选中评论数据数据所在的位置,也就是左侧的表格,可以看到右侧有评论数据内容,我们就可以通过css选择器来定位到该数据:
      在这里插入图片描述
    4. 同样也可以定位到用户评分和用户名等内容,如下图所示:
      在这里插入图片描述
    5. 我们可以使用selenium中By.CSS_SELECTOR来定位到我们需要的元素,css样式通过下图中copy selector来获取。
    6. 分析完成后,使用selenium中的webdriver模拟对浏览器进行操作
      from selenium import webdriver # 模拟浏览器操作
      from selenium.webdriver.common.by import By # 后面用By里面的css选择器定位网页元素
      
      driver = webdriver.Chrome()# 初始化一个浏览器对象
      driver.get("https://app.diandian.com/app/4q3uzuzpe5x8es7/ios-review?system=4") # 打开网页
      
      table=driver.find_element(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table')#使用css定位网页表格位置
      #获取表格包含的行,并将行数赋值
      time.sleep(2)
      table_rows=table.find_elements(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table > tbody > tr')# table包含行数的集合,包含标题                                        
      print('table_rows',table_rows)
      
      vrows=len(table_rows)#将总行数赋给变量vrows,后面对每一行进行遍历
      print('vrows',vrows)
      
      # 对表格进行按行遍历
      for table_num in range(1, vrows):
          time.sleep(2)
      	 # 爬取评论内容
          content = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.dd-word-wrap > div > p > span").text
         
      	 # 爬取评论分数
      	 time.sleep(2)
      	 score = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(1) > div.dd-table-cell > div > div").get_attribute("aria-valuenow")
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    完整代码如下:

    # diandian.py 文件
    from selenium import webdriver # 模拟浏览器操作
    from selenium.webdriver.common.by import By # 后面用By里面的css选择器定位网页元素
    import pymysql # 对MySQL数据库进行连接、插入数据等操作
    
    def myscrapy(urls,market,appname): # 这里封装了函数,需要传入三个参数,分别是应用的url,market哪个应用市场,appname是数据库表名,指示要写入哪个数据库表
        driver = webdriver.Chrome()# 初始化一个浏览器对象
    
        # 连接数据库
        db = pymysql.connect(
        host="127.0.0.1", 
        port=3306,
        user='root',    #在这里输入用户名
        password='root',     #在这里输入密码
        database='comments', #数据库名
        charset='utf8mb4'
        )
        cursor = db.cursor() #创建游标对象
    
        # 删除表中数据 (非必需模块,每次爬取都先删除数据库中已有部分,重新爬取)
        sql='truncate table database_comments_'+str(appname)+';'
        cursor.execute(sql)   
        db.commit()
    
        for k,v in urls.items():# 遍历要爬取的应用urls
            driver.get("https://app.diandian.com/app/"+str(v)+"/android-review?market="+str(market)+"&summer=") # 打开网页
            # 爬前10页
            for i in range(10):
                print("正在爬取第",i,"页")
                # 点击下一页
                if (i!=0): # 在第一页之后的页面,爬取之前需要点击当前页面的‘下一页’按钮,刷新后再进行爬取
                    try:#try处理部分应用只有一页评论,没有nextbutton按钮
                        next_button = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > div > div > button.btn-next")
                        disable = next_button.get_attribute("disabled")
                        # 如果下一页按钮可用再点击,否则跳出此次循环
                        if(disable!='true'):
                            next_button.click()
                            print("next page")
                        else: 
                            print("没有点击下一页")
                            break
                    except:
                        break
                # 开始爬表格数据 sleep是为了防止刷新过快,页面元素失效无法获取
                time.sleep(2) 
                table=driver.find_element(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table')#使用css定位网页表格位置
                #获取表格包含的行,并将行数赋值
                time.sleep(2)
                table_rows=table.find_elements(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table > tbody > tr')# table包含行数的集合,包含标题                                        
                print('table_rows',table_rows)
    
                vrows=len(table_rows)#将总行数赋给变量vrows,后面进行循环
                print('vrows',vrows)
                for table_num in range(1, vrows):
                    time.sleep(2)
                    # 爬取评论内容
                    try:
                        content = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.dd-word-wrap > div > p > span").text
                        print('content:',content)                             
                    except:
                        continue  
                    # 爬取评论分数
                    time.sleep(2)
                    score = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(1) > div.dd-table-cell > div > div").get_attribute("aria-valuenow")
                    print('score:',score)                            
                    # 爬取评论时间
                    time.sleep(2)
                    date = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(3) > div > div").text
                    print('date:',date)
                    # 爬取评论应用版本
                    try:
                        version = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.comment-info.dd-flex.dd-flex-warp > span:nth-child(6)").text
                        print('version:',version)
                    except:
                        version = " "
                        print("not found version!")
    
                    time.sleep(2)
                    try:
                        user = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.comment-info.dd-flex.dd-flex-warp > div > a").text
                        print('user:',user)
                    except:
                        user = "anoy"
                        print("not found user!")
                    
                    time.sleep(2)
                    name = driver.find_element(by=By.CSS_SELECTOR, value=" #appinfo-content > div.container > div:nth-child(2) > div.content-side > div.container-head > div.dd-flex-1.dd-flex.dd-flex-column.dd-flex-space.dd-overflow-hidden > div.logo-wrap > div.max-width-80 > div.app-name > h1").text
                    print('name:',name)
    
                    data = [date,user,content,score,version,name]
                    print(data)
    
                    sql = 'insert into database_comments_'+str(appname) +'(date,user,content,score,version,name) values(%s,%s,%s,%s,%s,%s);'
                    cursor.execute(sql,data)     # 插入数据
                    db.commit() # 提交sql语句
    
        cursor.close() 
        db.close()  # 关闭数据库连接
        driver.quit() # 退出浏览器
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    下面是调用部分,以某第三方数据网的vivo商店评论为例:

    # all.py文件
    import diandian
    # 爬取第三方数据网蓝厂商店的下面几个应用评论数据
    vivo_urls={
        'Outlook':'jn79t9ueolpbqy3',
        'Office':'3403tku16vp5t14',
        'Edge':'rn6vtpukqjzvhw4',
        
    }
    
    diandian.myscrapy(vivo_urls,4,'vivo')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 定时任务 sched库

    参考链接:Python事件调度器定时任务sched

    1. 利用内置模块 sched 实现定时任务
      sched 模块实现了一个通用事件调度器,在调度器类中使用一个延迟函数,等待特定的时间,执行任务。但该方法会阻塞线程,直到所有被调度的任务都执行完成。

    2. class sched.scheduler(timefunc, delayfunc) 这个类定义了调度事件的通用接口,需要外部传入两个参数,
      timefunc:无参数,返回时间戳的函数。常用的有time模块里面的 time。
      delayfunc :需要一个参数,与timefunc的输出兼容,常用的有time模块的 sleep。

      '''
      初始化scheduler类对象
      time.time 返回时间戳
      time.sleep 在定时未到达之前阻塞
      用time模块的这两个函数来实例化scheduler对象
      '''
      schedule = sched.scheduler(time.time, time.sleep)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. scheduler 对象主要方法:

      1. enter(delay, priority, action, argument),延迟 delay 个时间单位安排一个事件。
      2. cancel(event):从队列中删除事件。如果事件不是当前队列中的事件,则该方法将抛出一个 ValueError。
      3. run():运行所有预定的事件。这个函数将等待(使用delayfunc() 函数),然后执行事件,直到不再有预定的事件。

    代码示例:

    import sched # 定时任务模块
    import time # 时间模块
    import datetime # 日期时间模块
    
    s = sched.scheduler(time.time, time.sleep) # 实例化scheduler对象
    
    def print_time(a='default'): # 要调用的函数
        print("From print_time", time.time(), a)
    
    def print_some_times():
        print(time.time())
        s.enter(10, 1, print_time) # 加入队列
        s.enter(5, 2, print_time, argument=('positional',))# 加入队列
        s.enter(5, 1, print_time, kwargs={'a': 'keyword'})# 加入队列
        print(s.queue)
        s.run()
        print(time.time())
    
    print_some_times()
    # 1607676900.9483116
    # From print_time 1607676905.9483757 keyword
    # From print_time 1607676905.9483757 positional
    # From print_time 1607676910.9485233 default
    # 1607676910.9485233
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3. django后端框架 版本3.2.5

    参考链接:
    菜鸟教程-django
    Django官方教程 (推荐)

    3.1 安装Django
    1. 使用pip install Django==x.x,安装自己需要的版本
    2. 测试是否可用:
      import django
      print(django.get_version()) # 如果可用的话会打印出版本号,我这里是3.2.5
      
      • 1
      • 2
    3. 创建Django项目
      django-admin startproject mysite # mysite是项目名称。(这句是示例,后面图中我实际创建的项目名是django_app)
      
      • 1

    在这里插入图片描述
    我们主要修改urls.pysettings.py文件

    3.2 数据库
    1. 先设置好本地服务器环境,我使用的是phpstudy pro,启动mysql服务
      在这里插入图片描述
      使用上图中右上角的数据库工具——SQL_Front,新建一个数据库,数据库名为comments

    2. 修改Django项目settings.py中数据库部分:
      在这里插入图片描述
      上图中NAME是数据库名,USER是数据库用户名,PASSWORD是数据库密码

    3. 使用 python manage.py startapp database新建一个app,名字叫database,用于数据库操作。
      进入database目录,修改models.py,如下图设置好数据表类:
      在这里插入图片描述

    4. 然后修改settings.pyINSTALLED_APPS,添加上面新建的app,也就是database,如下图所示:
      在这里插入图片描述

    5. 使用python manage.py makemigrations databasepython manage.py migrate database对该数据表进行初始化,也就是创建这些表

      1. makemigrations的官方解释如下:
        在这里插入图片描述

      2. migrate命令的官方解释如下:
        在这里插入图片描述

      3. 运行上面两个命令之后,使用phpstudy pro的SQL_Front查看数据库,可以看到已经创建了我们刚才在model.py里定义的数据表
        在这里插入图片描述

      总之,模型更改的三步指南:
      1. 在models.py中更改模型。
      2. 运行python manage.py makemigrations app名字,存储这些更改。
      3. 运行python manage.py migrate,将这些更改应用到数据库。

    6. 修改views.py,编写返回数据表内容的函数
      urls
      get_360实现了以json格式返回Comments_360表中的所有数据

    7. 修改urls.py,也就是网站目录。
      在这里插入图片描述
      在终端输入python manage.py runserver回车,运行django项目,实现了在http://127.0.0.1:8000/360/中返回数据库中数据,如下图所示:
      在这里插入图片描述

    至此,后端部分完成。


    4. 将后端部署到腾讯云和阿里云服务器上,进行生产环境部署

    参考链接:【Django】宝塔面板部署Django+MySQL项目实战 这篇文章写的很详细,可以直接去看这篇文章

    1. 部署前准备

      1. 调开发模式为生产模式
        Django配置文件settings.py修改,调开发模式为生产模式:
        DEBUG = False  # 开发模式为True,上线时设置为False
        ALLOWED_HOSTS = ['8.130.98.214']  # 开发模式为*,实际生产为真实IP或域名
        STATIC_URL = '/static/' #上线模式
        # STATICFILES_DIRS = [
        #     os.path.join(BASE_DIR, "static"),    # 开发模式
        # ]
        STATIC_ROOT = os.path.join(BASE_DIR, "static")    # 生产模式
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      2. 导出项目依赖包
        pip freeze > requirements.txt
      3. 收集静态文件
        python manage.py collectstatic # 必须调到生产模式才能成功
        其中带有后台的项目必须要收集静态文件,部署时才能显示后台。
    2. 登录腾讯云控制台,设置云服务器系统,我这里选择的系统是腾讯云专享版宝塔Linux面板
      在这里插入图片描述

    3. 登录宝塔面板,软件商店 — 下载安装MySQL、Nginx、Python项目管理器;在Python项目管理器中安装Python 3.8
      在这里插入图片描述
      在这里插入图片描述

    4. 添加站点

    5. 上传本地项目至站点文件夹下

    6. 编辑uwsgi.ini文件

    7. 配置数据库,修改项目配置文件 settings.py 中 MySQL配置

    8. Python项目管理器添加项目

    9. 配置网站的配置文件

  • 相关阅读:
    Python面向对象(三)
    pyqt实现简易浏览器
    02-URL与资源
    Leetcode 1572.矩阵对角线元素之和
    rviz上不显示机器人模型(模型只有白色)
    21天挑战赛算法学习打卡——顺序查找
    相机标定 >> 坐标系转换@内参、外参
    编程内功心法「底层原理系列」 回归与本质,让本文带你认识什么是计算机软件系统
    数据结构与算法之折半查找
    全流量安全分析发现内部系统外联异常
  • 原文地址:https://blog.csdn.net/qq_40859587/article/details/125913583