【开源】2024最新python豆瓣电影数据爬虫+可视化分析项目 ...

莱莱  论坛元老 | 2024-10-7 01:18:59 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1017|帖子 1017|积分 3051

项目先容

【开源】项目基于python+pandas+flask+mysql等技术实现豆瓣电影数据获取及可视化分析展示,以为有用的朋友可以来个一键三连,感谢!!!
项目演示


     【开源】2024最新python豆瓣电影数据爬虫+可视化分析项目
  
项目截图



  • 首页

  • 列表页

  • 爬虫演示

项目地点

https://github.com/mudfish/python-douban-view
项目布局


焦点模块

电影爬虫

  1. """
  2. 异步并发爬虫
  3. """
  4. # 本次运行获取的最大页数
  5. MAX_PAGES = 5
  6. # 进度控制文件
  7. PAGE_PROGRESS_FILE = "page_progress.json"
  8. # 电影类型
  9. MOVIE_TYPES = ["剧情", "喜剧", "动作", "爱情", "科幻", "动画"]
  10. # CSV文件名
  11. CSV_NAME = "movie_data.csv"
  12. # CSV头
  13. CSV_HEADS = [
  14.     "id",
  15.     "movie_id",
  16.     "title",
  17.     "year",
  18.     "directors",
  19.     "casts",
  20.     "rating",
  21.     "cover",
  22.     "country",
  23.     "summary",
  24.     "types",
  25.     "lang",
  26.     "release_date",
  27.     "time",
  28.     "url",
  29. ]
  30. # 上映日期匹配正则,剔除非数字和-
  31. RELEASE_DATE_REMOVE_RE = r"[^0-9-]"
  32. engine = create_engine("mysql+pymysql://root:123456@127.0.0.1:3306/db_douban")
  33. def get_id():
  34.     return str(random.randint(1, 100000000)) + str(time.time()).split(".")[1].strip()
  35. class Spider:
  36.     def __init__(self):
  37.         self.movie_page_url = "https://m.douban.com/rexxar/api/v2/movie/recommend?"
  38.         self.movie_detail_url = "https://movie.douban.com/subject/{}/"
  39.         self.headers = {
  40.             "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
  41.             "Referer": "https://movie.douban.com/explore",
  42.         }
  43.         self.movie_types = MOVIE_TYPES
  44.         self.page_progress = {}
  45.         # 需要抓取的页面数
  46.         self.total_pages = 0
  47.         self.completed_pages = 0
  48.         self.global_progress_bar = None
  49.     def init(self):
  50.         # 每次跑之前,先删除之前的csv文件
  51.         if os.path.exists(CSV_NAME):
  52.             os.remove(CSV_NAME)
  53.         with open(CSV_NAME, "w", newline="", encoding="utf-8") as writer_f:
  54.             writer = csv.writer(writer_f)
  55.             writer.writerow(CSV_HEADS)
  56.     def load_page_progress(self):
  57.         if os.path.exists(PAGE_PROGRESS_FILE):
  58.             with open(PAGE_PROGRESS_FILE, "r", encoding="utf-8") as f:
  59.                 # 判断文件内容是否为空
  60.                 if os.stat(PAGE_PROGRESS_FILE).st_size == 0:
  61.                     # 初始化页面进度
  62.                     print("初始化页面进度")
  63.                     self.page_progress = {}
  64.                     self.save_page_progress()
  65.                 else:
  66.                     self.page_progress = json.load(f)
  67.     def save_page_progress(self):
  68.         with open(PAGE_PROGRESS_FILE, "w", encoding="utf-8") as f:
  69.             json.dump(self.page_progress, f, ensure_ascii=False)
  70.     async def get_movie_pages(self, session, type_name):
  71.         start_page = self.page_progress.get(type_name, 1)
  72.         if start_page <= MAX_PAGES:
  73.             for page in range(start_page, MAX_PAGES + 1):
  74.                 # print(f'{type_name}第{page}页:')
  75.                 start_time = time.time()
  76.                 params = {"start": (page - 1) * 20, "count": 10, "tags": type_name}
  77.                 try:
  78.                     async with session.get(
  79.                         self.movie_page_url, headers=self.headers, params=params
  80.                     ) as resp:
  81.                         resp.raise_for_status()
  82.                         respJson = await resp.json()
  83.                         movie_list = respJson["items"]
  84.                         for i, m in enumerate(movie_list):
  85.                             if m["type"] == "movie":
  86.                                 await self.process_movie(session, m)
  87.                                 # progress_bar.update(round(1/len(movie_list)))
  88.                         self.page_progress[type_name] = page + 1
  89.                         # 记录进度
  90.                         self.save_page_progress()
  91.                         # 刷新全局进度
  92.                         self.update_global_progress()
  93.                 except Exception as e:
  94.                     print(f"处理:{type_name}第{page}页失败: {e}")
  95.                     traceback.print_exc()
  96.                     continue
  97.     async def process_movie(self, session, movie):
  98.         movie_data = []
  99.         movie_data.append(get_id())
  100.         movie_data.append(movie["id"])
  101.         movie_data.append(movie["title"])
  102.         movie_data.append(movie["year"])
  103.         async with session.get(
  104.             self.movie_detail_url.format(movie["id"]), headers=self.headers
  105.         ) as resp:
  106.             resp.raise_for_status()
  107.             html_text = await resp.text()
  108.         path = etree.HTML(html_text)
  109.         # 导演
  110.         movie_data.append(",".join(path.xpath('//a[@rel="v:directedBy"]/text()')))
  111.         # 主演
  112.         movie_data.append(",".join(path.xpath('//a[@rel="v:starring"]/text()')))
  113.         # 评分
  114.         movie_data.append(path.xpath('//strong[@property="v:average"]/text()')[0])
  115.         # 封面
  116.         movie_data.append(path.xpath('//img[@rel="v:image"]/@src')[0])
  117.         # 国家
  118.         movie_data.append(
  119.             path.xpath(
  120.                 '//span[contains(text(),"制片国家")]/following-sibling::br[1]/preceding-sibling::text()[1]'
  121.             )[0].replace(" / ", ",")
  122.         )
  123.         # 摘要
  124.         movie_data.append(path.xpath('//span[@property="v:summary"]/text()')[0].strip())
  125.         # 类型
  126.         movie_data.append(
  127.             ",".join(path.xpath('//div[@id="info"]/span[@property="v:genre"]/text()'))
  128.         )
  129.         # 语言
  130.         movie_data.append(
  131.             path.xpath(
  132.                 '//span[contains(text(),"语言")]/following-sibling::br[1]/preceding-sibling::text()[1]'
  133.             )[0]
  134.         )
  135.         # 上映日期
  136.         movie_data.append(
  137.             re.sub(
  138.                 RELEASE_DATE_REMOVE_RE,
  139.                 "",
  140.                 path.xpath('//span[@property="v:initialReleaseDate"]/text()')[0][:10],
  141.             )
  142.         )
  143.         # 时长(空处理)
  144.         # print(movie["id"])
  145.         movie_time = path.xpath('//span[@property="v:runtime"]/text()')
  146.         if len(movie_time) > 0:
  147.             movie_data.append(movie_time[0])
  148.         else:
  149.             movie_data.append("")
  150.         # url
  151.         movie_data.append(self.movie_detail_url.format(movie["id"]))
  152.         self.save_to_csv(movie_data)
  153.     def save_to_csv(self, row):
  154.         with open(CSV_NAME, "a", newline="", encoding="utf-8") as f:
  155.             writer = csv.writer(f)
  156.             writer.writerow(row)
  157.     def clean_csv(self):
  158.         print("===========清理数据============")
  159.         df = pd.read_csv(CSV_NAME, encoding="utf-8")
  160.         df.drop_duplicates(subset=["movie_id"], keep="first", inplace=True)
  161.         print("存储到数据库...")
  162.         df.to_sql("tb_movie", con=engine, index=False, if_exists="append")
  163.         print("清理重复数据...")
  164.         engine.connect().execute(
  165.             text(
  166.                 "delete t1 from tb_movie t1 inner join (select min(id) as id,movie_id from tb_movie group by movie_id having count(*) > 1) t2 on t1.movie_id=t2.movie_id where t1.id>t2.id"
  167.             )
  168.         )
  169.     def update_global_progress(self):
  170.         self.completed_pages += 1
  171.         # print(self.completed_pages)
  172.         self.global_progress_bar.update(1)
  173.         self.global_progress_bar.refresh()
  174.     async def run(self):
  175.         self.init()
  176.         self.load_page_progress()
  177.         # self.total_pages = MAX_PAGES*len(MOVIE_TYPES) - sum(self.page_progress.get(type_name, 1) for type_name in MOVIE_TYPES)
  178.         for type_name in MOVIE_TYPES:
  179.             if MAX_PAGES > self.page_progress.get(type_name, 1):
  180.                 self.total_pages += MAX_PAGES + 1 - self.page_progress.get(type_name, 1)
  181.         print(self.total_pages)
  182.         if self.total_pages > 0:
  183.             self.global_progress_bar = tqdm(
  184.                 total=self.total_pages, desc="progress", unit="page", colour="GREEN"
  185.             )
  186.             async with aiohttp.ClientSession() as session:
  187.                 tasks = [
  188.                     self.get_movie_pages(session, type_name)
  189.                     for type_name in self.movie_types
  190.                 ]
  191.                 await asyncio.gather(*tasks)
  192.             # 请求结束后,清空页面进度
  193.             # self.page_progress = {}
  194.             # self.save_page_progress()
  195.             self.global_progress_bar.close()
  196.             self.clean_csv()
  197. if __name__ == "__main__":
  198.     loop = asyncio.get_event_loop()
  199.     spider = Spider()
  200.     loop.run_until_complete(spider.run())
复制代码
电影可视化

接口代码

  1. from flask import Flask, render_template, request, redirect, url_for, session
  2. from utils import db_query
  3. app = Flask(__name__)
  4. app.secret_key = "mysessionkey"
  5. # 统一请求拦截
  6. @app.before_request
  7. def before_request():
  8.     # 利用正则匹配,如果/static开头和/login, /logout,/register的请求,则不拦截;其他的判断是否已登录
  9.     if (
  10.         request.path.startswith("/static")
  11.         or request.path == "/login"
  12.         or request.path == "/logout"
  13.         or request.path == "/register"
  14.     ):
  15.         return
  16.     # 如果没有登录,则跳转到登录页面
  17.     if not session.get("login_username"):
  18.         return redirect(url_for("login"))
  19. # 首页
  20. @app.route("/")
  21. def index():
  22.     # 获取电影统计数据
  23.     movie_stats = db_query.fetch_movie_statistics()
  24.     # 获取电影分类统计
  25.     movie_type_distribution = db_query.fetch_movie_type_distribution()
  26.     # 获取电影评分统计
  27.     movie_rating_distribution = db_query.fetch_movie_rating_distribution()
  28.     print(movie_rating_distribution)
  29.     return render_template(
  30.         "index.html",
  31.         login_username=session.get("login_username"),
  32.         movie_stats=movie_stats,
  33.         movie_type_distribution=movie_type_distribution,
  34.         movie_rating_distribution=movie_rating_distribution,
  35.     )
  36. # 登录
  37. @app.route("/login", methods=["GET", "POST"])
  38. def login():
  39.     if request.method == "POST":
  40.         req_params = dict(request.form)
  41.         # 判断用户名密码是否正确
  42.         sql = "SELECT * FROM `tb_user` WHERE `username` = %s AND `password` = %s"
  43.         params = (req_params["username"], req_params["password"])
  44.         if len(db_query.query(sql, params)) > 0:
  45.             # 存储session
  46.             session["login_username"] = req_params["username"]
  47.             return redirect(url_for("index"))
  48.         else:
  49.             return render_template(
  50.                 "error.html",
  51.                 error="用户名或密码错误",
  52.             )
  53.     elif request.method == "GET":
  54.         return render_template("login.html")
  55. # 退出
  56. @app.route("/logout")
  57. def logout():
  58.     session.pop("login_username", None)
  59.     return redirect(url_for("index"))
  60. # 注册
  61. @app.route("/register", methods=["GET", "POST"])
  62. def register():
  63.     if request.method == "POST":
  64.         req_params = dict(request.form)
  65.         if req_params["password"] == req_params["password_confirm"]:
  66.             # 判断是否已存在该用户名
  67.             sql = "SELECT * FROM `tb_user` WHERE `username` = %s"
  68.             params = (req_params["username"],)
  69.             result = db_query.query(sql, params)
  70.             if len(result) > 0:
  71.                 return render_template(
  72.                     "error.html",
  73.                     error="用户名已存在",
  74.                 )
  75.             sql = "INSERT INTO `tb_user` (`username`, `password`) VALUES (%s, %s)"
  76.             params = (
  77.                 req_params["username"],
  78.                 req_params["password"],
  79.             )
  80.             db_query.query(sql, params, db_query.QueryType.NO_SELECT)
  81.             return redirect(url_for("login"))
  82.         else:
  83.             return render_template(
  84.                 "error.html",
  85.                 error="两次密码输入不一致",
  86.             )
  87.     elif request.method == "GET":
  88.         return render_template("register.html")
  89. @app.route("/list")
  90. def movie_list():
  91.     # 查询数据库获取电影列表
  92.     movies = db_query.fetch_movie_list()  # 假设此函数返回一个包含电影信息的列表
  93.     # 渲染并返回list.html,同时传递movies数据
  94.     return render_template(
  95.         "list.html", login_username=session.get("login_username"), movies=movies
  96.     )
  97. @app.errorhandler(404)
  98. def page_not_found(error):
  99.     return render_template("404.html"), 404
  100. @app.errorhandler(500)
  101. def system_error(error):
  102.     return render_template("500.html"), 500
  103. if __name__ == "__main__":
  104.     # 静态文件缓存自动刷新
  105.     app.jinja_env.auto_reload = True
  106.     app.run(host="127.0.0.1", port=8002, debug=True)
复制代码
首页

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.   <head>
  4.     <meta charset="utf-8" />
  5.     <meta http-equiv="X-UA-Compatible"
  6.     content="IE=edge" />
  7.     <meta
  8.       name="viewport"
  9.       content="width=device-width, initial-scale=1, shrink-to-fit=no"
  10.     />
  11.     <meta name="description" content="" />
  12.     <meta name="author" content="" />
  13.     <title>首页</title>
  14.     <!-- Custom fonts for this template-->
  15.     <link
  16.       href="/static/vendor/fontawesome-free/css/all.min.css"
  17.       rel="stylesheet"
  18.       type="text/css"
  19.     />
  20.     <link
  21.       href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
  22.       rel="stylesheet"
  23.     />
  24.     <!-- Custom styles for this template-->
  25.     <link href="/static/css/sb-admin-2.min.css" rel="stylesheet" />
  26.   </head>
  27.   <body id="page-top">
  28.     <!-- Page Wrapper -->
  29.     <div id="wrapper">
  30.       <!-- Sidebar -->
  31.       <ul
  32.         class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion"
  33.         id="accordionSidebar"
  34.       >
  35.         <!-- Sidebar - Brand -->
  36.         <a
  37.           class="sidebar-brand d-flex align-items-center justify-content-center"
  38.           href="index.html"
  39.         >
  40.           <div class="sidebar-brand-icon rotate-n-15">
  41.             <i class="fas fa-laugh-wink"></i>
  42.           </div>
  43.           <div class="sidebar-brand-text mx-3">豆瓣电影可视化</div>
  44.         </a>
  45.         <!-- Divider -->
  46.         <hr class="sidebar-divider my-0" />
  47.         <!-- Nav Item - Dashboard -->
  48.         <li class="nav-item active">
  49.           <a class="nav-link" href="/">
  50.             <i class="fas fa-fw fa-tachometer-alt"></i>
  51.             <span>首页</span></a
  52.           >
  53.         </li>
  54.         <!-- 列表 -->
  55.         <li class="nav-item">
  56.           <a class="nav-link" href="/list">
  57.             <i class="fas fa-fw fa-table"></i>
  58.             <span>电影列表</span></a
  59.           >
  60.         </li>
  61.         <!-- Divider -->
  62.         <hr class="sidebar-divider d-none d-md-block" />
  63.         <!-- Sidebar Toggler (Sidebar) -->
  64.         <div class="text-center d-none d-md-inline">
  65.           <button class="rounded-circle border-0" id="sidebarToggle"></button>
  66.         </div>
  67.       </ul>
  68.       <!-- End of Sidebar -->
  69.       <!-- Content Wrapper -->
  70.       <div id="content-wrapper" class="d-flex flex-column">
  71.         <!-- Main Content -->
  72.         <div id="content">
  73.           <!-- Topbar -->
  74.           <nav
  75.             class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"
  76.           >
  77.             <!-- Sidebar Toggle (Topbar) -->
  78.             <button
  79.               id="sidebarToggleTop"
  80.               class="btn btn-link d-md-none rounded-circle mr-3"
  81.             >
  82.               <i class="fa fa-bars"></i>
  83.             </button>
  84.             <!-- Topbar Search -->
  85.             <!-- <form class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
  86.             <div class="input-group">
  87.               <input type="text" class="form-control bg-light border-0 small" placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2">
  88.               <div class="input-group-append">
  89.                 <button class="btn btn-primary" type="button">
  90.                   <i class="fas fa-search fa-sm"></i>
  91.                 </button>
  92.               </div>
  93.             </div>
  94.           </form> -->
  95.             <!-- Topbar Navbar -->
  96.             <ul class="navbar-nav ml-auto">
  97.               <div class="topbar-divider d-none d-sm-block"></div>
  98.               <!-- Nav Item - User Information -->
  99.               <li class="nav-item dropdown no-arrow">
  100.                 <a
  101.                   class="nav-link dropdown-toggle"
  102.                   href="#"
  103.                   id="userDropdown"
  104.                   role="button"
  105.                   data-toggle="dropdown"
  106.                   aria-haspopup="true"
  107.                   aria-expanded="false"
  108.                 >
  109.                   <span class="mr-2 d-none d-lg-inline text-gray-600 small"
  110.                     >{{login_username}}</span
  111.                   >
  112.                   <img
  113.                     class="img-profile rounded-circle"
  114.                     src="/static/img/avatar.png"
  115.                   />
  116.                 </a>
  117.                 <!-- Dropdown - User Information -->
  118.                 <div
  119.                   class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
  120.                   aria-labelledby="userDropdown"
  121.                 >
  122.                   <a
  123.                     class="dropdown-item"
  124.                     href="#"
  125.                     data-toggle="modal"
  126.                     data-target="#logoutModal"
  127.                   >
  128.                     <i
  129.                       class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"
  130.                     ></i>
  131.                     Logout
  132.                   </a>
  133.                 </div>
  134.               </li>
  135.             </ul>
  136.           </nav>
  137.           <!-- End of Topbar -->
  138.           <!-- Begin Page Content -->
  139.           <div class="container-fluid">
  140.             <!-- Page Heading -->
  141.             <!-- <div class="d-sm-flex align-items-center justify-content-between mb-4">
  142.             <h1 class="h3 mb-0 text-gray-800">Dashboard</h1>
  143.             <a href="#" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm"><i class="fas fa-download fa-sm text-white-50"></i> Generate Report</a>
  144.           </div> -->
  145.             <!-- Content Row -->
  146.             <div class="row">
  147.               <!-- Earnings (Monthly) Card Example -->
  148.               <div class="col-xl-3 col-md-6 mb-4">
  149.                 <div class="card border-left-primary shadow h-100 py-2">
  150.                   <div class="card-body">
  151.                     <div class="row no-gutters align-items-center">
  152.                       <div class="col mr-2">
  153.                         <div
  154.                           class="font-weight-bold text-primary text-uppercase mb-1"
  155.                         >
  156.                           电影总数
  157.                         </div>
  158.                         <div class="h5 mb-0 font-weight-bold text-gray-800">
  159.                           {{ movie_stats['total_movies'] }}
  160.                         </div>
  161.                       </div>
  162.                       <div class="col-auto">
  163.                         <i class="fas fa-calendar fa-2x text-gray-300"></i>
  164.                       </div>
  165.                     </div>
  166.                   </div>
  167.                 </div>
  168.               </div>
  169.               <!-- Earnings (Monthly) Card Example -->
  170.               <div class="col-xl-3 col-md-6 mb-4">
  171.                 <div class="card border-left-success shadow h-100 py-2">
  172.                   <div class="card-body">
  173.                     <div class="row no-gutters align-items-center">
  174.                       <div class="col mr-2">
  175.                         <div
  176.                           class="font-weight-bold text-success text-uppercase mb-1"
  177.                         >
  178.                           电影最高评分
  179.                         </div>
  180.                         <div class="h5 mb-0 font-weight-bold text-gray-800">
  181.                           {{ movie_stats['highest_rating'] }}
  182.                         </div>
  183.                       </div>
  184.                       <div class="col-auto">
  185.                         <i class="fas fa-dollar-sign fa-2x text-gray-300"></i>
  186.                       </div>
  187.                     </div>
  188.                   </div>
  189.                 </div>
  190.               </div>
  191.               <!-- Earnings (Monthly) Card Example -->
  192.               <div class="col-xl-3 col-md-6 mb-4">
  193.                 <div class="card border-left-info shadow h-100 py-2">
  194.                   <div class="card-body">
  195.                     <div class="row no-gutters align-items-center">
  196.                       <div class="col mr-2">
  197.                         <div
  198.                           class="font-weight-bold text-info text-uppercase mb-1"
  199.                         >
  200.                           出演最多演员
  201.                         </div>
  202.                         <div class="row no-gutters align-items-center">
  203.                           <div class="col-auto">
  204.                             <div
  205.                               class="h5 mb-0 mr-3 font-weight-bold text-gray-800"
  206.                             >
  207.                               {{ movie_stats['most_popular_cast'] }}
  208.                             </div>
  209.                           </div>
  210.                           <div class="col">
  211.                             <div class="progress progress-sm mr-2">
  212.                               <div
  213.                                 class="progress-bar bg-info"
  214.                                 role="progressbar"
  215.                                 style="width: 50%"
  216.                                 aria-valuenow="50"
  217.                                 aria-valuemin="0"
  218.                                 aria-valuemax="100"
  219.                               ></div>
  220.                             </div>
  221.                           </div>
  222.                         </div>
  223.                       </div>
  224.                       <div class="col-auto">
  225.                         <i
  226.                           class="fas fa-clipboard-list fa-2x text-gray-300"
  227.                         ></i>
  228.                       </div>
  229.                     </div>
  230.                   </div>
  231.                 </div>
  232.               </div>
  233.               <!-- Pending Requests Card Example -->
  234.               <div class="col-xl-3 col-md-6 mb-4">
  235.                 <div class="card border-left-warning shadow h-100 py-2">
  236.                   <div class="card-body">
  237.                     <div class="row no-gutters align-items-center">
  238.                       <div class="col mr-2">
  239.                         <div
  240.                           class="font-weight-bold text-warning text-uppercase mb-1"
  241.                         >
  242.                           制片最多国家
  243.                         </div>
  244.                         <div class="h5 mb-0 font-weight-bold text-gray-800">
  245.                           {{ movie_stats['most_common_country'] }}
  246.                         </div>
  247.                       </div>
  248.                       <div class="col-auto">
  249.                         <i class="fas fa-comments fa-2x text-gray-300"></i>
  250.                       </div>
  251.                     </div>
  252.                   </div>
  253.                 </div>
  254.               </div>
  255.             </div>
  256.             <!-- Content Row -->
  257.             <div class="row">
  258.               <!-- Area Chart -->
  259.               <div class="col-xl-6 col-lg-6">
  260.                 <div class="card shadow mb-4">
  261.                   <!-- Card Header - Dropdown -->
  262.                   <div
  263.                     class="card-header py-3 d-flex flex-row align-items-center justify-content-between"
  264.                   >
  265.                     <h6 class="m-0 font-weight-bold text-primary">
  266.                       电影分类统计
  267.                     </h6>
  268.                   </div>
  269.                   <!-- Card Body -->
  270.                   <div class="card-body">
  271.                     <div
  272.                       id="movie_type_chart"
  273.                       style="width: 100%; height: 450px"
  274.                     ></div>
  275.                     <!-- <div class="chart-area">
  276.                      
  277.                     </div> -->
  278.                   </div>
  279.                 </div>
  280.               </div>
  281.               <!-- Line Chart -->
  282.               <div class="col-xl-6 col-lg-6">
  283.                 <div class="card shadow mb-4">
  284.                   <!-- Card Header - Dropdown -->
  285.                   <div
  286.                     class="card-header py-3 d-flex flex-row align-items-center justify-content-between"
  287.                   >
  288.                     <h6 class="m-0 font-weight-bold text-primary">
  289.                       电影评分统计
  290.                     </h6>
  291.                     <div class="dropdown no-arrow">
  292.                       <a
  293.                         class="dropdown-toggle"
  294.                         href="#"
  295.                         role="button"
  296.                         id="dropdownMenuLink"
  297.                         data-toggle="dropdown"
  298.                         aria-haspopup="true"
  299.                         aria-expanded="false"
  300.                       >
  301.                         <i
  302.                           class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"
  303.                         ></i>
  304.                       </a>
  305.                       <div
  306.                         class="dropdown-menu dropdown-menu-right shadow animated--fade-in"
  307.                         aria-labelledby="dropdownMenuLink"
  308.                       >
  309.                         <div class="dropdown-header">Dropdown Header:</div>
  310.                         <a class="dropdown-item" href="#">Action</a>
  311.                         <a class="dropdown-item" href="#">Another action</a>
  312.                         <div class="dropdown-divider"></div>
  313.                         <a class="dropdown-item" href="#"
  314.                           >Something else here</a
  315.                         >
  316.                       </div>
  317.                     </div>
  318.                   </div>
  319.                   <!-- Card Body -->
  320.                   <div class="card-body">
  321.                     <div
  322.                     id="movie_score_chart"
  323.                     style="width: 100%; height: 450px"
  324.                   ></div>
  325.                   </div>
  326.                 </div>
  327.               </div>
  328.             </div>
  329.             <!-- Content Row -->
  330.            
  331.           </div>
  332.           <!-- /.container-fluid -->
  333.         </div>
  334.         <!-- End of Main Content -->
  335.         <!-- Footer -->
  336.         <footer class="sticky-footer bg-white">
  337.           <div class="container my-auto">
  338.             <div class="copyright text-center my-auto">
  339.               <span
  340.                 >@Laoxu Open Source.<a
  341.                   target="_blank"
  342.                   href="https://github.com/mudfish"
  343.                   >Github</a
  344.                 ></span
  345.               >
  346.             </div>
  347.           </div>
  348.         </footer>
  349.         <!-- End of Footer -->
  350.       </div>
  351.       <!-- End of Content Wrapper -->
  352.     </div>
  353.     <!-- End of Page Wrapper -->
  354.     <!-- Scroll to Top Button-->
  355.     <a class="scroll-to-top rounded" href="#page-top">
  356.       <i class="fas fa-angle-up"></i>
  357.     </a>
  358.     <!-- Logout Modal-->
  359.     <div
  360.       class="modal fade"
  361.       id="logoutModal"
  362.       tabindex="-1"
  363.       role="dialog"
  364.       aria-labelledby="exampleModalLabel"
  365.       aria-hidden="true"
  366.     >
  367.       <div class="modal-dialog" role="document">
  368.         <div class="modal-content">
  369.           <div class="modal-header">
  370.             <h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
  371.             <button
  372.               class="close"
  373.               type="button"
  374.               data-dismiss="modal"
  375.               aria-label="Close"
  376.             >
  377.               <span aria-hidden="true">×</span>
  378.             </button>
  379.           </div>
  380.           <!-- <div class="modal-body">Select "Logout" below if you are ready to end your current session.</div> -->
  381.           <div class="modal-footer">
  382.             <button
  383.               class="btn btn-secondary"
  384.               type="button"
  385.               data-dismiss="modal"
  386.             >
  387.               Cancel
  388.             </button>
  389.             <a class="btn btn-primary" href="/logout">Logout</a>
  390.           </div>
  391.         </div>
  392.       </div>
  393.     </div>
  394.     <!-- Bootstrap core JavaScript-->
  395.     <script src="/static/vendor/jquery/jquery.min.js"></script>
  396.     <script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
  397.     <!-- Core plugin JavaScript-->
  398.     <script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
  399.     <!-- Custom scripts for all pages-->
  400.     <script src="/static/js/sb-admin-2.min.js"></script>
  401.     <!-- Page level plugins -->
  402.     <script src="/static/vendor/chart.js/Chart.min.js"></script>
  403.     <!-- Page level custom scripts -->
  404.     <script src="/static/js/demo/chart-area-demo.js"></script>
  405.     <script src="/static/js/demo/chart-pie-demo.js"></script>
  406.     <script src="/static/js/echarts.min.js"></script>
  407.     <script>
  408.       var chartDom = document.getElementById("movie_type_chart");
  409.       var myChart = echarts.init(chartDom);
  410.       var option;
  411.       var movieTypeData = {{ movie_type_distribution|tojson }};
  412.       // console.log(movieTypeData)
  413.       option = {
  414.         title: {
  415.           text: "",
  416.           subtext: "来源:豆瓣数据",
  417.           left: "center",
  418.         },
  419.         tooltip: {
  420.           trigger: "item",
  421.         },
  422.         legend: {
  423.           orient: "vertical",
  424.           left: "left",
  425.         },
  426.         series: [
  427.           {
  428.             name: "Access From",
  429.             type: "pie",
  430.             radius: "50%",
  431.             data: movieTypeData,
  432.             emphasis: {
  433.               itemStyle: {
  434.                 shadowBlur: 10,
  435.                 shadowOffsetX: 0,
  436.                 shadowColor: "rgba(0, 0, 0, 0.5)",
  437.               },
  438.             },
  439.           },
  440.         ],
  441.       };
  442.       option && myChart.setOption(option);
  443.     </script>
  444.     <script>
  445.       var chartDom = document.getElementById("movie_score_chart");
  446.       var myChart = echarts.init(chartDom);
  447.       var option;
  448.       var ratingData = {{ movie_rating_distribution|tojson }};
  449.       console.log(ratingData)
  450.       option = {
  451.         title: {
  452.           text: "",
  453.           subtext: "来源:豆瓣数据",
  454.           left: "center",
  455.         },
  456.         xAxis: {
  457.           type: "category",
  458.           boundaryGap: false,
  459.           data: ratingData.map(item => item[0]),
  460.         },
  461.         yAxis: {
  462.           type: "value",
  463.         },
  464.         series: [
  465.           {
  466.             data: ratingData.map(item => item[1]),
  467.             type: "line",
  468.             areaStyle: {},
  469.           },
  470.         ],
  471.         tooltip: {
  472.               trigger: 'axis', //坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用
  473.               axisPointer: {// 坐标轴指示器,坐标轴触发有效
  474.                 type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
  475.               }
  476.             },
  477.       };
  478.       option && myChart.setOption(option);
  479.     </script>
  480.   </body>
  481. </html>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莱莱

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表