本项目仅供学习使用, 请勿用来进行商业用途

本期知识点:

  1. 使用JS制作弹幕的方法
  2. 使用分组定位来实现弹幕不重叠
  3. 使用flask构建网站
  4. 爬虫: 百度新闻, B站榜单, 知乎热榜

前言

你是否在 刷B站 或 刷知乎 时觉得不够畅? 是否想在一个平台上同时获取多个平台的有趣内容?

这个网站将为你打开一扇快速通道

先来看效果

  1. 弹幕可分类显示, 也可以全部显示(可自己添加更多网站, 接口的使用方法见下文)
  2. 弹幕列表展示当前网站上显示的所有弹幕
  3. 点击弹幕可以查看详情, 包括作者/热度 和预览图(可扩展)
  4. 前后端分离, 后端无论使用什么语言和框架, 只要有数据传输到接口即可实现.

制作网站的缘由是我在刷新闻时的突发奇想, 纯属个人爱好, 项目源码: https://github.com/zhanghao19/LetMeSee

网站的核心框架选择的是Flask, 优势是便捷, 且官方文档中有详细的入门教程: 快速上手flask

文章的描述顺序也是笔者搭建的流程

1>前端

弹幕新闻的重点在于展示, 其原理简单来说就像"往杯子里倒水"一样

1.1>网站框架

这里网站的框架指的是弹幕所显示在的地方, 我使用的是之前在学习Django的时候搭建的一个框架

以原网站作为基础框架, 使用jinja的继承功能来使我们的主要内容融入基础框架

你可以使用任意一款你喜欢的网站模板, 来作为放置弹幕的容器, 参考网站: Bootstrap

下载好你的模板, 参考以下代码中的block位置, 对自己的模板进行静态抽取:

<!-- Web/App/templates/base.html -->
<!DOCTYPE html>
<html lang="zh-cn">
<head><meta charset="utf-8"><title id="title">{% block title %}{% endblock %}</title><link rel="stylesheet" href="../static/css/reset.css"><link rel="stylesheet" href="../static/css/base.css"><!-- 上面的是模板自带的静态文件, 下面是为项目需要准备的 -->{% block link %}{% endblock %}
</head>
<body>
<!-- header start -->
<header id="header"><div class="mw1200 header-contain clearfix"><!-- logo start --><h1 class="logo"><a href="javascript:void(0);" class="logo-title">Python</a></h1><!-- logo end --><!-- nav start --><nav class="nav"><ul class="menu"><!-- 这里是导航条上的一些选项-->{% block nav %}{% endblock %}</ul></nav><!-- nav end --></div>
</header>
<!-- header end -->
<!-- mian start -->
<main id="main">
<!-- 弹幕的容器 -->
{% block main %}{% endblock %}
</main>
<!-- main end -->
<!-- footer start -->
<footer id="footer"...>
<!-- footer end -->
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
{% block script %}{% endblock %}
</body>
</html>

1.2>网站内容

这里的内容是弹幕的主体, 可以放在大部分的网站模板上使用

下面的代码包含, 弹幕的容器, 弹幕列表, 弹幕详情页

<!-- Web/App/templates/barrage.html -->
{% extends 'base.html' %}
{% block title %}LetMeSee-弹幕新闻网{% endblock %}
{% block link %}<link rel="stylesheet" href="../static/css/barrage.css"><!-- 解决图片加载失败的问题 --><meta name="referrer" content="no-referrer" />
{% endblock %}{% block nav %}
<li><a href="/">全部</a></li>
<li><a href="/baidu/">新闻</a></li>
<li><a href="/bilibili/">B站</a></li>
<li><a href="/zhihu/">知乎</a></li>
{% endblock %}
{% block main %}<div class="box"><div class="barrage-container-wrap clearfix"><div class="barrage-container"><!-- 弹幕主体 --></div><div class="expand"><img src="../static/img/list.png" alt="expand" title="弹幕列表"></div></div></div><!-- 弹幕列表 start --><div class="barrage-list"><div class="list-header">弹幕列表<img src="../static/img/close.png" alt="close" class="close-btn" title="关闭"></div><ul>{% for barrage in barrages %}<!-- for循环展示弹幕 --><li class="barrage-list-item" data-id="{{ barrage.BID }}"><!-- truncate_text过滤器,过长字符串末尾显示为... -->{{ barrage.BText | truncate_text }}</li>{% endfor %}</ul></div><!-- 弹幕列表 end --><!-- 弹幕详情 start --><div class="barrage-detail-panel"><div class="list-header">弹幕详情<img src="../static/img/close.png" alt="close" class="close-btn" title="关闭"></div><h3 class="title"></h3><p class="author"></p><img src="../static/img/loading.gif" alt="弹幕封面" class="cover"><a class="source"><--查看源网页--></a></div><!-- 弹幕列表 弹幕详情 -->
{% endblock %}
{% block script %}<script type="text/javascript">//js和html文件是分开的,传递数据需要先定义好参数,再执行js。参考:https://blog.csdn.net/m0_38061194/article/details/78891125var Server = {barrages:{{ barrages|safe }}};</script><script src="../static/js/barrage.js"></script><script src="../static/js/barrage_list.js"></script><script src="../static/js/barrage_details.js"></script>
{% endblock %}

自定义的过滤器truncate_text如下, 作用是过长字符串末尾显示为…

# Web/App/my_filters/truncate_text.py
def truncate_text(text):if len(text) > 19:new_text = text[0:17] + "..."return new_textelse:return text

整理一下上面代码在页面中实现的框架, 如图(同色表示同级):

  1. barrage-container-wrap是弹幕容器的底层画布, barrage-container是盛放弹幕的容器

  2. barrege-listbarrage-detail是触发点击事件才显示的.

1.3>JS部分

1.3.1>弹幕主体

网上有很多中弹幕的设计方式, 个人认为区别点在于弹幕的不重叠, 本次使用的方式是通过分组定位来实现弹幕不重叠.

//Web/App/static/js/barrage.js
//弹幕的实现
(function () {/*******定义参数********/let barrageColorArray = {baidu : '#5519EB', bilibili: '#ff53e0', zhihu: '#0099cc'};let barrageBoxWrap = document.querySelector('.barrage-container-wrap');let barrageBox = document.querySelector('.barrage-container');//容器的宽高度let contentWidth = ~~window.getComputedStyle(barrageBoxWrap).width.replace('px', '');let boxHeight = ~~window.getComputedStyle(barrageBox).height.replace('px', '');//当前窗口可以垂直展示多少个弹幕, 30代表弹幕字体大小let howManyBarrageY = Math.round(boxHeight / 30);//定义一个包含弹幕的宽和高度范围的数组let heightArray = [];//将每个可用的高度,放入数组, 以便在创建数组时使用for (let i = 30; i < boxHeight - 10; i += 30) {heightArray.push(i)}/*******创建弹幕**********/function createBarrage(item, index, forTime) {if (index >= howManyBarrageY) {//如果索引达到高度数组的长度,则需重置索引到0,因此取余数index = index % howManyBarrageY;}let divNode = document.createElement('div');    //弹幕的标签let divChildNode = document.createElement('div');  //提示文本的标签divNode.innerHTML = item.BText;    //将弹幕内容插入标签中, innerHTML表示这个标签中的字符内容divNode.classList.add('barrage-item');  //追加classbarrageBox.appendChild(divNode);    //弹幕的标签作为弹幕容器的子代标签divChildNode.innerHTML = '点击查看详情';  //鼠标悬停展示的内容divChildNode.classList.add('barrage-link');divNode.appendChild(divChildNode);  //提示文本的标签作为弹幕标签的子代标签//***设置弹幕的初始位置***//以容器的宽度为基准随机生成每条弹幕的左侧偏移值let barrageOffsetLeft = getRandom(contentWidth * forTime, contentWidth * (forTime + 0.618));//以容器的高度为基准随机生成每条弹幕的上方偏移值let barrageOffsetTop = heightArray[index];//通过弹幕类型选择颜色let barrageColor = barrageColorArray[item.BType];//执行初始化滚动//fun.call()传入的第一个参数作为之后的this,详解:https://codeplayer.vip/p/j7sj5initBarrage.call(divNode, {left: barrageOffsetLeft,top: barrageOffsetTop,color: barrageColor,barrageId: item.BID,});}/*******初始化弹幕移动(速度,延迟)*********/function initBarrage(obj) {//初始化位置颜色this.style.left = obj.left + 'px';this.style.top = obj.top + 'px';this.style.color = obj.color;//添加属性this.distance = 0;  //移动速度基准值this.width = ~~window.getComputedStyle(this).width.replace('px', '');   //弹幕的长度this.offsetLeft = obj.left;this.timer = null;this.timeOut = null;//弹幕子节点,即提示信息,span标签let barrageChileNode = this.children[0];barrageChileNode.style.left = (this.width - barrageTipWidth) / 2 + 'px';//定义span标签的位置//运动barrageAnimate(this);//鼠标悬停停止this.onmouseenter = function () {cancelAnimationFrame(this.timer);//弹幕停止移动function showDetailPopups() {//显示提示****此处用于展示详情窗口barrageChileNode.style.display = 'block';}//设置延迟显示this.timeOut = setTimeout(showDetailPopups, 1000);};//鼠标移走this.onmouseleave = function () {//鼠标移走,隐藏提示barrageChileNode.style.display = 'none';barrageAnimate(this);//弹幕继续移动clearTimeout(this.timeOut)};//打开弹幕对应的目标页面this.onclick = function () {let url = "/detail/",data = {barrage_id:obj.barrageId};$.ajax({type : "get",async : false,  //同步请求url : url,data : data,dataType: "json",success:function(barrage){showDetailPanel(barrage)// console.log(barrage)},error: function() {alert("失败,请稍后再试!");}});};}/*******辅助弹幕移动*********///弹幕动画function barrageAnimate(obj) {move(obj);if (Math.abs(obj.distance) < obj.width + obj.offsetLeft) {//满足以上条件说明弹幕在可见范围内obj.timer = requestAnimationFrame(function () {//在页面重绘之前会调用这个回调函数-->让弹幕继续移动barrageAnimate(obj);});} else {//超出可见范围,取消回调函数的调用-->让弹幕停止移动cancelAnimationFrame(obj.timer);//删除节点obj.parentNode.removeChild(obj);}}//回流:增删元素会引起回流,重绘:改变样式会引起重绘//弹幕移动function move(obj) {obj.distance -= 2; //移动速度为一次1像素//transform可以对元素进行翻转、移动、倾斜等操作,这里主要使用了移动元素的效果obj.style.transform = 'translateX(' + obj.distance + 'px)';}//随机获取区间内的一个值function getRandom(start, end) {return start + (Math.random() * (end - start)); //Math.random()随机获取一个0~1之间的值}/*******初始化事件**********/    //整个事件的入口//获取弹幕数据集let barrageArray = Server.barrages;//循环弹幕数组所需的切片次数, 弹幕总数/垂直可以显示的弹幕数=弹幕播放组数let howManyGroupBarrages = Math.ceil(barrageArray.length / howManyBarrageY);for (let i = 0; i < howManyGroupBarrages; i++) {//对弹幕数组切片,取出一部分要显示的弹幕,一直循环到取完let eachBarrageArray = barrageArray.slice(howManyBarrageY * i, howManyBarrageY * (i + 1));for (let item of eachBarrageArray) {//遍历每个弹幕, 并传入弹幕元素的索引,和循环次数(用作定位)createBarrage(item, eachBarrageArray.indexOf(item), i + 1);}}
})();

上面的代码主要完成的了弹幕的生成, 简单来讲就是:生成->分组->定位, 下面这张图能更清楚的表达逻辑:

  1. 初始化弹幕: 从后端获取弹幕数据. 计算屏幕的高度可以显示多少弹幕, 并对其进行切片分组. 然后传入创建弹幕事件.
  2. 创建弹幕: 在一个指定区域内, 通过随机值的方式设置弹幕的初始位置. 将设置好的弹幕元素传入初始化弹幕移动事件.
  3. 初始化弹幕移动: 左侧偏移值递减, 从而使弹幕移动, 然后将元素带入移动动画方法使移动轨迹更丝滑. 同时给弹幕元素设置一些事件(滑入, 滑出, 点击)
  4. 到这里第一组弹幕就开始移动了, 之所以弹幕会顺序播放且不会重叠, 根本原因就是他们的初始位置有足够的距离.

PS: 弹幕不重叠还可以使用时间延迟的方式来实现, 有兴趣的同学可以参考文章:不碰撞弹幕的研究与实现

1.3.2>弹幕列表

//Web/App/static/js/barrage_list.js
let barrageList = document.querySelector('.barrage-list'),barrageDetailPanel = document.querySelector('.barrage-detail-panel');
//弹幕列表的实现
(function () {let expandBtn = document.querySelector('.expand');expandBtn.onclick = function () {//点击展开再次点击关闭if (barrageList.style.display === "none") {barrageList.style.display = "block";}else {barrageList.style.display = "none";}//关闭详情页显示列表页barrageDetailPanel.style.display = 'none'};let barrageItems = document.getElementsByClassName('barrage-list-item');    //li的集合for (let item of barrageItems){let barrageId = item.getAttribute('data-id');//点击单项打开详情页item.onclick = function () {let url = "/detail/",data = {barrage_id:barrageId};//ajax请求, 携带参数barrage_id$.ajax({type : "get",async : false,  //同步请求url : url,data : data,dataType: "json",success:function(barrage){showDetailPanel(barrage)},error: function() {alert("失败,请稍后再试!");}});};}
})();

1.3.3>弹幕详情

//Web/App/static/js/barrage_details.js
//展示弹幕详情页
function showDetailPanel(obj) {let barrageTitle = document.querySelector('.title'),barrageAuthor = document.querySelector('.author'),barrageCover = document.querySelector('.cover'),barrageURL = document.querySelector('.source');//关闭列表页显示详情页barrageDetailPanel.style.display = 'block';barrageList.style.display = "none";//设置详情页的参数barrageTitle.innerHTML = obj.BText;barrageAuthor.innerHTML = '--' + obj.BAuthor;barrageCover.setAttribute('src', obj.BCover);barrageURL.onclick = function () {window.open(obj.BUrl);};
}//close button event
let closeBtns = document.querySelectorAll('.close-btn');
for (let closeBtn of closeBtns){closeBtn.onclick = function () {barrageDetailPanel.style.display = "none";barrageList.style.display = "none";};
}

1.4>其它静态文件

CSS

https://github.com/zhanghao19/LetMeSee/tree/master/Web/App/static/css

Image

https://github.com/zhanghao19/LetMeSee/tree/master/Web/App/static/css

2>后端

2.1>用flask构建网站

# Web/App/views/first_blue.py
import random
from pymongo import MongoClient
from flask import Blueprint, render_template, request, jsonify# Blueprint(蓝图),提供快速注册端口,方便快捷.
# https://dormousehole.readthedocs.io/en/latest/blueprints.html#blueprints
first_blue = Blueprint('index', __name__)   # 创建一个蓝图对象coll = MongoClient(host="localhost", port=27017).Spider.LetMeSee# 从数据库中获取数据
baidu_barrages = [i for i in coll.find({'BType': 'baidu'},{'_id': 0, 'BID': 1, 'BText': 1, 'BUrl': 1, 'BType': 1})]bilibili_barrages = [i for i in coll.find({'BType': 'bilibili'},{'_id': 0, 'BID': 1, 'BText': 1, 'BUrl': 1, 'BType': 1})]zhihu_barrages = [i for i in coll.find({'BType': 'zhihu'},{'_id': 0, 'BID': 1, 'BText': 1, 'BUrl': 1, 'BType': 1})]@first_blue.route('/')
def index():# 拼接两个弹幕列表barrages = baidu_barrages + bilibili_barrages + zhihu_barragesrandom.shuffle(barrages)    # 打乱列表的顺序# 渲染模板, 传递数据return render_template('barrage.html', barrages=barrages)@first_blue.route('/baidu/')
def baidu():return render_template('barrage.html', barrages=baidu_barrages)@first_blue.route('/bilibili/')
def bilibili():return render_template('barrage.html', barrages=bilibili_barrages)@first_blue.route('/zhihu/')
def zhihu():return render_template('barrage.html', barrages=zhihu_barrages)@first_blue.route('/detail/')
def barrage_details():# 获取ajax请求携带的data中的barrage_idbarrage_id = request.args.get('barrage_id')# 通过barrage_id取匹配数据库里的项barrage = coll.find_one({'BID': barrage_id},{'_id': 0, 'WriteTime': 0})print(barrage, barrage_id, type(barrage_id))# 以json的形式返回响应return jsonify(barrage)
# Web/App/views/__init__.py
from .first_blue import first_blue
from Web.App.my_filters.truncate_text import truncate_textdef init_view(app):# 在应用对象上注册这个蓝图对象app.register_blueprint(first_blue)# 指定jinja引擎env = app.jinja_env# 加载自定义过滤器env.filters["truncate_text"] = truncate_text
# Web/App/__init__.py
from flask import Flaskfrom Web.App.views import init_viewdef create_app():# 创建一个应用对象app = Flask(__name__)# 调用该方法,以初始化路由init_view(app)return app
# manage.py
from flask_script import Managerfrom Web.App import create_appapp = create_app()
manager = Manager(app=app)if __name__ == '__main__':manager.run() # 使flask能够像django一样使用命令启动, "python manage.py runserver -r -d"

参考文档: 快速上手flask / Blueprint / jsonify

参考视频: 黑马程序员-6节课入门Flask框架web开发视频

ps: 我也是看这个视频学的flask, 老师讲解的很棒!

2.2>爬虫

2.2.1>百度新闻

# Spider/spider_mode/baidu_spider.py
import requests
from datetime import datetime
from lxml import etreefrom pymongo import MongoClientcoll = MongoClient(host="localhost", port=27017).Spider.LetMeSeeresp = requests.get('https://news.baidu.com/') # 请求页面
html = etree.HTML(resp.text)   # 创建xpath对象
barrage = []
item = {}title_ls = html.xpath('//*[@id="pane-news"]//a//text()')   # 提取标题
url_ls = html.xpath('//*[@id="pane-news"]//a/@href')    # 提取链接for n in range(len(title_ls)):item['BID'] = f'{n + 86000}'  # iditem['BText'] = title_ls[n]item['BUrl'] = url_ls[n]item['BType'] = 'baidu'item['BCover'] = r'D:\Fire\PycharmProject\LetMeSee\Web\App\static\img\loading.gif'  # 封面item['BAuthor'] = '未知作者'  # 作者item['WriteTime'] = datetime.utcnow()  # 写入时间, 用于设置过期时间coll.insert_one(dict(item))
print('百度新闻--爬取完成!')

2.2.2>B站榜单

# Spider/spider_mode/bilibili_spider.py
from datetime import datetime
import json
import requests
import refrom pymongo import MongoClientcoll = MongoClient(host="localhost", port=27017).Spider.LetMeSeeresp = requests.get('https://www.bilibili.com/ranking') # 请求页面
# 使用正则获取源码中存放在script标签中的数据
data_url = re.findall('window.__INITIAL_STATE__=(.*);\(function', resp.text)[0]
data_loaded = json.loads(data_url)  # 使用loads方法从 字符串 变成 字典
rankList = data_loaded['rankList']  # 排行榜中100个视频的信息item ={}
for i in range(len(rankList)):item['BID'] = f'{i + 81000}'     # iditem['BText'] = rankList[i]['title']    # 标题item['BAuthor'] = rankList[i]['author']  # 作者item['BUrl'] = 'https://www.bilibili.com/video/' + rankList[i]['bvid']   # 拼接的视频av号item['BType'] = 'bilibili'item['BCover'] = rankList[i]['pic']    # 封面item['WriteTime'] = datetime.utcnow()   # 写入时间, 用于设置过期时间coll.insert_one(dict(item))
print('B站榜单--爬取完成!')

2.2.3>知乎热榜

# Spider/spider_mode/zhihu_spider.py
import json
from datetime import datetimeimport requests
from lxml import etree
from pymongo import MongoClient# 用户登录后的cookies,直接F12->Network复制Request Headers的cookie即可, 这里只是自己建了一个放cookies的文件
from util.zhihu_cookies import Cookiescoll = MongoClient(host="localhost", port=27017).Spider.LetMeSeeheaders = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','cache-control': 'max-age=0','cookie': Cookies,  # 也可以直接将cookies直接copy到这里'upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
resp = requests.get('https://www.zhihu.com/hot', headers=headers)   # 请求页面html = etree.HTML(resp.text) # 创建xpath对象data = html.xpath('//*[@id="js-initialData"]/text()')[0]  # 提取数据集
data_loaded = json.loads(data) # 使用loads方法从 字符串 变成 字典
hotList = data_loaded["initialState"]["topstory"]["hotList"] # 提取目标数据'hotList'item ={}
for i in range(len(hotList)):item['BID'] = f'{i + 83000}'     # iditem['BText'] = hotList[i]["target"]["titleArea"]["text"]    # 标题item['BAuthor'] = hotList[i]["target"]["metricsArea"]["text"]    # 标题item['BUrl'] = hotList[i]["target"]["link"]["url"]   # 拼接的视频av号item['BType'] = 'zhihu'item['BCover'] = hotList[i]["target"]["imageArea"]["url"]    # 封面item['WriteTime'] = datetime.utcnow()   # 写入时间, 用于设置过期时间coll.insert_one(dict(item))
print('知乎热榜--爬取完成!')

2.3>运行爬虫

爬虫文件都可以直接运行, 为了节省不必要的时间, 所以将它们整理到一个文件中运行, 如下:

# Spider/runSpider.py
from pymongo import MongoClient
import os# 创建数据库对象
coll = MongoClient(host="localhost", port=27017).Spider.LetMeSee
coll.drop() # 清空LetMeSee, 目的是使数据保持最新
# 设置延迟删除字段, 单位为秒
coll.create_index([('WriteTime', 1)], expireAfterSeconds=43200)os.system(r"python D:\Fire\PycharmProject\LetMeSee\Spider\spider_mode\bilibili_spider.py")
os.system(r"python D:\Fire\PycharmProject\LetMeSee\Spider\spider_mode\baidu_spider.py")
os.system(r"python D:\Fire\PycharmProject\LetMeSee\Spider\spider_mode\zhihu_spider.py")

3>总结

好了以上就是本次分享的全部内容了, 目前项目的规模不算大, 但有很大的扩展性, 后续如果有更多点子会再更新这个项目.

搭建一个弹幕新闻网站相关推荐

  1. 快手如何搭建一个好的数据指标体系?

    导读:本篇文章源自钱英男老师在『快手大数据|数据内容建设交流会』上的演讲,相关视频回放可用快手APP搜索"快手大数据"观看. 一个好的数据指标体系可以助力业务快速的解构业务.理解业 ...

  2. tomcat网站根目录在哪里_不会代码怎么自己搭建一个小说网站

    如果你不会代码,没有什么计算机基础知识,但是自己又特别喜欢看小说,可不可以自己搭建一个小说网站呢?答案是可以的,本文就教大家如何零基础搭建一个小说网站. 自己搭建小说网站准备 想要搭建一个小说网站,我 ...

  3. 如何搭建一个自己的网站-优化篇

    咪哥杂谈 本篇阅读时间约为 3 分钟. 1 前言 这个主题一共写了一个多月,终于在今天迎来了终篇. 本主题的最后一个章节,网页优化篇,对历史文章感兴趣的朋友,可以点击文章上面的专辑部分查看历史内容. ...

  4. 如何快速搭建一个完整的移动直播系统?

    **如何快速搭建一个完整的移动直播系统?** 原文连接:http://toutiao.com/a6301796359717945601/ http://toutiao.com/a62784095597 ...

  5. [Web]如何利用Boostrap框架搭建一个还可以的静态网站(六_子页)

    文章目录 返回总结 整体效果 组件 vedio题目 vedio博主介绍 vedio vedio用户留言 电子烟评测块 辩论块 代码 html ElectronicCigarettes.html 返回总 ...

  6. 如何搭建一个垂直兴趣社区

    2019独角兽企业重金招聘Python工程师标准>>> 90后.00后这些新时代的年轻人,自小就生活中互联网浸润的环境中,并且见证了移动互联网的爆发,他们对互联网的熟悉程度如图使用家 ...

  7. 超强教程:如何搭建一个 iOS 系统的视频直播 App?

    现今,直播市场热火朝天,不少人喜欢在手机端安装各类直播 App,便于随时随地观看直播或者自己当主播.作为开发者来说,搭建一个稳定性强.延迟率低.可用性强的直播平台,需要考虑到部署视频源.搭建聊天室.优 ...

  8. vue+node.js手把手教你搭建一个直播平台(一)

    上一期,帅气的小羽给老铁们简单介绍了项目的功能以及需要用到的一些环境和工具,现在就让我们荡起双桨,撸起袖子,准备开始敲代码啦!!! 先甩锅,小羽主要是搞前端开发的,所以这期张主要讲后端内容,可能讲的不 ...

  9. 从零开始用 Flask 搭建一个网站(二)

    从零开始用 Flask 搭建一个网站(一) 介绍了如何搭建 Python 环境,以及 Flask 应用基本项目结构.我们要搭建的网站是管理第三方集成的控制台,类似于 Slack. 本篇主要讲解数据如何 ...

最新文章

  1. ACCP学习旅程之-----基础篇
  2. C++入门经典-例4.7-变量的作用域
  3. 软件测试工作常用linux命令,软件测试工程师工作中常用的Linux命令
  4. java数组转list(Arrays .asList)
  5. php如何读出xml的节点内容 两个例子
  6. redis集成spring_将Redis集成到您的Spring项目中
  7. 第19次csp认证 202006-2 稀疏向量(C++)
  8. java 自带导出excel_4.java项目页面导出excel功能
  9. PHP函数-判断字符是否在于指定的字符串中
  10. 2个程序员街头卖唱,挽救了自己的秃头
  11. 罗格斯大学电子与计算机系排名,罗格斯大学美国大学排名及专业排名汇总(USNEWS美国大学排名版)...
  12. Hadoop之MapReduce分布式计算
  13. linux ssh 域,Linux SSH 与 SCP命令简述
  14. 配置一台用于深度学习的个人工作站系统的实验记录[主机可以买现成的或自己配,裸机即可]
  15. 卸载ruby on rails
  16. 解决Linux出现“cannot create temp file for here-document: No space left on device”的问题
  17. Qt 中Socket编程实例
  18. Sql Developer 调试函数和过程 Sql window Command window Program window Test window 区别
  19. [系统] Linux Iptable (Log) 技巧和实例
  20. u盘插上显示计算机限制,U盘插到电脑上不显示图标的解决方法(以金士顿8GU盘为例)...

热门文章

  1. oracle utl file putf,UTL_FILE学习
  2. SLAM大牛Cyrill 开源SuMa ++:基于语义激光雷达过滤动态物体提高定位精度
  3. dedecms自定义表单 发送邮件
  4. 国内CMS网站内容管理系统介绍
  5. html5 机械动画制作软件,KoolShow(HTML5动画制作助手)V2.4.3 正式版
  6. 【unity shader】基于UGUI字体的outline优化
  7. java开源b2b2c商城系统_java开源b2b2c商城系统有好用的吗?
  8. OCM实战之RAC集群打补丁
  9. Unity3D插件 AnyPortrait 2D骨骼动画制作
  10. 联通 位置服务器,中国联通服务器名称或地址