裁判文书网
爬取动态加载的数据(js加密eval,jsfuck )
分析网页
1.打开首页
[外链图片转存失败(img-sFgp9WYn-1566049211688)(E:\CSDN 博客\裁判文书网\首页.png)]
从各个标签入手,当点击其中一个分类,刑事案件的时候,会跳转到下一个页面,那么可以知道,在这个页面我们要获取到的信息就是各级标签的url
start_urls = ['http://wenshu.court.gov.cn/']def parse(self, response):'''解析出案件类型的url'''str1 = response.body.decode('utf-8')case_urls = re.findall(r'''<li class="zhuye"><a href="/Index">首页</a></li>(.*?)<li class="yuyan"><a id="yuyan_a"''',str1, re.S)[0]case_url = re.findall(r'''<li><a href="(.*?)" target="_blank">''',case_urls, re.S)[0:1]for urls in case_url:url = 'http://wenshu.court.gov.cn' + urlsyield scrapy.Request(url=url,callback=self.case_links,)
2.打开下一页面后,发现该页面的数据都是ajex动态加载出来的,所以要从该页面重新发送一个请求,来获取信息
,分析后发现,该请求是POST,并且里面的数据都是加密的,需要去获取js信息破解出需要的信息[外链图片转存失败(img-po5Xr6dr-1566049211690)(E:\CSDN 博客\裁判文书网\动态加载的数据信息.png)]
这里我们需要的参数为 guid ,vl5x ,param
直接在浏览器中搜索对应的参数名,筛选查找出来的js文件,定位到有效信息
vl5x很直观的可以从cookies里看出来和cookies里的vjkl5很像
[外链图片转存失败(img-v0sPjRfd-1566049211690)(E:\CSDN 博客\裁判文书网\获取cookies.png)]
[外链图片转存失败(img-7s6b0i62-1566049211690)(E:\CSDN 博客\裁判文书网\vl5x.png)]
[外链图片转存失败(img-AEa4zuWx-1566049211691)(E:\CSDN 博客\裁判文书网\guid vl5x.png)]
[外链图片转存失败(img-Zgr2LHIE-1566049211691)(E:\CSDN 博客\裁判文书网\get_key()].png)
在Lawyee.CPWSW.ListExtend.js文件中,发现了getKey函数的定义
在getKey函数的定义中,发现eval()函数,该函数是对js代码进行加密的函数,所以需要对eval()函数进行还原
百度一下就能找到eval()解密的网址https://wangye.org/tools/scripts/eval/
解密的步骤:
a.发现有些eval函数里面还有一层,所以有些需要解密两次,从外向里进行替换,例如eval(de("eval(_fxxx( 先替换de,然后解密,在替换_fxxx,再解密;这里的de和_fxxx我们可以从Lawyee.CPWSW.ListExtend.js文件中找到,_fxxx
b.将以上所有解码的函数存放在一个自己建的js文件中,
c.除此以外还发现在解码得到的的函数中还用到了其他的js(md5.js、base64、sha1.js),我们直接在浏览器中搜索既可以直接找到,并将它们一起放到自己建的js文件中
在解码的最后果然发现了[外链图片转存失败(img-LpgSUboq-1566049211691)(E:\CSDN 博客\裁判文书网\vj联系.png)]
所以我们我们就明白了vl5x的来源了,其实就是gek_key(vjkl5)只不过get_key被加密了
获取vjkl5代码
def case_links(self,response):str1 = response.body.decode('utf-8')param = re.findall(r'\+AJLX\+\+(.*)', response.url)[0]param = urllib.parse.unquote(param)# 获取到cookies里的vjkl5vjkl5 = response.headers.getlist('Set-Cookie')[0].decode().split(';')[0].split('=')[1]
求出vl5x的值(这里需要使用execjs执行js代码,wenshu.js就是我们存放get_key()解码得到的js代码及其他js文件的文件)
js_content = open('D:\python练习\wenshu\wenshu\wenshu.js', encoding='utf8').read()# js_obj = execjs.compile #(js_content)返回一个js对象,该对象包含了js运行的环境。# 再使用js对象调用call()函数。# call('getKey', vjkl5) #参数1-要执行的js文件中对应的函数,参数2-给调用函数传递的参数。vl5x = execjs.compile(js_content).call('getKey', vjkl5)
guid 的js代码并没有与加密,直接copy执行就可以了
def get_guid(self):guid = self.creat_guid() + self.creat_guid() + "-" + self.creat_guid() + "-" + self.creat_guid() + self.creat_guid() + "-" + self.creat_guid() + self.creat_guid() + self.creat_guid();return guiddef creat_guid(self):return execjs.eval('(((1 + Math.random()) * 65536) | 0).toString(16).substring(1)')
3.发送post请求后就可以获取到文书的列表信息了,接下来就是获取到文书的详情,这里需要注意的是url里面的DocID的值也是被加密的,直接在浏览器里面搜索DocID
[外链图片转存失败(img-Vgy3NYkz-1566049211692)(E:\CSDN 博客\裁判文书网\docid4.png)]
[外链图片转存失败(img-uEiQBnhb-1566049211693)(E:\CSDN 博客\裁判文书网\docid1.png)]
这里直接发现了Navi函数,以及他需要的参数key就是获取到的文书列表里面的 “文书ID”信息
搜索Navi函数[外链图片转存失败(img-lhoERS8Y-1566049211693)(E:\CSDN 博客\裁判文书网\navi.png)]
[外链图片转存失败(img-VsSWqny0-1566049211693)(E:\CSDN 博客\裁判文书网\unzip.png)]
本以为直接执行js既可以的但是,发现没有那么简单,在分析后发现其实他是需要一个秘钥来解密的,在unzip的函数里发现 这里用的是AES的加密方式 ,
encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
})
所以我们需要获得他的秘钥,及_KEY这里的key是默认的,但是尝试了以后发现并不可行,那么我们就需要去找这个秘钥的来源,这个秘钥一定是要和加密的秘钥一样那么要想在浏览器中解密,服务器一定会返回一个秘钥回来,在得到的信息在里发现除了文书ID外还有一个参数RunEval并且一直在变,所以就大胆猜测,这个就是
a.把RunEval带入unzip函数后得到
# 构建js的执行对象 执行js文件中的decode函数
runeval = execjs.compile(js_content).call('decode',runEval)
# 正则筛选出被JSFUCK加密的获取变量值得代码
[外链图片转存失败(img-iufgQ7wJ-1566049211694)(E:\CSDN 博客\裁判文书网\jsfuck.png)]
这是一段被jsfuck加密的js代码,可以自行百度获取更多关于jsfuck的知识
分析这段代码
$hidescript=string,fromCharCode,
这是一个赋值语句,并且后面有大量的$hidescript出现,直接运行这段代码也获得了对应的值,
在jsfuck的代码中还有_=“constructor”; _ [ _ ] [ _ ]()这个函数,也正是这个函数里面需要$hidescript
所以把前面获得的值带入到函数中我就获得了str_keyde 值,这也就是我们需要的秘钥
runeval = execjs.compile(js_content).call('decode',runEval)
# 正则筛选出被JSFUCK加密的获取变量值得代码
js_key = '$hidescript='+re.findall(r'\$hidescript=(.*?)_="constructor"',runeval)[0]
# 构建js执行对象
ctx = execjs.compile('''function js_KEY(){return %s}''' % js_key)
# 执行js代码 获取$hidescript的值
ctx_key = ctx.call("js_KEY")
print(ctx_key)
#这里需要将"进行转义
ctx_key = '"'+ctx_key.replace('._KEY=','._KEY=\\')+'"'
# 正则筛选出被JSFUCK加密的获取str_key函数的js代码,其中需要先获取到$hidescript的值
js_str_key = re.findall(r'''_="constructor";_\[_\]\[_\](.*?)\(\);''',runeval)[0]
# 将$hidescript替换成他的真实值
js_str_key = js_str_key.replace('$hidescript',ctx_key)
# 构建js执行对象
str_key = execjs.compile('''function js_KEY(){return %s}''' % js_str_key)
# 执行 js 获取str_key
str_key = str_key.call("js_KEY")
print(str_key)
运行结果
Tm('._KEY="452318096;,*Mh)
setTimeout('com.str._KEY="44cb5235f18c40d2a9896101d0169d1d";',8000*Math.random());
接下来就可以直接带入js获取doc_id了
for doc_id in doc_ids:# 构建js的执行对象 执行js文件中的Navi函数real_id = execjs.compile(js_content).call('Navi', doc_id, real_str_key)print(real_id)
结果
029bb843-b458-4d1c-8928-fe80da403cfe
f08d44ee-b647-11e3-84e9-5cf3fc0c2c18
eff7f53c-b647-11e3-84e9-5cf3fc0c2c18
f074f302-b647-11e3-84e9-5cf3fc0c2c18
f08d1d40-b647-11e3-84e9-5cf3fc0c2c18
508508aa-f317-4845-b560-a178bc4245d4
e05ad449-e7cf-4f5c-8b21-711351599674
cb564602-23aa-46e3-abe3-841f59727e0f
2aae74eb-3c59-42ad-95d7-85d683249c6b
75542beb-5da3-4926-9330-a5948f2b629f
完整代码
spider.py
# -*- coding: utf-8 -*-
# @Time : 2019/8/8 11:19
# @Author : lsc
# @Site ;
# @File : wenshu_spider.py
# @Software : PyCharm
import scrapy
import urllib.parse
import re
import execjs
import osimport timeclass WenshuSpider(scrapy.Spider):name = 'wenshu'# 要注意这个地方# allowed_domains = ['wenshu.court.gov.cn/']start_urls = ['http://wenshu.court.gov.cn/']def parse(self, response):'''解析出案件类型的url'''str1 = response.body.decode('utf-8')# print(str1)print('==========================================================')case_urls = re.findall(r'''<li class="zhuye"><a href="/Index">首页</a></li>(.*?)<li class="yuyan"><a id="yuyan_a"''',str1, re.S)[0]case_url = re.findall(r'''<li><a href="(.*?)" target="_blank">''',case_urls, re.S)[0:1]for urls in case_url:url = 'http://wenshu.court.gov.cn' + urlsyield scrapy.Request(url=url,callback=self.case_links,)def case_links(self,response):print('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')str1 = response.body.decode('utf-8')param = re.findall(r'\+AJLX\+\+(.*)', response.url)[0]param = urllib.parse.unquote(param)# 获取到cookies里的vjkl5vjkl5 = response.headers.getlist('Set-Cookie')[0].decode().split(';')[0].split('=')[1]print(vjkl5)js_content = open('D:\python练习\wenshu\wenshu\wenshu.js', encoding='utf8').read()# js_obj = execjs.compile #(js_content)返回一个js对象,该对象包含了js运行的环境。# 再使用js对象调用call()函数。# call('getKey', vjkl5) #参数1-要执行的js文件中对应的函数,参数2-给调用函数传递的参数。vl5x = execjs.compile(js_content).call('getKey', vjkl5)# print(vl5x)meta = self.get_metas(index='1', page='10', param=param, vl5x=vl5x,)# print(meta)url = 'http://wenshu.court.gov.cn/List/ListContent'# FormRequest: 发送POST请求的类。# Request: 默认发送GET请求,method设置请求方法。# 由于在请求列表页接口时,cookie中vjkl5的值每次都是变化的,所以在请求时,需要将从response的Set-Cookie中获取的vjkl5的值更换一下,否则使用同一个vjkl5的值,会出现 'remind key' 错误。# urllib/requests: Cookie的自动化管理(cookiejar)?为什么要自动化管理,而不手动去解析?# 1. 复杂,每一个请求的响应都要去提取并存储;# 2. 容易遗漏;# scrapy框架是如何管理Cookie的?# 默认启用了一个Cookie的中间件,实现了Cookie的自动化管理。将所有响应的Set-Cookie中的cookie信息保存下来,在后续的请求中,将这些cookie携带上。# 为什么单独添加Cookies?# 因为每一次请求列表页,都会返回一个新的Set-Cookie: vjkl5=,但是scrapy请求时自动携带的cookie,可能还是之前的旧的vjkl5=,就会导致这个POST请求携带的Cookie无法和服务器保存的Cookie不一致。# 设置上cookies,就意味着每次请求都携带最新返回的vjkl5的值。yield scrapy.FormRequest(url=url,callback=self.case_links_detail,formdata=meta,cookies={'vjkl5': vjkl5})# 在使用cookie的时候,观察cookie的值的变化。def case_links_detail(self,response):print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%')json_string = re.search(re.compile(r'\[(.*?)\]', re.S), response.text)# 设置js代码 的执行环境os.environ["EXECJS_RUNTIME"] = "PhantomJS"js_content = open('D:\python练习\wenshu\wenshu\docid.js', encoding='utf8').read()# js_obj = execjs.compile #(js_content)返回一个js对象,该对象包含了js运行的环境。# 再使用js对象调用call()函数。# call('getKey', vjkl5) #参数1-要执行的js文件中对应的函数,参数2-给调用函数传递的参数。if json_string:# 将字符串中 '\' 都替换为空。# \是特殊字符,需要表达普通字符\,需要使用\进行转义。result = json_string.group(1).replace("\\", '').replace('&ldquo;', '(').replace('&rdquo;', ')')# result是一个字符串 "{'RunEval':''},{'裁判要旨':'', '文书ID':''},{'裁判要旨':'', '文书ID':''},{'裁判要旨':'', '文书ID':''}"# 获取RunEval的值,加密字符串。runEval = re.search(re.compile(r'"RunEval":"(.*?)","', re.S), result).group(1)print(runEval)# 获取文书的IDdoc_ids = re.findall(re.compile(r'"文书ID":"(.*?)","', re.S), result)print(doc_ids)# 构建js的执行对象 执行js文件中的decode函数runeval = execjs.compile(js_content).call('decode',runEval)# 正则筛选出被JSFUCK加密的获取变量值得代码js_key = '$hidescript='+re.findall(r'\$hidescript=(.*?)_="constructor"',runeval)[0]# 构建js执行对象ctx = execjs.compile('''function js_KEY(){return %s}''' % js_key)# 执行js代码 获取$hidescript的值ctx_key = ctx.call("js_KEY")print(ctx_key)#这里需要将"进行转义ctx_key = '"'+ctx_key.replace('._KEY=','._KEY=\\')+'"'# 正则筛选出被JSFUCK加密的获取str_key函数的js代码,其中需要先获取到$hidescript的值js_str_key = re.findall(r'''_="constructor";_\[_\]\[_\](.*?)\(\);''',runeval)[0]# 将$hidescript替换成他的真实值js_str_key = js_str_key.replace('$hidescript',ctx_key)# 构建js执行对象str_key = execjs.compile('''function js_KEY(){return %s}''' % js_str_key)# 执行 js 获取str_keystr_key = str_key.call("js_KEY")print(str_key)real_str_key = re.findall(r'''_KEY="(.*?)";''',str_key)[0]print(real_str_key)for doc_id in doc_ids:# 构建js的执行对象 执行js文件中的Navi函数real_id = execjs.compile(js_content).call('Navi', doc_id, real_str_key)print(real_id)url = 'http://wenshu.court.gov.cn/content/content?DocID='+real_id+"&KeyWord="# yield scrapy.Request(url=url, callback=self.case_doc )def case_doc(self,response):print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@**********************************')print(response.url)def get_metas(self,param,index,page,vl5x):''' 需要构造 POST 参数Param: 案件类型:刑事案件Index: 1Page: 10Order: 法院层级Direction: ascvl5x: 19df1bfbf20eb3ea82cba533number: wensguid: d39aa838-d6ad-2792bcbd-f975ac094318'''guid = self.get_guid()meta = {'Param': param,'Index': index,'Page': page,'Order': '法院层级','Direction': 'asc','vl5x': vl5x,'number': 'wens','guid': guid,}return metadef get_guid(self):guid = self.creat_guid() + self.creat_guid() + "-" + self.creat_guid() + "-" + self.creat_guid() + self.creat_guid() + "-" + self.creat_guid() + self.creat_guid() + self.creat_guid();return guiddef creat_guid(self):return execjs.eval('(((1 + Math.random()) * 65536) | 0).toString(16).substring(1)')
guid(self):
guid = self.creat_guid() + self.creat_guid() + “-” + self.creat_guid() + “-” + self.creat_guid() + self.creat_guid() + “-” + self.creat_guid() + self.creat_guid() + self.creat_guid();
return guid
def creat_guid(self):return execjs.eval('(((1 + Math.random()) * 65536) | 0).toString(16).substring(1)')