python爬虫四

异步爬虫

异步是在同一时间点多个任务共同进行,爬虫是抓取互联网数据,那么异步和爬虫能碰撞出怎样的火花?

提到异步后端开发的朋友们的反应肯定是线程协程等一系列概念,那么首先让我们介绍基于多线程的爬虫。

1.基于多线程的爬虫

既然我们要开启多线程那么就不得不用到线程池了,接下来我将用一个实例简单地介绍多线程爬虫的原理

#首先我们基于flask框架开放一个模拟网页(flask是一个前后端分离的web框架,具体是啥大家不用深究)
from flask import Flask
from flask import render_template
import time

app = Flask(__name__)


@app.route("/aa")
def test1():
    time.sleep(1)
    return render_template("test.html")

@app.route("/bb")
def test2():
    time.sleep(1)
    return render_template("test.html")

@app.route("/cc")
def test3():
    time.sleep(1)
    return render_template("test.html")

if __name__ =="__main__":
    app.run()

#之后就让我们基于线程池来爬取我们自己开放的网页数据
from multiprocessing.dummy import Pool   #导入线程池的模块
import requests
from lxml import etree
import time

start = time.time()   #计算开启时间

urllist = [
    "http://127.0.0.1:5000/aa",
    "http://127.0.0.1:5000/bb",
    "http://127.0.0.1:5000/cc",
]   #一个存放了目标url的容器

pool = Pool(3)#实例化一个三个空间的池子

def get_data(url):

    return requests.get(url).text

page_text_list = pool.map(get_data,urllist)   #将执行函数和被执行容器放如池子得到一个详细信息的容器

def get_tag(page_text):

    tree = etree.HTML(page_text)
    res = tree.xpath("//div[@class='tang']//text()")
    return res

path_data = pool.map(get_tag,page_text_list)   #爬取详细信息

print(path_data)
print("总计耗时%s"%(time.time()-start))

上述就是一个简单的多线程爬虫实现,没有深入了解的原因是性能损耗高,不知道大家有没有了解过自己的计算机是几核几线程的(本人的5年前游戏本本是双核双线程),这就意味着我们在开启线程的时候要考虑计算机性能,否则蓝屏可就不是特别美好了。那么接下来我将为大家介绍一种更加符合昌黎的方法,协程爬虫。

异步爬虫之协程:

线程不只是在资源占用上存在问题,在发生io阻塞的时候也会出现一定程度上的效率不高问题,所以我们就需要一种方式来解决多线程或者多进程造成的io阻塞。如果之前有看过我博客或者其他方式了解过相关知识就可以知道,我们可以通过发生阻塞的时候手动将当前任务挂起并执行其他任务的方式来欺骗cpu,让其一直处于一种高效率的工作方式中。那么python为我们提供了asyncio模块解决了这一问题,同时需要注意的是这一模块本身是没有发送网络请求的能力的,如果想要实现基于其爬虫功能需要使用另一个模块aiohttp相配合。

目标:爬取cnblog几个博文的标题

import asyncio
import time
import aiohttp
from lxml import etree
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
urls = [
    'https://blog.csdn.net/Jmilk/article/details/103218919',
    'https://blog.csdn.net/stven_king/article/details/103256724',
]
#url池

async def async_get_url(url):
    '''定义一个特殊的函数,当函数被如此定义的时候代表协程函数'''
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'
    }
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64,verify_ssl=False)) as session:  #这里需要进行一步上下文解释,这样才能再下面的代码中使用到声明的变量,同时需要关闭证书验证
        async with session.get(url, headers=headers) as r:   #发送请求
            html = await r.read()   #每一个可能发生阻塞的地方都需要await来标注
            title = etree.HTML(html).xpath('//h1[@class="title-article"]/text()')[0]
            print(title)


def async_main():
    loop = asyncio.get_event_loop()    #事件循环的对象
    tasks = [async_get_url(url) for url in urls]   #任务对象
    loop.run_until_complete(asyncio.wait(tasks))   #启动任务循环
    loop.close()


if __name__ == '__main__':
    start = time.time()
    async_main()
    print(f'cost time: {time.time() - start}s')

异步爬虫执行的效率要更高,并且我们在进行数据爬取的时候任务量往往是很大的,所以异步爬虫似乎是更好地抉择。但是我们选择python这门语言有很大一部分原因是因为其强大的第三方库,所以这么复杂且底层的操作肯定会有人封装好供我们使用,当然这些要之后在介绍了。

js加密,混淆,逆向

从字面上意思就可以理解就是在js中进行数据的加密以及对关键函数的混淆,而我们要做的就是反向将js解析出来。在这里要提前说明一个问题,下面的东西可能对没有web基础的小伙伴没有那么友好,所以大家还是要接触一下web的,如果感觉我这里的文字讲解有些难以理解,可以观看晓波老师的讲解视频,非常详细https://www.bilibili.com/video/BV1EE411s7ED?p=1

我们从一个实例来讲解js相关知识,当然混淆这方面我们没有设么办法去直接解决,需要借助第三方工具,加密又可以有非常多的拓展,我们先浅显的接触一下。

爬取目标地址:https://www.aqistudy.cn/html/city_detail.html,这个网站不知道是不是后来出问题了,导致我们无论是前端显示还是拿到的数据都不正确。

爬取过程:首先我们到目标网站先做一波分析,我们再其页面进行解析的时候并没有直接在页面上获得我们想要的信息,那么大概率就是ajax发送了,所以我们先进行抓包,看我们在刷新的时候会获得什么样的数据包。通过network抓取了两个非常想相似的数据包,我们知道只要知道其中一个就可以了。

当我们点击搜索的时候会收到两个数据包,打开数据包我们收获了以下几条较为重要信息

—请求目标地址:https://www.aqistudy.cn/apinew/aqistudyapi.php

—请求方式:post

—携带参数:d(通过几次尝试我们发现这个携带参数是一个动态变化且加密的一串数据)

—相应数据:一大串火星语(一串加密了的字符串)

那么相应问题就来了,首先我们post携带的参数我们不能直接复制粘贴,其次返回的相应数据根本不能直接阅读,那么我们该如何去做?

解决过程:参数d既然每次请求都是变化的,那么说明我们是在前端按照一定规则生成的参数,只要找到这个规则(函数)那么问题就解决了啊,首先我们找到搜索图标所对应的点击方法,取其中找到发送的ajax,因为我本身是不太懂php的所以看不太懂,偷了个懒使用火狐浏览器可以直接定位到对应的事件,接下来就是逐层寻找ajax的过程了,最后我们发现ajax方法存在于一个getSelectData的函数里,但是当我们去寻找这个函数的时候发现我们得到的是一大段乱七八糟得东西,这就是js混淆。我们只能将整段复制下来利用线上的js反混淆工具来进行破解。终于我们得到了ajax发送所需要的加密函数和响应数据的解密函数。

最后我们只需要面对一个问题了,我们使用python写爬虫那么js该如何运行?

最简单的方法就是将js语句转换为python语句,可惜我相信大部分看到我这篇文章的人都没有这个能力,我也没有,那么就该使用python强大的第三方库了,大神们为我们提供了一个插件可以让我们在python中调用js方法。

- PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。我们需要pip install PyExecJS对其进行环境安装。

  - 开始执行js:

    - 1.将反混淆网站中的代码粘贴到jsCode.js文件中

  • 2.在该js文件中添加一个自定义函数getPostParamCode,该函数是为了获取且返回post请求的动态加密参数

  • 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);}

     

  • 3.书写py文件:

  • 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'
     
    # 启动js
    file = 'jsCode.js'
    ctx = node.compile(open(file).read())
     
    # 向方法传递参数
    js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
    params = ctx.eval(js)    #执行js
    
    #发起post请求
    url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
    response_text = requests.post(url, data={'d': params}).text
    
    #对加密的响应数据进行解密
    js = 'decodeData("{0}")'.format(response_text)
    decrypted_data = ctx.eval(js)
    print(decrypted_data)

     

posted @ 2020-10-06 14:07  TopJocker  阅读(180)  评论(0编辑  收藏  举报