目录
1.模块设计
2.手机页面代码编写
2.1数据获取
2.2主页面编写
3.功能编写
3.1通过人民查询
3.2清空选项和查询函数
3.3列表相关功能定义
4.最终成功展示和总结
上一篇:使用scrapy框架抓取手机信息(1)_Ryucy的博客-CSDN博客
上次我们爬取到了京东商城的手机数据,这次我们要使用tkinter来处理和显示这些数据
主页使用最常规的设计,通过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即可
首先在写主要代码之前,要先规划一下模块继承问题
我们首先创建一个类,继承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()就可以获取到相应的数据了,不过其他增删改查的函数我还没写完,之后有机会在补充
开始在create_page函数里写第一段代码
我们首先在__init__里定义变量来获取选择的数据
之后创建选择框的信息,并且给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的值,不然会叠起来导致上个数据被覆盖
最后可以看到效果如图所示
接下来我们需要创建一个树形列表来显示数据
我们设置每一列的值和列头的名称
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']
))
之后在创建树形组件的下面使用
就可以使数据填充到列表中去了,效果如下图所示
到现在为止,主页面大体的设计就完成了,如下图所示
首先编写一个可以通过名字来查询的功能,写一个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']
))
定义了两个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}条数据')
这里我给列表定义了三个功能,分别是单击列名排序,双击进入商品页面,和选中后右键单机下载并显示手机图片
第一个单击排序使用的是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))
之后点击价格就可以看到排序后的结果了
成功排序
第二个是双击进入商品页面的功能,通过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的垃圾处理机制自动销毁,导致图片无法显示
最后的效果就是下图这样
整体的页面就是这样了
项目地址:https://github.com/Liucyer/jdcrawler