Python之项目课
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) # 页面加载超时时间为10s browser.set_page_load_timeout(10) # 页面js加载超时时间为10s browser.set_script_timeout(10) load_driver_success = True except Exception as ex: load_driver_success = False err_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行的双击事件 # :双击事件 # <>:行选中事件 movie_tv.bind('', 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): pass def 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或者tuple x_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 manager mngr = 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()