使用scrapy框架抓取手机商品信息(2)

目录

1.模块设计

2.手机页面代码编写

2.1数据获取

2.2主页面编写

3.功能编写

3.1通过人民查询

3.2清空选项和查询函数

3.3列表相关功能定义

4.最终成功展示和总结


上一篇:使用scrapy框架抓取手机信息(1)_Ryucy的博客-CSDN博客

上次我们爬取到了京东商城的手机数据,这次我们要使用tkinter来处理和显示这些数据

1.模块设计

主页使用最常规的设计,通过menu来切换模块,同时将每个menu的模块单独写,更加利于代码可读性和后续拓展性,之后只要每新写一个页面后将其加入到menubar里并且导入进来就好了

#main_page.py

import tkinter as tk
from tkinterpart.phone_page import ShowPhone
from tkinterpart.gpu_page import ShowGPU

class MainPage:
    def __init__(self,master):
        self.window = master
        self.window.title('京东商城手机数据管理')
        self.window.geometry('980x880')
        self.create_page()

    def create_page(self):
        self.phone_info = ShowPhone(self.window)
        #self.gpu_info = ShowGPU(self.window)



        menubar = tk.Menu(self.window)
        menubar.add_command(label='手机数据',command=self.show_phone)
        #menubar.add_command(label='显卡数据', command=self.show_gpu)


        self.window['menu'] = menubar

    def show_phone(self):
        self.phone_info.pack()
        #self.gpu_info.pack_forget()

    #def show_phone(self):
        #self.phone_info.pack()
        #self.gpu_info.pack_forget()



if __name__ == '__main__':
    window = tk.Tk()
    main = MainPage(window)
    window.mainloop()

创建新的page只需要导入到mainpage即可

2.手机页面代码编写

2.1数据获取

首先在写主要代码之前,要先规划一下模块继承问题
我们首先创建一个类,继承tk.Frame,这里的tk.Frame就是我们从main_page传过去的,那我们在这里使用 super函数获取的父类对象,之后在创建组件的同时放入root,新建的组件也会安放在我们在main_page创建的框架里面

class ShowPhone(tk.Frame):
    def __init__(self,root):
        super().__init__(root)
        self.create_page()

我们之前在爬取的时候获得了这些数据

名称','价格', '商品地址', '内存', '储存','后置像素','屏幕','CPU','5G支持

可以利用他们来做一个筛选的功能,利用tkinter的单选框获取选择的数据,然后在数据库里进行过滤筛选,类似这样的效果

同时,我们需要来获取到我们之前爬取的数据
这里我单独定义了一个模块,叫db.py里面写的是数据读写相关的函数,主要是这类函数的使用场景很多,单独写一个模块可以减少代码的重复性

import json
from csv import DictReader
from pymongo import MongoClient
import pandas as pd

class GetData:
    def __init__(self):
        client = MongoClient()
        collection = client['jdcrawler']['jdphone']
        self.data = [i for i in collection.find()]

    def all(self):
        return self.data

db = GetData()

这样只要在其他模块里面导入db.py,同时使用db.xxx()就可以获取到相应的数据了,不过其他增删改查的函数我还没写完,之后有机会在补充

2.2主页面编写

开始在create_page函数里写第一段代码

我们首先在__init__里定义变量来获取选择的数据

使用scrapy框架抓取手机商品信息(2)_第1张图片

之后创建选择框的信息,并且给self.price设置一个默认值

self.price.set('1362-3573')
tk.Label(self, text='价格区间').grid(row = 0, column = 0, sticky = "W", pady = 5)

因为组件过多,我们使用grid定位

使用Radiobutton来创建单选框,其中text是显示的数据,而variable是我们要获取到的数据且将多个单选框分为了一组,

price1 = tk.Radiobutton(self, value ='0-349',text="0-349", variable=self.price).grid(row = 0, column = 1, sticky = "W", pady = 5)
        price2 = tk.Radiobutton(self, value ='349-1362',text="349-1362", variable=self.price).grid(row = 0, column = 2, sticky = "W", pady = 5)
        price3 = tk.Radiobutton(self, value='1362-3573', text="1362-3573", variable=self.price).grid(row=0, column=3,sticky="W", pady=5)
        price4 = tk.Radiobutton(self, value='3573-8096', text="3573-8096", variable=self.price).grid(row=0, column=4,sticky="W", pady=5)
        price5 = tk.Radiobutton(self, value='8096-36999', text="8096-36999", variable=self.price).grid(row=0, column=5,sticky="W", pady=5)

后面的几个数据也是同理,不过每次都要重复创建太麻烦了,我们可以写一个循环来创建单选框

        self.ram.set('8GB')
        values = ('4GB', '6GB', '8GB', '12GB', '其他', '未知运行内存')
        texts = ('4GB', '6GB', '8GB', '12GB', '其他', '未知运行内存')
        tk.Label(self, text='内存').grid(row = 1, column = 0, sticky = "W", pady = 5)
        i = 1
        for value, text in zip(values, texts):
            b = tk.Radiobutton(self, value=value,text=text, variable=self.ram).grid(row = 1, column = i, sticky = "W", pady = 5)
            i = i + 1

注意每次都要修改grid的row的值,不然会叠起来导致上个数据被覆盖

最后可以看到效果如图所示

使用scrapy框架抓取手机商品信息(2)_第2张图片

接下来我们需要创建一个树形列表来显示数据

我们设置每一列的值和列头的名称

 columns = ('name','price','inner_href','RAM','ROM','After_taken_pixel','resolution','CPU','S5G')
        columns_values = 
('名称','价格', '商品地址', '内存', '储存','后置像素','屏幕','CPU','5G支持')
        self.tree_view = ttk.Treeview(self,show='headings',columns=columns)

通过两个循环将这些值插入到树形列表里

for column in columns:
    self.tree_view.column(column, width=100, anchor='center')

for column,columns_value in zip(columns,columns_values):
    self.tree_view.heading(column, text=columns_value)

最后设置树形列表的位置

self.tree_view.grid(row=10, column=0, columnspan=10,sticky='nsew')

!!!注意!!! 这边的 columnspan=10 是必须设置的项,不然这个树形列表控件就会将grid里的单元格给撑的很大,会导致上面的单选框控件被挤到很后面,设置 columnspan 参数可以将列表扩展到后面的单元格,使其不会撑大第一个单元格

之后就可以将我们之前获取的数据显示到这个列表里去了,这边我们定义了一个新的方法叫做
show_data_frame() 里面的代码如下

 def show_data_frame(self):
        for _ in map(self.tree_view.delete,self.tree_view.get_children('')):
            pass
        jdphone = db.all()
        index = 0
        for info in jdphone:
            self.tree_view.insert('',index + 1,values=(
                info['name'],info['price'],info['inner_href'],info['RAM'],info['ROM'],info['After_taken_pixel'],info['resolution'],info['CPU'],info['S5G']
            ))

之后在创建树形组件的下面使用

就可以使数据填充到列表中去了,效果如下图所示

使用scrapy框架抓取手机商品信息(2)_第3张图片

到现在为止,主页面大体的设计就完成了,如下图所示

使用scrapy框架抓取手机商品信息(2)_第4张图片

3.功能编写

3.1通过人民查询

首先编写一个可以通过名字来查询的功能,写一个search函数绑定按钮

 tk.Label(self, text='通过名字查询').grid(row=7, column=0, pady=10)
        tk.Entry(self, textvariable=self.name).grid(row=7, column=1, pady=10)
        tk.Button(self, text='查询', command=self.search).grid(row=7, column=2, sticky="W", pady=2)

查询的代码如下,就是通过判断self.name和数据库的info[‘name’]来选择性的插入数据

def search(self):
        for _ in map(self.tree_view.delete, self.tree_view.get_children('')):
            pass

        #这段代码就是清空列表的意思
        jdphone = db.all()
        index = 0
        for info in jdphone:
            if info['name'] == self.name.get():
                self.tree_view.insert('', index + 1, values=(
                    info['name'], info['price'], info['inner_href'], info['RAM'], info['ROM'], info['After_taken_pixel'],
                    info['resolution'], info['CPU'], info['S5G']
                ))

3.2清空选项和查询函数

定义了两个button来绑定两个功能,一个是让单选框的选项回到默认值,一个是对单选框的选项进行查询

tk.Button(self, text='清空选项', command=self.clear).grid(row=8, column=8, sticky="W", pady=2)
        tk.Button(self, text='确认查询', command=self.confirm).grid(row = 8, column = 9, sticky = "W", pady = 2)

confirm函数和clear函数如下

 def clear(self):
        for _ in map(self.tree_view.delete, self.tree_view.get_children('')):
            pass
        self.price.set('None Selected')
        self.ram.set('None Selected')
        self.rom.set('128GB')
        self.atp.set('2400万像素')
        self.resolution.set('全高清FHD+')
        self.CPU.set('暂无型号')
        self.S5G.set('None Selected')

confirm函数的筛选逻辑还不完善,日后等我水平上来了再说

 def confirm(self):
        i = 0
        for _ in map(self.tree_view.delete, self.tree_view.get_children('')):
            pass
        jdphone = db.all()
        index = 0
        for info in jdphone:
            min_price = int(self.price.get().split('-')[0])
            max_price = int(self.price.get().split('-')[1])
            info_price = int(info['price'].split('.')[0])

            # c1 = "info['RAM'] == self.ram.get()"
            # c2 = "info_price >= min_price and info_price <= max_price"
            # c3 = "info['S5G'] == self.S5G.get()"
            if info['RAM'] == self.ram.get() and (info_price >= min_price and info_price <= max_price) \
                                             and info['ROM'] == self.rom.get() and info['After_taken_pixel'] == self.atp.get() and info['resolution'] == self.resolution.get() \
                                             and info['CPU'] == self.CPU.get()  and info['S5G'] == self.S5G.get():
                self.tree_view.insert('', index + 1, values=(
                    info['name'], info['price'], info['inner_href'], info['RAM'], info['ROM'], info['After_taken_pixel'],
                    info['resolution'], info['CPU'], info['S5G']
                ))
                i = i + 1
        self.status.set(f'总共查找到{i}条数据')

3.3列表相关功能定义

这里我给列表定义了三个功能,分别是单击列名排序,双击进入商品页面,和选中后右键单机下载并显示手机图片
第一个单击排序使用的是starkoverflow里的方法,不过他的方法不能比较2和10的大小,所以我改良了一下

 def treeview_sort_column(self, tv, col, reverse):  # Treeview、列名、排列方式
        l = [(tv.set(k, col), k) for k in tv.get_children('')]
        try:
            l.sort(key=lambda t: int(t[0].split('.')[0]), reverse=reverse)  # 排序方式
        # rearrange items in sorted positions
            for index, (val, k) in enumerate(l):  # 根据排序后索引移动
                tv.move(k, '', index)
            tv.heading(col, command=lambda: self.treeview_sort_column(tv, col, not reverse))
        except:
            l.sort(reverse=reverse)
            for index, (val, k) in enumerate(l):
                tv.move(k, '', index)
            tv.heading(col, command=lambda: self.treeview_sort_column(tv, col, not reverse))

使用try是因为非数字的排序会报错,那我索性就在报错后直接使用原来的方法就好了

之后在列表控件下添加

for col,columns_value in zip(columns,columns_values):
            self.tree_view.heading(col, text=columns_value, command=lambda _col=col: self.treeview_sort_column(self.tree_view, _col, False))

之后点击价格就可以看到排序后的结果了

使用scrapy框架抓取手机商品信息(2)_第5张图片

成功排序

第二个是双击进入商品页面的功能,通过bind函数将列表与双击事件绑定即可

self.tree_view.bind('',self.onDBClick)

onDBClick函数如下,通过选择的selection返回的数据和数据库比较即可,最后使用对应的open_new函数即可跳转到商品页面

 def onDBClick(self,event):
        item = self.tree_view.selection()[0]
        info = self.tree_view.item(item, "values")
        url = info[2]
        web.open_new(url)

第三个是右击进入显示商品的功能,通过bind函数将列表与右击事件绑定

self.tree_view.bind('', self.onTRClick)

这边同样是通过selection返回的数据和数据库比较获取到对应的pic_url,通过requests请求地址返回来图片数据,保存到对应的文件夹下,并且显示到框架里

 def onTRClick(self, event):
        item = self.tree_view.selection()[0]
        info = self.tree_view.item(item, "values")
        url = info[2]
        jdphone = db.all()
        for info in jdphone:
            pic_href = info['pic']
            if url == info['inner_href']:
                r = requests.get(pic_href,headers=self.headers)
                open('../img/'+pic_href.split('/')[-1], 'wb').write(r.content)
                # time.sleep(2)
                global photo
                photo = ImageTk.PhotoImage(file='../img/'+pic_href.split('/')[-1])  # 用PIL模块的PhotoImage打开
                imglabel = tk.Label(self, image=photo).grid(row=11, column=0, columnspan=10, sticky='nsew')

值得注意的是,这里需要引入PIL模块显示图片,不然tkinter默认支持的格式只有gif,而且需要将photo设置为全局变量,不然会被python的垃圾处理机制自动销毁,导致图片无法显示
最后的效果就是下图这样

使用scrapy框架抓取手机商品信息(2)_第6张图片

4.最终成功展示和总结

整体的页面就是这样了

使用scrapy框架抓取手机商品信息(2)_第7张图片

项目地址:https://github.com/Liucyer/jdcrawler

你可能感兴趣的:(python,爬虫)