电影聚合网站的建设-前后端建站全过程回顾

Programming Massively Parallel Processors

Posted by BY 张建 on April 20, 2023

​ 疫情三年,经历很多了事情,年纪也来到了32岁,这三年熟悉的朋友大部分都离开了北京,自己的心境也发生了很多变化,由焦虑、抑郁逐渐转为目前的自洽,才发现这是一场与自己斗争的战争,幸运的事,这场战争目前我幸存下来了。由于在抗争的过程中,发现自己的时间原来可以去做好多事情,不是只躺在床上打游戏浪费时间,时间真是个好东西。

​ 言归正传,本篇呢,是今年开始逐渐给自己找事做,自己建了一个小站的项目总结。当然,现在本站只是一个小开始,会慢慢优化并丰富内容。大家可以先去目睹一下元气

元气电影网站的建设的初衷是因为想提供给大家更精确、且可以聚合的搜索结果,后来经过调研发现已经有不少的同类网站,但出于练手目的,还是想尝试下。下面我将重点介绍一下,整个的搭建过程。

​ 网站的建设主要包含数据准备、前后端实现以及网站部署过程。

数据准备工作

​ 作为电影聚合网站,数据部分自然是需要各影视种类的信息及相关影视的播放地址。第一部分电影信息的数据库建设通过爬取豆瓣网站完成。第二部分则是根据爬取的豆瓣电影信息调取聚合搜索,进行结果的过滤,只保留高质量影视网站信息来完成。

豆瓣爬取

​ 豆瓣电影信息的爬取根据公开可搜取的教程,基本都是爬取TOP250电影及豆瓣影视种类首页的教程,但是豆瓣影视种类为了防爬取,每个种类最多只有500部,这对于我们的聚合网站来说,数量太少,远远不够用。另外一种方式为豆瓣提供了查询API,网站可只展示部分影视信息,搜索时调用豆瓣API即可,这种方式最容易,且实施成本最小,但该方式需要同豆瓣公司以公司名义进行合作,所以该方法也不可行了。

​ 基于以上分析,只有想办法尽可能的爬取豆瓣的影视信息并抱以后缺啥补啥的原则,对我们来说是最可行的。豆瓣的影视链接最后都是以随机整数结尾,经过初步分析后,我们发现该ID在1200000到36000000之间分布,但是由于反爬虫措施,我们不可能进行全部遍历。通过尽可能的网上搜寻,我们获取了一份早期网友爬取的豆瓣ID列表信息,数量级大概在10万左右,通过之前我们遍历的部分ID,总的量级大概在11万左右,当然这个数量级还远远够不到所有发布的电影数量。下面将简要介绍,是如何对单个影视页进行爬取的。

豆瓣

​ 如图上所示,我们仅需要中间至右侧的所有信息,该部分我们可通过谷歌浏览器查看网页源码,并借助xpath路径,获得相关信息。由于我用的python,所以借助parsel,便可轻松实现该部分的信息爬取。

网页爬虫

​ 对于稍微有前端与爬虫知识的人来讲,爬豆瓣信息很简单,下面我简单说下是如何爬取该页面详情信息的。

​ 如上图所示:

​ 目标1: 我们想爬取电影名称及导演姓名,示例代码如下

import request
import parsel
url = "https://movie.douban.com/subject/26631790/"
html_prefix = '/html/body/div[3]/div[1]/div[2]'
response = requests.get(url)
selector = parsel.Selector(response.text)
movie_name = selector.xpath('//div[@id="content"]/h1/span/text()').get()
daoyan_name = selector.xpath(html_prefix + '/div[1]/div[1]/div[1]/div[1]/div[2]/span[1]/span[2]/a[contains(@rel, "v:directedBy")]/text()').get()
print( "电影名称: {} 导演名称: {}".format(movie_name, daoyan_name))

通过以上代码,我们可以轻松爬取该信息详情页的电影名称和导演姓名,同理主演、评分等其他详细信息也是用同样的方法。这里有同学会问,我怎么知道xpath括号里面的每个信息的xpath路径呢,这里只能告诉你需要根据前端网页源码自己尝试,比较快捷的路径为谷歌浏览器右键检查网页源码,右键有复制xpath路径,但是此路径可能不适用于每个网页详情,所以需要统计处理。在此,涉及网络安全原因,其他信息的爬取源码不公布,不鼓励爬虫豆瓣。

反爬虫措施

​ 各大网站都有自己的反爬虫措施,当然豆瓣还算是爬虫友好型(PS: 不用登陆),根据在网上查到的反反爬虫方法,大概有以下几个措施:

  • User-Agent: 尝试更换不同的浏览器User-Agent,以使后端服务器认为是从不同浏览器发起的请求
  • Proxy-IP: 似乎哪里都少不了代理,那就穷举所有免费的IP地址吧

​ 没有反反爬虫,那么在我们进行网页爬虫时,可能连续爬取十几个网页后就会被豆瓣屏蔽了。通过上述两个主要措施,我们可以尽量减少被屏蔽的次数,甚至做到根本不会被屏蔽。

User-Agent:

user_agents = [
    # Firefox 77 Mac
     {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.5",
        "Referer": "https://www.google.com/",
        "DNT": "1",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1"
    },
    # Firefox 77 Windows
    {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate, br",
        "Referer": "https://www.google.com/",
        "DNT": "1",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1"
    },
    # Chrome 83 Mac
    {
        "Connection": "keep-alive",
        "DNT": "1",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Dest": "document",
        "Referer": "https://www.google.com/",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8"
    },
    # Chrome 83 Windows 
    {
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-User": "?1",
        "Sec-Fetch-Dest": "document",
        "Referer": "https://www.google.com/",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "en-US,en;q=0.9"
    }

]

Proxy-IP: 这里就不贡献出来了,我们谷歌搜索可以发现一大堆免费网站,同样获取这些列表,还是靠爬虫,我呢,是从这里爬虫的快代理。同样,获取IP也是靠反反爬虫措施。

有了这些准备后,那么在上边get请求时,我们就可以如下

headers = random.choice(user_agents)
proxy = random.randint(0, len(http_address) - 1)
proxies = {"http": http_address["address"][proxy]}# 使用选择的代理构建代理处理器对象
response = requests.get(jpg_url, proxies=proxies, headers = headers)

​ 嗯,爬虫的介绍告一断落,至于源码,我就不放出来了,相信聪明的你在我这么详细的介绍后,肯定能快速的爬到你想要的内容的。

聚合影视

​ 免费电影观看,我们自然是想到搜索引擎,这里我用的是开源的聚合搜索,通过本地搭建请求的方式,谷歌搜searxng,有详细的docker搭建部署方式,值得一题的是聚合搜索也是请求各大搜索引擎,所以如果请求次数过多,也是会失败的。

​ 这里通过对部分影视爬取的网站信息,进行网页爬取并统计分析,对部分网站实现了含广告的标签识别。通过过滤,聚合结果基本是高质且可信的。

数据建表

主要用到了两个表,一个是电影的详情表,另外一个是资源列表。详细见表语句如下

TABLES['movies'] = (
    "CREATE TABLE `movies` ("
    "  `movie_id` int(10) NOT NULL AUTO_INCREMENT,"
    "  `movie_name` varchar(500) NOT NULL,"
    "  `director` varchar(1000),"
    "  `actors` varchar(2000),"
    "  `poster` varchar(1000),"
    "  `type` varchar(50) NOT NULL,"
    "  `duration` varchar(500),"
    "  `douban_url` varchar(200),"
    "  `douban_id` int(20),"
    "  `releaseDate` varchar(2000),"
    "  `releaseRegion` varchar(100),"
    "  `introduce` varchar(2000),"
    "  `commentsPerson` int(20),"
    "  `rating` double(16,2),"
    "  `onestar` double(16,2),"
    "  `twostar` double(16,2),"
    "  `threestar` double(16,2),"
    "  `fourstar` double(16,2),"
    "  `fivestar` double(16,2),"
    "  `playDate` date,"
    "  `hot` int(20),"
    "  `create_time` timestamp NOT NULL DEFAULT NOW(),"
    "  `update_time` timestamp DEFAULT NOW() ON UPDATE NOW(),"
    "  PRIMARY KEY (`movie_id`, `movie_name`),"
    "  UNIQUE(`douban_id`)"
    ") ENGINE=InnoDB")

TABLES['movie_resources'] = (
    "CREATE TABLE `resource` ("
    "  `resource_id` int(10) NOT NULL AUTO_INCREMENT,"
    "  `name` varchar(100) NOT NULL,"
    "  `movie_id` int(10) NOT NULL,"
    "  `href_url` varchar(1000) NOT NULL,"
    "  `tag`  varchar(100) NOT NULL,"
    "  `click`  int(100) NOT NULL,"
    "  PRIMARY KEY (`resource_id`),"
    "  CONSTRAINT `resource_ibfk_1` UNIQUE (`name`, `movie_id`),"
    "  CONSTRAINT `resource_ibfk_2` FOREIGN KEY (`movie_id`) "
    "     REFERENCES `movies` (`movie_id`) ON DELETE CASCADE"
    ") ENGINE=InnoDB")

有了以上数据准备,在我们爬取信息的过程中,便可以将结构化信息存储到数据表中。

搜索引擎

​ 网站少不了搜索,但是对于一个简单的个人站,像ES这种重量级选手又不合适,那该怎么办呢?幸亏我发现了ZincSearch,这是国内团队在维护的轻量级类ES搜索,通过对电影名称构建索引,我们可以实现基本的搜索功能。后期我们出一个详细的教程,来介绍下这里踩的坑。

前端

​ 前端部分是找取的公共模板,然后自己修改得到。

后端

​ 后端采取python + flask