电商网评论采集与分析

要求:

1.基于PyQT设计客户端界面,选定某种采集方法,动态采项目中需要的数据并存储到MySQL数据库。采集的数据要动态在QTableWidget上滚动插入,不要在PyCharm中输出。

2.在客户端界面上实现对数据探索性分析,进行数据统计,绘图,给出分析结论和决策。

3.可以考虑使用Pyecharts静态绘图。可以选做结合前端技术引入ECharts库基于HTML文件方式动态绘图。

4.最终提交的是一个完整的PyQT的客户端的系统。

任务分配:

界面与数据库操作:

使用PyQT设计客户端界面。
选择适当的采集方法,如网络爬虫或API调用,来获取京东评论数据。
将采集的数据动态存储到MySQL数据库中。
在QTableWidget上动态滚动显示数据。

数据探索性分析:

对从数据库中获取的数据进行探索性分析。
进行数据统计,如平均值、中位数、众数等。
使用Pyecharts或ECharts库进行绘图,展示评论数据的分布、趋势等。
给出分析结论,为决策提供依据。

前端技术集成:

如果选择ECharts库,则负责集成ECharts到PyQT客户端中,实现动态绘图。
如果使用HTML文件方式,则需要将Pyecharts或ECharts集成到HTML文件中,并确保与PyQT客户端的交互性。

系统整合与测试:

确保所有功能模块能够协同工作,形成一个完整的PyQT客户端系统。
进行系统测试,确保数据采集、存储、显示和分析功能均正常工作。

分工建议:

人员A:主要负责界面设计与数据库操作,包括使用PyQT设计客户端界面、实现数据存储和动态显示。同时,也可以协助进行数据探索性分析。
人员B:主要负责数据探索性分析和前端技术集成。进行数据统计、绘图和分析,给出结论。同时,根据选定的前端技术(如ECharts),实现动态绘图功能。

html页面设计

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.min.js"></script>
    <script type="text/javascript" src="../js/echarts-wordcloud.js"></script>
    <script type="text/javascript" src="../js/china.js"></script>
<!--引入外部javascript库和脚本echarts.min.js是echarts中的主库,用来创建各种图表,wordcloud.js,china.js"扩展echarts功能,提供额外数据脚本-->
</head>
<style>
    .container {
        display: grid;
        /*display: inline-grid;*/
        grid-template-columns: repeat(3, 1fr);
<!--container:使用网格布局,将内容分为三列-->
    }

    .box {
<!--box类,图表容器是300*300的方块,黑色边框,10像素外边距-->
        width: 300px;
        height: 300px;
        border: 1px solid #000;
        margin: 10px;
    }
</style>
<body>
<div class="container">
    <div class="box" id="chartWord1"></div>
    <div class="box" id="chart2"></div>
    <div class="box" id="chart3"></div>
    <div class="box" id="chart4"></div>
    <div class="box" id="chart5"></div>
    <div class="box" id="chart6"></div>
<!--六个容器的独特id,初始化echarts图表-->
</div>
<!--javascript部分-->
<script type="text/javascript">
    var chart2 = echarts.init(document.getElementById('chart2'));初始化并更新数据,初始化一个echarts图表放在ID为chart2容器中

    function updateEchartsChart(dict_info) {
<!--接受字典作为参数,更新echarts图表-->
        var option = {
            title: {设置标题
                text: '柱状图'
            },
            tooltip: {悬停到某个数据点时显示相关数据
                trigger: 'item'
            },
            xAxis: {
                type: 'category',
                //data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
                data: dict_info['name']分类:评分
            },
            yAxis: {//y轴数值
                type: 'value'
            },
            series: [{
                // data: [120, 200, 150, 80, 70, 110, 130],
                data: dict_info['score'],
                type: 'bar',//bar表示柱状图
                showBackground: true,
                backgroundStyle: {
                    color: 'rgba(180, 180, 180, 0.2)'
//背景样式,半透明灰色
                },
                    avoidLabelOverlap: false,
//避免标签重叠
                    itemStyle: {//柱状图样式
                        borderRadius: 10,//边框半径
                        borderColor: '#fff',
                        borderWidth: 2
                    },
                    label: {
                        show: false,//标签样式不显示
                        position: 'center'
                    },
                    emphasis: {//悬停在某个位置标签会显示出来
                        label: {
                            show: true,
                            fontSize: 40,//大小四十
                            fontWeight: 'bold'//字体加粗
                        }
                    },
                    labelLine: {//显示标签线
                        show: true
                    },
            }]
        };
        chart2.setOption(option);
    }

    //  基于准备好的dom,初始化echarts实例 饼图
    var chart3 = echarts.init(document.getElementById('chart3'));//初始图表

    function updateEchartsChart3(dict_info) {//接受参数,更新数据
        var option = {
            title: {
                text: 'VIP饼图'//标题
            },
            tooltip: {
                trigger: 'item'//悬停显示数据
            },
            legend: {
                top: '5%',//图例位置,距顶部百分之5,左侧居中对齐
                left: 'center'
            },
            series: [
                {
                    name: 'VIP',
                    type: 'pie',//数据系列类型:饼图
                    radius: ['40%', '70%'],//半径范围
                    avoidLabelOverlap: false,//允许重叠
                    itemStyle: {//每个扇区样式
                        borderRadius: 10,//圆角
                        borderColor: '#fff',
                        borderWidth: 2
                    },
                    label: {
                        show: false,//标签不显示
                        position: 'center'
                    },
                    emphasis: {//鼠标悬停,标签显示
                        label: {
                            show: true,
                            fontSize: 40,
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: true
                    },
                    data: dict_info
                }
            ]
        };
        chart3.setOption(option);
    }


    var chartWord = echarts.init(document.getElementById('chartWord1'));//绑定id

    // 渲染词云图
    function updateEchartsWordCloud(word_cloud_data) {
        var optionWord = {
            title: {
                text: '词云图'
            },
            series: [{
                type: 'wordCloud',
                shape: 'circle',//词汇以圆形展示
                data: word_cloud_data,
                avoidLabelOverlap: false,//词汇可能重叠
                itemStyle: {
                    borderRadius: 10,
                    borderColor: '#fff',
                    borderWidth: 2
                },
                label: {
                    show: false,
                    position: 'center'
                },
                emphasis: {
                    label: {
                        show: true,
                        fontSize: 40,
                        fontWeight: 'bold'
                    }
                },
                labelLine: {
                        show: true
                    },
            }]
        };
        chartWord.setOption(optionWord);
    }

    dict_info = {//数据字典
        'score': [1, 2, 3, 4, 5],//权重
        'name': ['1', '2', '3', '4', '5'],//词汇名称
    }
    updateEchartsChart(dict_info);
    word_cloud_data = [
        {"name": "Apple", "value": 15},
        {"name": "Banana", "value": 10},
        {"name": "Orange", "value": 8},
        {"name": "Grapes", "value": 5},
        {"name": "Watermelon", "value": 3}
    ]
    updateEchartsWordCloud(word_cloud_data);

    var chart4 = echarts.init(document.getElementById('chart4'));

    function updateEchartsChart4(dict_info) {
        var option = {
            title: {
                text: '漏斗图'
            },
            tooltip: {//提示框配置
                trigger: 'item',//悬停在某个数据项则显示
                formatter: '{a} <br/>{b} : {c}'//提示显示格式
            },
            toolbox: {
                feature: {
                    dataView: {readOnly: false},//允许用户查看数据
                    restore: {},
                    saveAsImage: {}
                }
            },
            legend: {
                data: []
            },
            series: [
                {
                    name: '漏斗图',
                    type: 'funnel',
                    left: '10%',
                    top: 60,
                    bottom: 60,
                    width: '80%',
                    min: 0,
                    // max: 100,
                    minSize: '0%',
                    // maxSize: '100%',
                    sort: 'descending',
                    gap: 2,
                    label: {
                        show: true,
                        position: 'inside'
                    },
                    labelLine: {
                        length: 10,
                        lineStyle: {
                            width: 1,
                            type: 'solid'
                        }
                    },
                    itemStyle: {
                        borderColor: '#fff',
                        borderWidth: 1
                    },
                    emphasis: {
                        label: {
                            fontSize: 20
                        }
                    },
                    data: dict_info
                }
            ]
        };
        chart4.setOption(option);
    }

    var chart5 = echarts.init(document.getElementById('chart5'));

    function updateEchartsChart5(dict_info) {
        var option = {
            tooltip: {//提示框
                trigger: 'item'
            },
            legend: {//图例配置
                top: '5%',
                left: 'center'
            },
            toolbox: {//工具箱配置显示工具,设置功能选项
                show: true,
                feature: {
                    mark: {show: true},
                    dataView: {show: true, readOnly: false},
                    restore: {show: true},
                    saveAsImage: {show: true}
                }
            },
            series: [
                {
                    name: '评价玫瑰图',
                    type: 'pie',
                    radius: [10, 40],
                    center: ['50%', '50%'],
                    roseType: 'area',
                    data: dict_info,
                avoidLabelOverlap: true,
                itemStyle: {
                    borderRadius: 3,
                    borderColor: '#fff',
                    borderWidth: 2
                },
                  label: {
                    formatter: '{a|{a}}{abg|}\n{hr|}\n  {b|{b}:}{c}  {per|{d}%}  ',
//格式化标签显示内容,使用模板字符串
                    backgroundColor: '#F6F8FC',
                    borderColor: '#8C8D8E',
                    borderWidth: 1,
                    borderRadius: 4,
                    rich: {//更复杂标签内容样式
                      a: {
                        color: '#6E7079',
                        lineHeight: 2,
                        align: 'center'
                      },
                      hr: {
                        borderColor: '#8C8D8E',
                        width: '100%',
                        borderWidth: 1,
                        height: 0
                      },
                      b: {
                        color: '#4C5058',
                        fontSize: 8,
                        fontWeight: 'bold',
                        lineHeight: 2
                      },
                      per: {
                        color: '#fff',
                        backgroundColor: '#4C5058',
                        padding: [1, 3],
                        borderRadius: 2
                      }
                    }
                  },
                emphasis: {
                    label: {
                        show: true,
                        fontSize: 20,
                        fontWeight: 'bold',
                        backgroundColor: '#F6F8FC',
                        borderColor: '#8C8D8E',
                        borderWidth: 1,
                        borderRadius: 4,
                    }
                },
                labelLine: {
                        show: true
                    },
                }
            ]
        };
        chart5.setOption(option);
    }

    function updateEchartsChart6(dict_info,minValues) {
        // 基于准备好的dom,初始化echarts实例
        var chart6 = echarts.init(document.getElementById('chart6'));
        // var dict_info = [{'name': '上海', 'value': 318}, {'name': '云南', 'value': 162}, {'name': '安徽', 'value': 50}, {'name': '江苏', 'value': 60}];
        var option6 = {
            title: {
                text: '评论人员分布图',
                subtext: '',
                x: 'left'
            },
            tooltip: {
                trigger: 'item'
            },
            //左侧小导航图标
            visualMap: {
                show: true,
                x: 'bottom',
                y: 'bottom',
                textStyle: {
                    fontSize: 8,
                },
                max: 30,
                // splitNumber: 5,
                color: ['#8A3310', '#C64918', '#E55B25', '#F2AD92', '#F9DCD1', '#6e92ad']
            },
            //配置属性
            series: [{
                name: '评论人数',
                type: 'map',
                mapType: 'china',
                roam: true, //拖动和缩放
                itemStyle: {
                    emphasis: {
                        areaColor: '#ffd700'
                    }
                },
                label: {
                    normal: {
                        show: true, //省份名称
                        fontSize: 10,
                    },
                    emphasis: {
                        show: true,
                        fontSize: 13,
                    }
                },
                data: dict_info //mydata //数据
            }]
        }
        chart6.setOption(option6);
        // window.addEventListener("resize", function () {
        //     myChart.resize();
        // });
    }

</script>
</body>
</html>

jdcoments.py(数据采集与存储)

# -*- coding:utf-8 -*-
import os
import random
import re
import time
import pandas as pd
import requests
from colorama import Fore,init
from sqlalchemy import create_engine

init(autoreset=True,wrap=True)

class JDComments:
    def __init__(self):
        self.cookies = {
            'shshshfpa': 'ec885459-55b0-434e-87da-f28cce138893-1693402107',
            'shshshfpx': 'ec885459-55b0-434e-87da-f28cce138893-1693402107',
            '3AB9D23F7A4B3CSS': 'jdd03EYLS5TWAJ5YIW6AEL6WXVD6L5G3JD7KWCL4ZDX72J36VNCBSELHJDRXJPXDIBXOLOJYM3U277ZIJFGJXLA5QMORSFAAAAAMNEGR55YIAAAAACCKFC6EAPNVPBQX',
            '_gia_d': '1',
            '__jdv': '181111935|direct|-|none|-|1705666404282',
            'areaId': '14',
            'ipLoc-djd': '14-1116-3431-57939',
            '__jdu': '17056664042822094326959',
            'TrackID': '1w6WdR513s75-MYesbmElV9qj9XXZDTq_0kbP4w1sjegV4XhuWSviB_W-3nGL4nMcvpAtv4xv-clUDJMP8p8yGPRAi6otcaw07kasCrv0pj0',
            'thor': '44A7CFCF0CAF492132633AEFEA2DE7B2207235328CDBF486A2E2A4D9E641C4B2477379500E83847840B7CAF3E5CB86C75B35F548E08080AD104BA7B35D3801848EC8F244144F1466858A04539D4055085D4F5803545AFD1F18F68A1F8F2310784058132B66A0F27AFCB368F8C12D6778AADA5C9A0133A7403D6081C765B6FE94DC961CC77E5D2A02CCA15D7B6C5A45AFCF977CEF63D18E7AA8AFE9B58C756B58',
            'flash': '2_yYntjOCXfn2roFg-cp9zVrMj_BqskUPzGlpBoMQ94q42dezAus3VGhJf5EcVmQwVAxIrlBlcoKg4uPzNAp8u5_uPkymIiELmEfcla5xih2o*',
            'pinId': 'x-AZ3d0Upjm3K0-vU19etrV9-x-f3wj7',
            'pin': 'jd_4e5882d2c4f87',
            'unick': '%E9%9A%90%E8%80%85%E8%87%AA%E6%80%A1%E6%82%A6',
            'ceshi3.com': '000',
            '_tp': 'ZK7b7Ke721kAE7%2FMXRMzd3bMO2HeAwJ%2FjldXnPhzY9g%3D',
            '_pst': 'jd_4e5882d2c4f87',
            'jsavif': '1',
            'shshshsID': '7d36d36cb7484d9bc9d8f0bb029ecd5d_2_1705666480601',
            '3AB9D23F7A4B3C9B': 'EYLS5TWAJ5YIW6AEL6WXVD6L5G3JD7KWCL4ZDX72J36VNCBSELHJDRXJPXDIBXOLOJYM3U277ZIJFGJXLA5QMORSFA',
            'token': '62146e929d7780c1240f022d15db76d9,3,947592',
            '__tk': '4YawBYaz4VgzAzex4Uo0krgD4caD4V4ykcWD4ua1AUn54Voz4Yhs4w,3,947592',
            '__jda': '181111935.17056664042822094326959.1705666404.1705666404.1705666404.1',
            '__jdc': '181111935',
            'shshshfpb': 'BApXeia-tIuhAfvpJp_et-jUTm_t11TlJB9EBJihs9xJ1Mthct4O2',
            '__jdb': '181111935.9.17056664042822094326959|1.1705666404',
        }
        # self.cookies={"cookie":"__jdv=122270672%7Ccn.bing.com%7C-%7Creferral%7C-%7C1706423484117; __jdu=170642348411725460747; 3AB9D23F7A4B3CSS=jdd03L2C7BN43IN4BUO5Z63R6XIQIDKURLGNVADAMHLJPE25FZYDQWUTEB5ICJUNVUPKNNU73YJTP5WYYK3WMRAIZKAEQEEAAAAMNJ3CCGYAAAAAADP2ZATK2ZAHYMUX; _gia_d=1; areaId=12; ipLoc-djd=12-925-0-0; PCSYCityID=CN_320000_320800_0; shshshfpa=98283228-b418-3822-b195-4ce56b79913e-1706423496; shshshfpx=98283228-b418-3822-b195-4ce56b79913e-1706423496; token=e58a4ceceb85acfc22da4d4989d28f95,2,948013; __tk=1zbWrAfCKwPYKUV31w1DKwxErcMCqATu1AfB2YrXqDx3qcS5qA2XqM,2,948013; jsavif=0; __jda=181111935.170642348411725460747.1706423484.1706423484.1706423484.1; __jdb=181111935.4.170642348411725460747|1.1706423484; __jdc=181111935; shshshsID=31cd8d89d725361879163f75af5f2b96_2_1706423521239; shshshfpb=BApXepATMTehA3pw4Rl_WlgNcJZLQvyDkBkQDM7lp9xJ1MnXPqIO2; 3AB9D23F7A4B3C9B=L2C7BN43IN4BUO5Z63R6XIQIDKURLGNVADAMHLJPE25FZYDQWUTEB5ICJUNVUPKNNU73YJTP5WYYK3WMRAIZKAEQEE"}
        self.headers = {
            'authority': 'api.m.jd.com',
            'accept': 'application/json, text/javascript, */*; q=0.01',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'cache-control': 'no-cache',
            'content-type': 'application/json;charset=gbk',
            # 'cookie': 'shshshfpa=ec885459-55b0-434e-87da-f28cce138893-1693402107; shshshfpx=ec885459-55b0-434e-87da-f28cce138893-1693402107; 3AB9D23F7A4B3CSS=jdd03EYLS5TWAJ5YIW6AEL6WXVD6L5G3JD7KWCL4ZDX72J36VNCBSELHJDRXJPXDIBXOLOJYM3U277ZIJFGJXLA5QMORSFAAAAAMNEGR55YIAAAAACCKFC6EAPNVPBQX; _gia_d=1; __jdv=181111935|direct|-|none|-|1705666404282; areaId=14; ipLoc-djd=14-1116-3431-57939; __jdu=17056664042822094326959; TrackID=1w6WdR513s75-MYesbmElV9qj9XXZDTq_0kbP4w1sjegV4XhuWSviB_W-3nGL4nMcvpAtv4xv-clUDJMP8p8yGPRAi6otcaw07kasCrv0pj0; thor=44A7CFCF0CAF492132633AEFEA2DE7B2207235328CDBF486A2E2A4D9E641C4B2477379500E83847840B7CAF3E5CB86C75B35F548E08080AD104BA7B35D3801848EC8F244144F1466858A04539D4055085D4F5803545AFD1F18F68A1F8F2310784058132B66A0F27AFCB368F8C12D6778AADA5C9A0133A7403D6081C765B6FE94DC961CC77E5D2A02CCA15D7B6C5A45AFCF977CEF63D18E7AA8AFE9B58C756B58; flash=2_yYntjOCXfn2roFg-cp9zVrMj_BqskUPzGlpBoMQ94q42dezAus3VGhJf5EcVmQwVAxIrlBlcoKg4uPzNAp8u5_uPkymIiELmEfcla5xih2o*; pinId=x-AZ3d0Upjm3K0-vU19etrV9-x-f3wj7; pin=jd_4e5882d2c4f87; unick=%E9%9A%90%E8%80%85%E8%87%AA%E6%80%A1%E6%82%A6; ceshi3.com=000; _tp=ZK7b7Ke721kAE7%2FMXRMzd3bMO2HeAwJ%2FjldXnPhzY9g%3D; _pst=jd_4e5882d2c4f87; jsavif=1; shshshsID=7d36d36cb7484d9bc9d8f0bb029ecd5d_2_1705666480601; 3AB9D23F7A4B3C9B=EYLS5TWAJ5YIW6AEL6WXVD6L5G3JD7KWCL4ZDX72J36VNCBSELHJDRXJPXDIBXOLOJYM3U277ZIJFGJXLA5QMORSFA; token=62146e929d7780c1240f022d15db76d9,3,947592; __tk=4YawBYaz4VgzAzex4Uo0krgD4caD4V4ykcWD4ua1AUn54Voz4Yhs4w,3,947592; __jda=181111935.17056664042822094326959.1705666404.1705666404.1705666404.1; __jdc=181111935; shshshfpb=BApXeia-tIuhAfvpJp_et-jUTm_t11TlJB9EBJihs9xJ1Mthct4O2; __jdb=181111935.9.17056664042822094326959|1.1705666404',
            'origin': 'https://item.jd.com',
            'pragma': 'no-cache',
            'referer': 'https://item.jd.com/',
            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-site',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'x-referer-page': 'https://item.jd.com/13285690.html',
            'x-rp-client': 'h5_1.0.0',
        }
        # print(type(self.cookies),dict)
        if type(self.cookies)==dict:
            self.uuid=self.cookies['__jda']
        else:
            uuid = self.cookies['__jda']
            self.uuid = re.findall(r'__jda=(.*?);', uuid)[0]
        self.current_scrawl_data=pd.DataFrame()
//初始化一个空的pandas dataframe,用于存储爬取数据
    def funcPages(self,productId=13285690)://爬取评论
        # 5 是追评;0 是全部评价;3 是好评;2是中评;1是差评;
        for score in [0]:
            for page in range(1,101):
                print(Fore.YELLOW+ f'开始爬取商品,第{score}个分数,第{page}页!')
                retry=2
                flag=0
                while retry:
                    try:
                        flag=self.reqeustPageComments(page=page,score=score, productId=productId)//发送http请求返回结果
                        break
                    except Exception as e:
                        print(f"异常信息:{e}")
                        if retry==0:
                            # input('异常信息!!!')
                            time.sleep(100)
                        retry-=1
                if flag == 0:
                    break
                time.sleep(random.uniform(2,5))
                # input()

    def reqeustPageComments(self,page=0,score=0,productId=13285690):
        current_time=int(time.time()*1000)
        params = {
            'appid': 'item-v3',
            'functionId': 'pc_club_productPageComments',
            'client': 'pc',
            'clientVersion': '1.0.0',
            't': f'{current_time}',
            'loginType': '3',
            'uuid': self.uuid,
            'productId': f'{productId}',
            'score': f'{score}',#5 是追评;0 是全部评价;3 是好评;2是中评;1是差评;
            'sortType': '6',#5默认排序 6按时间排序
            'page': f'{page}',#1 2 3
            'pageSize': '30',
            'isShadowSku': '0',
            'fold': '1',
            'bbtf': '',
            'shield': '',
        }
        response = requests.get('https://api.m.jd.com/', params=params, cookies=self.cookies, headers=self.headers,timeout=5)//发送get请求
        if 'creationTime' not in response.text:
            # print(Fore.RED+"请求出现异常!!!"+response.text)
            return 0
        json_data=response.json()
        # print(json_data)
        # with open('json.json',encoding='utf-8',mode='w') as f:
        #     f.write(json.dumps(json_data,ensure_ascii=False))
        try:
            self.parJsonComments(json_data,productId)
        except:
            pass
        if len(json_data['comments']) < 10://异常判断
            return 0
        else:return 1

    def _dealText(self,x):
        try:
            x = str(x).replace('  ', '').replace('\t', '').replace('\n', '').replace('\r', '').replace(',', ',').replace('"',
                                                                                                                    '”').replace(
                "'", "‘").replace("查看更多", "")
        except:
            x = ''
        return x
    def parJsonComments(self,json_data={},productId=13285690):
        # with open('json.json',encoding='utf-8',mode='r') as f:
        #     json_data=json.loads(f.read())
        items=json_data['comments']
        dict_info={}
        list_dict_info=[]
        for item in items:
            comment_id=item['id']
            # guid=item['guid']
            nickname=item['nickname']#用户名称
            nickname = self._dealText(nickname)
            creationTime=item['creationTime']#评论时间
            try:
                location=item['location']#评价地点
            except:
                location=''
            score=item['score']#评论分数
            usefulVoteCount=item['usefulVoteCount']#点赞数
            mobileVersion=item['mobileVersion']#手机版本
            plusAvailable=item['plusAvailable']#plus 201表示plus
            if plusAvailable==201:
                plusAvailable='plus'
            else:
                plusAvailable='no'
            replyCount=item['replyCount']#评论数量
            content=item['content']#评论内容
            content=self._dealText(content)
            try:
                videos=item['videos']#视频数量
                videos = len(videos)
            except:
                videos=0
            try:
                images=item['images']#图片数量
                images_num=len(images)
            except:
                images_num=0
            # userImageUrl=item['userImageUrl']#用户头像链接
            productColor=item['productColor']#产品颜色
            productColor = self._dealText(productColor)
            # referenceTime=item['referenceTime']#引用时间
            # referenceName=item['referenceName']#引用名称
            dict_info={
                '商品id':productId,
                '评论id':comment_id,
                # 'guid':guid,
                '用户名称':nickname,
                '发布时间':creationTime,
                '地区':location,
                '评价分数':score,
                '点赞数':usefulVoteCount,
                '是否是VIP':plusAvailable,
                '回复数量':replyCount,
                '视频数量':videos,
                '图片数量':images_num,
                '商品属性':productColor,
                '收集版本': mobileVersion,
                '评论内容': content,//信息提取组成新的字典
            }
            print(Fore.GREEN + str(dict_info))
            list_dict_info.append(dict_info)
        save_df=pd.DataFrame(list_dict_info, columns=list(dict_info.keys()))
        save_df.rename(columns={'评价分数': '分数', '点赞数': '点赞', '是否是VIP': 'VIP', '回复数量': '回复', '视频数量': '视频','图片数量': '图片', }, inplace=True)
        self.saveDf(save_df, file_name='data/京东评论')
        save_df.drop(columns=['评论id', '收集版本', '商品属性'], inplace=True)
        save_df['地区'] = save_df['地区'].apply(lambda x: str(x).replace('nan', ''))
        self.current_scrawl_data=save_df

    def saveDf(self,df,file_name="文件名称"):  # 单条插入
        if len(df)==0:
            return
        print(Fore.YELLOW + '存到文件的数据有{}条'.format(len(df)))
        # if not os.path.exists(f'{file_name}.csv'):
        #     df.to_csv(f'{file_name}.csv', encoding='utf_8_sig', mode='a', index=False, index_label=False)
        # else:
        #     df.to_csv(f'{file_name}.csv', encoding='utf_8_sig', mode='a', index=False, index_label=False, header=False)
        self.saveSqlData(df, tableName='jdcomments', dbName='comments', psw='123456', user='root', ip='localhost')
        print('保存成功!')
    def saveSqlData(self, df, tableName, dbName, psw, user='root', ip='localhost'):
        # self.Df_to_sql( output_df, tableName='result', dbName='web', psw='123456', user='root', ip='localhost')

        con = create_engine(
            'mysql+pymysql://{}:{}@{}/{}'.format(user, psw, ip, dbName))  # mysql+pymysql的意思为:指定引擎为pymysql
        df.to_sql(tableName, con, index=False, if_exists='append')

if __name__ == '__main__':
    jdComments=JDComments()
    jdComments.funcPages(productId=13285690)#批量爬取

appmian.py

import os
import time
import pandas as pd
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTableView, QPushButton, QMessageBox
from PyQt5.QtGui import QStandardItemModel, QStandardItem,QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QInputDialog, QLineEdit,QTreeWidgetItem
from PyQt5.QtCore import QUrl
from colorama import Fore
from sqlalchemy import create_engine
import wordCloudInfo
from jdComments import JDComments
import warnings
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal
warnings.filterwarnings('ignore')


class ScrawlThread(QThread):
    data_ready = pyqtSignal(pd.DataFrame)
    finished = pyqtSignal()//有新的数据准备好时,传递一个pandas dataframe作为参数

    def __init__(self, param1,parent=None)://类的初始化
        super(ScrawlThread, self).__init__(parent)
        self.jdComments = JDComments()  # 实例化爬虫
        self.param1 = param1//传入参数作为属性之一

    def run(self):
        input_data=self.param1
        print("开始执行:",input_data)
        flag = 0
        count = 0
        for page in range(1, 50):
            print(Fore.YELLOW + f'开始爬取商品,第{page}页!')
            try:
                flag = self.jdComments.reqeustPageComments(page=page, score=0, productId=int(input_data))
                df_data_dataframe = self.jdComments.current_scrawl_data.copy()
                if len(df_data_dataframe) == 0:
                    del df_data_dataframe
                    break
                self.data_ready.emit(df_data_dataframe)
            except Exception as e:
                # print(f"异常信息:{e}")
                pass
            # print(flag)
            if flag == 0:
                break
            count += 1
            time.sleep(2)
        print('爬取完毕后发出完成信号')
        self.finished.emit()

class DataFrameViewer(QWidget)://创建窗口
    def __init__(self, dataframe)://可视化一个指定的dataframe
        super().__init__()
        self.QMessageBox=QMessageBox()
        self.df_data_dataframe = dataframe
        self.df_data_dataframe_null = pd.DataFrame()
        self.setGeometry(100, 100, 1200, 1350)
        self.setWindowTitle('京东评论数据可视化')
        self.setWindowIcon(QIcon("./images/cartoon1.ico"))
        layout = QVBoxLayout()# 创建布局
        self.setLayout(layout)
        self.webview = QWebEngineView()//可以显示html内容,创建QWebEngineView窗口
        self.webview.setGeometry(100, 100, 1064, 670)
        self.table_view = QTableView()# 创建表格视图
        # self.table_view.setGeometry(100,100,1064,300)
        layout.addWidget(self.table_view)//新的表格视图添到之前的布局
        self.model = QStandardItemModel()# 创建数据模型
        self.table_view.setModel(self.model)//新的项目模型,表格视图展示数据
        self.model.setHorizontalHeaderLabels(dataframe.columns)      # 设置表头
        self.show_data(dataframe)# 显示数据

        # 创建Label和输入框
        self.label = QLabel('商品ID:')
        layout.addWidget(self.label)
        self.line_edit = QLineEdit()
        layout.addWidget(self.line_edit)

        # 创建按钮
        self.button = QPushButton('开始爬取评论数据')
        self.button.clicked.connect(self.show_input_data)  # 连接按钮点击的槽函数
        layout.addWidget(self.button)


        # 获取 QWebEngineSettings 对象
        settings = self.webview.settings()
        # 设置 LocalContentCanAccessRemoteUrls 为 True
        settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
        layout.addWidget(self.webview)
        self.load_echarts()
        self.webview.loadFinished.connect(self.updateChartBar)
    def load_echarts(self):
        # print('将Echarts的HTML代码加载到QWebEngineView窗口中')
        # 将Echarts的HTML代码加载到QWebEngineView窗口中
        # self.webview.setHtml(echarts_html)
        pwd = os.getcwd().replace('\\', '/')
        file_path = f"{pwd}/html/echarts_html.html"
        # print(file_path)
        file_url = QUrl.fromLocalFile(file_path)
        self.webview.load(file_url)
        self.webview.show()# 将QWebEngineView窗口嵌入到PyQt5窗口中
    def show_data(self, dataframe):
        # 清空数据模型
        self.model.clear()
        self.model.setHorizontalHeaderLabels(list(dataframe.columns))  # 设置表头
        # 设置行列数
        self.model.setRowCount(dataframe.shape[0])
        self.model.setColumnCount(dataframe.shape[1])

        # 填充数据模型
        for row in range(dataframe.shape[0]):
            for col in range(dataframe.shape[1]):
                item = QStandardItem(str(dataframe.iloc[row, col]))
                self.model.setItem(row, col, item)
        # 自动调整列宽
        self.table_view.resizeColumnsToContents()
        # print('展示数据')
    def show_input_data(self):
        """显示爬取的数据"""
        try:
            input_data = self.line_edit.text()
            # print(len(self.line_edit.text()))
            # print(int(input_data))
            if len(self.line_edit.text())==8:
                print(int(input_data))
                # QMessageBox.information(self, 'Input Data', f'{input_data}')
                self.QMessageBox.information(self, "提醒", "输入的商品ID为: "+ input_data, QMessageBox.Ok)
            else:
                self.QMessageBox.information(self, '提醒', '请输入8位商品ID!', QMessageBox.Ok)
                return
        except:
            self.QMessageBox.information(self, '提醒', '请输入8位商品ID!', QMessageBox.Ok)
            return
        print("输入内容:",input_data)
        # thrad0=threading.Thread(target=self.scrawlData,args=(input_data,))
        # thrad0.setDaemon(True)
        # thrad0.start()
        # 创建并启动评论爬取线程
        self.scrawl_thread = ScrawlThread(input_data)
        self.scrawl_thread.data_ready.connect(self.update_dataframe)
        self.scrawl_thread.start()
        self.scrawl_thread.finished.connect(self.show_message)

    def update_dataframe(self, data):
        # 在主线程中接收爬取的数据并更新 dataframe
        # 将 data 添加到 dataframe 中
        self.df_data_dataframe_null = pd.concat([self.df_data_dataframe_null, data])
        self.df_data_dataframe=self.df_data_dataframe_null.copy()
        self.show_data(self.df_data_dataframe_null)
        self.updateChartBar()

    def show_message(self):
        # 爬取完毕后弹出提醒框
        QMessageBox.information(self, "爬取完毕提醒", "评论爬取完毕!")
            # self.QMessageBox.information(self, "爬取完毕提醒", "评论爬取完毕!")

    def updateChartBar(self):
        print('更新柱状图数据')
        scores = self.df_data_dataframe['分数'].value_counts().sort_index()
        list_dict = {
            'score': scores.values.tolist(),
            'name': scores.index.tolist(),
        }
        js_code = f"updateEchartsChart({list_dict})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)
        # 柱状图
        vip_info = self.df_data_dataframe['VIP'].value_counts().to_dict()

        list_dict_info = []
        for k, v in vip_info.items():
            dict_info = {
                'name': k,
                'value': v,
            }
            list_dict_info.append(dict_info)
        print("VIP", list_dict_info)
        js_code = f"updateEchartsChart3({list_dict_info})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)
        list_word_info = self.wordCloudValues(self.df_data_dataframe)
        print("updateEchartsWordCloud", list_word_info)
        js_code = f"updateEchartsWordCloud({list_word_info})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)
        list_word_info=self.weiduAnaysise(self.df_data_dataframe)
        print("updateEchartsChart4", list_word_info)
        js_code = f"updateEchartsChart4({list_word_info})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)

        list_word_info=self.meiGuiImg(self.df_data_dataframe)
        print("updateEchartsChart5", list_word_info)
        js_code = f"updateEchartsChart5({list_word_info})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)

        list_word_info,minValues=self.areaMap(self.df_data_dataframe)
        print("updateEchartsChart6", list_word_info)
        js_code = f"updateEchartsChart6({list_word_info})"  # 调用JavaScript中的updateChart函数更新图形
        self.webview.page().runJavaScript(js_code)
        del self.df_data_dataframe




    def wordCloudValues(self, df_data):
        """第1个图词云图分析"""
        df_data['评论内容'] = df_data['评论内容'].astype('str')
        comments = ' '.join(df_data['评论内容'])
        df_data = wordCloudInfo.jieba_cut_stoword(comments)
        word_cloud_data = []
        for i in range(len(df_data)):
            name = df_data['词汇'][i]
            value = df_data['词频'][i]
            dict_info = {"name": name, "value": value}
            word_cloud_data.append(dict_info)
        return word_cloud_data[:30]

    def weiduAnaysise(self,df_data):
        """第4个图维度分析"""
        df_data0=wordCloudInfo.weiDuAnalyse(df_data)
        word_cloud_data = []
        for i in range(len(df_data0)):
            name = df_data0['维度'][i]
            value = df_data0['维度数量'][i]
            dict_info = {"name": name, "value": value}
            word_cloud_data.append(dict_info)
        return word_cloud_data

    def meiGuiImg(self,df_data):
        word_cloud_data=wordCloudInfo.meiGui(df_data)
        return word_cloud_data

    def areaMap(self,df_data):
        area_info=wordCloudInfo.areaMapInfo(df_data)
        return area_info,min([area['value'] for area in area_info])


def readSqlData():
    # 创建数据库连接引擎
    username = 'root'  # 用户名
    password = '123456'  # 密码
    host = 'localhost'  # 主机地址
    database = 'comments'  # 数据库名称
    table_name = 'jdcomments'  # 数据库名称
    engine = create_engine(f'mysql+pymysql://{username}:{password}@{host}/{database}')
    df_data = pd.read_sql(f"SELECT * FROM {table_name}", engine)
    df_data.drop_duplicates(inplace=True)
    df_data.reset_index(drop=True,inplace=True)
    # print(df_data)
    return df_data

if __name__ == '__main__':
    df_data=readSqlData()
    df_data.drop(columns=['评论id', '收集版本', '商品属性'], inplace=True)
    df_data['地区'] = df_data['地区'].apply(lambda x: str(x).replace('nan', ''))
    app = QApplication([])# 创建应用程序实例
    widget = DataFrameViewer(df_data[:50])# 选择评论数量
    widget.show()
    app.exec()# 启动应用程序事件循环

这段代码定义了一个名为ScrawlThread的Python类,该类继承自QThread,这意味着它是一个继承自Qt线程的自定义线程。这个线程用于执行网络爬虫任务,特别是在一个单独的线程中。以下是对代码的逐行解释:

class ScrawlThread(QThread)::定义一个名为ScrawlThread的类,该类继承自QThread。
data_ready = pyqtSignal(pd.DataFrame):定义一个PyQt信号,名为data_ready。当有新的数据准备好时,这个信号将被发出,并传递一个pandas DataFrame作为参数。
finished = pyqtSignal():定义另一个PyQt信号,名为finished。当爬虫任务完成时,这个信号将被发出。
def __init__(self, param1,parent=None)::这是类的初始化方法,它接受一个参数param1和一个可选的父对象parent。
super(ScrawlThread, self).__init__(parent):调用父类(QThread)的初始化方法。
self.jdComments = JDComments():创建一个名为JDComments的实例(可能是一个网络爬虫类)。
self.param1 = param1:将传入的param1参数保存为类的属性。
def run(self)::这是线程的主要执行方法。当线程开始运行时,这个方法将被调用。
input_data=self.param1:从类的属性中获取传入的参数,并将其存储在局部变量input_data中。
print("开始执行:",input_data):打印一条消息,表明爬虫任务已经开始执行,并显示传入的参数。
flag = 0 和 count = 0:初始化两个局部变量。
for page in range(1, 50)::开始一个循环,从第1页到第50页。
print(Fore.YELLOW + f'开始爬取商品,第{page}页!'):打印当前正在爬取的页数,使用黄色文字。
try::开始一个异常处理块。
flag = self.jdComments.reqeustPageComments(page=page, score=0, productId=int(input_data)):调用JDComments实例的方法来请求特定页数的商品评论。
df_data_dataframe = self.jdComments.current_scrawl_data.copy():从JDComments实例中获取当前爬取的数据,并将其复制到一个新的DataFrame中。
if len(df_data_dataframe) == 0::检查DataFrame是否为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值