爬虫 第五天
爬虫 第五天
1. js 解密,混淆,逆向
- url:https://www.aqistudy.cn/html/city_detail.html
- 分析:
- 空气指标的数据是动态加载出来
- 修改了搜索条件后点击搜索按钮会发起ajax请求,请求到我们想要的指标数据。
- 从上一步定位到的数据包中提取出url,请求方式,请求参数
- url和请求方式可以拿来直接用
- 请求参数是动态变化且加密
- 响应数据也是加密的密文数据
- 空气指标的数据是动态加载出来
- 找到点击搜索按钮发起的ajax请求对应的代码
- 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
- getData(),该函数的实现
- type=HOUR:以小时为单位进行数据的查询
- 调用了另两个函数:getAQIData(), getWeatherData()
- 并没有找到ajax请求对应的的代码
- 分析getAQIData&getWeatherData:
- 这两个函数的实现几乎一致,唯一的区别是
- var method = ‘GETDETAIL‘;
- var method = ‘GETCITYWEATHER‘;
- 也没有找到ajax请求对应的代码,但是发现了另一个函数的调用:
- getServerData(method, param, function(obj),0.5 )
- method:
- ‘GETDETAIL‘
- ‘GETCITYWEATHER‘
- param是一个字典,有四组键值对:
- city;
- type;
- startTime;
- endTime;
- method:
- getServerData(method, param, function(obj),0.5 )
- 分析getServerData函数的实现:
- 基于抓包工具进行全局搜索,定位到了一个指定的数据包,出现了getServerData关键词,这个关键词对应的js代码被加密了
- JS混淆:将js中的核心代码加密
- JS反混淆:
- 暴力破解:
- url:https://www.bm8.com.cn/jsConfusion/
- 分析反混淆后的getServerData函数的实现代码:
- 终于发现了ajax请求对应的代码:
- getParam(method, object)返回动态变化且加密的请求参数d的值。
- method == method
- object == param
- decodeData(data):接受加密的响应数据返回解密后的明文数据
- data:加密的响应数据
- getParam(method, object)返回动态变化且加密的请求参数d的值。
- 终于发现了ajax请求对应的代码:
- 暴力破解:
- js逆向:
- 自动逆向:
- PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
- 一定实现在本机装好nodejs的开发环境
- 我们需要pip install PyExecJS对其进行环境安装。
- PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
- 自动逆向:
- 这两个函数的实现几乎一致,唯一的区别是
- getData(),该函数的实现
- 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
In [3]:
#模拟执行js函数获取动态变化且解密的请求参数d的值 import execjs node = execjs.get() # Params method = 'GETCITYWEATHER' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js)#模拟执行指定的js函数 print(params) tdgHOYxwKdDSgYXe+RLPzYCgLvrddahasI5XXklB4gVLYqab+XRPpMD/oSqnJ/aEmFwzVEUhLnPzRy03+X1BI4qc9EYeRPqiKrT+f1JQExGQ4ii8kKvZhGH+nPffaX/xq5iLB6vblcvBC/L8e6UxdnHlajfkXrLQf1qv5Hcg3c++RoGxPAMOgNc6HbCbQG2sE6yemJ7l8HI9CyNktTP7AwQC04bTbY+s+o7lljhqUvsyMZq88MU1VV46TFExCP7vxfmEl6YFeV892bU27lPedTCtSnYbCEfFCJDP0DfEBHe0XFOcgXs+Yl5h58efciX69k9IEvGCKenhokOJQ2tS178anRoT37sEBV5cZeLY8Uzh8UUWgxg2sH+JJsg8ARclHhK0AN/SA4wFy8XmwdBun1zHxV8LoPfn3cxqzXnNKOp/nowpNnbyuMSZtftbf41HB1dEdkm07a2LzCaJgUEpPmLZUuA7+lDlCKqTsEZVh9w=
- 对ajax的url携带d请求参数进行post请求的发送可以获取加密的响应数据
In [5]:
import execjs import requests node = execjs.get() # Params method = 'GETCITYWEATHER' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #发起post请求 url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params}).text print(response_text) eAkzHZvdWqslCrP29e8XgEP22qdvyxus1TrEFB8uvsD0ChwbOTBCJErsCqVJyLQJ9wdhdK9lk3nl/SEeVqoXSY48w11ODT7v6rhQkkXuZ3Vv+VOQ7C7zXtLvbJJDIq9Nu3RRA+8rS/R0lnyMUk98IQ==
In [12]:
#将密文的响应数据进行解密:模拟调用decodeData(data) import execjs import requests node = execjs.get() # Params method = 'GETCITYWEATHER' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #发起post请求 url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params}).text # #对加密的响应数据进行解密 jss = 'decodeData("{0}")'.format(response_text) print(jss) decrypted_data = ctx.eval(jss) print(decrypted_data) decodeData("S+PG+bQmwr20q9LEnYxZb6d9kwGuj+GKqm/YqBW0N9VTTsfFzS6mR86ne1uxqNuepTIfI+opvFV/np093XWIf2IXLkXoN7yUEFNnINrJBIN9MFj2Y9rWgCXZXe4k0PtMub9YKalryHwuO7IlNJN3OA==") --------------------------------------------------------------------------- ProgramError Traceback (most recent call last) <ipython-input-12-775eb2217305> in <module>() 26 jss = 'decodeData("{0}")'.format(response_text) 27 print(jss) ---> 28 decrypted_data = ctx.eval(jss) 29 print(decrypted_data) ~\Anaconda3\lib\site-packages\execjs\_abstract_runtime_context.py in eval(self, source) 25 if not self.is_available(): 26 raise execjs.RuntimeUnavailableError ---> 27 return self._eval(source) 28 29 def call(self, name, *args): ~\Anaconda3\lib\site-packages\execjs\_external_runtime.py in _eval(self, source) 76 77 code = 'return eval({data})'.format(data=data) ---> 78 return self.exec_(code) 79 80 def _exec_(self, source): ~\Anaconda3\lib\site-packages\execjs\_abstract_runtime_context.py in exec_(self, source) 16 if not self.is_available(): 17 raise execjs.RuntimeUnavailableError ---> 18 return self._exec_(source) 19 20 def eval(self, source): ~\Anaconda3\lib\site-packages\execjs\_external_runtime.py in _exec_(self, source) 86 else: 87 output = self._exec_with_pipe(source) ---> 88 return self._extract_result(output) 89 90 def _call(self, identifier, *args): ~\Anaconda3\lib\site-packages\execjs\_external_runtime.py in _extract_result(self, output) 165 return value 166 else: --> 167 raise ProgramError(value) 168 169 ProgramError: Error: Malformed UTF-8 data
selenium
回顾
- 单线程+多任务的异步协程
- 特殊的函数
- 调用后实现内部的程序语句不会被立即执行
- 调用后返回一个协程对象
- 协程对象
- 协程==特殊函数==一组指定的操作
- 任务对象
- 高级的协程对象
- 绑定回调函数
- task.add_done_callback(func)
- func:
- 必须要有一个参数(当前的任务对象)
- 参数.result()表示的就是特殊函数的返回值
- 任务对象 == 一组指定的操作
- 事件循环对象
- 创建一个eventloop对象
- 作用:
- 必须要装载一个或者多个任务对象(任务对象是需要注册到eventloop)
- 启动事件循环对象
- 可以异步的执行其内部注册的每一个任务对象对应的指定操作
- 等待await:确保eventloop一定会执行阻塞操作
- 挂起:让当前发生阻塞的任务对象交出cpu的使用权
- asyncio.wait(tasks)
- 重点:
- 特殊函数内部不可以出现不支持异步模块的代码
- aiohttp:支持异步的网络请求模块
- 使用使用上下文机制(with...as)
- 实例化一个请求对象(ClientSession())
- get/post()进行请求发送。(阻塞操作)
- proxy参数:‘http://ip:port‘
- 获取响应数据(阻塞操作)
- response.text():字符串
- response.read():byte
- 特殊的函数
- selenium
- 动作链
- 无头浏览器
- 规避检测
- 浏览器托管
- 12306的模拟登陆
- 动作链
- from selenium.webdriver import ActionChains
- NoSuchElementException:没有定位到指定的标签
- 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
- bro.switch_to.frame(‘iframe标签id的属性值‘):将当前浏览器页面切换到指定的子页面的范围中
- 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
- 针对指定的浏览器实例化一个动作链对象
- action = ActionChains(bro)
- action.click_and_hold(tagName)
- move_by_offset(10,15)
- perform()是的动作链立即执行
In [6]:
from selenium import webdriver from selenium.webdriver import ActionChains from time import sleep # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的 bro = webdriver.Chrome('./chromedriver.exe') bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') #标签定位 bro.switch_to.frame('iframeResult') div_tag = bro.find_element_by_id('draggable') #需要使用ActionChains定制好的行为动作 action = ActionChains(bro)#针对当前浏览器页面实例化了一个动作链对象 action.click_and_hold(div_tag)#点击且长按指定的标签 for i in range(1,7): action.move_by_offset(10,15).perform()#perform()是的动作链立即执行 sleep(0.5) bro.quit()
- 无头浏览器
- 没有可视化界面的浏览器
- phantomJS:无头浏览器
- 谷歌无头浏览器:
- 就是你本机安装的谷歌浏览器,只是需要通过代码进行相关配置就可以变成无头浏览器
In [8]:
from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options) bro.get('https://www.baidu.com/') sleep(1) bro.save_screenshot('./1.png')#截屏 print(bro.page_source)
- selenium规避检测
- 浏览器托管
- 环境配置:
- 1.本机谷歌浏览器驱动程序所在的目录的路径添加到环境变量中
- 2.使用本机谷歌的驱动程序开启一个浏览器
- chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
- 9222:端口(任意)
- "C:\selenum\AutomationProfile":已经事先存在的一个空目录
- chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
- 使用如下代码接管目前打开的浏览器:
- 环境配置:
- 浏览器托管
In [30]:
from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)#代码托管代开的浏览器,不会实例化一个新的浏览器。 bro.get('https://kyfw.12306.cn/otn/login/init') C:\Users\laonanhai\Anaconda3\lib\site-packages\ipykernel_launcher.py:8: DeprecationWarning: use options instead of chrome_options
12306模拟登陆
- url:https://kyfw.12306.cn/otn/login/init
- 分析:
- 识别的验证码图片必须通过截图获取然后存储到本地
- 登陆操作和唯一的验证码图片一一对应
- 识别的验证码图片必须通过截图获取然后存储到本地
In [25]:
#pip install Pillow from PIL import Image from selenium.webdriver import ActionChains from selenium import webdriver #识别验证码的函数 def transformCode(imgPath,imgType): chaojiying = Chaojiying_Client('13614167787', '13614167787', '903126') im = open(imgPath, 'rb').read() return chaojiying.PostPic(im,imgType)['pic_str']
In [26]:
bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') sleep(2) bro.find_element_by_id('username').send_keys('xxxxxxx') bro.find_element_by_id('password').send_keys('12345465') #验证码的点击操作 bro.save_screenshot('main.png')#将页面当做图片保存到本地 #将单独的验证码图片从main.png中裁剪下载 img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')#将验证码图片的标签定位到了 location = img_tag.location size = img_tag.size # print(location,size) #裁剪的范围(验证码图片左下角和右上角两点坐标) rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height'])) #使用Image类根据rangle裁剪范围进行验证码图片的裁剪 i = Image.open('./main.png') frame = i.crop(rangle)#验证码对应的二进制数据 frame.save('./code.png') result = transformCode('./code.png',9004)#99,71|120,140 #99,71|120,140 == [[99,71],[120,140]] all_list = []#[[99,71],[120,140]] if '|' in result: list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) y = int(list_1[i].split(',')[1]) xy_list.append(x) xy_list.append(y) all_list.append(xy_list) else: x = int(result.split(',')[0]) y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) for data in all_list: x = data[0]#11 y = data[1]#22 ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform() sleep(1) sleep(2) bro.find_element_by_id('loginSub').click() bro.quit()
In [21]:
#下载好的示例代码 #!/usr/bin/env python # coding:utf-8 import requests from hashlib import md5 class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): """ im_id:报错题目的图片ID """ params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json() # if __name__ == '__main__': # chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001') #用户中心>>软件ID 生成一个替换 96001 # im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要// # print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
相关推荐
chensen 2020-11-14
lwnylslwnyls 2020-11-06
ATenhong 2020-10-15
yanzhelee 2020-10-13
佛系程序员J 2020-10-10
guojin0 2020-10-08
佛系程序员J 2020-10-08
bluewelkin 2020-09-16
wwzaqw 2020-09-04
zhongdaowendao 2020-09-02
favouriter 2020-08-18
奎因amp华洛 2020-08-15
一青年 2020-08-13
千锋 2020-08-10
nangongyanya 2020-08-09
dongxurr 2020-08-08
明天你好 2020-08-03
kyelu 2020-08-03
Ashes 2020-08-03