python3.7 爬去 web flex网站

一,背景

业务背景

模拟账号登陆,抓取 缴费周期 本期缴纳 缴费基数 等信息。这里可能会有国杠1573会杠:"为什么 自己知道账号还抓取这些数据有什么意义"。类似这种问题此处不做解释。这里只谈论抓取的思路和方法,具体哪家社保网站此处为了避嫌就不指名道姓了,以免给维护这家社保网站的同行造成困扰。希望大家就算知道是哪家网站也不要公布,大家都混口饭吃,都不容易。咳咳 ~~ 闲扯了。

技术背景赘述

由于该网站使用的是flex技术开发的web端 ,所以这里讲的方法理论支持所有flex网站的爬去。引用下摆渡的flex介绍:

Flex 是一个高效、免费的开源框架,可用于构建具有表现力的 Web应用程序,这些应用程序利用Adobe Flash Player和Adobe AIR, 可以实现跨浏览器、桌面和操作系统。虽然只能使用 Flex 框架构建 Flex应用程序,但Adobe Flash Builder(之前称为 Adobe Flex Builder)软件可以通过智能编码、交互式遍历调试以及可视设计用户界面布局等功能加快开发。
使用 Flex 创建的 RIA 可运行于装有 Adobe Flash Player 插件的浏览器中,或运行于跨操作系统的 Adobe AIR上,它们可以跨所有主流浏览器、操作系统实现一致的运行。通过利用 AdobeAIR,Flex应用程序可以访问本地数据和系统资源。Flex技术的三大组成部分:UI、数据、服务器技术介绍。从根本上说,Flex技术是表现层解决方案,像所有其他类似技术一样,表现层技术要解决三个基本问题:表现层界面展示和人机交互,客户端数据操作及服务器端数据交互和整合。Flex针对这三个根本问题提供了卓越的解决方案。
使用 Flex 创建的 RIA 可运行于装有 Adobe Flash Player 插件的浏览器中,或运行于跨操作系统的 Adobe AIR上,它们可以跨所有主流浏览器、操作系统实现一致的运行。通过利用 AdobeAIR,Flex应用程序可以访问本地数据和系统资源。Flex技术的三大组成部分:UI、数据、服务器技术介绍。从根本上说,Flex技术是表现层解决方案,像所有其他类似技术一样,表现层技术要解决三个基本问题:表现层界面展示和人机交互,客户端数据操作及服务器端数据交互和整合。Flex针对这三个根本问题提供了卓越的解决方案。

flex通讯协议使用的是 AMF 协议,所以我们下面的python3.7技术栈中会使用 amf 相关模块。

二,技术栈

语言:python3.7.4
ide工具:pyCharm
使用的模块:PyAMF2 (0.6.1.5)| pycryptodome (做AES加密用)
-----------2021年4月13日 更新----------------------------
这里安装 pyamf2 模块已经403、该模块已经改为 py3amf
直接:pip3 install py3amf 即可
------------------2021年4月13日 更新---------------------
分析工具:Charles (抓包)这里不讲这个工具的使用,需自行摆渡 。swf文件反编译 解压密码:python3
还有常用的模块这里不 一 一 说明安装了 看如下截图:
如果还有使用python2的同学这里可以使用这个模块 PyAMF (0.8.0),AES加密的自行网上查找对应py2的模块

image.png

三,分析

first of all

原理
一个http请求 会有一个响应 我们要做的是 接收基于amf协议响应数据的处理和模拟amf协议的请求,由于传输的是二进制流,这里不管他的使用的是什么方式传输,对于python的热火程度肯定有相关的模块来处理,这里使用了pyamf 模块帮我们封装流的操作,现在只需要关注要做的业务就好。我们只需要根据pyamf 模块的请求和解析方式来封装数据结构然后构造请求就行。当然模拟请求 添加header 头是少不了的 。后面我会 放出源码,展示这一个完整的过程。

secondly

1,打开要爬去的目标网站 F12打开调试模式 点击网络 注意观察请求次数和路径,打开 charles 软件 一般是打开默认就开启抓包。


charlesRecord.png

2, 开始输入账号密码登陆 直到登陆完成 按下 charles软件中的stop record 按钮
从上图可以看到这么几个amf请求,每一个amf 就可以理解为一次amf数据交互 ,


loginUrl.png

3,点击 onlineServiceActionPersonNormal.do 请求我们可以看到提交的表单数据这里有三个参数,可以猜测是账号,密码还有类型。注意这个请求响应码是302 说明重定向了。这里可以确定这一步是登陆,到这里我们要思考登陆了之后swf文件怎么知道我们当前用户已经登陆?这里的重定向到哪里去了?带着这两个问题到下一步
4,看到sessionId的东西直觉感觉有用先记一下,在浏览器请求的时候

一图介紹charles如何看

一图介绍.png

分析结果:

上图介绍了如何找到swf文件以及下载,下载完成后就可以使用我们文章中提到的 反编译工具去查看源码,从源码分析。我们知道加密方式为AES加密

image.png

在打开的目标网页的浏览器中F12 或者 mac版的chrome cmd + option+ I 打开浏览器调试。控制台中 输入此函数得到如下结果,从摆渡找一个在线aes加密的网页验证下加密方式和结果,从而得到加密的方式和类型如下截图:
image.png

image.png

上图16位加密结果一致有可能是 不足16位补全方法不一样造成的

上js运行结果和在线AES加密算法的值作比较。发现加密结果是一致的 说明目标网站加密算法和在线加密算法一致,那我们只需要找到密文去进行解密就可以拿到未加密的字符串。

image.png

经过一番查看反编译源码的努力我找到了他是加载其中的响应参数key来作为加密的密码。当然 反编译看源码的功劳少不了。通过这一步可以通过在线AES解码来获得未加密的字符串。拿到这串数字我们需要猜想 这个可能是 什么根据直觉我这里获取到的是个七位的数字。我猜想可能是个人信息的编码之类的。后来在基础信息模块点击查询 确实看到了这个七位数字。那么到这里就可以完全的写出来查询程序了。
image.png

知道这么些信息基本差不多可以模拟请求查询数据了

代码区

python3小白一枚,代码难看了点,期待python高手优化,重点参照思路.
代码环境文章开头有说 这里啰嗦一下 python3.7

写篇原创实属不易,如果对你有启发,我就心满意足了。可以的话谢谢各位朋友点个赞

import urllib
import http.cookiejar
from urllib import request
import uuid
import pyamf
import json, datetime
from pyamf import remoting
from pyamf.flex import messaging
import operator as op
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
from Crypto import Random
import base64


class httpBuilder:
    def __init__(self):
        self.header = None
        self.url = None
        self.postData = None


targetUrl = "http://www.xxxx.com/messagebroker/amf";
httpBuilder.url = "http://www.xxxx.com/onlineServiceActionPersonNormal.do"
httpBuilder.header = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "utf-8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Connection": "keep-alive",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0",
    "Content-Type": "application/x-www-form-urlencoded",
    "Content-Length": 94,
}
httpBuilder.postData = urllib.parse.urlencode({
    "normalPersonUserName": "username",
    "normalPersonPassWord": "password",
    "normalPersonUserType": "0"
}).encode('utf-8')

req = urllib.request.Request(httpBuilder.url, httpBuilder.postData, httpBuilder.header)
# 自动记住cookie
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))
r = opener.open(req)

print('重定向完毕获取 session :%s ' % r.url)
##截取获取 sessionId
sessionId = r.url[r.url.find('=') + 1:]
print('截取后字符串 sessionId : %s ' % sessionId)


#######html登陆结束

### AES 加密
def aes_encrypt(data, password):
    bs = AES.block_size
    pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs)
    cipher = AES.new(password.encode('utf-8'), AES.MODE_ECB)
    data = cipher.encrypt(pad(data).encode('utf-8'))
    return base64.b64encode(data)


# 这里需要查询到请求加密的 编号 以便用来生成查询的token
class userInfo:
    def __init__(self):
        self = {}


pyamf.register_class(userInfo, alias='flex.messaging.messages.CommandMessage')
getLoginUserSessionMsg = messaging.RemotingMessage(messageId=str(uuid.uuid1()).upper(),
                                                   clientId=None,
                                                   operation='getLoginUserSession',
                                                   destination='appLogin',
                                                   timeToLive=0,
                                                   timestamp=0)


class grzhInfo:
    def __init__(self):
        self = {}


pyamf.register_class(grzhInfo, alias='flex.messaging.messages.CommandMessage')
userNoMsg = messaging.RemotingMessage(messageId=str(uuid.uuid1()).upper(),
                                      clientId=None,
                                      operation='queryGrzhList',
                                      destination='infoPersonManager',
                                      timeToLive=0,
                                      timestamp=0)


class decodeInfo:
    def __init__(self):
        self = {}


pyamf.register_class(decodeInfo, alias='flex.messaging.messages.CommandMessage')
decodeMsg = messaging.RemotingMessage(messageId=str(uuid.uuid1()).upper(),
                                      clientId=None,
                                      operation='queryAa10_table',
                                      destination='aa10',
                                      timeToLive=0,
                                      timestamp=0)


## 查询应缴实缴
class queryXY:
    def __init__(self):
        self = {}


# 这里构造要查询的方法
pyamf.register_class(queryXY, alias='flex.messaging.messages.CommandMessage')
yjsjModelMsg = messaging.RemotingMessage(messageId=str(uuid.uuid1()).upper(),
                                         clientId=None,
                                         operation='queryAc20ByAac001',
                                         destination='infoPersonManager',
                                         timeToLive=0,
                                         timestamp=0)


def getRequestData(msg, MsgBody, reqType):
    msg.body = MsgBody
    msg.headers['DSEndpoint'] = 'my-amf'
    msg.headers['DSId'] = str(uuid.uuid1()).upper()
    # 按AMF协议编码数据
    req = remoting.Request('null', body=(msg,))
    env = remoting.Envelope(amfVersion=pyamf.AMF3)
    env.bodies = [(reqType, req)]
    data = bytes(remoting.encode(env).read())
    return data


# 获取响应源数据
def getResponse(data):
    req = urllib.request.Request(targetUrl, data, headers={'Content-Type': 'application/x-amf'})
    opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))
    return opener.open(req).read()


def getReslutList(response):
    amf_parse_info = remoting.decode(response)
    list = amf_parse_info.bodies[0][1].body.body['resultList']
    return list


def getKey(response):
    amf_parse_info = remoting.decode(response)
    keyList = amf_parse_info.bodies[0][1].body.body['key']
    for k in keyList.keys():
        return k;


def getUserNo(response):
    amf_parse_info = remoting.decode(response)
    list = amf_parse_info.bodies[0][1].body.body['resultList']
    for item in list:
        return item['aac001']

# 解析查询的数据值
def builderResult(response):
    amf_parse_info = remoting.decode(response)
    list = amf_parse_info.bodies[0][1].body.body['resultList']
    return list

# 获取身份证身份证号
reqData = getRequestData(getLoginUserSessionMsg, [sessionId], '/2');
responseData = getResponse(reqData)
resultList = getReslutList(responseData);
userId = None
for a in resultList:
    userId = a['aac002']

# 获取加密密钥
decodeReqData = getRequestData(decodeMsg, [], '/3');
decodeResponseData = getResponse(decodeReqData)
useKey = getKey(decodeResponseData);
print(useKey)
# 获取个人编号
# 获取 个人编号 aes加密 后的值
userIdenCode = aes_encrypt(userId, useKey)

#
userNoReqData = getRequestData(userNoMsg, [userIdenCode], '/3');
# 获得响应结果的身份证号
userNoResponse = getResponse(userNoReqData)
userNo = getUserNo(userNoResponse);
queryToken = aes_encrypt(userNo, useKey)
queryToken = queryToken.decode('utf-8')
print(queryToken)

# 这里才开始请求真正的数据 queryToken最重要 前面几个步骤就是为了拿它 后面这些数字就是常规的查询菜蔬
yjsjQueryParam = [queryToken,
                  "'11','12','14','15','21','31','32','33','35','36','41','51','61','1','2','3','4','5','302','301'",
                  "'10','20'", "'0','1'", "199001", "209912", False]
yjsjModelReqData = getRequestData(yjsjModelMsg, yjsjQueryParam, '/2');
yjsjModelResponse = getResponse(yjsjModelReqData)
yjsjResultList=builderResult(yjsjModelResponse);
for item in yjsjResultList:
    print(' 费款所属期 %s , 本期缴纳 %s , 缴费基数 %s ' % (str(item['aae003']),str(item['aac123']),str(item['aac150'])))

你可能感兴趣的:(python3.7 爬去 web flex网站)