商品数据分析与可视化之 flask 开发记录(三)

项目更多开发细节参见:


一、项目环境搭建

1、利用 pipenv 为当前项目创建虚拟环境 (参见文末 pipenv安装详情

使用pip安装Pipenv:

pip install pipenv

为当前项目创建虚拟环境:

pipenv install

2、安装 flask (可能需要开代理)

pipenv install flask

3、安装 python-dotenv 用于配置环境变量 (参见文末 python-dotenv配置详情

pipenv install python-dotenv

创建一个名称为 .flaskenv 的文件,文件内容如下:

# 开启调试模式(1: 开启 0: 关闭)
# 开启后,经测试发现修改代码后无需重启服务即可实现更新
FLASK_DEBUG = 1

4、创建 app.py 文件

在创建好虚拟环境的目录下新建一个 app.py 文件(建议用这个文件名,否则后续无法启动内置服务器,如若不使用此文件名,可在 .flaskenv 文件中将 FLASK_APP 变量设置成自己的文件名称 参见 python-dotenv配置详情) 文件内容如下:

from flask import Flask

app = Flask(__name__)

@app.route('/')

def index():
    return 'hello flask'

创建完虚拟环境后,记得选择对应的 Python 解释器(参见文末 vscode中的python解释器选择

二、主程序

1、导入开发所需的第三方包

下载并导入:flaskpymongopandasflask_cors

from flask import Flask,request,jsonify

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure,ServerSelectionTimeoutError

import pandas as pd

app = Flask(__name__)

CORS(app,cors_allowed_origins="*")  

2、读取MongoDB数据库数据并去重

安装 pymongo

pipenv install pymongo

更新 app.py 文件的内容如下:

# 定义聚合函数 aggregate 的参数
PIPLINE_PARM = [
    {
        "$group": {
            # 基于 商品名称(name) 进行分组
            "_id": "$name",
            # 数据出现重复时,只保留最后一次出现的数据
            "doc": { "$last": "$$ROOT" }
        }
    },
    {
        "$replaceRoot": { "newRoot": "$doc" }
    },
    {
        # 不包含 _id 字段
        "$project": {
            "_id": 0  
        }
    }
]

# 获取数据库数据并去重
def getMongoDBData():
    with MongoClient("localhost",27017) as client:
        # smzdm: 为数据库名称
        db = client.smzdm
        # goods_info: 为数据库中的集合(相当于mysql中的表)
        collection = db.goods_info

        # 应用聚合函数 aggregate 过滤文档内容
        data = collection.aggregate(PIPLINE_PARM)
        result = list(data)
    return result

2、实现模糊查询

app.py 文件中追加以下内容:

@app.route('/api/getGoodsByName',methods=['POST'])

def get_goods_by_name():
    with MongoClient("localhost",27017) as client:
        db = client.smzdm
        collection = db.goods_info

        # 直接查询不去重
        # data = collection.find({"name": {'$regex': request.json['data']}},{"_id":0})
        
        NEW_PIPLINE_PARM = PIPLINE_PARM.copy()
        # 追加聚合函数的参数 此处根据 商品名称 进行模糊查询
        NEW_PIPLINE_PARM.extend([
            {"$match": 
                {"name": {'$regex': request.json['data']}}
            }
        ])
        data = collection.aggregate(NEW_PIPLINE_PARM)
        
        data_list = []
        for each in data:
            data_list.append(each)
            
    return data_list

代码说明:

  • $regex:该参数为 MongoDB 中的查询运算符,作用为选择值与指定的正则表达式匹配的文档(数据)

关于request对象的其他属性:

  • request.method:获取 HTTP 请求的方法

  • request.args:获取 HTTP 请求中的查询参数

  • request.form:获取 POST 请求中的表单数据

  • request.files:获取文件上传数据

  • request.json:将请求的主体解码为 json

更多 MongoDB 查询运算符参见:https://docs.mongoing.com/can-kao/yun-suan-fu/query-and-projection-operators/evaluation-query-operators

接口名称模糊查询
请求方法POST
URLhttp://localhost:5000/api/getGoodsByName

前端请求(dev)http://localhost:8080/api/getGoodsByName

请求体携带参数(json):

{"data":"牛肉"}

返回值:

[{"name": "xxx","brand": "xxx",...},{...}]

3、翻页

app.py 文件中追加以下内容:

@app.route('/api/pageTurn/<int:pageNo>')

def page_up(pageNo):
    with MongoClient("localhost",27017) as client:
        db = client.smzdm
        collection = db.goods_info

        NEW_PIPLINE_PARM = PIPLINE_PARM.copy()
        # 追加参数:每次获取 15 条数据
        NEW_PIPLINE_PARM.extend([
            {"$skip": 15 * pageNo},
            {"$limit": 15}
        ])

        data = collection.aggregate(NEW_PIPLINE_PARM)
        # 获取数据总数目
        pageCount = collection.count_documents({})
        
        data_list = []
        for each in data:
            data_list.append(each)

        pageInfo = {
            # 当前页码
            "pageNo": pageNo,
            # 页码对应数据
            "pageData": data_list,
            # 商品总数目
            "pageCount": pageCount
        }
            
    return pageInfo
接口名称翻页
请求方法GET
URLhttp://localhost:5000/api/pageTurn/1

前端请求(dev)http://localhost:8080/api/pageTurn/1

返回值:

{
    "pageNo": 1,
    "pageData": [{"name": "xxx","brand": "xxx",...},{...}],
    "pageCount": 148
}

4、获取商品筛选对应的选项

app.py 文件中追加以下内容:

@app.route('/api/getFilterOptions')

def get_filter_options():
    db_data = getMongoDBData()
    df = pd.DataFrame(db_data)
    
    filterObj = {
        "brand": df['brand'].unique().tolist(),
        "buy_channels": list(df['buy_channels'].unique()),
        "goods_classification": list(df['goods_classification'].unique()),
    }
        
    return filterObj

代码说明:

获取数据库数据,再借助 pandas 将数据转换成 DataFrame 类型的数据后,调用 unique() 方法获取对应列的唯一值列表,再利用 tolist() 方法将其转换成列表类型的数据(也可使用 list 方法)。使用 nunique() 则可以获取其对应的唯一值个数。

接口名称获取商品筛选对应的选项
请求方法GET
URLhttp://localhost:5000/api/getFilterOptions

前端请求(dev)http://localhost:8080/api/getFilterOptions

返回值:

{
    "brand": ['xxx',...],
    "buy_channels": ['xxx',...],
    "goods_classification": ['xxx',...]
}

5、商品筛选

app.py 文件中追加以下内容:

@app.route('/api/handleForm',methods=['POST'])

def handle_form():
    with MongoClient("localhost",27017) as client:
        db = client.smzdm
        collection = db.goods_info

        query = {}
        # 优惠券选项是否选中
        if request.json['data']['coupons'] == 'yes':
            query['coupons'] = {"$ne": []}
        elif request.json['data']['coupons'] == 'no':
            query['coupons'] = []

        # 按时间进行排序
        if request.json['data']['sortTime'] == 'rise_time':
            sortTime = 1
        elif request.json['data']['sortTime'] == 'drop_time':
            sortTime = -1
        else:
            pass
            # sortTime = 0

        # 按点赞进行排序 
        if request.json['data']['sortLikes'] == 'rise_likes':
            sortLikes = 1
        elif request.json['data']['sortLikes'] == 'drop_likes':
            sortLikes = -1
        else:
            pass
            # sortLikes = 0


        # 复选框不为空时
        if request.json['data']['brand']:
            query['brand'] = {"$in":request.json['data']['brand']}
        if request.json['data']['buy_channels']:
            query['buy_channels'] = {"$in":request.json['data']['buy_channels']}
        if request.json['data']['goods_classification']:
            query['goods_classification'] = {"$in":request.json['data']['goods_classification']}

        # 原查询语句,无去重
        # data = collection.find(query).sort({"update_time": sortTime,"likes":sortLikes,"collects":sortLikes})
        
        # 去重升级版
        NEW_PIPLINE_PARM = PIPLINE_PARM.copy()
        NEW_PIPLINE_PARM.extend([
            {"$match": query},
            {"$sort": 
                    {
                        "update_time": sortTime,
                        "likes":sortLikes,
                        "collects":sortLikes
                    }
            }
        ])
        data = collection.aggregate(NEW_PIPLINE_PARM)
        
        data_list = []
        for each in data:
            data_list.append(each)
            
    return data_list
接口名称商品筛选
请求方法POST
URLhttp://localhost:5000/api/handleForm

前端请求(dev)http://localhost:8080/api/handleForm

请求体携带参数(json):

{"data":{"coupons":"yes","sortTime":"rise_time","sortLikes":"rise_likes","brand":["百里炙"],"buy_channels":["京东"],"goods_classification":["牛羊肉"]}}

返回值:

[{"name": "xxx","brand": "xxx",...},{...}]

6、获取柱状图数据

app.py 文件中追加以下内容:

@app.route('/api/getBarChartData/<string:barFieldName>')

def get_bar_chart_data(barFieldName):
    db_data = getMongoDBData()
    df = pd.DataFrame(db_data)
    # 转换时间格式
    date = pd.to_datetime(df['update_time'],format='%Y-%m-%d %H:%M')
    # 获取日期格式 例:2023-1-1
    day = date.dt.floor('d')
    # 获取日期列表
    date_list = df.groupby(day).sum().index.strftime('%Y-%m-%d').tolist()
    # 获取总数
    day_sum = len(df.groupby(day).sum())

    # 控制截取日期的数量,此处只截取最近10天的数据
    if day_sum > 10:
        start = day_sum - 10
    else:
        start = 0

    # 获取每一天当中(商品点赞/商品收藏/商品评论数)的最大值及对应的商品分类
    goods_class_max_list = df.loc[df.groupby(day)[barFieldName].idxmax()][['goods_classification',barFieldName]].values.tolist()[start:]

    goods_class_bar_obj = {}
    goods_class_name_list = []
    field_value_list = []

    for i in goods_class_max_list:
        goods_class_name_list.append(i[0])
        field_value_list.append(i[1])
        
    goods_class_bar_obj['goods_class_name_list'] = goods_class_name_list
    goods_class_bar_obj['field_value_list'] = field_value_list
    goods_class_bar_obj['date_list'] = date_list[start:]

    return goods_class_bar_obj
接口名称获取柱状图数据
请求方法GET
URLhttp://localhost:5000/api/getBarChartData/likes
说明获取近10天(点赞/收藏/评论数)最多的商品分类

前端请求(dev)

  • 获取点赞数据: http://localhost:8080/api/getBarChartData/likes
  • 获取收藏数据: http://localhost:8080/api/getBarChartData/collects
  • 获取评论数据:http://localhost:8080/api/getBarChartData/comment_count

返回值:

{
    // 日期列表(近10天)
    "date_list": ['2023-1-1','xxx'],
    // 点赞/收藏/评论数 对应的数值
    "field_value_list": [1,2,...],
    // 商品分类列表
    "goods_class_name_list": ['xxx',...]
}

实现说明:

1、设置日期的格式

# 转换时间格式
date = pd.to_datetime(df['update_time'],format='%Y-%m-%d %H:%M')

运行结果:

0     2023-12-24 09:13:00
1     2023-12-21 16:40:00
2     2023-12-24 11:04:00
        ...        
305   2023-12-24 10:19:00
306   2023-12-26 15:08:00
307   2023-12-25 01:06:00

2、针对日期的年月日数据进行分组,并转化成列表

# 获取日期的年月日 例:2023-12-24
day = date.dt.floor('d')
# 获取日期列表
date_list = df.groupby(day).sum().index.strftime('%Y-%m-%d').tolist()

运行结果:

['2023-12-14',
 '2023-12-15',
     ...
 '2024-01-10',
 '2024-01-11']

3、再根据日期列表获取每一天 点赞/收藏/评论 数最多的商品的索引,后续再通过该索引找到对应的商品的商品分类

# 例如:此处获取每一天 点赞 数最多的商品的索引
df.groupby(day)['likes'].idxmax()

运行结果:

update_time
2023-12-14    163
    ...
2024-01-11    120

说明:后续的折线图、饼图等皆以类似的方法实现,不再说明。

7、导出数据

@app.route('/api/toExcel')

def data_to_excel():
    db_data = getMongoDBData()
    df = pd.DataFrame(db_data)
    # 导出至桌面    
    df.to_excel(r'D:\桌面\smzdm.xlsx')

    return '1'

8、数据库的插入、删除、修改操作

插入:数据库的插入操作,其他方法类似

@app.route('/api/insertDB',methods=['POST'])

def insert_db():
    # print(request.json)
    with MongoClient("localhost",27017) as client:
        db = client.smzdm
        collection = db.goods_info

        collection.insert_many(request.json.get('data'))

    return '1'

删除:

# 此处通过商品的名称进行删除
collection.delete_many({"name": "xxx"})

修改:

# 此处通过商品的名称查找到目标元素,再修改其对应的字段
collection.update_one({"name": "xxx"},{"$set": {"star": 1}})

9、获取数据

说明:爬取数据并实时传递至前端进行展示

准备工作:

1、下载 flask_socketio

pipenv install flask_socketio

2、导入需要使用到的模块

from flask_socketio import SocketIO,disconnect
import subprocess

socketio = SocketIO(app,cors_allowed_origins='*')

3、需要有一个由 scrapy 创建的爬虫项目。因为此处是通过执行 scrapy 命令来爬取数据的。

scrapy项目结构目录

4、实现

# 爬取数据
@socketio.on('start_spider')

# 默认爬取的网站 quotes
def start_spider(scrapyCMD=['scrapy', 'crawl', 'quotes']):
    # 向前端发送数据
    # socketio.emit("console_output",{"data": "test"})
    try:
        # 创建子进程
        process = subprocess.Popen(

            # 此命令爬取 什么值得买 网站
            # ['scrapy', 'crawl', 'smzdm'],
            # 此命令爬取 quotes 网站
            # ['scrapy', 'crawl', 'quotes'],

            # 根据前端传递的 scrapy 命令,来选择爬取的网站(该参数由前端传递)
            scrapyCMD,

            # 目标文件夹
            cwd='scrapy_smzdm',
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            # 该文件的 stdout 以文本流方式打开
            universal_newlines=True
        )

        # 获取命令输出并实时推送到前端
        for line in process.stdout:
            socketio.emit('console_output', {'data': line.strip()})

        # 等待命令执行完成
        process.wait()

        # 爬取完成后打印提示信息
        socketio.emit('console_output', {'data': 'Scrapy crawl completed','finish': '1'})
        
    except Exception as e:
        socketio.emit('console_output', {'data': f'Error: {str(e)}','finish': '0'})
    
if __name__ == "__main__":
    socketio.run(app,debug=True,host="0.0.0.0",port=5000)

说明: 参数 scrapyCMD 是由前端传递过来的 scrapy 命令,例如:scrapy crawl smzdm 命令可以执行爬虫获取数据。


三、其他

一、pipenv 的安装和环境变量的配置

1、安装 Pipenv

Pipenv 是基于pip的Python包管理工具,用于包安装、包依赖管理和虚拟环境管理。

使用pip安装Pipenv:

pip install pipenv

检查安装是否成功:

pipenv --version

2、创建虚拟环境

通过创建虚拟环境,可以为每一个项目创建独立的Python解释器环境,便于相关依赖包的管理。

在创建虚拟环境前,可在当前文件夹下创建一个 .venv 的文件夹,这样执行创建虚拟环境命令后所下载的依赖都会在这个 .venv 文件夹中。默认安装路径则是 C:\Users\Administrator\.virtualenvs\当前项目文件夹名称+随机字符

也可通过配置环境变量的方式将所下载的依赖放在当前执行命令的文件夹中:

配置环境变量:

变量名:WORKON_HOME
变量值:PIPENV_VENV_IN_PROJECT

配置环境变量

配置完环境变量后,执行 pipenv install 命令创建虚拟环境后,文件目录如下:

文件目录结构

查看项目对应的虚拟环境路径:

pipenv --venv

为当前的项目创建虚拟环境:

pipenv install

执行结果:

D:\桌面\GoodsDataAnalysis\GoodsFlask> pipenv install
......
Successfully created virtual environment!

显式地激活虚拟环境:(激活虚拟环境后才可以执行命令)

pipenv shell

激活虚拟环境后,查看当前环境所安装的包:

pip list

不显式激活虚拟环境,在当前项目的虚拟环境中执行命令:

pipenv run python index.py

3、安装 Flask

为当前虚拟环境安装 Flask:

pipenv install flask

说明:
Pipenv 会自动管理虚拟环境,所以在执行 pipenv install 安装Python包时,无论是否激活虚拟环境,包都会安装到虚拟环境中。


二、python-dotenv 安装及环境变量的配置

python-dotenv: Flask的自动发现程序实例机制如果发现安装了 python-dotenv ,则会在使用 flask run 或其他命令时自动从 .flaskenv 文件和 .env 文件中加载环境变量。

.flaskenv 文件(手动创建):用来存储和Flask相关的公开环境变量。
.env 文件(手动创建):用来存储包含敏感信息的环境变量。

当安装了python-dotenv时,Flask在加载环境变量的优先级是:手动设置的环境变量 > .env中设置的环境变量 > .flaskenv设置的环境变量

① 安装 python-dotenv :

pipenv install python-dotenv

② 在项目根目录下创建一个 .flaskenv 文件,文件内容如下:

# 使服务器外部可见
FLASK_RUN_HOST = 0.0.0.0
# 改变默认端
FLASK_RUN_PORT = 8000

③ 执行 flask run 命令再次启动服务

PS D:\桌面\FlaskBasic\Basic01> flask run
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8000
 * Running on http://10.22.140.118:8000
Press CTRL+C to quit

更多环境变量补充,皆可放置在 .flaskenv 文件中

# 当用户修改了 `app.py` 文件名时
FLASK_APP = 新的文件名

# 设置运行环境为开发环境
FLASK_ENV=development

# 开启调试模式(1: 开启 0: 关闭)
# 开启后,经测试发现修改代码后无需重启服务即可实现更新
FLASK_DEBUG = 1

# 使服务器外部可见
FLASK_RUN_HOST = 0.0.0.0

# 改变默认端
FLASK_RUN_PORT = 8000

三、在 VsCode 中,创建完虚拟环境后,选择 Python 解释器

1、点击 VsCode 右下角的 Python 解释器:

Python 解释器选择

2、选择以下目录中的 Python.exe

D:\桌面\GoodsDataAnalysis\GoodsFlask\PIPENV_VENV_IN_PROJECT\GoodsFlask-S_qAB8d7\Scripts

说明:GoodsFlask-S_qAB8d7 为项目文件夹名称+随机生成的字符,应根据自己实际选择。

动图详情:

动图详情

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值