利用python与nodejs爬取某眼电影票房数据

查看 140|回复 11
作者:Jackyyy   
#  爬取MY电影专业版票房数据
第一次发帖子,可能有做得不够好的地方,请各位大佬指点
主页:aHR0cHM6Ly9waWFvZmFuZy5tYW95YW4uY29tL2Rhc2hib2FyZA==
难点:
* Js参数逆向
* 字体反爬
#############################参数逆向###################################
首先通过浏览器F12抓包看到**接口**是:aHR0cHM6Ly9waWFvZmFuZy5tYW95YW4uY29tL2Rhc2hib2FyZC1hamF4
多次请求后发现,params里面的`timestamp`, `index`, `signKey`三个参数不断发生变化,即需要对这三个参数进行逆向,接下来分别观察这三个参数的值
1. timestamp:根据英文释义,是时间戳,而且数据是13位的
2. index和signKey:首先打下XHR断点,然后进行调试,,然后找堆栈,可以看到有个setTimeout用于刷新,所以从之后的eval函数里开始寻找,打上断点,开始单步调试
进入到这个函数之后可以看到这里有对uuid的赋值,打上断点,看看复制之后得到了什么
可以看到 i 通过了` _veri.getQueryKey`函数进行赋值,而且其中也有我们所需要的index和signKey元素,所以进一步跟进这个函数里
在这里我们终于看到我们想要的东西的,timestamp和index还有signKey都在这里有构造说明,只需要一一cv出来就好了,在nodejs环境下调试,缺啥补啥即可,最终代码是
[JavaScript] 纯文本查看 复制代码window = this
window.btoa = function (ms){return Buffer.from(ms).toString('base64')}
CryptoJS = require('crypto-js')
_0x590248 = _0x4302
var o = _0x590248
var i = {
    method: "GET",
    timeStamp: +new Date,
    "User-Agent": window.btoa("" + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'),
    index: Math[o(250)](1e3 * Math.random() + 1),
    channelId: 40009,
    sVersion: 2,
    key: o(260)
}
console.log(i)
function _0x2517() {
    var e = ["keys", "log", "GET", "171WtxWGj", "floor", "6586595KrzIYt", "=''", "4INeORR", "reduce", "303256TgflDf", "1818384COzaid", "2216650lRBaPR", "error", "signKey", "A013F70DB97834C0A5492378BD76C53A", "userAgent", "replace", "11nRKPzJ","7tyVrdd", "method", "380474DWSxHC", "4045278HuYQWq", "5921330tHaNcD"];
    return (_0x2517 = function() {
        return e
    }
    )()
}
function _0x4302(e, t) {
    var r = _0x2517();
    return (_0x4302 = function(e, t) {
        return e -= 246,
        r[e]
    }
    )(e, t)
}
var d = Object[o(246)](i)[o(254)](function(e, t) {
                var r = o;
                return e = 0 === i[t] || i[t] ? e + "&" + t + "=" + i[t] : e + "&" + t + r(252)
            }, "").slice(1)
function params() {
    result = [i.index, CryptoJS.MD5(d.replace(/\s+/g, " ")).toString(), i.timeStamp]
    return result
}
注意:
1. _0x2715的里的var e不知道为什么在浏览器环境执行的时候会自动偏移了4位,但在我的nodejs里面会不变,导致来了o(xxx)的结果会有所不同,所以顺序我进行了手动调整
2. result里面有三个参数,分别对应着index,signKey和timestamp,因为请求的时候这三个参数是一起构造的,所以我们也要在js文件里一起构造三个参数,而不是一个在python文件一个在js文件,这样子python文件在调用js文件的时候会有时间差,请求就会失败
3. 在构造signKey的时候可以看到使用了MD5算法,所以我就直接导入了一个crypto-js包来替代了,让代码可以简洁一点
三个参数全都已经被我们逆向构造出来了,接下来就可以去请求数据了
#############################字体反爬###################################
先上代码:
[Python] 纯文本查看 复制代码import io
import re
import jsonpath
import requests
import execjs
from faker import Faker
from fontTools.ttLib import TTFont
from PIL import ImageFont, ImageDraw, Image
import ddddocr
class MaoyanSpider(object):
    def __init__(self):
        faker = Faker()
        self.url = 'aHR0cHM6Ly9waWFvZmFuZy5tYW95YW4uY29tL2Rhc2hib2FyZC1hamF4'
        self.headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Pragma": "no-cache",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin",
            "User-Agent": faker.chrome(),
        }
        self.ocr = ddddocr.DdddOcr()
    def get_pages(self):
        with open('./maoyan(1).js', 'r', encoding='utf8') as f:
            parse_js = f.read()
        result = execjs.compile(parse_js).call('params')
        index = result[0]
        signKey = result[1]
        _time = result[2]
        params = {
            "orderType": "0",
            "uuid": "185df28337fc8-01871e640a76dc-26021151-144000-185df28337fc8",
            "timeStamp": _time,
            "User-Agent": "TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMS4wLjAuMCBTYWZhcmkvNTM3LjM2",
            "index": index,
            "channelId": "40009",
            "sVersion": "2",
            "signKey": signKey,
        }
        response = requests.get(self.url, headers=self.headers, params=params)
        return response
    def parse_font(self,response):
        # 找到字体文件
        txt = response.json()['fontStyle']
        fontfile = 'https:' + re.search('opentype"\),url\("(//.*?\.woff)"', txt).group(1)
        # 获取字体文件
        resp = requests.get(fontfile)
        with open('temp.woff', 'wb') as f:
            f.write((resp.content))
        ttfont = TTFont('temp.woff')
        # 返回字形名称的列表,以其在文件中的顺序排序
        fontlist = ttfont.getGlyphOrder()[2:]
        # 实现字体的对应关系
        charlist = [] # 定义了实际字体的列表
        # 加载字体文件
        font = ImageFont.truetype('temp.woff', 40)
        # 将fontlist里的字体通过画图画出来,然后用ddddoc来识别
        for uchar in fontlist:
            unknown_char = f"\\u{uchar[3:]}".encode().decode("unicode_escape")
            # 对字体坐标进行读取,并且转换为黑白,图像为RGB
            im = Image.new(mode="RGB", size=(42, 40), color="white")
            # 创建一个可以再给定图像上绘制的图像对象
            draw = ImageDraw.Draw(im=im)
            # text要绘制的文本,fill用于文本的颜色,font是一个ImageFont实例
            draw.text(xy=(0, 0), text=unknown_char, fill=0, font=font)
            # 二进制的文件,在内存中读写的bytes
            img_byte = io.BytesIO()
            im.save(img_byte, format="JPEG")
            # dddddor进行图像的识别
            charlist.append(self.ocr.classification(img_byte.getvalue()))
        return charlist, fontlist
    def show(self, response, charlist, fontlist):
        moviename_list = jsonpath.jsonpath(response.json(), "$.movieList.data.list..movieInfo.movieName")
        boxSplitUnit = jsonpath.jsonpath(response.json(), "$.movieList.data.list..boxSplitUnit.num")
        # 映射完以后就可以进行字体的替换了
        for j in range(len(moviename_list)):
            rstr = ""
            number = boxSplitUnit[j].split(';')
            for i in number:
                if i == "":
                    continue
                tmp = "uni" + i.replace("&#x", "", 1).replace('.', '').upper()
                for k in range(len(fontlist)):
                    if tmp == fontlist[k]:
                        if '.' in i:
                            rstr = rstr + '.' + charlist[k]
                        else:
                            rstr = rstr + charlist[k]
            print(moviename_list[j], rstr)
if __name__ == "__main__":
    spider = MaoyanSpider()
    response = spider.get_pages()
    charlist, fontlist = spider.parse_font(response)
    spider.show(response, charlist, fontlist)
这里我分别构造了3个函数,get_pages()获得页面,parse_font()解析字体, show()展示
get_pages不用多说直接构造好headers,url,再引用js文件获取我们需要的参数即可
当我们获取到页面以后,我们可以看到有一些数据是乱码
这就涉及到了字体反爬,在parse_font部分,我们可以通过浏览器F12的font模块里看到字体文件,看到有很多,而且是随时间不断刷新的,那么很有可能是在我们原本的接口里会有说明用哪个字体文件
果然,我们通过全局搜索可以看到在接口的fontStyle包含了字体文件的url,我们就可以通过请求字体文件获得字形的名称以及顺序,我们需要一一将其与真实的字体对照起来,我用的方法是先读取字体文件,将里面的字体通过PIL库画图画出来然后用ddddocr进行图像识别。识别完以后就可以按顺序返回真实的字体列表了
最后,通过show()把乱码替换成真实字体并且打印出来,这样就成功展示我们需要的数据啦
成果:
请大佬们指点一二:lol, 你们的宝贵意见都是我进步的阶梯

字体, 文件

aqin15   

挂服务器上会有滑动验证,怎么解决
Jackyyy
OP
  


aqin15 发表于 2023-3-26 10:55
挂服务器上会有滑动验证,怎么解决

确实会出现滑块验证的问题,目前我还没有能破解掉这个滑块的水平,之后会不断完善,谢谢大佬提醒~
不过我想的是它并不需要登陆,所以应该只是检测UA以及访问的IP,所以不断更换ua以及代{过}{滤}理ip我想应该能绕过
Jackyyy
OP
  

第一次发帖,不太会嵌入代码来进行展示,只能以图片的形式放上来了,请多多包涵
Jackyyy
OP
  


Jackyyy 发表于 2023-3-24 10:26
第一次发帖,不太会嵌入代码来进行展示,只能以图片的形式放上来了,请多多包涵

输入框上面的按钮有代码框,可以点击输入一下,会好看很多,论坛还支持markdown格式,会更好看一些。
Jackyyy
OP
  


Hmily 发表于 2023-3-24 12:20
输入框上面的按钮有代码框,可以点击输入一下,会好看很多,论坛还支持markdown格式,会更好看一些。

好的好的,我看到了!!谢谢你 这就去修改一下我的贴子
lande199   


Jackyyy 发表于 2023-3-24 13:18
好的好的,我看到了!!谢谢你 这就去修改一下我的贴子

那几幅多余的图点击上传图片那可以删除。
wangl147   


Hmily 发表于 2023-3-24 17:51
那几幅多余的图点击上传图片那可以删除。

好的,我再去修改一下~
yaphoo   

学习一下
科西嘉滕   

感谢分享
您需要登录后才可以回帖 登录 | 立即注册

返回顶部