1.项目准备
1.1.项目创建
此处省略一万字...
1.2.项目目标
-
创建数据库movie并设置编码格式,并完成t_user(用户信息表)、t_movie(电影信息表)的创建任务;
-
完成用户登录功能,登录成功之后跳转到电影主界面;
-
完成电影排行榜和关键字电影查询功能;
-
完成电影信息图表统计(选作)
1.3.项目结构
dao|-- __init__.py|-- movie_dao.py # 电影dao层接口类|-- login_dao.py # 用户dao层接口类 ui |-- __init__.py|-- charts_ui.py # 统计界面|-- login_ui.py # 登录界面|-- movie_ui.py # 电影主界面 utils|-- __init__.py|-- db_helper.py # dbhelper帮助类|-- movie_util.py # 电影排行榜和关键字查询电影接口定义 main.py # 运行程序入口
2.功能实现
安装
创建movie_util.py
类,用于统一处理电影排行榜和关键字查询电影接口定义及测试。
首先初始化电影类型,如下:
movieData = ' [' \'{"title":"纪录片", "type":"1", "interval_id":"100:90"}, ' \' {"title":"传记", "type":"2", "interval_id":"100:90"}, ' \' {"title":"犯罪", "type":"3", "interval_id":"100:90"}, ' \' {"title":"历史", "type":"4", "interval_id":"100:90"}, ' \' {"title":"动作", "type":"5", "interval_id":"100:90"}, ' \' {"title":"情色", "type":"6", "interval_id":"100:90"}, ' \' {"title":"歌舞", "type":"7", "interval_id":"100:90"}, ' \' {"title":"儿童", "type":"8", "interval_id":"100:90"}, ' \' {"title":"悬疑", "type":"10", "interval_id":"100:90"}, ' \' {"title":"剧情", "type":"11", "interval_id":"100:90"}, ' \' {"title":"灾难", "type":"12", "interval_id":"100:90"}, ' \' {"title":"爱情", "type":"13", "interval_id":"100:90"}, ' \' {"title":"音乐", "type":"14", "interval_id":"100:90"}, ' \' {"title":"冒险", "type":"15", "interval_id":"100:90"}, ' \' {"title":"奇幻", "type":"16", "interval_id":"100:90"}, ' \' {"title":"科幻", "type":"17", "interval_id":"100:90"}, ' \' {"title":"运动", "type":"18", "interval_id":"100:90"}, ' \' {"title":"惊悚", "type":"19", "interval_id":"100:90"}, ' \' {"title":"恐怖", "type":"20", "interval_id":"100:90"}, ' \' {"title":"战争", "type":"22", "interval_id":"100:90"}, ' \' {"title":"短片", "type":"23", "interval_id":"100:90"}, ' \' {"title":"喜剧", "type":"24", "interval_id":"100:90"}, ' \' {"title":"动画", "type":"25", "interval_id":"100:90"}, ' \' {"title":"同性", "type":"26", "interval_id":"100:90"}, ' \' {"title":"西部", "type":"27", "interval_id":"100:90"}, ' \' {"title":"家庭", "type":"28", "interval_id":"100:90"}, ' \' {"title":"武侠", "type":"29", "interval_id":"100:90"}, ' \' {"title":"古装", "type":"30", "interval_id":"100:90"}, ' \' {"title":"黑色电影", "type":"31", "interval_id":"100:90"}' \']'
2.1.电影排行榜
在movie_util.py
类中定义电影排行榜接口:
https://movie.douban.com/j/chart/top_list?type=&interval_id=100:90&action=unwatched&start=0&limit=
def get_movie_top(m_type: str,num: int,rating: float,pj: int):"""排行榜查询方法:param m_type: 电影类型:param num: 爬取数量:param rating: 影片评分:param pj: 评价人数:return:"""try:headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}url = 'https://movie.douban.com/j/chart/top_list?type=' + str(m_type) + '&interval_id=100:90&action=unwatched&start=0&limit=' + str(num)req = request.Request(url=url, headers=headers)# 用于打开一个远程的url连接,并且向这个连接发出请求,获取响应结果f = request.urlopen(req)# 获取响应对象response = f.read()# 将json转为python对象jsonData = loads(response)movies_list = []# 循环获取的电影信息并提取有效数据for subData in jsonData:# 判断影片评分和评价人数是否达到要求if (float(subData['score']) >= float(rating)) and (float(subData['vote_count']) >= float(pj)):movie = {...}movies_list.append(movie)# 返回字典格式的结果,200代表成功;500代表异常return {'code':200,'movies':movies_list} except Exception as ex:err_str = "出现未知异常:{}".format(ex)return {'code':500,'msg':err_str}
2.2.关键字查询电影
下载selenium模块:
pip install selenium==4.10.0
通过selenium实现关键字电影查询之前,请在接口中先进行chrome配置,如下:
chrome_options = Options() # 设置为无头模式,即不显示浏览器 chrome_options.add_argument('--headless') # 设置user=agent chrome_options.add_argument( 'user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"') # 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Selenium chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) # 不加载图片,加快访问速度 chrome_options.add_experimental_option("prefs",{"profile.managed_default_content_settings.images": 2}) # 加载chromedriver驱动是否成功 load_driver_success = False browser = None try:# 设置chromedriver驱动路径browser = webdriver.Chrome(options=chrome_options)# 页面加载超时时间为10sbrowser.set_page_load_timeout(10)# 页面js加载超时时间为10sbrowser.set_script_timeout(10)load_driver_success = True except Exception as ex:load_driver_success = Falseerr_str = "加载chromedriver驱动失败,异常信息:{}".format(ex)return {'code':500,'msg':err_str}
在movie_util.py
类中定义关键字查询电影接口:
电影 - 豆瓣搜索
def get_movie_kw(kw: str):"""基于关键字查询电影信息:param kw::return:"""# 先进行chrome的配置,请参考上面的配置if load_driver_success:try:url = 'https://search.douban.com/movie/subject_search?search_text='+urllib.parse.quote(kw)+'&cat=1002'# print(url)# get方式获取返回数据browser.get(url)...except Exception as ex:# 关闭浏览器browser.quit()err_str = "Selenium获取数据出现其他未知异常:{}".format(ex)return {'code':200,'msg':err_str}
3.技术难点
3.1.下拉框数据绑定
初始化下拉框列表:
# 下拉列表框 comvalue = StringVar() movie_combox = Combobox(labelframe, width=5, textvariable=comvalue, state='readonly') # 将影片类型输入到下拉列表框中 # json数据 jsonMovieData = loads(movieData) movieList = [] # 对每一种类的电影题材进行操作 for subMovieData in jsonMovieData: movieList.append(subMovieData['title']) # 初始化 movie_combox["values"] = movieList # 选择第一个 movie_combox.current(9) # 设置显示位置 movie_combox.place(x=65, y=15)
获取下拉框的数据:
# 下拉框的数据 jsonMovieData = loads(movieData) # 循环获取选择下拉框中选中的值 movie_type = None for subMovieData in jsonMovieData:# 判断JSON数据中的title与选中的title是否相同if subMovieData['title'] == self.movie_combo.get():movie_type = subMovieData['type']break
3.2.列表滚动条设置
# 框架布局,承载多个控件 frame_root = Frame(labelframe,width=720) frame_l = Frame(frame_root) frame_r = Frame(frame_root)movie_columns=("title","rank",...) movie_tv = Treeview(frame_l,columns=movie_columns,show="headings")# 设置列宽和显示位置 movie_tv.column("title",width=242,anchor="center") .. # 设置显示表头 movie_tv.heading("title",text="电影名称") ...# 垂直滚动条 vbar = ttk.Scrollbar(frame_r, command=movie_tv.yview) movie_tv.configure(yscrollcommand=vbar.set) movie_tv.pack() vbar.pack(side=RIGHT, fill=Y)# 框架的位置布局 frame_l.grid(row=0, column=0) # sticky=NS表示拉高组件,让组件上下填充到表格框的顶端和底端 frame_r.grid(row=0, column=1, sticky=NS) # 显示位置 frame_root.place(x=6, y=84)
3.3.列表事件
# 绑定treeview行的双击事件 # <Double-1>:双击事件 # <<TreeviewSelect>>:行选中事件 movie_tv.bind('<Double-1>', self.select_treeview)
3.4.点击查询防止GUI界面假死无响应
定义线程函数:
def thread_it(func, *args):"""将函数打包进线程(重要):param func::param args::return:"""# 创建t = Thread(target=func, args=args)# 守护t.setDaemon(True)# 启动t.start()
给按钮绑定事件:
# lambda表示绑定的函数需要带参数,请勿删除lambda,否则会出现异常 # thread_it表示新开启一个线程执行这个函数,防止GUI界面假死无响应 btn_keyword = Button(labelframe, text="从关键字搜索",command=lambda: thread_it(self.do_search_kw)) btn_keyword.place(x=566, y=46)
此处self.do_search_kw
是单独定义的函数。
3.5.统计图表
import time import matplotlib import matplotlib.pyplot as plt from pylab import mpl from itertools import groupby matplotlib.use('TkAgg')class charts_ui():def __init__(self):passdef show_charts(self,source_data,root):# 指定默认字体mpl.rcParams['font.sans-serif'] = ['FangSong']# 解决保存图像是负号'-'显示为方块的问题mpl.rcParams['axes.unicode_minus'] = False # 提取电影数据中的评分score = []for k in source_data:score.append(k['score'])# 根据评分进行分组筛选统计计数data = {}groups = groupby(sorted(score))for k,group in groups:data[k] = len(list(group))for k, v in data.items():# ha 文字指定在柱体中间, va指定文字位置 fontsize指定文字体大小plt.text(k, v + 0.05, '%.0f' % v, ha='center', va='bottom', fontsize=11) # 设置X轴Y轴数据,两者都可以是list或者tuplex_axis = tuple(data.keys())y_axis = tuple(data.values())# 如果不指定color,所有的柱体都会是一个颜色plt.bar(x_axis, y_axis, color='orange') # 指定图表描述信息plt.title("电影评分统计表") # 指定Y轴的高度plt.ylim(0,max(data.values()) + 10) # 获取当前figure managermngr = plt.get_current_fig_manager()screenwidth = root.winfo_screenwidth()screenheight = root.winfo_screenheight()x = int((screenwidth - 600) / 2)y = int((screenheight - 500) / 2)# 调整窗口在屏幕上弹出的位置mngr.window.wm_geometry(f"+{x}+{y}")plt.show()