记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了

这篇文章记录一下我跟一个网站的恩怨纠葛,为了爬这个网站,不断学习新知识,不断尝试,水平提高了不少。总算有点成就了,这里做一个记录,当然还是不完美,期待未来可能技术更精进,能有更好的方法吧。

这个网站是:aHR0cDovL3NkLmNoaW5hdm9sdW50ZWVyLm1jYS5nb3YuY24vc3Vic2l0ZS9zaGFuZG9uZy9ob21l

读者可以自己解码(后面的爬取过程还是有很多提示,不会解码也没关系,可以看后面的一些截图)。

说起这个网站,跟它的缘分应该是从好几年前开始,那时候需要写一篇志愿服务的论文,正好看到这个网站,只不过——那时候网站是静态网页,爬取静态网页的技术我还是有的,可以说没费什么力气,只不过花些时间。目前这个网站的静态版本还是可以在网上看到的。也就是说,这个网站正在经历改版,从静态网页改成动态网页。静态网页的网址是这个。

aHR0cHM6Ly9zZC56aGl5dWFueXVuLmNvbS8=

比较一下两个网站的界面

动态版本

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第1张图片

静态版本

 记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第2张图片

我们不是网站运维人员,当然也不知道他们怎么怎么做出来这两种效果。我估计是同一个sql的数据库吧。只不过两个页面系统,数据应该是做了迁移,或者就是前端的两种渲染方式。静态网页和动态网页所用的数据应该是一样的。

几年前爬取数据的时候,只是用了简单的requests,我的目标是爬取一些志愿者服务的时长,比如像这样的页面。

 记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第3张图片

 要进入到这个页面,需要每一个项目都点进去,然后再点击时长公示。当然在静态页面的网页系统下,这个时长的统计表格是也页面一起发送的,发包的时候,是一个大包,里面有时长的table,只需要把这个table提取出来就可以。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第4张图片

 可以直接提取table里面的tr,剩下的问题就是保存数据的问题。

暑假里面没事的时候又想用这个网站的数据,本着避免麻烦的思想,我还是使用传统套路,爬取这个网站的数据。可是后来发现这个网站的数据非常多,爬起来很慢,而且相当的麻烦。主要问题是:

你不知道哪一个志愿者组织是否有项目,也不知道有几个项目,同时也不知道每一个项目是否有记录时长。而且你也不知道每一个项目的时长是几页的。每次都要做很多的判断,很多的try,except。相当麻烦,而且爬取下来数据后还要存。本来我是想着以组织为单位,每一个志愿者组织一个字典。然后把组织的信息,项目的信息都存在这个字典里。但是也很麻烦。

后来下定决心,放弃之前的静态网站,从动态的入手,毕竟动态的网站有一个优势吸引我,就是返回的数据都是json。免得我一步一步的处理。而且暑假里面也学了不少js逆向的知识,这个网站的请求参数是加密的,试试用逆向解密这个网站的加密参数,然后请求,拿到数据。

#想法还是很简单,但是实际做起来,一点也不简单。

接下来开始一步步的踩坑之旅。

首先第一步,我准备爬取这个网站的志愿队伍,

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第5张图片

 点击进去,可以看到一些条件筛选界面,如果不加筛选,默认是返回的全山东省的志愿队伍。

比如下面这样。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第6张图片

 我想这次把爬取数据的范围缩小一些,只爬取烟台市的。

比如这样。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第7张图片

 在地区里面,选择烟台市,然后选择各个区。

这一步需要用到js逆向了。

刷新一下网页,发现还需要重新点击选择地区,

再点击两次地区之后,发现这个网站的返回结果里面有几个query。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第8张图片

 上图是开发者工具里的网络面板,搞爬虫的应该不陌生。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第9张图片

 点击这个query,发现返回的都是我们想要的数据。

接下来要做的就是我们看看请求头怎么。

果然,动态的网站虽好,但是加密了就不那么友善了。

可以看到这个query请求是需要携带参数的。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第10张图片

 这个bean参数,可以看出一大堆。初步判断,肯定不是简单的md5。所以放弃幻想吧,撸起袖子加油干吧。

我们看看源码吧,找找这个加密过程,看看能不能逆向出来这个bean。

搜索bean,发现只有一个文件,这还是不错。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第11张图片

 进入这个js,发现bean有8个,也很容易定位的。

还不算难,先尝试在第一bean的地方打上断点。

var o = {
                    bean: __WEBPACK_IMPORTED_MODULE_3_babel_runtime_core_js_json_stringify___default()({
                        encryData: getSM4().encrypt(i)
                    })

这段代买很可疑。打上断点,继续跑一下。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第12张图片

很顺利的断住了。这里可以看出,e是 请求网址的部分内容。t是请求的原始参数,i是处理后的t,不过i还有一些其他内容,

加密过程主要是 

 encryData: getSM4().encrypt(i)

这个getSM4函数,应该就是加密库。(后来才知道,这个SM4的加密方法应该是国产的一种加密方式,跟标准加密方式是不一样的。)我当时要是简单地认为,这个加密过程不是很复杂,点进去getSM4这个函数,看了一圈,也是没看太明白。主要是因为这个函数的代码是webpack打包的, 我逆向也就刚入门,对于分析这种webpack的代码还是很头大,上一个函数很简单,就是那个,

__WEBPACK_IMPORTED_MODULE_3_babel_runtime_core_js_json_stringify__

我初步判断这个函数就是一个JSON.stringify()函数,但还是困难就在下面的加密函数。

挑战一下,点进去看一下吧。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第13张图片

 点进去这个app.b21af4文件。

 记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第14张图片

 一看这种形式,好像也并不难,没有混淆,只不过是一般的webpack打包,算是很友好了。我觉得对于大神来说,这个可能就是一般的扣代码过程,奈何我的抠代码技术不是很过关。目前只能分析,这个getsm4函数是一个大的加密库。

其实这个跳转并没有调出原来的文件,还是原来的那个app的js文件。

折叠一下代码,

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第15张图片

 这个getsm4应该是在一个大的包里面,包结束的位置是 L2RF,再网上找,

最后找到

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第16张图片 

 这个var的地方,应该就是包的开始部分吧。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第17张图片

 理论上来说,这个函数应该是可以抠出来的。

只不过我想了一个取巧的方法,想直接通过标准加密还原,当时是发现这条路走不通的。后来放弃了。虽然没成功,还是把过程贴出来吧。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第18张图片

 因为进去这个getsm4,发现有几个熟悉的东西,key,iv,mode,猜测这应该是一个aes,

我也没犹豫,直接上标准加密,

拿到函数里面的i,不是可以直接出来加密结果了吗?当时是这样想的。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第19张图片

 ok,结果出来了。

是不是这个呢,我们把请求发出去,看看下面的query的参数是什么?

结果发现不一样,悲剧了,上面的标准加密的结果,明显和下面的esff2A这个结果不一样。

后来在B站里面也请教了一些大神。经常看他们的视频,给3个大神冲了一个月的电,发现充一个月也才6块,3个就18块,这我还是花得起。静等他们的回音。后来有一个大神回了我信息,他说他也看了,确实不是标准加密,但是他比较忙,没时间帮我抠代码,建议我使用jsrpc或者是selenium。另外两个大神到现在还是没有回音,一方面可能是因为我爬取的这个网站是.gov结尾的,大神也怕惹麻烦,另一方面可能他们也太忙吧,毕竟6块钱不是很多。

JSRPC过程

好在毕竟有热心的。跟那个回复的大神交流了以下,觉得他说的也行,不行咱就试一下jsRPC。一直觉得rpc比较高大上,不敢入门。怕自己水平达不到啊。

从网上找了一些rpc的教程看了,也在B站看了看jsrpc的视频。先看的其中一个大神的,他可能使用的是比较老的框架,看他用的很熟练,奈何我模仿他的操作方式,还是不行。后来无脑在网上乱搜一气,找到了 github上的一个黑脸怪的jsRpc框架。又看了一个视频介绍使用这个框架爬取建筑市场的一个数据,反复操作了几次,基本熟练了。接下来开始移植到这个志愿者网站上。

我的理解是jsrpc就是一个添加网络进程的方式,比如平时我们访问网页,主要走的是http过程,我们访问网页,服务器返回数据,这里面只用到了一般的网络通信。但是jsrpc的方式就是在我们进行一般网络通信的时候,再开一个进程,这个进程是实时的跟踪我们浏览网页的过程,但是我们还可以在这个新开的进程里面做一些其他的事。比如和服务器交换一些参数。类似于这样一个过程,比如我们去看一个演出,我们看就是观众,舞台上演什么我们就看什么,这个过程里面,我们自己和普通观众都是一样的。但是jsrpc就相当于,我看演出的时候,走到前排,塞给演员一个纸条,说,小姐姐,你有没有微信啊,加我一个微信吧,晚上约你吃饭吧,或者是美女,你这个演出其中有一个动作,可以这样做,飞吻可以飞出去的更妩媚一些,等等了。这个过程我们不仅仅看演出,还在另一过程里面跟台上的演员互动。达到我们的目的。

话不多说了,开始。

先去github把黑脸怪的框架下载下来,其中有一个localhost,是一个可执行文件,需要单独运行。

按照他的步骤一步步操作,应该没有啥困难。

1. 线运行localhost

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第20张图片

 这个界面黑漆漆一片,啥也没有,不用担心,后面会有显示的。

2. 开启一个新的进程,相当于我们现在要给舞台上的演员递小纸条了。

首先你得开启一个通道,比如走到前排,这个开启通道的过程,大神已经给我们写好了,直接复制就可以。

resource里面有一个dev.js

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第21张图片

整个文件全部复制,当然你也可以看看,里面的代码基本上是开启通信过程的各种函数,比如发送消息,接收消息。

 记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第22张图片

 粘贴到控制台,这相当于已经有了一个政策通道了。也就是说,这个作用是什么呢?就是出台一个政策,说,以前舞台上的演员只是表演,下面只是观看,现在有一个新玩法,就是可以观众给演员提意见。这个文件就是相当于告诉观众和演员,有了一种新玩法。

接下来,我们要为自己开启一个单独的通信,我们希望给我们单独开一个小窗私聊。

 var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&name=hlg");

 这一句相当于开启私聊,我们的私聊群组就叫zzz,我们的代号就是hlg。

然后我们通过这个demo的私聊窗口给服务器,传递信息。就是提出我们的要求。

注意:这里的操作,都是需要再浏览器的非调试状态下开启的。如果还在断点的时候,是不行的。就相当于,你得等人家演员至少表演完一个节目啊,不能毫无征兆的中间就打断,说咱们有了一个新玩法,这多不礼貌。

好了,接下来,我们要往小窗里发送一个东西了。就是我们希望服务器给我们返回他的 getSM4函数的结果。

demo.regAction("hello3", function (resolve,param) {
    //这里还是param参数 param里面的key 是先这里写,但到时候传接口就必须对应的上
    res=getSM4().encrypt(param["i"])
    resolve(res);
})

这个hello3就是我们的小窗标题,我们想的是,服务器给我们返回我们的参数i的处理结果,这个处理的过程就是getSM4。

注意:这里我其实也有一个疑问,就是这个小窗私聊的过程是应该什么时候发送。其实我觉得应该是在调试的过程中发送,就是表演过程中。我也是一直这么做的。

 我们先开一个小窗,显示rpc连接成功。

然后进入我们的断点,

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第23张图片

 把我们的regAction函数复制到控制台,这样就算是注入成功了。接下来,就可以等待服务器返回我们想要的结果,结果是保存到res里面,我们在爬虫的过程,使用这个res就可以了。

过掉之前的断点。接下来,就是使用python调用刚才的res了。

直接上代码了。

import requests
import json

#  var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&name=hlg");
# {"areaId":"370602000000000000","pageNum":3,"pageSize":12,"albe0002":"","albe0017":"","albe0005":"","albe0041":"","albe0026":1,"albe0056Start":"","albe0056End":"","albe0046Start":"","albe0046End":""}


'''
demo.regAction("hello3", function (resolve,param) {
    //这里还是param参数 param里面的key 是先这里写,但到时候传接口就必须对应的上
    res=getSM4().encrypt(param["i"])
    resolve(res);
})

'''


dt_list = []
for i in range(1):

    i = '{"areaId":"370691000000000000","pageNum":' + str(i+1) + ',"pageSize":12,"albe0002":"","albe0017":"","albe0005":"","albe0041":"","albe0026":1,"albe0056Start":"","albe0056End":"","albe0046Start":"","albe0046End":""}'

    url = "http://localhost:12080/go"

    data = {
        "group": "zzz",
        "name": "hlg",
        "action": "hello3",
        "param": json.dumps({"i":i})
    }
    print(data["param"]) #dumps后就是长这样的字符串{"user": "\u9ed1\u8138\u602a", "status": "\u597d\u56f0\u554a"}
    res=requests.post(url, data=data) #这里换get也是可以的
    print(json.loads(res.text)['data'])

    result = json.loads(res.text)['data']

    # cookies = {
    #     'SF_cookie_73': '23217056',
    #     'http_waf_cookie': '8cdae343-ec71-4123e94002750b074357af42a14b10c968c6',
    #     'SF_cookie_135': '42503913',
    # }

    cookies = {
        'http_waf_cookie': '8cdae343-ec71-4123e94002750b074357af42a14b10c968c6',
        'SF_cookie_73': '23906175',
        'SF_cookie_135': '42503913',
    }



    headers = {
        'Origin': 'http://sd.chinavolunteer.mca.gov.cn',
        'Referer': 'http://sd.chinavolunteer.mca.gov.cn/subsite/shandong/group',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.0.0',
        'deviceid': 'cb5c1906-70b1-4a7d-bbf7-ab46b5f50fcc',
        'devicetype': 'web',
        'ip': '2001:da8:7018:1111:5c7f:9624:d218:ca8f',
        'token': 'null',
    }
    print("\n"*2)
    print('{"encryData":' + '"' +  result + '"}')

    data = {
      'bean': '{"encryData":' + '"' + result + '"}'
    }



    # data = {
    # '{"encryData":"'+ ' ' + result + '"}'
    # }

    # data = {
    #   'bean': '{"encryData":"ugYbgxxHxcIFFYezD0TvCbP/B04JNieRjbRvT8Ww99NQGbXi8h5Kn/qrCqzspUE01ujy5ciNOmRRGl5dzjkqIIMv4JDWZQBKDkCwtFEIznuDWb1vJ3H8a3B4GLtl2xXJK7GFYVd3/dc5TmUDfETP/NRwk1oi1wTgFeZbYA2K/WiFQvrX8EaY1da374f6F4MHTEIPU7x8hz8hWFsko2tr1OoQ5KllYQR1+FDcimLqemJxWue01Fkgm4vn1hytfeIrYHWsVfEF/07krVTqpckVbg=="}'
    # }


    resp = requests.post('http://sd.chinavolunteer.mca.gov.cn/nvsidfapis/NVSIDF/restservices/webapi/queryTeamPageWeb/query', headers=headers,cookies = cookies,  data=data, verify=False)
    print(resp.json())
    dt_list.append(resp.json())

print("=============      ************            =================")
print(dt_list)

with open("team_Yantai_gaoxinqu.json", 'w', encoding='utf-8') as f:
    f.write(json.dumps(dt_list, ensure_ascii=False))

运行一下,可以看到返回的加密参数,也可以看到我们的请求结果。

记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第24张图片

可以看到这个7kpM2开头的就是我们的加密参数,结果也符合我们的要求。 

 记录一个爬虫过程,从基础爬虫到逆向,再到jsrpc,再到selenium,啥都包括了_第25张图片

至此,我们使用jsrpc已经解决了我们爬取组织的问题。

文章先写到这里,下一篇再介绍,selenium使用和har吧。因为担心文章会有太多信息,通不过审核,我就先写到这里吧。 

你可能感兴趣的:(爬虫,selenium,测试工具)