爬虫基础(异步爬虫,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:bm8.com.cn/jsConfusion/
    • 进行了反混淆后,终于看到了ajax请求对应的代码,从代码中提取到的信息:
      • 动态变化的请求参数d是由一个getParam(method, object)返回的,且函数的两个参数就是getServerData的前两个参数
      • ajax请求道的加密的数据是通过调用 decodeData(data)进行的解密。
  • js逆向:想要使用python调用js相关的代码。
  • PyExecJS模块:通过python调用js代码
    • 环境安装:
      • 安装nodejs开发环境
      • pip install PyExecJS

思路:

打开网站

aqistudy.cn/html/city_d

点击抓包工具,可以看到他的ajax是这样的,其中有一个d参数,是动态的


爬虫基础(异步爬虫,js逆向)

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


爬虫基础(异步爬虫,js逆向)

所以我们现在有两个难题,一个是要获取param的动态参数d,一个是要获取数据的解密公式。那么这两个东西都需要用js逆向去做,但是我们需要知道的是这两个东西是js的函数去操作的,所以我们去找js封装的函数。

这个网站何时会发送ajax呢,是点击这个搜索按钮的时候


爬虫基础(异步爬虫,js逆向)

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


爬虫基础(异步爬虫,js逆向)

点开event事件


爬虫基础(异步爬虫,js逆向)

我们现在知道了函数为getData(),我们去搜索一下哪里有getData方法

那么来到谷歌浏览器


爬虫基础(异步爬虫,js逆向)

那么这个时候我们看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逆向)

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

发现在这里


爬虫基础(异步爬虫,js逆向)

那么现在这个函数的出现的形式我们看不懂,所以我们要暴力破解他的这种形式,来到混淆js网站

bm8.com.cn/jsConfusion/

把这行js代码贴进去


爬虫基础(异步爬虫,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)

这样就找到了我们需要的数据

原创文章,作者:27149,如若转载,请注明出处:http://wpbbw.com/4613.html

发表评论

登录后才能评论