腾讯视频cKey9.1的生成分析和实现

说明:

这文章并不是对算法的分析,而仅仅是为了实现cKey9.1(因为有Nodejs了,所以就不需要担心JS脚本的运行)。

而其中的算法就是取自于腾讯视频的9.1加密版本的cKey,所以本文章仅仅是对腾讯视频cKey生成算法的提取。

项目实现

  • https://github.com/ZSAIm/iqiyi-parser
  • https://github.com/ZSAIm/iqiyi-parser/blob/master/js/tencent.js

前提

建议

在阅读本文章之前先阅读我的上一篇文章 爱奇艺视频H5解析分析过程 ,因为这一文章将基于默认已知上一篇的一些使用操作方法的前提下进行。
这文章只对cKey生成的实现和分析,其他所需提交的参数不会在这里分析说明。

预备知识

以下所需要的知识不需要精通,只需要了解和知道基本的语法即可。

  • JavaScript
  • WebAssembly

正文

关于腾讯视频cKey

  • 腾讯视频的cKey是解析视频直接地址的核心关键,只有通过该算法的加密才能够请求到要解析的数据。
  • 目前我所知的腾讯算法有8.1和9.1两个版本,8.1版本是为了兼容不支持WebAssembly的那一部分用户。所以实际上这两者在PC网页端里面所做的工作基本一致。因为8.1版本的cKey基本上和爱奇艺那一部分算法的分析过程差不多所以我就不在这里分析了(腾讯视频cKey的那一部分解析可能要繁琐一些)。
  • 所以这文章将对9.1版本进行分析实现。

关于工具

  • 这一文章将不使用Fiddler来抓包,而是全程使用chromium核的开发者工具进行分析。

  • 一说到chromium,可能大家第一反应就是使用谷歌浏览器。但是很遗憾,在这里不使用谷歌浏览器,因为谷歌浏览器所用到的cKey是8.1版本的。(我这里的浏览器版本是74.0.3729.131, 以后的版本可能会使用9.1)

  • 除了谷歌浏览器外还有很多使用chromium核的浏览器,360浏览器(未知),搜狗浏览器(ckey8.1),,我这里将使用百分浏览器(ckey9.1),并且在腾讯视频解析里面他使用到的是cKey9.1版本。这正是我们所要分析的对象。

  • 下面将以https://v.qq.com/x/cover/bzfkv5se8qaqel2/j002024w2wg.html 为分析例子。

初次分析

  • F12打开开发者工具。

  • 打开链接:https://v.qq.com/x/cover/bzfkv5se8qaqel2/j002024w2wg.html

  • 切换Network查看抓包。

  • 搜索proxyhttp,找到两项 https://vd.l.qq.com/proxyhttp 。(这里搜proyhttp是因为前面省略了从视频到解析链接的寻找过程,如果不知道怎么做,就先看上文的前提建议)

  • 既然知道了请求视频的链接是proxyhttp,那么在proxyhttp发送前中断如何?

    • 转到Sources页面,在XHR/fetch Breakpoints+进行添加条件断点 proxyhttp,意思就是在包含proxyhttp字串的请求链接时进行中断。
    • [图1.1]
    • 按F5刷新,等待中断发生。
    • [图1.2]
    • 之后看到右边的调用栈信息Call Stack,可以看到调用函数的右边表明了被调用函数所在的JS链接。
    • [图1.3]
    • 为什么要看这些呢,因为对于一个具有庞大的JS脚本链接的视频网站来说,找准加密所在的JS算法所在的链接是第一步。首先要知道的是,在POSThttps://vd.l.qq.com/proxyhttp之前肯定先需要先收集所要发送的data,所以必然这将调用到获取data的函数,而获取部分必然会与加密部分有联系,所以可以通过这样的方式来找到加密部分。
    • (事实上你可以直接在Network页面搜索proxyhttp来定位到目标链接(注意这不是一定的),但是由于在爱奇艺分析过程中使用了这一方法,我在这里用一下别的方法来解决。)
    • 由[图1.3]可以知道的是tvx.core.js是用来对发送请求的。所以大概可以估计这文件就是对请求函数的集合,既然已经到了发送的地步了,那么data肯定是已经获取完成了。
    • 第二个JS文件pecker.js,点击他,然后往下滚看到Scope项,看到e,f两项就是要发送的请求的所有数据,展开发现data中cKey已经存在,所以这里Call Stack往上走(往上一层调用走)。
    • [图1.4][图1.5][图1.6]
    • e.requestPostCgi位于htmlframe.......(关于Call Stack看图1.3),粗看函数名似乎就是提交data的获取。将其作为重点深找一下。
    • 进入e.requestPostCgi后往下滚看到Scope,下图,本地变量c就是要提交的data,图1.7的中间红框部分就是本地变量c的获取,发现vinfoparam是由62455行生成的数据。f.param(b.vinfoparam),发现该函数传入了参数b.vinfoparam,鼠标停在该参数出现了数据cKey。所以可以断定重点在于b.vinfoparam,而不是函数f.param
    • [图1.7]
    • 发现b.vinfoparam中的变量b是调用e.requestPostCgi时传入的参数(位于62446
    • 既然这样,看【图1.3】Call Stack,往上一层调用栈走,进入调用栈c
    • [图1.8]
    • 传入的是
    {vinfoparam: g,adparam: e,domain: v,method: w
    }
    
    • 我们关注的对象是vinfoparam: g,往前找g的生成代码。看【图1.8】的62742进入函数f.getInfoConfig。却没有发现cKey的踪迹,既然我们无法直接知道,不如放个断点走一走。
    • [图1.9][图1.10][图1.11]
    • 看上图1.11,我们进入了getInfoConfig的调试中。
    • [图1.12][图1.13]
    • 一直往下走【看图1.12、图1.13】都发现cKey还没获取,一直到了e(h)。【图1.14】【图1.15】
    • [图1.14][图1.15]
    • a.cKey = b || ""这就是cKey生成的地方。就是变量b,也就是
    f ? (a.encryptVer = "9.1",b = f(a.platform, a.appVer, a.vids || a.vid, "", a.guid, a.tm)) : (a.encryptVer = "8.1",b = i(a.vids || a.vid, a.tm, a.appVer, a.guid, a.platform)),a.cKey = b || ""
    
    • 从这里可以看到8.1版本和9.1版本的控制是由f()参数控制的。但这不是我们的重点,既然我们分析的是9.1版本,那么进入函数f()

重点分析

function i(a, b, c, d, e) {// 注意下面的函数k(a, b)是函数f(a)里面的k函数,为了方便起见直接在合起来写了function k(a, b) {if (0 === b || !a)return "";for (var c, d = 0, e = 0; ; ) {if (g(a + e < db),c = Ga[a + e >> 0],d |= c,0 == c && !b)break;if (e++,b && e == b)break}b || (b = e);var f = "";if (d < 128) {for (var h, i = 1024; b > 0; )h = String.fromCharCode.apply(String, Ga.subarray(a, a + Math.min(b, i))),f = f ? f + h : h,a += i,b -= i;return f}return m(a)}function f(a) {return "string" === b ? k(a) : "boolean" === b ? Boolean(a) : a}var i = h(a), j = [], l = 0;if (g("array" !== b, 'Return type should not be "array".'),d)for (var m = 0; m < d.length; m++) {var n = $a[c[m]];n ? (0 === l && (l = Ub()),j[m] = n(d[m])) : j[m] = d[m]}var o = i.apply(null, j);return o = f(o),0 !== l && Tb(l),o}
  • 由上面找到的【图1.15】开始。
  • 断点继续往下走,进入【图2.1】【图2.2】
    • [图2.1][图2.2][图2.3]
    • 返回的是变量o,那么我们重点关注他,走到o64084行,进去,【图2.3】看到ua._getckey,可以知道看来是找对地方了。
ua._getckey = function() {return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),ua.asm._getckey.apply(null, arguments)
}
  • 进去ua.asm._getckey.apply(null, arguments),???wocao这是什么鬼【图2.4】
  • [图2.4][图2.5]
  • 这函数名怎么是个数字???而且发现也进不去,而且提示的是native code,这说明了这不是JS的原生代码,可能是其他语言实现的方法。
  • 事实上这是WebAssembly,这是一种JS的一种可以理解成是交叉编程的一种方式,目的是为了提高JS运行效率,这是由C或者其他编程语言生成的代码,生成*.wasm然后交给WebAssembly加载处理运行。
  • 可以通过【图2.5】看到加载的wasm文件,而其中的函数名29就是对应wasm-0005098e-29,你点进去查看就看反汇编到具体的指令。
  • 好了,基本说明了这一种JS的技术,如果要了解更多就百度谷歌把。
    • 那么重要的是要找到这被加载的wasm文件。
    • 一个最简单的方法就是直接在Sources页面搜索wasm就能找到加载的wasm文件。
    • [图2.6]
    • 对于找wasm也可以使用其他方法实现,但是既然是请求GET到的,当然能抓包到了,所以这里就偷懒不通过代码分析了。(不然篇幅会很长)
  • 要知道的是,我们虽然得到了wasm文件,但是任何交叉编程类的东西,都需要有接口,而这些接口或者必须提供的,所以我们还需要找到wasm接口部分,但这里先放一边,待会再进行。
    • 通过【图2.4】可以看到的是传了参数arguments,虽然我们得到了wasm,但是我们还是需要知道参数arguments才能实现算法。
    • arguments就是前面【图2.3】传递的参数j,我们要得到j
    • 看【图2.2】进入函数Ub()n(),而n()是由var n = $a[c[m]];提供的。所以我们F5刷新下页面在【图2.2】重新断点。为的就是单步执行,找所需。
    • [图2.7]
    • 由【图2.7】出单步走,你会发现有两种n,一种是undefined
stringToC: function(a) {var b = 0;if (null !== a && void 0 !== a && 0 !== a) {var c = (a.length << 2) + 1;b = Sb(c),o(a, b, c)}return b
}
  • 往下一直找能找到Sb()o()n(),其中包括了循环中的Ub还有f()函数中的k()然后你能整理出来
Ub = function() {return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),ua.asm.stackSave.apply(null, arguments)
}Sb = function() {return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),ua.asm.stackAlloc.apply(null, arguments)}function o(a, b, c) {return g("number" == typeof c, "stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),n(a, Ga, b, c)
}function n(a, b, c, d) {if (!(d > 0))return 0;for (var e = c, f = c + d - 1, g = 0; g < a.length; ++g) {var h = a.charCodeAt(g);if (h >= 55296 && h <= 57343) {var i = a.charCodeAt(++g);h = 65536 + ((1023 & h) << 10) | 1023 & i}if (h <= 127) {if (c >= f)break;b[c++] = h} else if (h <= 2047) {if (c + 1 >= f)break;b[c++] = 192 | h >> 6,b[c++] = 128 | 63 & h} else if (h <= 65535) {if (c + 2 >= f)break;b[c++] = 224 | h >> 12,b[c++] = 128 | h >> 6 & 63,b[c++] = 128 | 63 & h} else if (h <= 2097151) {if (c + 3 >= f)break;b[c++] = 240 | h >> 18,b[c++] = 128 | h >> 12 & 63,b[c++] = 128 | h >> 6 & 63,b[c++] = 128 | 63 & h} else if (h <= 67108863) {if (c + 4 >= f)break;b[c++] = 248 | h >> 24,b[c++] = 128 | h >> 18 & 63,b[c++] = 128 | h >> 12 & 63,b[c++] = 128 | h >> 6 & 63,b[c++] = 128 | 63 & h} else {if (c + 5 >= f)break;b[c++] = 252 | h >> 30,b[c++] = 128 | h >> 24 & 63,b[c++] = 128 | h >> 18 & 63,b[c++] = 128 | h >> 12 & 63,b[c++] = 128 | h >> 6 & 63,b[c++] = 128 | 63 & h}}return b[c] = 0,c - e
}
  • 大家应该发现了上面的函数o(a, b, c)调用了方法n(a, Ga, b, c),其中a, b,c 我们都知道,但是Ga是什么东西?
  • 既然在Locan变量无法找到,那么网上一级找。看下图2.8
  • [图2.8][图2.9]
  • 发现上一级有Ga,所以,我们找到他了,看【图2.9】
  • 既然知道了要找Ga的缘由,那么把所有对于给Ga赋值的东西联系起来。
  • 这将是个漫长的过程。
// 只要知道ArrayBuffer的都知道这将导致下面的 Fa,Ha, Ja, Ga, Ia, Ka, La, Ma绑在了Ea下
// 也就是说Ea是数据,而Fa,Ha, Ja, Ga, Ia, Ka, La, Ma就是描述这个数据的方式,所有的改变只是对Ea操作。所以对其中一个改变都会改变我们的目标Ga
function w() {Fa = new Int8Array(Ea),Ha = new Int16Array(Ea),Ja = new Int32Array(Ea),Ga = new Uint8Array(Ea),Ia = new Uint16Array(Ea),Ka = new Uint32Array(Ea),La = new Float32Array(Ea),Ma = new Float64Array(Ea);
}function d(a) {var b = Oa;return Oa = Oa + a + 15 & -16,b
}
function e(a, b) {b || (b = Da);var c = a = Math.ceil(a / b) * b;return c
}var Da = 16;var Ea, Fa, Ga, Ha, Ia, Ja, Ka, La, Ma, Na, Oa, Pa, Qa, Ra, Sa, Ta, Ua, Va = {"f64-rem": function(a, b) {return a % b},"debugger": function() {}
}, Wa = (new Array(0), 1024) ;Na = Oa = Qa = Ra = Sa = Ta = Ua = 0,Pa = !1;
var cb = 5242880 , db = 16777216, ab = 65536;var wasmMemory = new WebAssembly.Memory({initial: db / ab,maximum: db / ab
});
Ea = wasmMemory.buffer;w();
Ja[0] = 1668509029;
Ha[1] = 25459;var eb = [], fb = [], gb = [], hb = [], ib = !1, jb = !1;Na = Wa,Oa = Na + 6928,fb.push();Oa += 16;Ua = d(4),
Qa = Ra = e(Oa),
Sa = Qa + cb,
Ta = e(Sa),
Ja[Ua >> 2] = Ta,
Pa = !0;
  • 以上解决了Ga的初始化。
  • 目前为止解决了循环这一部分了。
for (var m = 0; m < d.length; m++) {var n = $a[c[m]];n ? (0 === l && (l = Ub()),j[m] = n(d[m])) : j[m] = d[m]
}
  • 那么下一部分就是
var o = i.apply(null, j);return o = f(o),0 !== l && Tb(l),o
  • 前面我们已经说了i.apply(null, j);,他的代码位于wasm中。
  • 所以目前我们需要的是正确加载wasm,只要完成这一步,所有函数都可以串起来实现cKey了。
  • 我们先看下如下代码
var ub = ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)var Cb = ub._getckey;
ub._getckey = function() {return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),Cb.apply(null, arguments)
}
  • 也就是说,我们先知道ub也就是ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 调试进去,找到以下代码。
ua.asm = function(a, b, c) {if (!b.table) {var d = ua.wasmTableSize;void 0 === d && (d = 1024);var f = ua.wasmMaxTableSize;"object" == typeof WebAssembly && "function" == typeof WebAssembly.Table ? void 0 !== f ? b.table = new WebAssembly.Table({initial: d,maximum: f,element: "anyfunc"}) : b.table = new WebAssembly.Table({initial: d,element: "anyfunc"}) : b.table = new Array(d),ua.wasmTable = b.table}b.memoryBase || (b.memoryBase = ua.STATIC_BASE),b.tableBase || (b.tableBase = 0);var h;return h = e(a, b, c),g(h, "no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html#binaryen-methods"),h
}
  • 这里面就是对wasm的加载了。而这一切加载的前提是知道参数a,b,c,所以再回到ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 也就是ua.asmGlobalArg, ua.asmLibraryArg, Ea,而其中Ea我们已经在前面说过了,跟Ga有关系。
  • 很容易找到
ua.wasmTableSize = 99,ua.wasmMaxTableSize = 99,ua.asmGlobalArg = {},
ua.asmLibraryArg = {abort: sa,assert: g,enlargeMemory: B,getTotalMemory: C,abortOnCannotGrowMemory: A,abortStackOverflow: z,nullFunc_ii: ca,nullFunc_iiii: da,nullFunc_v: ea,nullFunc_vi: fa,nullFunc_viiii: ga,nullFunc_viiiii: ha,nullFunc_viiiiii: ia,invoke_ii: ja,invoke_iiii: ka,invoke_v: la,invoke_vi: ma,invoke_viiii: na,invoke_viiiii: oa,invoke_viiiiii: pa,__ZSt18uncaught_exceptionv: Q,___cxa_find_matching_catch: S,___gxx_personality_v0: T,___lock: U,___resumeException: R,___setErrNo: ba,___syscall140: V,___syscall146: X,___syscall54: Y,___syscall6: Z,___unlock: $,_abort: _,_emscripten_memcpy_big: aa,_get_unicode_str: P,flush_NO_FILESYSTEM: W,DYNAMICTOP_PTR: Ua,tempDoublePtr: rb,STACKTOP: Ra,STACK_MAX: Sa
};var ub = ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 可以看到wasm的加载连接了很多接口,但是我在这里只说其中比较重要的方法P,也就是_get_unicode_str: P,中的P,对应如下
function P() {function a(a) {return a ? a.length > 48 ? a.substr(0, 48) : a : ""}function b() {var b = document.URL, c = window.navigator.userAgent.toLowerCase(), d = "";document.referrer.length > 0 && (d = document.referrer);try {0 == d.length && opener.location.href.length > 0 && (d = opener.location.href)} catch (e) {}var f = window.navigator.appCodeName, g = window.navigator.appName, h = window.navigator.platform;return b = a(b),d = a(d),c = a(c),b + "|" + c + "|" + d + "|" + f + "|" + g + "|" + h}var c = b(), d = p(c) + 1, e = Pb(d);return o(c, e, d + 1),e
}
  • 为什么这个重要呢?当你把刚开始重点分析后面的那一个函数单步走一遍你就会发现了,当在执行_getckey()的时候,他会call 20,也就是wasm文件中的编号20的函数,但是你仔细看【图2.5】会发现缺少缺少了20号函数,这是因为他会上面在链接接口的时候链接了函数P(),而函数P()就是20号函数。
  • 而除此之外其他的函数对我们来说用处不大,所以你大可以使用空函数来链接。
  • 所以我如下处理了接口链接和wasm环境的配置
var fun_ = function(){};wasm_env = {abort: fun_,assert: fun_,enlargeMemory: fun_,getTotalMemory: C,abortOnCannotGrowMemory: fun_,abortStackOverflow: fun_,nullFunc_ii: fun_,nullFunc_iiii: fun_,nullFunc_v: fun_,nullFunc_vi: fun_,nullFunc_viiii: fun_,nullFunc_viiiii: fun_,nullFunc_viiiiii: fun_,invoke_ii: fun_,invoke_iiii: fun_,invoke_v: fun_,invoke_vi: fun_,invoke_viiii: fun_,invoke_viiiii: fun_,invoke_viiiiii: fun_,__ZSt18uncaught_exceptionv: fun_,___cxa_find_matching_catch: fun_,___gxx_personality_v0: fun_,___lock: fun_,___resumeException: fun_,___setErrNo: fun_,___syscall140: fun_,___syscall146: fun_,___syscall54: fun_,___syscall6: fun_,___unlock: fun_,_abort: fun_,_emscripten_memcpy_big: fun_,_get_unicode_str: P,              // function 20( ) => P( )flush_NO_FILESYSTEM: fun_,DYNAMICTOP_PTR: 7968,               //UatempDoublePtr: 7952,                //rbSTACKTOP: 7984,                     //RaSTACK_MAX: 5250864,                 //SamemoryBase: 1024,tableBase: 0,memory: wasmMemory,table: new WebAssembly.Table({initial: 99,maximum: 99,element: "anyfunc"})
};var importObject = {'env': wasm_env,'asm2wasm': {"f64-rem": function(a, b) {return a % b},"debugger": function() {}},'global': {NaN: NaN,Infinity: 1 / 0},"global.Math": Math,// "parent": {};};
  • 到目前为止,已经完成了对接口的链接,也就是可以进行加载wasm了,再然后就可以对cKey进行测试了。
  • 注意前面花了很大篇幅来再次回顾了对变量或函数的定位方法,所以后面很大一部分我会省略这一步骤,只是直接一步带过,直说个结果。

结束分析

因为本来这篇幅就很长,所以完整的JS代码不会在这里贴出来。如果要看JS的实现代码可以进入 https://github.com/ZSAIm/iqiyi-parser/blob/master/js/tencent.js。

参考和说明

  • Github项目链接: 点这
  • 本文章仅用于技术交流。

腾讯视频cKey9.1的生成分析和实现相关推荐

  1. 腾讯视频VIP会员,周卡特价9元!腾讯官方直充,会员立即生效!

    腾讯视频新剧热播,但去广告必须是腾讯视频VIP会员,今天就给大家推荐特价充值腾讯视频VIP会员的平台--幻海优品,官方在线直充,会员秒到! 腾讯视频VIP会员特价充值 周卡:原价12元,特价9元 月卡 ...

  2. JS逆向之x讯视频wasm的ckey分析

    篇幅有限 完整内容及源码关注公众号:ReverseCode,发送 冲 起因 最近对腾讯视频下手了,因为公众号中的视频来源都是腾讯视频,也就是说通过2分钟阅读本文,腾讯视频下整站视频都可以下载下来,也是 ...

  3. 数据分析与爬虫实战视频——学习笔记(二)(千图网图片爬虫、fiddler抓包分析、腾讯视频评论爬虫、多线程爬虫(糗百))

    网址:[数据挖掘]2019年最新python3 数据分析与数据爬虫实战 https://www.bilibili.com/video/av22571713/?p=26 第三周第二节课 1抓包分析实战 ...

  4. 爱奇艺视频与腾讯视频竞品分析

    随着视频直播业的火爆,市场上视频直播的APP也层出不穷,这些APP主拼的内容和资源,更需进一步推动用户付费习惯的养成.从用户关注因素出发,以用户体验的多方面的校对市场上热门视频直播类APP进行对比分析 ...

  5. 网络爬虫---抓包分析,用抓包分析爬取腾讯视频某视频所有评论(Fiddler工具包的分享)

    抓包分析,用抓包分析爬取腾讯视频某视频所有评论(Fiddler工具包的分享) 文章目录 抓包分析,用抓包分析爬取腾讯视频某视频所有评论(Fiddler工具包的分享) 一.抓包分析 1.下载工具并安装 ...

  6. 仿爱奇艺视频,腾讯视频,搜狐视频首页推荐位轮播图(二)之SuperIndicator源码分析

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/52510431 背景:仿爱奇艺视频,腾讯视频 ...

  7. 爱奇艺、优酷、腾讯视频竞品分析报告2016(二)

    接上一篇<爱奇艺.优酷.腾讯视频竞品分析报告2016(一)> http://milkyqueen520.blog.51cto.com/11233158/1760192 2.4 产品设计与交 ...

  8. 爱奇艺、优酷、腾讯视频竞品分析报告2016(一)

    1 背景 1.1 行业背景 1.1.1 移动端网民规模过半,使用时长份额超PC端 2016年1月22日,中国互联网络信息中心 (CNNIC)发布第37次<中国互联网络发展状况统计报告>,报 ...

  9. 分析日播放量超 6 亿的《延禧攻略》,看爱奇艺腾讯视频谁胜谁败

    作者 | 徐麟 责编 | 郭芮 随着<延禧攻略>的播出,魏璎珞.富察皇后等各位后宫小主的命运时刻牵动着各位观众的心.同时爱奇艺也因为该剧的大火,收获了单日超过6亿的播放量. 我们此次将对比 ...

最新文章

  1. np.array_split可以不均等划分 np.split为均等划分
  2. 【C 语言】内存四区原理 ( 栈内存与堆内存对比示例 | 函数返回的堆内存指针 | 函数返回的栈内存指针 )
  3. 【IPFS + 区块链 系列】 入门篇 - IPFS + Ethereum (下篇)-ipfs + Ethereum 大图片存储
  4. python使用shell环境变量_linux中添加环境变量(python为例)
  5. 什么是 Silverlight?
  6. 堆、栈及静态数据区详解 转
  7. Atitit it行业图像处理行业软件行业感到到迷茫的三大原因和解决方案
  8. SPOJ 20713 DIVCNT2 - Counting Divisors (square)
  9. 理想的正方形 HAOI2007(二维RMQ)
  10. ASP.NET 2.0中执行数据库操作命令之二
  11. python的标准库——turtle
  12. CTP接口封装相关贴---集合
  13. 统计学中p值计算公式_统计学中的P值如何计算?
  14. 图纸管理软件有哪些,免费图纸管理软件
  15. 二叉树 最小公共祖先 c++版
  16. Reactor模式详解(转)
  17. 7年时间从身无分文演变到资产近千万的我(下半部)
  18. linux boot分区创建,Linux 更换 Boot分区 磁盘 示例
  19. PTA 7-13 小明的家庭合影
  20. 基于 Tensorflow 的蘑菇分类

热门文章

  1. 人工智能观看100部电影学习如何识别接吻 | 广东省智能创新协会
  2. 刚刚拿下「中国AI最高奖」的语音技术,能给我们带来什么?
  3. 2022-2027年中国纤维素生物燃料行业市场全景评估及发展战略规划报告
  4. Pillow库学习笔记之Image.convert去底色详解
  5. 2020年 ICLR 国际会议最终接受论文(poster-paper)列表(四)
  6. MindSpore:【AIR模型导出】导出时提示源码中select_op参数类型转换失败
  7. 泛微E9移动平台使用范例,泛微Ecology9移动应用例子
  8. android 蓝牙设置界面高级选项功能解析
  9. HTML入门---慕课网
  10. 数据挖掘相关岗位分析及规划