- 异步爬虫
- js逆向
异步爬虫
- 线程池
- 协程:单线程+多任务异步协程
- 生产者消费者模式
线程池的应用
falsk_server.py,代码
from flask import Flask
from flask import render_template
import time
app = Flask(__name__)
@app.route("/aaa")
def index1():
time.sleep(2)
return render_template('test.html')
@app.route("/bbb")
def index2():
time.sleep(2)
return render_template('test.html')
@app.route("/ccc")
def index3():
time.sleep(2)
return render_template('test.html')
if __name__ == '__main__':
app.run(debug=True)
线程池代码
import requests
from multiprocessing.dummy import Pool
from lxml import etree
from time import sleep
import time
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
urls = [
"http://127.0.0.1:5000/aaa",
"http://127.0.0.1:5000/bbb",
"http://127.0.0.1:5000/ccc",
]
start = time.time()
# 构造线程池
# 开启三个线程
pool = Pool(3)
def get_request(url):
time.sleep(2)
return requests.get(url, headers=headers).text
#参数1表示回调函数,回调函数调用的次数和列表中元素的个数一致
#如果回调有返回值的化,我们是需要接受处理其返回值
page_text_list = pool.map(get_request, urls)
# 异步解析
def parse(page_text):
tree = etree.HTML(page_text)
return tree.xpath('//div[@class="tang"]//text()')
parse_data = pool.map(parse, page_text_list)
print(parse_data)
print("总时间为", (time.time() - start))
js逆向
协程
- 特殊的函数:被async关键字修饰的函数定义。特殊之处体现:
- 函数被调用后,函数内部的程序语句不会被立即执行.
- 函数调用后(不管函数内部是否存在return),会返回一个协程对象
- 想要返回的return的值,需要用callback_func回调函数
- 协程:特殊的函数==一组指定形式的操作==协程对象
- 协程 == 一组指定形式的操作
- 任务:
- 任务对象 == 高级协程对象 == 一组指定形式的操作
- 事件循环
- 可以表示一个容器。这个容器是用来装载任务对象。当启动事件循环后,期内部的任务对象就会被异步的执行
- 挂起:可以让任务对象交出cpu的使用权
- wait(tasks):可以给每一个任务对象赋予一个可被挂起的权限
- 关键字await:保证阻塞操作一定会被执行
- 注意:在特殊函数实现内部不可以出现不支持异步模块的代码
开启协程对应的代码
import asyncio #
import time
async def get_request(url):
# 只要定义函数的时候,前面带着async关键字,那么他就是一个特殊函数
print("正在请求", url)
time.sleep(2)
print("正在结束", url)
# 创建协程对象
c = get_request('www.shey.com')
# 创建任务对象
task = asyncio.ensure_future(c)
# 事件循环对象
loop = asyncio.get_event_loop()
# 启动事件循环
loop.run_until_complete(task)
绑定回调函数
#给任务对象绑定回调
#封装任务对象的回调函数
#该函数必须携带一个参数:任务对象
def parse(task):
print('i am task callback!')
print('特殊函数内部的返回值:',task.result())
async def get_request(url):
print('正在请求:',url)
time.sleep(2)
print('请求结束,',url)
return 123
#创建协程对象
c = get_request('www.1.com')
#创建任务对象
task = asyncio.ensure_future(c)
task.add_done_callback(parse)
#事件循环对象
loop = asyncio.get_event_loop()
#启动事件循环
loop.run_until_complete(task)
多任务的异步协程
import asyncio
import time
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/jay'
]
start = time.time()
#在特殊函数实现内部不可以出现不支持异步模块的代码
async def get_request(url):
print('正在请求:',url)
await asyncio.sleep(2)
print('请求结束,',url)
tasks = [] #任务列表
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)
aiohttp:标准的可以支持异步的网络请求模块
编码流程:
- 1.写出大致架构:
with aiohttp.ClientSession() as sess:
with sess.get(url=url) as response:
page_text = response.text()
return page_text
- 2.补充细节:
- 在每一个with前加上async关键字
- 在阻塞操作前加上await关键字
- 完整代码:
async def get_request(url):
#实例化一个请求对象
async with aiohttp.ClientSession() as sess:
async with await sess.get(url=url) as response:
page_text = await response.text()
return page_text
多任务的异步爬虫
import asyncio
import time
import requests
import aiohttp
from lxml import etree
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/jay'
]
start = time.time()
async def get_request(url):
#实例化一个请求对象
async with aiohttp.ClientSession() as sess:
#发起请求:sess的get和post方法除了proxy参数不一致,其他参数用法一直
#sess的get或者post处理代理的参数:proxy=”http://ip:port“
async with await sess.get(url=url) as response:
#text()返回字符串形式的响应数据
#read()返回二进制形式的响应数据
page_text = await response.text()
return page_text
#任务对象的回调函数
def parse(task):
#获取页面源码数据
page_text = task.result()
tree = etree.HTML(page_text)
print(tree.xpath('//a[@id="feng"]/text()')[0])
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
task.add_done_callback(parse)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)
js逆向案例分析
- 基于抓包工具进行初步分析,获取了两个结论: 1.请求对应的方式为post,请求参数d是实时动态变化 2.获取响应数据是一组密文,必须对其进行解密
- 核心重点:
- 1.知道动态变化的请求参数d的值是怎么生成的
- 2.d被解决后,请求到密文数据,知道密文数据如何进行解密
- 在首页中,我们是可以指定相关的搜索条件,点击搜索按钮后,可以获取对应的数据
- 发现:搜索按钮点击后,其实发起的是一个ajax请求。如果ajax请求代码可以找到,在代码实现中就可以知道d和密文数据的解密方式是什么。
- 通过火狐浏览器找寻搜索按钮对应的点击事件-》getData的js函数,从该函数中找到ajax对应的代码
- 在getData中没有发现ajax代码,但是发现了另外两个函数的调用: getWeatherData和getAQIData
- 注意:在getData中type可以等于HOUR
- 找寻getWeatherData和getAQIData函数的实现,查找ajax代码
- method = GETDETAIL / GETCITYWEATHER
- param是一个字典,存有4个键值对(查询的条件:city,starttime,endtime,type)
- 在其实现内部调用了getServerData(method,param,func,0.5),查看该函数的实现,找寻ajax代码
- 基于抓包工具进行全局搜索:getServerData
- 可以找到对应的实现,但是发现该函数的实现看不懂(不同格式的编码数值)
- 该函数的实现进行了js混淆
- 对核心关键的函数实现进行了加密
- 处理:暴力破解实现反混淆
- url:https://www.bm8.com.cn/jsConfusion/
- 进行了反混淆后,终于看到了ajax请求对应的代码,从代码中提取到的信息:
- 动态变化的请求参数d是由一个getParam(method, object)返回的,且函数的两个参数就是getServerData的前两个参数
- ajax请求道的加密的数据是通过调用 decodeData(data)进行的解密。
- js逆向:想要使用python调用js相关的代码。
- PyExecJS模块:通过python调用js代码
- 环境安装:
- 安装nodejs开发环境
- pip install PyExecJS
- 环境安装:
思路:
打开网站
https://www.aqistudy.cn/html/city_detail.html
点击抓包工具,可以看到他的ajax是这样的,其中有一个d参数,是动态的

然后我们去看他的response,可以看到返回的是加密的数据

所以我们现在有两个难题,一个是要获取param的动态参数d,一个是要获取数据的解密公式。那么这两个东西都需要用js逆向去做,但是我们需要知道的是这两个东西是js的函数去操作的,所以我们去找js封装的函数。
这个网站何时会发送ajax呢,是点击这个搜索按钮的时候

那我们去火狐浏览器下面找到这个搜索按钮的点击事件

点开event事件

我们现在知道了函数为getData(),我们去搜索一下哪里有getData方法
那么来到谷歌浏览器

那么这个时候我们看getData函数没有任何的ajax,但是他还调用了两个函数getAQIData和getWeatherData。
那我们接着去找这两个函数。
function getAQIData()
{
var method = 'GETDETAIL';
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
getServerData(method, param, function(obj) {
data = obj.data;
if(data.total>0)
{
dataAQI.splice(0, dataAQI.length);
dataPM25.splice(0, dataPM25.length);
dataPM10.splice(0, dataPM10.length);
dataCO.splice(0, dataCO.length);
dataNO2.splice(0, dataNO2.length);
dataO3.splice(0, dataO3.length);
dataSO2.splice(0, dataSO2.length);
dataRank.splice(0, dataRank.length);
for(i=0;idata.rows.length;i++)
{
dataAQI.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].aqi)
});
dataPM25.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].pm2_5)
});
dataPM10.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].pm10)
});
dataCO.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseFloat((parseFloat(data.rows[i].co)).toFixed(2))
});
dataNO2.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].no2)
});
dataO3.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].o3)
});
dataSO2.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].so2)
});
dataRank.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].rank)
});
}
dataPolar = [calAvg(dataAQI),calAvg(dataPM25),calAvg(dataPM10),calAvg(dataSO2),calAvg(dataO3),calAvg(dataNO2)];
state ++ ;
if(state>=2)
{
showCurrentTab();
}
}
}, 0.5);
}
function getWeatherData()
{
var method = 'GETCITYWEATHER';
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
getServerData(method, param, function(obj) {
data = obj.data;
if(data.total>0)
{
dataTemp.splice(0, dataTemp.length);
dataHumi.splice(0, dataHumi.length);
dataWind.splice(0, dataWind.length);
for(i=0;idata.rows.length;i++)
{
dataTemp.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].temp)
});
dataHumi.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].humi)
});
dataWind.push({
x: converTimeFormat(data.rows[i].time).getTime(),
y: parseInt(data.rows[i].wse),
d: data.rows[i].wd,
w: data.rows[i].tq,
marker:{symbol: getWindDirectionUrl(data.rows[i].wd)}
});
}
state ++ ;
if(state>=2)
{
showCurrentTab();
}
}
}, 0.5);
}

那么我们就可以继续搜索这个函数,但是在当前搜索不到他的函数定义式,所以我们去全局找,发现在这里
发现在这里

那么现在这个函数的出现的形式我们看不懂,所以我们要暴力破解他的这种形式,来到混淆js网站
https://www.bm8.com.cn/jsConfusion/
把这行js代码贴进去

那么现在我们只需要将这些东西拷贝到pycharm里面,让python去调用这两个函数就可以了
封装了一个js函数,来用pyhton代码执行js的函数
拷贝好的js文件里面加上这个函数
function getPostParamCode(method, city, type, startTime, endTime){
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
然后去python里面做这个操作
import execjs
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
#实例化一个node对象
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)
# print(params) #获取了动态变化的请求参数d
#进行ajax请求的发送
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
data = {
'd':params
}
code_text = requests.post(url,headers=headers,data=data).text
# print(code_text)
#对加密的响应数据进行解密
func_name = 'decodeData("{0}")'.format(code_text)
text = ctx.eval(func_name) #进行解密函数的调用
print(text)
这样就找到了我们需要的数据
本文来自投稿,不代表文案馆立场,如若转载,请注明出处:https://wpbbw.com/4613.html