基于Python+Django+MySQL+HTML的豆瓣影视剧推荐系统
打开系统界面, 登录页面,用户打开浏览器并访问系统的登录页面,可以看到主要功能包括:
输入账户名称和密码进行登入
在影视剧推荐系统中,可以看到按照热度排序的电影名称包括影视剧的时间,名称等信息,右边有基于内容推荐几个影视剧
点击一个影视剧的图片可以进入详情页面,可以查看影视剧的上映日期,主演等信息,还有影视剧的简介信息。
对影视剧进行评论发表,可以看到评论的数据。
对影视剧进行打分,可以看到打分的数据。
查看电影的分类情况。
文件结构
本项目在python3.7下通过测试,具体可以查看requirements.txt(或者r.txt)中的环境要求,在这里出一个简单的项目使用教程,一般项目中的requirements.txt中包含了项目的python依赖环境,在安装好python的前提下只需要在cmd窗口中pip install -r requirements.txt有时候因为路径问题会提示requirements这个文件不存在,可以改为完整的路径,比如c:\requirements.txt,对于本项目只需要运行app.py,然后再浏览器打开地址就好啦。在pycharm的配置更为方便,可以不用每次都在终端输入命令使用。为了加快安装下载速度可以更换为国内源,使用命令为 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
有需要的小伙伴可以通过后台联系方式获取,如果加不上可以后台留言留下联系方式,不经常看后台,但是看到了会回复的~,源码获取只收取很少的钱钱,除非是标记了For Free的。
PConline 杂谈]最近,豆瓣App截图暗含水印的新闻,绷紧了很多人的神经。有网友发现,在豆瓣App中截图,当中竟然隐藏着肉眼难以识别的水印,水印的内容则是截图者的UID等信息!这意味着,在豆瓣App中截屏,根据水印很容易就可以追查到截屏者的某层身份。对此,豆瓣回应称,这是豆瓣小组的防搬运机制。
豆瓣App截图带有隐藏水印,默认难以察觉,某些手机开启夜间模式后可以清楚看到
豆瓣解释,豆瓣小组长开启了内容防搬运设置后,在对小组内容进行截图时,截图上将自动生成经加密的截图用户 ID、被截图帖子 ID、截图时间信息。而网友发现,这个信息对于截屏者来说是难以察觉的,水印文字的颜色和背景相似,只有开启夜间模式后才能比较显眼地观察到。
实际上,豆瓣App的这种做法,被业内称之为加“盲水印”。顾名思义,盲水印很难被觉察,但懂得其中窍门的话,就可以通过一些技术手段检测、还原水印。盲水印一般应用于一些对保密有需求的场合,例如企业内部。企业在内部论坛或者聊天工具加上员工信息身份的盲水印,如果员工截图发到外网,通过盲水印就很容易定位到泄密者,作出处理。
豆瓣显然不是一个内部使用的App,现在一个面向公众的应用,居然启用了追查身份作震慑手段的防泄密机制,这正是让很多网友感到震惊之处。
据了解,如果豆瓣小组启用了“防搬运”功能,会在主贴底部有明显提示的字眼。但即便如此,豆瓣盲水印带来的影响,也已挥之不去了——豆瓣能这么做,其他App要不要也跟着做?这样做是不是能带来一些什么好处?
今天,就来简单聊聊盲水印和互联网社区的话题吧。
我们先来简单了解一下盲水印的相关技术。
豆瓣使用的盲水印,其策略是融入背景色,虽然看似比较隐秘,但其实这还远算不上防不胜防。
某些图片盲水印,用肉眼根本无法察觉,需要使用特定的算法还原,才能观测到。同时,这类盲水印还非常难以去除,就算对打了盲水印的图片反转、裁剪、遮挡、涂抹等处理,水印依然可以被算法还原出来。
a是原始图像,b是加了盲水印的图像,肉眼看不出区别
这类盲水印,既做到了“盲”的隐秘——水印对其他人不可见,也做到了“印”的牢靠——想要追踪的话,无论图片经过了怎样的处理,保证绝大部分情况下水印不会被破坏。
这类盲水印是怎样实现的呢?原理并不复杂,通常是将图片进行离散余弦、小波或者傅里叶变换,得到图片的频谱信息,再将水印的编码信息叠加到图片的频谱上,然后再进行一次逆变换,生成的图片就带有几乎无法检测、但又可以确切还原出来的盲水印了。
一种基于离散傅里叶变换添加盲水印的方法
而这样的盲水印制作也非常简单,网络上就有开源算法可以实现,下面以“blind_watermark”为例。
blind_watermark:https://blindwatermark.github.io/blind_watermark/#/zh/
是一个关于盲水印的开源项目,依赖于Python运行。安装了blind_watermark后,只需要简单的几行命令,就可以为图片添加文字或者另一张图作为盲水印。
blind_watermark向图片添加文字盲水印的案例
添加了盲水印后的图,即便经过多种修改,依然可以还原出水印。而这一切,都是可以用开源免费的方案实现的。
加了水印的图即使经过各种修改,也依然可以提取出水印
除了图片可以添加水印,文字也是可以作标记的。下面举个“text_blind_watermark”的例子。
text_blind_watermark(demo):https://www.guofei.site/pictures_for_blog/app/text_watermark/v1.html
text_blind_watermark为文字加“盲水印”的原理也很简单,在文字当中穿插特殊的符号(demo演示的是空格),解码算法识别出特定符号,就可以解出隐藏的信息了。或许demo中的空格键肉眼看上去还比较明显,但如果所使用的是更加隐秘的字符呢?字符分布更加稀疏呢?恐怕就非常难以觉察了。
简而言之,无论是图片还是文字,都可以用已有的成熟方案轻易添加“盲水印”。只要某个App有这个心思,完全可以0成本使用强力的盲水印,而且还不会被察觉到,不至于像豆瓣一样引发舆情。
从这个角度来看,豆瓣的水印方案只能算是小儿科了。如果启用更强力的盲水印,用户甚至完全无法察觉,这才是真正的“防不胜防”。
正如前文所说,盲水印主要用于防泄密,企业内部页面、电影放映画面等都是盲水印的常见应用场合。由于盲水印不可见且带有泄密者的信息,因此它通常用于追查、震慑泄密者(所谓“抓内鬼”),而从某个层面来说,盲水印具备区分“敌我”的功能。
例如企业内部会议投影,加水印可以防泄密(图为必捷网络产品演示)
这就很微妙了。豆瓣作为一个面向公众的App,为何竟然有“抓内鬼”的需求,甚至说竟然有区分“敌我”的需求?
豆瓣官方的说法是,加入盲水印是为了“防搬运”,这似乎并不能完全平息舆论。如果是一些版权网站,例如小说网站、视频网站启用盲水印“防搬运”,是说得通的,但即使是版权网站,也往往只在水印中添加版权信息,或者禁止复制文字或下载内容,水印也通常不会掺杂用户个人信息,且往往会明确告知水印的存在。而豆瓣作为一个社区App,使用个人信息作为盲水印“防搬运”,似乎多少有点不妥。
文字版权网站保护内容不被抄袭的手段往往是禁止复制(例如起点),为何豆瓣要在截图加水印?
这次启用盲水印机制的是豆瓣的SNS社区豆瓣小组,而并非豆瓣全站。豆瓣小组分为不同主题的小组,各个小组需要申请加入才能发表讨论,而其管理员“小组长”则可以审核入组申请,也可以决定小组成员的去留。如果小组长启用“防搬运”,意味着可以通过截图的盲水印追查到截图者的身份,并对其进行处理。
换言之,如果小组长认为组员截图导致组内讨论内容产生了传播,是不恰当的,则可将该组员禁言。盲水印“防搬运”机制,会促使组员不再外泄小组内所讨论的内容。加之豆瓣小组长本来就有删帖等权限,如此一来,小组内的讨论内容会更容易变得更具符合小组长的管理意愿,组内的观点也会变得更趋同。
显然,如果豆瓣小组想要团结意见观点相似的人,盲水印会是一个非常立竿见影的机制。我们可以想象,如果QQ群微信群中也启用了盲水印机制,那么群员很有可能就不再敢轻易截图,将群聊记录留证或公诸于众,否则一旦被发现,就面临着被踢出群聊的风险。有了“抓内鬼”的能力,网络社群管理者的威权,也就得以加强。
豆瓣小组加入盲水印,有利建立起更加整齐划一的讨论基调、聚集起观点近似的人群,这或许非常符合豆瓣想要打造的社区氛围。但是盲水印毕竟包含个人信息,作为一个公众App,在这方面使用个人信息,并可能间接致使相关信息广泛传播,多少有侵犯隐私的嫌疑。或许正因如此,豆瓣小组已会明确提示已开启防搬运功能,用户不至于完全没有知情权。
但是,从技术的角度来看,App对截图加入盲水印且不被察觉,并不是一件困难的事。如果有更多网络社区想要打造价值观趋同的圈子,认同这种运营理念,神不知鬼不觉加入更加强力的盲水印,也是有可能的。
盲水印作为追查内部泄密者的手段,这次如此大规模用于公众社区,无可避免会引来议论纷纷。作为普通用户,或许大家并不希望个人信息用于这等用途,希望有关部门能够严加监管,进一步规范平台对个人信息的使用吧。
个写博客的朋友想让我帮忙获取一下豆瓣上的Top250的电影数据,说是做个什么电影推荐榜,没办法之后硬着头皮拿出我那一小点点的Python爬虫技术来完成人家的需求了。当然了也是在不违法的情况下进行的。
要爬取豆瓣电影排名信息,我们可以使用Python中的Request库来发送一个请求,然后使用一些HTML解析工具例如BeautifulSoup或者是通过Lxml库来对HTML页面进行解析,然后将解析到的结果打印出来。
import requests
from bs4 import BeautifulSoup
def crawl_douban_movies(url):
# 发送 HTTP GET 请求获取页面内容
response=requests.get(url)
if response.status_code==200:
# 使用 BeautifulSoup 解析页面内容
soup=BeautifulSoup(response.text, 'html.parser')
# 找到电影列表
movie_list=soup.find_all('div', class_='item')
for movie in movie_list:
# 获取电影名称和评分
title=movie.find('span', class_='title').text
rating=movie.find('span', class_='rating_num').text
print(f"电影:{title},评分:{rating}")
else:
print("请求失败")
if __name__=="__main__":
# 豆瓣电影 Top 250 页面 URL
url="https://movie.douban.com/top250"
crawl_douban_movies(url)
是不是有点简单了?在这个例子中我们通过requests.get()发送HTTP GET请求获取豆瓣电影Top250页面的HTML内容。然后,使用BeautifulSoup解析页面内容,提取出电影名称和评分,并打印出来。
运行程序之后,发现居然给我来了个请求失败?这是为什么呢?是网络请求不通?还是说豆瓣网站对相关的操作有所限制呢?
经过验证发现豆瓣网站可能设置了反爬虫机制,检测到了爬虫行为并阻止了请求。为了规避这种情况,我尝试设置请求头信息,来模拟正常的浏览器访问。
为了模拟正常浏览器的访问操作,所以添加了请求头信息,将代码升级成如下的样子。
import requests
from bs4 import BeautifulSoup
def crawl_douban_movies(url):
# 添加 User-Agent 请求头信息
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
}
# 发送带有请求头信息的 HTTP GET 请求
response=requests.get(url, headers=headers)
if response.status_code==200:
# 使用 BeautifulSoup 解析页面内容
soup=BeautifulSoup(response.text, 'html.parser')
# 找到电影列表
movie_list=soup.find_all('div', class_='item')
for movie in movie_list:
# 获取电影名称和评分
title=movie.find('span', class_='title').text
rating=movie.find('span', class_='rating_num').text
print(f"电影:{title},评分:{rating}")
else:
print("请求失败")
if __name__=="__main__":
# 豆瓣电影 Top 250 页面 URL
url="https://movie.douban.com/top250"
crawl_douban_movies(url)
与之前不同的是,我们添加了User-Agent请求头信息。这样这个请求就是模拟浏览器发送的。应该算是正常请求了。果然,运行代码之后,结果如下。
正当我以为这样就可以的时候,朋友居然说为什么没有导演的信息,为什么没有那个简单的评语的信息呀?我勒个去?还要这么麻烦么?这就不得不让我去分析一下页面了
打开网页开发这工具,简单的分析页面之后,有了这样的结果。如下所示。
导演信息,在一个div里面,并且class叫做bd,在这个div里面有个p标签,这个p标签中就是导演的信息。那么这样我们就可以通过如下的操作来获取了。
# 获取导演信息
directors=movie.find('div', class_='bd').find('p').text.split('\n')[1].strip().split('\xa0\xa0\xa0')
那么评语信息又在什么地方呢?
简单查找之后发现,评语在一个span标签中这就简单了,我们可以通过如下的方式来进行获取。
quote=movie.find('span', class_='inq').text if movie.find('span', class_='inq') else ''
整体代码修改变成了如下的样子。
import requests
from bs4 import BeautifulSoup
def crawl_douban_movies(url):
# 添加 User-Agent 请求头信息
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
}
# 发送带有请求头信息的 HTTP GET 请求
response=requests.get(url, headers=headers)
if response.status_code==200:
# 使用 BeautifulSoup 解析页面内容
soup=BeautifulSoup(response.text, 'html.parser')
# 找到电影列表
movie_list=soup.find_all('div', class_='item')
for movie in movie_list:
# 获取电影名称
title=movie.find('span', class_='title').text
# 获取导演信息
directors=movie.find('div', class_='bd').find('p').text.split('\n')[1].strip().split('\xa0\xa0\xa0')
director=directors[0].strip().split(':')[-1]
# 获取评语
quote=movie.find('span', class_='inq').text if movie.find('span', class_='inq') else ''
print(f"电影:{title},导演:{director},评语:{quote}")
else:
print("请求失败")
if __name__=="__main__":
# 豆瓣电影 Top 250 页面 URL
url="https://movie.douban.com/top250"
crawl_douban_movies(url)
运行上述代码之后,结果如下所示,我心想,这下应该就可以了吧,然后人家说评分没有了,我去,这东西还能难得我么?我就把评分的代码给复制粘贴到这段代码中。
将评分的获取代码复制粘贴完成之后,得到了如下的结果
我就说这是不是很完美了,他居然说还不行?他还要下面的时间信息、产地信息这些?我去这咋玩?
其实获取时间信息和获取产地信息的方式跟上面的操作是一样的。只需要通过查看代码,找到对应的HTML标识就可以提取到对应的信息。
在获取电影详细信息的时候遇到了格式处理的问题,如下所示。
# 获取电影详情信息
details=movie.find('div', class_='bd').find('p').text.split('\n')
info=[i.strip() for i in details if i.strip() !='']
#print("获取到信息",info) # 打印详情信息列表
# 提取时间、产地和剧情信息
if len(info) >=2:
year_region=info[1].split('\xa0/\xa0')
year=year_region[0].strip()
region=year_region[1].strip()
plot=info[1].strip()
else:
year="未知"
region="未知"
plot="未知"
要获取电影的详细页面 URL,需要从每个电影条目中提取链接信息。豆瓣电影条目的链接通常包含在a标签的href属性中如下图所示。
通过如下的操作来获取到对应属性中的数据
detail_url=movie.find('a')['href']
最终获取完成的电影信息如下图所示。
到这里,人家的需求才算提完,原来现在电影博主都这么卷的了么?想要这么多信息,居然不自己整理,让我这个小喽喽来帮他实现。真实有天赋呀?
在满足了他所有的要求之后,最终我们给出详细的代码
import requests
from bs4 import BeautifulSoup
def crawl_douban_movies(url):
# 添加 User-Agent 请求头信息
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
}
# 发送带有请求头信息的 HTTP GET 请求
response=requests.get(url, headers=headers)
if response.status_code==200:
# 使用 BeautifulSoup 解析页面内容
soup=BeautifulSoup(response.text, 'html.parser')
# 找到电影列表
movie_list=soup.find_all('div', class_='item')
for movie in movie_list:
# 获取电影名称
title=movie.find('span', class_='title').text
rating=movie.find('span', class_='rating_num').text
# 获取电影详细页面链接
detail_url=movie.find('a')['href']
# 获取导演信息
directors=movie.find('div', class_='bd').find('p').text.split('\n')[1].strip().split('\xa0\xa0\xa0')
director=directors[0].strip().split(':')[-1]
# 获取评语
quote=movie.find('span', class_='inq').text if movie.find('span', class_='inq') else ''
# 获取电影详情信息
details=movie.find('div', class_='bd').find('p').text.split('\n')
info=[i.strip() for i in details if i.strip() !='']
#print("获取到信息",info) # 打印详情信息列表
# 提取时间、产地和剧情信息
if len(info) >=2:
year_region=info[1].split('\xa0/\xa0')
year=year_region[0].strip()
region=year_region[1].strip()
plot=info[1].strip()
else:
year="未知"
region="未知"
plot="未知"
print(f"电影:{title}\n评分:{rating}\n导演:{director}\n评语:{quote}\n时间:{year}\n产地:{region}\n剧情:{plot}\n详细页面链接:{detail_url}\n")
else:
print("请求失败")
if __name__=="__main__":
# 豆瓣电影 Top 250 页面 URL
url="https://movie.douban.com/top250"
crawl_douban_movies(url)
通过上述代码,我们就可以获取到豆瓣电影TOP250的所有电影信息,当然这里需要手动的将页码信息进行添加,例如第二页的URL就会变成https://movie.douban.com/top250?start=25&filter=样子,在实际操作的时候我们可以自己进行调整。
*请认真填写需求信息,我们会在24小时内与您取得联系。