https://www.hongshu.com/content/3052/3317-98805.html
以某篇具体文章为例,咱来破解这个网站的加密,爬取到所有的小说内容
我们的目的是要小说内容,那么先来看一看直接请求https://www.hongshu.com/content/3052/3317-98805.html得到的响应会不会有我要的数据
你会失望的发现,响应体里面没有文章内容
那就得去思考了,小说内容来自哪里?最大的可能是ajax发起二次请求,拿到json格式的数据再渲染到页面上.
带着这个思路,就去找找json数据憋
诶呀,我**,找到了两个很可疑的响应:
| 第一个响应里的可疑字段: |
| 'key':动动脑子都能猜到,这东西绝对有用 |
| |
| 第二个响应里的可疑字段: |
| content:内容加密,瞅这个英语单词就知道加密内容小说内容有关 |
| other:内容也是加密的,虽然还猜不到它到底有什么用,但八九不离十和小说内容有一腿 |
我们再来看看,这两个请求如何模拟
都是post请求,form-data也很简单,bid,jid,cid就在url上
另外,咱没有登录过,所以请求里的cookie不需要
解密的英文单词是啥来着,decrypt,全文搜搜看吧.(当然,你也可以尝试其它的关键词,比如content
,other
,bookajax.do
)
很高兴,有两个结果匹配.我们挨个点进去,具体看看代码
一个个的找,会找到如下代码:
我们发现了熟悉的面孔,data.content
,data.other
还有key
很明显了,它就是解密算法无疑了
为了进一步认证,我们可以打个断点瞅一瞅
现在可以确认了,它就是content的解密算法了.
通过打断点的形式,把js解密算法,全部扣出来,如下:
全部扣出来会得到下面的函数(代码内容太长,这里就不写了):
| function base64decode(str) { |
| ....... |
| } |
| |
| function hs_decrypt(str, key) { |
| ....... |
| } |
| |
| function long2str(v, w) { |
| ....... |
| } |
| |
| function utf8to16(str) { |
| ....... |
| } |
然后,我们再瞅一瞅解密后的结果:
我们在console控制台运行一下这个代码,看结果
内容出来了,很开心.
不过并没有开心很久,细心的同学会发现,哎呀,小说内容少字啊
这时候我们就不得不再去怀疑了,这一定是other里做了手脚
解密other后会发现,解密后的结果是一堆js代码
不出意外,这些js代码,和我们缺失的字一定是有关系的.
很可能是js操作了html,然后把内容渲染上去的.
那怎么验证这个事呢,很容易,我们自己手动新建一个html文件,然后把html代码和js代码都拷贝进去,运行看看结果.
不过这里有个问题,contemt解密出来的是html占位符,而不是标签.所以,首先得把html占位符转化成html标签,很容易,把content交给浏览器渲染一遍就好了.
运行后的结果如下,会得到html标签
好,下面我们来验证other和content的关系:
然后运行,会得到如下结果:
内容全部出来了
不过!!!虽然我们知道了other的作用,内容渲染出来了.但是!!!这里还是有问题,细心的同学会发现,之前缺失的文字,被放在了:befor标签的css属性里.
这种情况下,我直接调用解析库去解析页面的话,根本得不到缺失的文字.
这怎么办呢?不要慌,我们还有js注入的手段
在我们调用浏览器内核对content和other进行渲染的时候,我们可以注入一段js代码,代码逻辑如下:
| 1.通过js定位到所有的:befor标签 |
| 2.然后,获取到css属性的值(缺失的文字) |
| 3.把缺失的文字插入到标签之间(innerText) |
基于此,可以写出如下js代码用于js注入:
| var element_list = document.querySelectorAll('#divChpContent span') |
| for(var i=0;i<element_list.length;i++){ |
| var content = window.getComputedStyle( |
| element_list[i],':before' |
| ).getPropertyValue('content') |
| element_list[i].innerText = content.trim('"'); |
| } |
| from requests_html import HTMLSession,HTML |
| import execjs |
| class Spider(): |
| def __init__(self): |
| self.session = HTMLSession() |
| self.book_info_api = 'https://www.hongshu.com/bookajax.do' |
| self.headers = { |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0', |
| } |
| |
| |
| self.book_url = 'https://www.hongshu.com/content/3052/3317-98805.html' |
| bool_url_search = HTML(html=self.book_url).search('https://www.hongshu.com/content/{bid}/{jid}-{cid}.html') |
| self.bid = bool_url_search['bid'] |
| self.jid = bool_url_search['jid'] |
| self.cid = bool_url_search['cid'] |
| def get_key(self): |
| data = { |
| 'method':'getchptkey', |
| 'bid':self.bid, |
| 'cid':self.cid, |
| } |
| |
| r = self.session.post(url=self.book_info_api,data=data,headers=self.headers) |
| res = r.json() |
| if res.get('msg') == '获取章节内容成功': |
| return res.get('key') |
| else: |
| print('key获取失败') |
| def get_book_info(self): |
| data = { |
| 'method': 'getchpcontent', |
| 'bid': self.bid, |
| 'cid': self.cid, |
| 'jid': self.jid, |
| } |
| |
| r = self.session.post(url=self.book_info_api, data=data, headers=self.headers) |
| res = r.json() |
| if res.get('msg') == '获取章节内容成功': |
| return {'content':res.get('content'),'other':res.get('other')} |
| else: |
| print('内容获取失败') |
项目目录下,新建一个decrypt.js的js文件,里面写上扣下来的解密函数:
| function base64decode(str) { |
| var c1, c2, c3, c4, base64DecodeChars = new Array(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1); |
| var i, len, out; |
| len = str.length; |
| i = 0; |
| out = ""; |
| while (i < len) { |
| do { |
| c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; |
| } while (i < len && c1 == -1);if (c1 == -1) |
| break; |
| do { |
| c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; |
| } while (i < len && c2 == -1);if (c2 == -1) |
| break; |
| out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); |
| do { |
| c3 = str.charCodeAt(i++) & 0xff; |
| if (c3 == 61) |
| return out; |
| c3 = base64DecodeChars[c3]; |
| } while (i < len && c3 == -1);if (c3 == -1) |
| break; |
| out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); |
| do { |
| c4 = str.charCodeAt(i++) & 0xff; |
| if (c4 == 61) |
| return out; |
| c4 = base64DecodeChars[c4]; |
| } while (i < len && c4 == -1);if (c4 == -1) |
| break; |
| out += String.fromCharCode(((c3 & 0x03) << 6) | c4); |
| } |
| return out; |
| } |
| |
| function hs_decrypt(str, key) { |
| if (str == "") { |
| return ""; |
| } |
| var v = str2long(str, false); |
| var k = str2long(key, false); |
| var n = v.length - 1; |
| var z = v[n - 1] |
| , y = v[0] |
| , delta = 0x9E3779B9; |
| var mx, e, q = Math.floor(6 + 52 / (n + 1)), sum = q * delta & 0xffffffff; |
| while (sum != 0) { |
| e = sum >>> 2 & 3; |
| for (var p = n; p > 0; p--) { |
| z = v[p - 1]; |
| mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); |
| y = v[p] = v[p] - mx & 0xffffffff; |
| } |
| z = v[n]; |
| mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); |
| y = v[0] = v[0] - mx & 0xffffffff; |
| sum = sum - delta & 0xffffffff; |
| } |
| return long2str(v, true); |
| } |
| |
| function long2str(v, w) { |
| var vl = v.length; |
| var sl = v[vl - 1] & 0xffffffff; |
| for (var i = 0; i < vl; i++) { |
| v[i] = String.fromCharCode(v[i] & 0xff, v[i] >>> 8 & 0xff, v[i] >>> 16 & 0xff, v[i] >>> 24 & 0xff); |
| } |
| if (w) { |
| return v.join('').substring(0, sl); |
| } else { |
| return v.join(''); |
| } |
| } |
| |
| function utf8to16(str) { |
| var out, i, len, c; |
| var char2, char3; |
| out = ""; |
| len = str.length; |
| i = 0; |
| while (i < len) { |
| c = str.charCodeAt(i++); |
| switch (c >> 4) { |
| case 0: |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| case 6: |
| case 7: |
| out += str.charAt(i - 1); |
| break; |
| case 12: |
| case 13: |
| char2 = str.charCodeAt(i++); |
| out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); |
| break; |
| case 14: |
| char2 = str.charCodeAt(i++); |
| char3 = str.charCodeAt(i++); |
| out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); |
| break; |
| } |
| } |
| return out; |
| } |
| |
| |
| function decrypt(str,key){ |
| return utf8to16(hs_decrypt(base64decode(str), key)) |
| } |