Bladeren bron

gk airline first commit

3407802468@qq.com 5 maanden geleden
commit
e62137eb97

File diff suppressed because it is too large
+ 828 - 0
3.31GK/akm/akm_5.19.js


File diff suppressed because it is too large
+ 828 - 0
3.31GK/akm/akm_5.26.js


File diff suppressed because it is too large
+ 836 - 0
3.31GK/akm/arg_dvc_revert_env.js


+ 229 - 0
3.31GK/akm/decrypt_sensor_data.js

@@ -0,0 +1,229 @@
+// 解密 sensor_data 值的密文部分 --------------------
+
+
+dec = '"QBkFI91H;-p!E9Do85,f+i/Z%l^-c!a0d/(e.t<fPL:FL;TJYYP0KvDo<2v[5^8k"i"_)K"m"FZ#qfX#wri6D$ju&dWL3)sWNgo! E3X<Bc66$B2.A44c"N"<x?"<+tUPyZAXy)Y6`8}o0q"A>D"h_U"^Aa"v"iifq"6yC"R&[")56DLNH`PZ)"wv[-":"d+w0M@"BZh"!qz"r3:ny")>"&""x"D|B"+", #dIZyfMRIz7c"9Wj"vUD"E9bmYor"(1"_CqDQ"wy"*Eu<h$]`rkM 5tca2!"+9V"/"f"<:r"2dv"K}H#}@x!@"D("n"Ae"7"{zs"&"","M$_"6"$"S(T"3G"]"~"Tx2"B1%"]""j".nW";R%E_"R3U"z&-cu"Y+8R"v"EWHkB;CAkR8+9VRd"3"r9X"fJh-Ooe]Dr"u7`"K""l"v+k"9""M"W$D"-=k.L"<$z~"%G)Fh[Lvr*"btb"q":X"f"S6Q"W""$"(g-"90sWSG&Tz*e>`f"@%h"M+@xy"{X!"7"o} rx"lN%"<(3"{""Z"WJ}"R""e"Ul"`hKQ<F-"Z5^"G/5^%":tw]"8"5DE7c(lQ"2J7"#6 "U""X$8"29c"}l7 6"H;"gI7=33JB"G%)">3zNe"2/1"q""3"iVg"7"C^_p~W""E"L#:"1P9{%nb`J"n=j"LuA0|9FMT$)6w-t,7G?GAE0wIp+nUg]p3XF=dneq}YhmijU/DQN@/4dEVIfB:xM:=~<O;[fEw*EPL/a.9F%XbP=D[GN-{e2HyS7nGg%KxO^&~LlT*2H).%>.^fvotP{&GvV"e"3a0"H]oY9<2;#mf"w{KO"7"Fke#wf!3Lo^zd&O`FwyslaQ4fm.<el=-2fSRrN.~pfnt%#!"JGA"%{e"M"r.gB^"0A["{x4"[v^*y;"t>I;"pgBwo"5%-">SAJi"7)o&"Y""f"Q~@"x@cxj"0]"|"5_-%._6dp4f1y;+|RAq>S(nz-0UfJbvD%h86*hfrt=|C}mD1(j{v5U?+87d#My.QB[-mx@}<K92A##t4kyx[b|Vi|-g)q:,];QUq7os!dA rUVH"z*S"0SB"z!Jy["qM9W"p.lM)O3flQbP_O* R"yl,"k<qg>7uf5v,JDHMwX",t<"XGK]`oCpSy f#Y"%ic2v7aXPwC3*"z"y/ "A Tz{eES"n[D"["?"4"H?x"c""R"Ums"6"S#+"V"S4F"V"4"`9P"`5a"o"#H"Usy"XzMs"~<2PeH|qmcW/G[a"_iku>"z"Ni1]D|_>LuBY;gBADi,cwRgE`6SDYY<cma]: [t-bo<9B@X$l)Y=w9 hyS%H2.%H"=(NM7:]<"e}E"oh(z{H=T"W.J"?I"7T%"|(m:_t@qgu!"OM["E""*"xrz","$H".sp"kW~I"Y[U[u6j,-B,}QxqFw|"fKzG"z.82#"yrLd"f""@zu"em=",!+Z{".AF"k""6"U}|"c`0"ZnG"<~)f4*0N"<ZT"Jr9a("*O3"Pw)Ar6"-3TVJ1l"F"TFvw+".%3"f{Q&L"}""."lLr"a.-"orJu"'
+
+arr = [3359553, 1111998]
+console.log(decrypt(dec, arr))
+
+
+function decrypt(dec, num_arr) {
+    // mU 和 Dz:加密函数中使用的映射数组。
+    var mU = [
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            -1,
+            0,
+            1,
+            -1,
+            2,
+            3,
+            4,
+            5,
+            -1,
+            6,
+            7,
+            8,
+            9,
+            10,
+            11,
+            12,
+            13,
+            14,
+            15,
+            16,
+            17,
+            18,
+            19,
+            20,
+            21,
+            22,
+            23,
+            24,
+            25,
+            26,
+            27,
+            28,
+            29,
+            30,
+            31,
+            32,
+            33,
+            34,
+            35,
+            36,
+            37,
+            38,
+            39,
+            40,
+            41,
+            42,
+            43,
+            44,
+            45,
+            46,
+            47,
+            48,
+            49,
+            50,
+            51,
+            52,
+            53,
+            54,
+            55,
+            56,
+            57,
+            -1,
+            58,
+            59,
+            60,
+            61,
+            62,
+            63,
+            64,
+            65,
+            66,
+            67,
+            68,
+            69,
+            70,
+            71,
+            72,
+            73,
+            74,
+            75,
+            76,
+            77,
+            78,
+            79,
+            80,
+            81,
+            82,
+            83,
+            84,
+            85,
+            86,
+            87,
+            88,
+            89,
+            90,
+            91
+        ],
+        Dz = " !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+
+
+    function reverseFun1(bF9, num) {
+        // 将加密后的字符串拆分为数组
+        const UM9Reversed = bF9.split(':');
+        const n = UM9Reversed.length;
+
+        // 模拟加密过程,记录所有交换对
+        const swapPairs = [];
+        let currentW59 = num;
+
+        for (let hw9 = 0; hw9 < n; hw9++) {
+            // 计算第一个索引 hj9
+            const hj9 = (currentW59 >> 8 & 0xFFFF) % n;
+
+            // 第一次更新 W59
+            currentW59 = (currentW59 * 65793) & 0xFFFFFFFF;
+            currentW59 += 4282663;
+            currentW59 &= 0x7FFFFF; // 8388607 是 23 位掩码
+
+            // 计算第二个索引 JO9
+            const JO9 = (currentW59 >> 8 & 0xFFFF) % n;
+
+            // 记录交换对
+            swapPairs.push({hj9, JO9});
+
+            // 第二次更新 W59
+            currentW59 = (currentW59 * 65793) & 0xFFFFFFFF;
+            currentW59 += 4282663;
+            currentW59 &= 0x7FFFFF;
+        }
+
+        // 逆序交换对并执行交换
+        swapPairs.reverse().forEach(({hj9, JO9}) => {
+            // 交换元素还原
+            const temp = UM9Reversed[hj9];
+            UM9Reversed[hj9] = UM9Reversed[JO9];
+            UM9Reversed[JO9] = temp;
+        });
+
+        // 拼接为原始字符串
+        return UM9Reversed.join(':');
+    }
+
+    function reverseFun2(tv, BxInitial) {
+        let Ig = '';
+        const L = Dz.length;
+        let currentBx = BxInitial;
+        for (let sm = 0; sm < tv.length; sm++) {
+            const c = tv.charAt(sm);
+            // 计算当前tE和UE
+            const tE = (currentBx >> 8) & 0xFFFF;
+            const UE = tE % L;
+            // 更新Bx为下一次迭代
+            currentBx = (currentBx * 65793) & 0xFFFFFFFF;
+            currentBx += 4282663;
+            currentBx &= 0x7FFFFF; // 保留23位
+
+            // 处理当前字符
+            const k = Dz.indexOf(c);
+            if (k === -1) {
+                Ig += c;
+                continue;
+            }
+
+            let pz = (k - UE) % L;
+            if (pz < 0) pz += L;
+
+            // 查找所有可能的Ad(32~126)
+            let found = false;
+            for (let Ad = 32; Ad < 127; Ad++) {
+                if (mU[Ad] === pz) {
+                    Ig += String.fromCharCode(Ad);
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) Ig += c; // 未找到,保留原字符
+        }
+        return Ig;
+    }
+
+    let dec1 = reverseFun2(dec, num_arr[0])
+    // console.log(a)
+    return reverseFun1(dec1, num_arr[1])
+}
+
+

+ 198 - 0
3.31GK/akm/obj_compare.js

@@ -0,0 +1,198 @@
+function isEqual(obj1, obj2) {
+    const keys1 = Object.keys(obj1);
+    const keys2 = Object.keys(obj2);
+
+    if (keys1.length !== keys2.length) return false;
+
+    for (const key of keys1) {
+        // if (Array.isArray(obj1[key])) {
+        //     compareArrays(obj1[key], obj2[key])
+        // } else {
+            if (obj1[key] !== obj2[key]) {
+                console.log(key)
+
+            }
+        // }
+        ;
+    }
+
+    return true;
+}
+
+
+function compareArrays(oldArray, newArray) {
+    const oldMap = new Map(oldArray.map(item => [item.id, item]));
+    const newMap = new Map(newArray.map(item => [item.id, item]));
+
+    const added = newArray.filter(item => !oldMap.has(item.id));
+    const deleted = oldArray.filter(item => !newMap.has(item.id));
+
+    const modified = [];
+    for (const newItem of newArray) {
+        const oldItem = oldMap.get(newItem.id);
+        if (oldItem) {
+            const changes = {};
+            let hasChange = false;
+            for (const key in newItem) {
+                if (!Object.is(newItem[key], oldItem[key])) {
+                    changes[key] = {old: oldItem[key], new: newItem[key]};
+                    hasChange = true;
+                }
+            }
+            if (hasChange) {
+                modified.push({id: newItem.id, changes});
+            }
+        }
+    }
+
+    console.log('新增:', added);
+    console.log('删除:', deleted);
+    console.log('修改:', modified);
+    return {added, deleted, modified};
+}
+
+obj1 =[
+    {
+        "swi": 1920
+    },
+    {
+        "wiw": 804
+    },
+    {
+        "pha": 0
+    },
+    {
+        "asw": 1920
+    },
+    {
+        "nap": "Gecko"
+    },
+    {
+        "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+    },
+    {
+        "ibr": 0
+    },
+    {
+        "dau": 0
+    },
+    {
+        "ash": 1040
+    },
+    {
+        "nps": "20030107"
+    },
+    {
+        "she": 1080
+    },
+    {
+        "tsd": 0
+    },
+    {
+        "hz1": 429998
+    },
+    {
+        "ucs": "8106"
+    },
+    {
+        "ran": "0.17157431385"
+    },
+    {
+        "xag": 12147
+    },
+    {
+        "hal": 873811374209
+    },
+    {
+        "nal": "zh-CN"
+    },
+    {
+        "wih": 919
+    },
+    {
+        "npl": 5
+    },
+    {
+        "wow": 1920
+    },
+    {
+        "wdr": 0
+    },
+    {
+        "adp": "cpen:0,i1:0,dm:0,cwen:0,non:1,opc:0,fc:0,sc:0,wrc:1,isc:0,vib:1,bat:1,x11:0,x12:1"
+    }
+]
+obj2 = [
+        {
+            "asw": 1920
+        },
+        {
+            "npl": 5
+        },
+        {
+            "nap": "Gecko"
+        },
+        {
+            "ucs": "8106"
+        },
+        {
+            "wiw": 0
+        },
+        {
+            "swi": 1920
+        },
+        {
+            "nal": "zh-CN"
+        },
+        {
+            "dau": 0
+        },
+        {
+            "pha": 0
+        },
+        {
+            "wdr": 0
+        },
+        {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+        },
+        {
+            "adp": "cpen:0,i1:0,dm:0,cwen:0,non:1,opc:0,fc:0,sc:0,wrc:1,isc:0,vib:1,bat:1,x11:0,x12:1"
+        },
+        {
+            "ibr": 0
+        },
+        {
+            "wih": 0
+        },
+        {
+            "hz1": 429875  //
+        },
+        {
+            "xag": 11891
+        },
+        {
+            "hal": 873562480929
+        },
+        {
+            "ash": 1040
+        },
+        {
+            "she": 1080
+        },
+        {
+            "tsd": 0
+        },
+        {
+            "ran": "0.0113193795"
+        },
+        {
+            "nps": "20030107"
+        },
+        {
+            "wow": 1920
+        }
+    ]
+
+
+isEqual(obj1, obj2)

+ 468 - 0
3.31GK/akm/parse_arguments.js

@@ -0,0 +1,468 @@
+navigator = {
+    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
+}
+window = global
+
+
+// 新的akm js文件 搜索,可以搜  = {}; 从而快速定位 指纹数组生成位置 第一次请求时,里面有些值可能为空。。。观察下
+
+
+function getObj() {
+    // 可写死的值
+    let ver = '',
+        url = "https://www.jetstar.com/hk/zh/home?adults=1&children=0&flexible=1&flight-type=2&infants=0&origin=PVG&tab=1",
+        fpt = ";-1;dis;;true;true;true;-480;true;24;24;true;false;-1",
+        dsi = [{"get": ""}, {"set": "0"}, {"ico": "070f409b82df3bdd2f51a6415c7895353c153c47fe6dd8a0f87f3d14c46ccb2b"}, {"ift": "3"}, {"xof": "4,11,1,1,8"}, {"xot": "4,11,1,1,8"}, {"wev": "Google Inc. (Intel);wev;Google Inc. (Intel)"}, {"wre": "Google Inc. (Intel);wre;Google Inc. (Intel)"}, {"wdr": "0"}, {"iks": ""}, {"lds": "1"}, {"sst": ""}],
+        // mev 可写死, 但可能会触发验证码??
+        mev = "0,1,114,1356,242;1,1,694,1351,250;2,1,783,1295,311;3,1,838,1281,331;4,1,1306,1275,333;5,1,1311,1266,333;6,1,1322,1255,333;7,1,1337,1233,337;8,1,1354,1216,340;9,1,1370,1209,340;10,1,1394,1202,340;11,1,1403,1201,340;12,1,2892,1191,340;13,1,2905,1183,340;14,1,2921,1181,339;15,1,3038,1177,338;16,1,3072,1175,336;17,1,3445,1174,336;18,1,3629,1170,336;19,1,3656,1169,336;20,1,3673,1167,337;21,1,3689,1163,338;22,1,3706,1152,338;23,1,3722,1135,338;24,1,3739,1113,328;25,1,3757,1080,302;26,1,3772,1052,281;27,1,3789,1006,251;28,1,3806,939,209;29,1,3823,865,174;30,1,3840,834,150;31,1,3857,820,141;32,1,3873,818,134;33,1,3889,818,123;34,1,3906,818,112;35,1,3922,818,103;36,1,3940,819,98;37,1,3956,820,96;38,1,4084,822,96;39,1,4106,827,105;40,1,4123,832,109;41,1,4139,843,112;42,1,4156,866,112;43,1,4173,908,105;44,1,4190,968,105;45,1,4253,1340,56;46,1,4281,1388,35;47,1,4294,1393,31;48,1,4373,1390,30;49,1,4390,1378,27;50,1,4406,1368,23;51,1,4424,1357,19;52,1,4440,1348,16;53,1,4457,1346,14;54,1,4490,1344,13;55,1,4507,1337,10;56,1,4523,1332,7;57,1,4533,1327,4;58,1,5324,1072,69;59,1,5343,1062,69;60,1,5357,1054,69;61,1,5374,1046,69;62,1,5390,1034,68;63,1,5407,1021,64;64,1,5424,1014,63;65,1,5443,1005,61;66,1,5458,994,60;67,1,5474,977,55;68,1,5491,965,51;69,1,5507,958,47;70,1,5525,950,45;71,1,5542,944,45;72,1,5557,943,45;73,3,5633,943,45,-1;74,4,5725,943,45,-1;75,2,5728,943,45,-1;76,1,8014,509,619;77,1,8021,509,622;78,1,8029,502,628;79,1,8103,447,670;80,1,8145,447,674;81,1,8156,447,675;82,1,8172,448,675;83,1,8332,449,675;84,1,8340,456,675;85,1,8348,465,672;86,1,8356,476,670;87,1,8364,487,668;88,1,8372,498,665;89,1,8380,511,658;90,1,8389,521,655;91,1,8396,530,652;92,1,8404,532,650;93,1,8414,533,649;94,1,8426,534,649;95,3,8544,534,649,-1;",
+
+        startTs = Date.now() - 1000
+    // window.bmak.startTs = startTs
+    ajr = ajr(startTs)  //用到 2 次,且要一致
+    // console.log(ajr)
+    ;
+    return {
+        "ver": ver,                         // 网页固定值,"C9vFEovTrypIjd1JK5C8oAAPjzy2LU11L9garZrvwm8="
+        "fpt": fpt,                         // 可写死
+        "fpc": fpc(fpt),                    // 根据fpt值生成
+        "ajr": ajr,                         // 变化了 根据 startTs生成  可写死, 根据ua值生成
+        "din": din(startTs),           // 可写死, 数组:23位
+        "eem": "do_en,dm_en,t_en",          // 可写死, 检测函数后拼接而成,  示例值:"do_en,dm_en,t_en"
+        "ffs": "",                          // 可写死, 值为空
+        "vev": "2,490;3,5328;2,6723;3,8541;",                                   // 可写死, ============================================
+        "inf": "",                          // 网站固页值  示例值:""
+        "ajt": '0,0',                       // 可写死 第一次请求为'0,0' 同一会话每次请求不一样,写死时 要注意要和生成 dvc 的入参一致
+
+        // kev mev tev pev oev if 都是从一个函数返回的6位数组(变量组成 只有 0 1 有值,其他都是空字符串 可能会变)中升序取值  应该是轨迹数组
+        "kev": "",                 // 可为空, 从6数组取值
+        "mev": mev,                // 可写死, 从6数组取值  鼠标移动轨迹
+        "tev": "",                 // 从6数组取值                示例值:""
+        "pev": "",                 // 从6数组取值                示例值:""
+        "oev": "",                 // 从6数组取值                示例值:""
+        "if": "",                  // 从6数组取值                示例值:""
+
+        "dme": "0,521,-1,-1,-1,-1,-1,-1,-1,-1,-1;",                // 可写死
+        "doe": "0,522,-1,-1,-1;",                                  // 可写死
+        "pur": url,                                                // 可写死 网页url 不能有反斜杠 \ 和双引号 "    示例值:
+
+        "mst": mst(startTs, ajr),                                                                         //   数组:30位  -------------------------analyse.txt
+
+        "o9": 0,                        // 可写死, 网页固定值
+        "sde": "0,0,0,0,1,0,0",         // 可写死
+        "pmo": "",                      // 可写死, 网页固定值
+        "dpw": "",                      // 可写死, 网页固定值
+        "pac": "",                      // 可写死, 网页固定值
+        "per": '能写死',                                                                                      // 逆向----------------------------------
+        "dsi": dsi,                     // 可写死, 数组:12位
+        "wsl": "2172649472,76515477,62483333,100,1,1,1,1,0,1,,,,,,0,,,1,1",   // 可写死  前3个数字是浏览器堆内存信息 中间2个数在变化
+        "hls": "-1,,,1,1",              // 可写死, 网页固定值
+        "pde": "",                      // 可写死
+        "fwd": [{"fmh": ""}, {"fmz": "1"}, {"ssh": "79d476b3ee7a1d053d47c234f8b00e881ef941614b47791e1d4610cb5e47a0ff"}],  // 可写死
+    }
+
+}
+
+
+// 公共方法
+var TSK = function (INK) {
+    if (INK == null)
+        return -1;
+    try {
+        var I8K = 0;
+        for (var IjK = 0; IjK < INK["length"]; IjK++) {
+            var UQK = INK["charCodeAt"](IjK);
+            if (UQK < 128) {
+                I8K = I8K + UQK;
+            }
+        }
+        return I8K;
+    } catch (JHK) {
+        console.log('报错逻辑....')
+        return -2;
+    }
+};
+
+// XHK, 需要 AMK["fpValStr"] 值
+function fpc(val) {
+    return "".concat(TSK(val));
+}
+
+
+// ajr ---------------------------------
+function ajr(startTimestamp) {
+    var lfv = function (Jw) {
+        var rTv = function (bxv, g8v) {
+            return bxv >>> g8v | bxv << 32 - g8v;
+        };
+        var kw = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
+        var Jl = 0x6a09e667;
+        var BXv = 0xbb67ae85;
+        var SVv = 0x3c6ef372;
+        var FCv = 0xa54ff53a;
+        var VTv = 0x510e527f;
+        var tEv = 0x9b05688c;
+        var ttv = 0x1f83d9ab;
+        var hVv = 0x5be0cd19;
+        var SUv = unescape(encodeURIComponent(Jw));
+        var hXv = SUv["length"] * 8;
+        SUv += String["fromCharCode"](0x80);
+        var Sfv = SUv["length"] / 4 + 2;
+        var pw = Math["ceil"](Sfv / 16);
+        var nfv = new (Array)(pw);
+        for (var Ol = 0; Ol < pw; Ol++) {
+            nfv[Ol] = new (Array)(16);
+            for (var fLv = 0; fLv < 16; fLv++) {
+                nfv[Ol][fLv] = SUv["charCodeAt"](Ol * 64 + fLv * 4) << 24 | SUv["charCodeAt"](Ol * 64 + fLv * 4 + 1) << 16 | SUv["charCodeAt"](Ol * 64 + fLv * 4 + 2) << 8 | SUv["charCodeAt"](Ol * 64 + fLv * 4 + 3) << 0;
+            }
+        }
+        var VCv = hXv / Math["pow"](2, 32);
+        nfv[pw - 1][14] = Math["floor"](VCv);
+        nfv[pw - 1][15] = hXv;
+        for (var Fbv = 0; Fbv < pw; Fbv++) {
+            var GTv = new (Array)(64);
+            var khv = Jl;
+            var jXv = BXv;
+            var Ydv = SVv;
+            var jtv = FCv;
+            var cl = VTv;
+            var Ehv = tEv;
+            var Vfv = ttv;
+            var ddv = hVv;
+            for (var Jvv = 0; Jvv < 64; Jvv++) {
+                var qCv = void 0
+                    , lvv = void 0
+                    , Kq = void 0
+                    , vtv = void 0
+                    , Dnv = void 0
+                    , NCv = void 0;
+                if (Jvv < 16)
+                    GTv[Jvv] = nfv[Fbv][Jvv];
+                else {
+                    qCv = rTv(GTv[Jvv - 15], 7) ^ rTv(GTv[Jvv - 15], 18) ^ GTv[Jvv - 15] >>> 3;
+                    lvv = rTv(GTv[Jvv - 2], 17) ^ rTv(GTv[Jvv - 2], 19) ^ GTv[Jvv - 2] >>> 10;
+                    GTv[Jvv] = GTv[Jvv - 16] + qCv + GTv[Jvv - 7] + lvv;
+                }
+                lvv = rTv(cl, 6) ^ rTv(cl, 11) ^ rTv(cl, 25);
+                Kq = cl & Ehv ^ ~cl & Vfv;
+                vtv = ddv + lvv + Kq + kw[Jvv] + GTv[Jvv];
+                qCv = rTv(khv, 2) ^ rTv(khv, 13) ^ rTv(khv, 22);
+                Dnv = khv & jXv ^ khv & Ydv ^ jXv & Ydv;
+                NCv = qCv + Dnv;
+                ddv = Vfv;
+                Vfv = Ehv;
+                Ehv = cl;
+                cl = jtv + vtv >>> 0;
+                jtv = Ydv;
+                Ydv = jXv;
+                jXv = khv;
+                khv = vtv + NCv >>> 0;
+            }
+            Jl = Jl + khv;
+            BXv = BXv + jXv;
+            SVv = SVv + Ydv;
+            FCv = FCv + jtv;
+            VTv = VTv + cl;
+            tEv = tEv + Ehv;
+            ttv = ttv + Vfv;
+            hVv = hVv + ddv;
+        }
+        return [Jl >> 24 & 0xff, Jl >> 16 & 0xff, Jl >> 8 & 0xff, Jl & 0xff, BXv >> 24 & 0xff, BXv >> 16 & 0xff, BXv >> 8 & 0xff, BXv & 0xff, SVv >> 24 & 0xff, SVv >> 16 & 0xff, SVv >> 8 & 0xff, SVv & 0xff, FCv >> 24 & 0xff, FCv >> 16 & 0xff, FCv >> 8 & 0xff, FCv & 0xff, VTv >> 24 & 0xff, VTv >> 16 & 0xff, VTv >> 8 & 0xff, VTv & 0xff, tEv >> 24 & 0xff, tEv >> 16 & 0xff, tEv >> 8 & 0xff, tEv & 0xff, ttv >> 24 & 0xff, ttv >> 16 & 0xff, ttv >> 8 & 0xff, ttv & 0xff, hVv >> 24 & 0xff, hVv >> 16 & 0xff, hVv >> 8 & 0xff, hVv & 0xff];
+    };
+    var gTv = function (RNv) {
+        return Math["floor"](Math["random"]() * RNv["length"]);
+    };
+    var Fw = function (kzv) {
+        var W3v = '';
+        for (var F6v = 0; F6v < kzv["length"]; F6v++) {
+            W3v += kzv[F6v]["toString"](16)["length"] === 2 ? kzv[F6v]["toString"](16) : "0"["concat"](kzv[F6v]["toString"](16));
+        }
+        return W3v;
+    };
+
+    var fDv = Fw(lfv(btoa(startTimestamp)));
+    var tfv = [];
+    var dnv = "";
+    for (var XVv = 0; XVv < 5; XVv++) {
+        var Pgv = gTv(fDv);
+        tfv["push"](Pgv);
+        dnv = dnv + fDv[Pgv];
+    }
+    var zCv = [dnv, tfv];
+    return zCv["join"]("|")
+}
+
+
+// din 23arr ----------------------- 第 5 个
+function din(startTs) {
+
+    ` **************** adp 生成过程(可写死) ***************
+    
+    var Q56 = window['callPhantom'] ? 1 : 0;                                        // 值为 0 ,  检测无头浏览器
+    var kR6 = window['ActiveXObject'] && 'ActiveXObject' in window ? 1 : 0;         // 值为 0 ,  识别 IE 浏览器(ActiveXObject 是 IE 特有的属性
+    var pW6 = typeof window['document']['documentMode'] == 'number' ? 1 : 0;        // 值为 0 ,  检测 IE 文档模式(document.documentMode 是 IE 特有属性)
+    var cc6 = window['chrome'] && window['chrome']['webstore'] ? 1 : 0;             // 值为 0 ,  检测 Chrome 浏览器
+    var UR6 = window['navigator']['onLine'] ? 1 : 0;                                // 值为 1 ,  检测网络状态,  navigator.onLine可判断浏览器是否在线
+    var mk6 = window['opera'] ? 1 : 0;                                              // 值为 0 ,  检测旧版 Opera 浏览器(opera 是旧版 Opera 特有)
+    var VZ6 = typeof window['InstallTrigger'] !== 'undefined' ? 1 : 0;              // 值为 0 ,  检测 Firefox 浏览器(InstallTrigger 是 Firefox 特有属性)
+    var rQ6 = window['HTMLElement'] && window['Object']['prototype']['toString'].call(window['HTMLElement'])['indexOf']('Constructor') > 0 ? 1 : 0;  // 值为 0 ,
+    var g36 = typeof window['RTCPeerConnection'] === 'function' || typeof window['mozRTCPeerConnection'] === 'function'  // 值为 1 , 判断浏览器是否支持 WebRTC
+    var Ng6 = 'mozInnerScreenY' in window ? window['mozInnerScreenY'] : 0;          // 值为 0 ,  检测 Firefox 特定属性
+    var OL6 = typeof window['navigator']['vibrate'] === 'function' ? 1 : 0;         // 值为 1 ,  检查设备是否支持振动
+    var Wc6 = typeof window['navigator']['getBattery'] === 'function' ? 1 : 0;      // 值为 1 ,  检查电池API支持
+    var U96 = !window['Array']['prototype']['forEach'] ? 1 : 0;                     // 值为 0 ,  检测 ES5 数组方法支持
+    var EC6 = 'FileReader' in window ? 1 : 0;                                       // 值为 1 ,  检测是否支持文件读取
+    
+    var nS6 = 'cpen:'['concat'](Q56, ',i1:')['concat'](kR6, ',dm:')['concat'](pW6, ',cwen:')['concat'](cc6, ',non:')['concat'](UR6, ',opc:')['concat'](mk6, ',fc:')['concat'](VZ6, ',sc:')['concat'](rQ6, ',wrc:')['concat'](g36, ',isc:')['concat'](Ng6, ',vib:')['concat'](OL6, ',bat:')['concat'](Wc6, ',x11:')['concat'](U96, ',x12:')['concat'](EC6);
+    `
+
+    // PMK ------------------------------------------ ran 生成 ----------------------------------------------------------
+    var ZEK = Math.random()
+    var KDK = parseInt(ZEK * 1000 / 2, 10)
+    var PMK = "".concat(ZEK).slice(0, 11) + KDK
+
+    var Arr23 = [
+        {"wdr": 0},                         // window.webdriver ? 1 : 0; =>  0
+        {"xag": 12147},                     // 不会js文件是不一样的  网页固定值 RcK(973, [])
+        {"asw": 1920},                      // window.screen.availWidth
+
+        {"nal": "zh-CN"},                   // navigator['language']
+        {"wow": 1920},                      // window.outerWidth
+
+        // 将时间戳 window.bmak.startTs 除以固定数值 (2016*2016)4064256,并将结果转换为十进制整数
+        {"hz1": parseInt(startTs / 4064256, 10)},
+
+        {"nps": "20030107"},                // navigator['productSub']
+        {"pha": 0},                         // window._phantom ? 1 : 0;  =>  0
+        {"ibr": 0},                         // 网页固定值
+        {"adp": "cpen:0,i1:0,dm:0,cwen:0,non:1,opc:0,fc:0,sc:0,wrc:1,isc:0,vib:1,bat:1,x11:0,x12:1"}, // SKK()
+        {"hal": startTs / 2},               // ZqK = window.bmak.startTs / 2
+        {"ucs": "8106"},                    // CCK 生成逻辑 ''.concat(TSK(navigator.userAgent))  =》 '8106'
+        {"she": 1080},                      // window.screen.height => 1080
+        {"wih": 919},                       // window.innerHeight   =>  919
+        {"ash": 1040},                      // window.screen.availHeight
+        {"dau": 0},                         // window.domAutomation ? 1 : 0; => 0
+        {"wiw": 1920},                      // window.innerWidth    => 1920  616??
+        {"nap": "Gecko"},                   // navigator['product']
+        {"npl": 5},                         // navigator['plugins']['length']
+        {"swi": 1920},                      // window.screen.width  =>
+        {"tsd": 0},                         // 网页固定值
+        {"ua": navigator.userAgent},
+        {"ran": PMK},                       // 随机值(应该可以写死)
+    ]
+
+    return Arr23
+}
+
+
+function mst(startTs, ajr) {
+
+    // 时间戳差值
+    var Bmd = Date.now() - startTs;
+
+    // window.bmak.startTs = startTs
+    require('./arg_dvc_revert_env')
+
+    var dbd = dDd(startTs);
+    console.log('ajr', ajr)
+    Agd = arg1.Gb(0, ajr, 1, 0)
+    console.log(Agd)
+    return [
+        {'kevl': 1},
+        {'mevl': 32},
+        {'tevl': 32},
+        {'devl': 0},
+        {'dmvl': 0},  // 变化的
+        {'pevl': 0},
+        {'tovl': 0},  // 变化的
+        {'delt': Bmd},  // 时间戳差值
+        {'it': 0},
+        {'sts': startTs},
+        {'fct': -999999},  // HAd['td']
+        {'dd2': parseInt(parseInt(startTs / 4064256, 10) / 23, 10)},
+        {'kc': 0},
+        {'mc': 0},
+        {'ww8': 0},
+        {'pc': 0},
+        {'tc': 0},
+        {'ssts': Bmd},  // 时间戳差值
+        {'tst': 0},
+        {'rval': '-1'}, // HAd['rVal']
+        {'rcfp': '-1'}, // HAd['rCFP']
+        {'nfas': 30261693},
+        {'jsrf': "PiZtE"},
+        {'jsrf1': dbd[0]}, //开始时间戳 计算的随机值
+        {'jsrf2': dbd[1]},
+        {'signals': '0'},
+        {'mwd': "0"},
+        {'hea': ''},
+        // 第1个值根据 ajr 生成   第2个值是时间戳差值  第3个是根据环境判断得来的  不同js会变
+        {'dvc': ''['concat'](Agd, ',')['concat'](0, ',')['concat']("i+j+g+d+k+h+f+l+")},  // 不能写死 尤其是第一个值 ------------
+        {'srd': "0"}
+    ];
+}
+
+var dDd = function (YS) {
+    var P3 = function (ZA) {
+        var SW = ZA[0] - ZA[1];
+        var ng = ZA[2] - ZA[3];
+        var sb = ZA[4] - ZA[5];
+        var CJ = Math["sqrt"](SW * SW + ng * ng + sb * sb);
+        return Math["floor"](CJ);
+    };
+    var H7d = Math["floor"](Math["random"]() * 100000 + 10000);
+    var vZ = String(YS * H7d);
+    var sQd = 0;
+    var P4d = [];
+    var Ffd = vZ["length"] >= 18 ? true : false;
+    while (P4d["length"] < 6) {
+        P4d["push"](parseInt(vZ["slice"](sQd, sQd + 2), 10));
+        sQd = Ffd ? sQd + 3 : sQd + 2;
+    }
+    var FMd = P3(P4d);
+    return [H7d, FMd];
+};
+
+
+// console.log(JSON.stringify(mst(1745981943082)));
+console.log(getObj(1746587916230))
+
+
+// console.log(arg1.Gb(141296, "46e1c|63,3,35,62,19", 1, 12250583))
+console.log(dDd(1746587916230))
+
+
+
+
+
+
+
+
+
+
+// wsl --------------------------------------------------------------------------------
+
+var U9x = function () {
+    var MGx = "-1,-1,-1";
+    //  window.performance.memory; 获取 JavaScript 内存使用情况
+    `   返回一个包含以下属性的对象(示例值):
+
+属性	类型	描述
+usedJSHeapSize	number	当前 JavaScript 堆内存已使用的字节数(实际使用量)。
+totalJSHeapSize	number	当前 JavaScript 堆内存总分配的字节数(包含未使用的空闲内存)。
+jsHeapSizeLimit	number	JavaScript 堆内存的最大限制字节数(由浏览器或系统决定)
+`
+    if (window['performance'] && window['performance']['memory']) {
+        var sGx = window['performance']['memory'];
+        MGx = ''['concat'](sGx['jsHeapSizeLimit'], ',')['concat'](sGx['totalJSHeapSize'], ',')['concat'](sGx['usedJSHeapSize']);
+    }
+    var OGx = ''['concat'](MGx, ',')['concat']("950");
+    return OGx
+
+};
+
+
+//用于获取浏览器支持的​​语音合成(TTS,Text-to-Speech)的语音列表
+var k5x = window["speechSynthesis"]["getVoices"]();  // 20
+V1x = k5x['length']
+
+Mkx = ''['concat'](U9x(), ',')['concat'](V1x);
+
+W0x = ''.concat(Mkx, ',')['concat']('1,1,1', ',')['concat']("0", ',')['concat'](pRx, ',,,,,,')['concat'](f5x, ',,,')
+
+
+// 第4个数要注意
+wsl = "2248146944,110285101,98152829, 950, 20, 1,1,1,0,1,,,,,,0,,,1,1"
+
+
+// per --------------------------------------------------------------------------------
+Q0x = '999999'['concat'](P1x['slice'](wm, 2)['join'](''), '9')['concat'](P1x[lz], '9')['concat'](P1x['slice'](S1)['join'](''), '999');
+
+
+// fwd --------------------------------------------------------------------------------
+var qtx = function () {
+    c1x = [{"fmh": ""}, {'fmz': "1"}, {"ssh": K5x || ''}]
+    return c1x;
+};
+
+
+// mst --------------------------------------------------------------------------------
+
+// 时间戳差值
+var Bmd = Date.now() - window.bmak["startTs"];
+NGd = parseInt(window.bmak['startTs'] / 4064256, 10);
+
+WYd = parseInt(NGd / 23, 10);
+
+// 时间戳差值
+var Omd = Date.now() - window.bmak["startTs"]
+var dDd = function (YS) {
+    var H7d = Math["floor"](Math["random"]() * 100000 + 10000);
+    var vZ = String(YS * H7d);
+    var sQd = 0;
+    var P4d = [];
+    var Ffd = vZ["length"] >= 18 ? true : false;
+    while (P4d["length"] < 6) {
+        P4d["push"](parseInt(vZ["slice"](sQd, sQd + 2), 10));
+        sQd = Ffd ? sQd + 3 : sQd + 2;
+    }
+    var FMd = P3(P4d);
+    return [H7d, FMd];
+};
+var P3 = function (ZA) {
+    var SW = ZA[0] - ZA[1];
+    var ng = ZA[2] - ZA[3];
+    var sb = ZA[4] - ZA[5];
+    var CJ = Math["sqrt"](SW * SW + ng * ng + sb * sb);
+    return Math["floor"](CJ);
+};
+var dbd = dDd(window.bmak['startTs']);
+
+DAd = "1,4815,3,1605,5,963"
+ Agd = Qc(Bmd, DAd, 0, 0);
+var r8d = [
+    {'kevl': 1},
+    {'mevl': 32},
+    {'tevl': 32},
+    {'devl': 0},
+    {'dmvl': 0},
+    {'pevl': 0},
+    {'tovl': 0},
+    {'delt': Bmd},  // 时间戳差值
+    {'it': 0},
+    {'sts': window.bmak['startTs']},
+    {'fct': -999999},  // HAd['td']
+    {'dd2': WYd},
+    {'kc': 0},
+    {'mc': 0},
+    {'ww8': 0},
+    {'pc': 0},
+    {'tc': 0},
+    {'ssts': Omd},
+    {'tst': 0},
+    {'rval': '-1'}, // HAd['rVal']
+    {'rcfp': '-1'}, // HAd['rCFP']
+    {'nfas': 30261693},
+    {'jsrf': "PiZtE"},
+    {'jsrf1': dbd[0]}, //开始时间戳 计算的值  会变
+    {'jsrf2': dbd[1]},
+    {'signals': '0'},
+    {'mwd': "0"},
+    {'hea': ''},
+    {'dvc': ''['concat'](Agd, ',')['concat'](0, ',')['concat']("l+g+e+a+i+f+j+c+h+b+d+k+")},  // kAd
+    {'srd': "0"}
+];
+
+
+// sde --------------------------------------------------------------------------------
+// var TKd = window['$cdc_asdjflasutopfhvcZLmcfl_'] || document['$cdc_asdjflasutopfhvcZLmcfl_'] ? true ? '1' : Mr()[LJ(kq)](XF, xMd) : gq(typeof Ik()[dY(bY)], 'undefined') ? Ik()[dY(F9)].apply(null, [T2, rdd, RN, Qm(F8)]) : Ik()[dY(Lm)](v9, rk, qr, v9);
+// var Kfd = RI(m4[AI()[Qz(mb)].call(null, gY, rV)][AI()[Qz(Ck)](lq, wF)][Ik()[dY(O3)](xr, Zr, jY, BN)][tk()[Ir(PB)].apply(null, [FW, Nm, mj, Ew])](tk()[Ir(S3)].apply(null, [lz, ON, YY, I4])), null) ? Mr()[LJ(BI)](vN, qA) : cJ(typeof Ik()[dY(PB)], gb('', [][[]])) ? Ik()[dY(Lm)](Qm(Qm(F8)), rk, qr, n9) : Ik()[dY(F9)](n3, cxd, KZ, Nm);
+// var Fnd = RI(typeof m4[cJ(typeof AI()[Qz(wp)], gb([], [][[]])) ? AI()[Qz(S3)](Nb, Am) : AI()[Qz(Lp)](nW, Dw)][tk()[Ir(S3)](xY, UN, YY, I4)], AI()[Qz(qm)].apply(null, [H9, FP])) && m4[AI()[Qz(S3)].call(null, Nb, Am)][tk()[Ir(S3)](kp, Sr, YY, I4)] ? Mr()[LJ(BI)].call(null, vN, qA) : Ik()[dY(Lm)](DJ, rk, qr, Ag);
+//
+//
+// var V6 = RI(typeof m4[cJ(typeof AI()[Qz(Ib)], gb([], [][[]])) ? AI()[Qz(mb)](gY, rV) : AI()[Qz(Lp)](rl, LO)][tk()[Ir(S3)](dz, sI, YY, I4)], AI()[Qz(qm)].apply(null, [H9, FP])) ? Mr()[LJ(BI)].apply(null, [vN, qA]) : Ik()[dY(Lm)](kq, rk, qr, X3);
+// dZ += rx;
+// var P1 = cJ(typeof m4[AI()[Qz(mb)].call(null, gY, rV)][Ik()[dY(Hm)].apply(null, [X3, WN, hk, Qm(Lm)])], AI()[Qz(qm)](H9, FP)) || cJ(typeof m4[AI()[Qz(Ck)](lq, wF)][gq(typeof Ik()[dY(jk)], 'undefined') ? Ik()[dY(F9)](dg, ZR, rB, Qm(Qm(Lm))) : Ik()[dY(Hm)].apply(null, [S3, WN, hk, Vr])], AI()[Qz(qm)].call(null, H9, FP)) ? Mr()[LJ(BI)](vN, qA) : Ik()[dY(Lm)](Ng, rk, qr, lz);
+// var zF = RI(m4[gq(typeof AI()[Qz(Fb)], gb([], [][[]])) ? AI()[Qz(Lp)].call(null, tN, IA) : AI()[Qz(mb)].apply(null, [gY, rV])][AI()[Qz(Ck)](lq, wF)][Ik()[dY(O3)].apply(null, [kr, Zr, jY, d8])][gq(typeof tk()[Ir(PJ)], gb([], [][[]])) ? tk()[Ir(X9)](mb, d8, tW, JR) : tk()[Ir(PB)](W3, LI, mj, Ew)](tk()[Ir(kb)](DJ, Ng, sq, zO)), null) ? gq(typeof Mr()[LJ(ON)], gb([], [][[]])) ? Mr()[LJ(kq)](t6, mF) : Mr()[LJ(BI)].apply(null, [vN, qA]) : Ik()[dY(Lm)](Qm(Qm(Lm)), rk, qr, xz);
+// var Tz = RI(m4[AI()[Qz(mb)](gY, rV)][AI()[Qz(Ck)](lq, wF)][Ik()[dY(O3)](Lm, Zr, jY, fr)][cJ(typeof tk()[Ir(HZ)], 'undefined') ? tk()[Ir(PB)].apply(null, [nW, Qm([]), mj, Ew]) : tk()[Ir(X9)](Qm(Qm([])), sp, YA, m3)](FY()[PW(lm)](Qm([]), lz, xs, ql, H9, Op)), null) ? gq(typeof Mr()[LJ(XY)], gb('', [][[]])) ? Mr()[LJ(kq)](IZ, I8) : Mr()[LJ(BI)].call(null, vN, qA) : Ik()[dY(Lm)].apply(null, [Qm([]), rk, qr, Lr]);
+// var vw = [TKd, Kfd, Fnd, V6, P1, zF, Tz];
+// var Kl = vw[Mr()[LJ(At)].apply(null, [JUd, YA])](lW()[Gp(XY)].apply(null, [vv, Lm, Nq, x3]));

+ 39 - 0
3.31GK/akm/test.js

@@ -0,0 +1,39 @@
+// AUx 也是数组
+Alx = []['concat'](AUx)["concat"](
+    [
+        {"lds": '1'},
+        {'sst': ''}
+    ]
+);
+
+var U9x = function () {
+    var MGx = "-1,-1,-1";
+    //  window.performance.memory; 获取 JavaScript 内存使用情况
+    `   返回一个包含以下属性的对象(示例值):
+
+属性	类型	描述
+usedJSHeapSize	number	当前 JavaScript 堆内存已使用的字节数(实际使用量)。
+totalJSHeapSize	number	当前 JavaScript 堆内存总分配的字节数(包含未使用的空闲内存)。
+jsHeapSizeLimit	number	JavaScript 堆内存的最大限制字节数(由浏览器或系统决定)
+`
+    if (window['performance'] && window['performance']['memory']) {
+        var sGx = window['performance']['memory'];
+        MGx = ''['concat'](sGx['jsHeapSizeLimit'], ',')['concat'](sGx['totalJSHeapSize'], ',')['concat'](sGx['usedJSHeapSize']);
+    }
+    var OGx = ''['concat'](MGx, ',')['concat']("950");
+    return OGx
+
+};
+
+
+//用于获取浏览器支持的​​语音合成(TTS,Text-to-Speech)的语音列表
+// var k5x = window["speechSynthesis"]["getVoices"]();  // 20
+// V1x = k5x['length']
+//
+// Mkx = ''['concat'](U9x(), ',')['concat'](V1x);
+//
+// W0x = ''.concat(Mkx, ',')['concat']('1,1,1', ',')['concat']("0", ',')['concat'](pRx, ',,,,,,')['concat'](f5x, ',,,')
+
+
+// 第4个数要注意
+wsl = "2248146944,110285101,98152829, 950, 20, 1,1,1,0,1,,,,,,0,,,1,1"

+ 103 - 0
3.31GK/analyse.txt

@@ -0,0 +1,103 @@
+
+加密参数:
+    bmsz cookie
+    同一个 bmsz 最多请求 4 次,第5次就报 (92) HTTP/2 stream 3 was not closed cleanly 错误
+
+
+第一次请求获取 bmsz  响应{"success": false}  可以不带任何参数。。不严格
+第二次携带bmsz 请求响应为{"success": true}  (响应只有一个abck) 才表示验证逻辑没问题,
+    但并没有通过风控验证(可能是指纹不对, 通过风控后才能请求数据)
+
+mt
+
+
+
+
+
+替换js时,只能替换当前最新的js文件, 用旧文件(如前一天)用作替换文件时,不会生效
+替换js后  也会返回  0值的abck
+
+
+// 新的akm js文件 搜索,可以搜  = {}; 从而快速定位 指纹数组生成位置 第一次请求时,里面有些值可能为空。。。观察下
+
+
+!!每次请求都有可能请求超时,这不是cookie不对,是网络问题
+
+
+2次请求的 startTs 一致
+
+开 fiddler 抓包工具获取的cookie 请求数据成功率是100%, 反之 可能请求超时/ 重定向到验证码 页面   =》 因为 tls指纹
+
+有 tls 指纹检测,同一个指纹请求过多会导致请求超时 (要换 tls指纹) 获取 0 值的abck同理
+
+
+0 abck
+    请求3次接口 第3次才返回真cookie ??
+
+    回响契机:
+        ajr
+        din
+            hal
+            ran
+            dme
+            doe
+        mev
+        mst
+            mevl
+            devl
+            dmvl
+            tovl
+            delt
+            sts
+            fct
+            mc
+            ssts
+            tst
+            jsrf1
+            jsrf2
+            dvc
+        vev
+        wsl
+        极道万岁!!!
+
+
+// din 参数顺序不能乱,要和网站保持一致
+
+
+
+机场城市信息接口
+
+    https://digitalapi.jetstar.com/v1/resource/flight-search-options  这个是
+    https://digitalapi.jetstar.com/v1/resource/airports-and-terminals  这个接口不是
+
+新ip要过验证码?  => ja3 切换的太频繁的话会一直出发验证码
+
+
+
+响应数据在 html的   <script type="application/json" id="bundle-data-v2"  文件中
+
+
+清洗
+    票价是根据经济舱 和商务舱的余票数 显示的,网页上显示的就是提取出来的票数
+    只不过不同的服务包票价 的余票都指向 同一个余票
+    网页显示的都是经济舱的不同票价服务包
+    商务舱 暂时没看到有余票的  观察一次如果商务舱有余票,网站也没上是否会显示商务舱票价与其对于票数
+
+
+抓取规则
+    抓个最低不带行李的价格再抓一个最低带行李的就行
+
+
+
+同样的参数,同样的加密 浏览器能过, py过不了  // 自己制造的问题
+
+
+
+余票
+运营航班号 和航班号一样
+航站楼  不用理会
+税金 提取  同一航班 一样
+
+
+线上跑不起来的原因:
+    一共提取 563个 ja3-string 有三百多个都是重复的

+ 155 - 0
3.31GK/ast/ast.js

@@ -0,0 +1,155 @@
+const fs = require('fs')
+const parser = require('@babel/parser') // 解析 JavaScript 代码
+const traverse = require('@babel/traverse').default // 遍历 AST
+const generator = require('@babel/generator').default // 生成代码
+const t = require('@babel/types')
+
+
+// 定义文件路径
+const input_js = './encode.js'
+const output_js = './decode.js'
+
+// 读取文件内容并解析成 AST
+const js_code = fs.readFileSync(input_js, {encoding: 'utf-8'})
+
+
+const ast = parser.parse(js_code)  // 解析代码为 AST
+
+// ------ 得出来 3元表达式
+// 只获取指纹数组的代码,全部获取还原后并不准确
+// 查找所有 lA()[Sr(l2)](rs, U3, pb)  和  Zb()[G9(Yx)].apply(null, [cg, N9])
+encode = {}
+const visitor_call = {
+    ConditionalExpression(path) {
+        js = path.toString()
+        encode[js] = null;
+
+    },
+    CallExpression(path) {
+        // 检查是否在三元表达式内部
+        const isInsideTernary = path.findParent(p =>
+            p.isConditionalExpression()
+        );
+        if (isInsideTernary) return;
+
+        let {callee,} = path.node
+        // let argumentsPathList = path.get('arguments')
+        if (!t.isMemberExpression(callee)) return;
+
+
+        // 匹配2种混淆表达式
+        let {object, property} = callee
+        if (!t.isCallExpression(object) && !t.isMemberExpression(object)) return;
+
+        // 处理 参数中有 三元表达式的(没必要)
+        // argumentsPathList.forEach(argPath => {
+        //     argPath.traverse({
+        //         ConditionalExpression(innerPath) {
+        //             js = innerPath.toString()
+        //             console.log('找到条件表达式完', js)
+        //         }
+        //     })
+        // })
+
+        // 更细节的处理
+        if (t.isCallExpression(object) && t.isIdentifier(object.callee) && object.arguments.length === 0) {
+            js = path.toString()
+            encode[js] = null; // 要设置值为 null 否则转 json字符串时会无值,因为值为 undefind 的json转不了
+        }
+        if (t.isMemberExpression(object) && t.isIdentifier(property)) {
+            // 跳过嵌套的成员表达式 只还原这种 Zb()[G9(Yx)].apply(null, [cg, N9])
+            if (!t.isCallExpression(object.object)) return;
+
+            js = path.toString()
+            encode[js] = null;
+
+        }
+
+
+        // if(!t.isCallExpression(object) || !t.isMemberExpression(object)) return;
+        //
+        // if(!) return;
+
+
+        // encode.push({key: js})
+
+    },
+    Program: {
+        exit() {
+            // console.log(encode)
+            const jsonData = JSON.stringify(encode);
+            console.log(jsonData); // 输出 JSON 字符串
+        }
+    }
+};
+
+decode = {
+    "zK()[GQ(fp)](d5, X0)": "Object",
+    "RP(typeof RT()[N5(Sj)], 'undefined') ? RT()[N5(xQ)].call(null, EN, BX7, Z2, OR) : RT()[N5(JI)](pN, tH7, QG, CF)": "keys",
+    "HR(typeof pQ()[vP(b5)], 'undefined') ? pQ()[vP(TN)].apply(null, [jK, jc, B0, YU]) : pQ()[vP(OR)](DA7, Pd, cW, EN)": "map"
+}
+const visitor2 = {
+    CallExpression(path) {
+        let {callee,} = path.node
+        // let argumentsPathList = path.get('arguments')
+        if (!t.isMemberExpression(callee)) return;
+
+        // 匹配2种混淆表达式
+        let {object, property} = callee
+        if (!t.isCallExpression(object) && !t.isMemberExpression(object)) return;
+
+                // console.log(path.toString())
+
+        // 更细节的处理
+        if (t.isCallExpression(object) && t.isIdentifier(object.callee) && object.arguments.length === 0) {
+            js = path.toString()
+            if (decode[js] !== null) {
+                value = decode[js]
+                console.log(js, '=>', value)
+                path.replaceWith(t.valueToNode(value))
+            }
+
+
+        }
+        if (t.isMemberExpression(object) && t.isIdentifier(property)) {
+            // 跳过嵌套的成员表达式 只还原这种 Zb()[G9(Yx)].apply(null, [cg, N9])
+            if (!t.isCallExpression(object.object)) return;
+
+            js = path.toString()
+            // try {
+            if (decode[js] !== null) {
+                value = decode[js]
+                console.log(js, '=>', value)
+                path.replaceWith(t.valueToNode(value))
+            }
+            // } catch (e) {
+            //     console.log(e)
+            // }
+
+
+        }
+
+    },
+    ConditionalExpression(path) {
+        js = path.toString()
+        if (decode[js] !== null) {
+            value = decode[js]
+            console.log(js, '=>', value)
+            path.replaceWith(t.valueToNode(value))
+        }
+
+    }
+
+};
+
+// traverse(ast, visitor_call)  // 获取混淆的表达式
+traverse(ast, visitor2)   // 还原
+
+
+// 使用 Babel 生成新的代码
+let {code} = generator(ast)
+
+
+// 将生成的代码写入指定的文件
+fs.writeFile(output_js, code, (err) => {
+})

+ 3 - 0
3.31GK/ast/decode.js

@@ -0,0 +1,3 @@
+hA7 = V6["Object"]["keys"](j77)["map"](function (fC7) {
+  j77[fC7];
+})[EN];

+ 4 - 0
3.31GK/ast/encode.js

@@ -0,0 +1,4 @@
+
+                 hA7 = V6[zK()[GQ(fp)](d5, X0)][RP(typeof RT()[N5(Sj)], 'undefined') ? RT()[N5(xQ)].call(null, EN, BX7, Z2, OR) : RT()[N5(JI)](pN, tH7, QG, CF)](j77)[HR(typeof pQ()[vP(b5)], 'undefined') ? pQ()[vP(TN)].apply(null, [jK, jc, B0, YU]) : pQ()[vP(OR)](DA7, Pd, cW, EN)](function (fC7) {j77[fC7];
+                })[EN];
+

+ 6 - 0
3.31GK/demo.py

@@ -0,0 +1,6 @@
+# 提取网站源数据
+inner_data = find_info_data.get('inner_data')
+
+# 取gk航班
+flights = inner_data.get('Trips', [])[0].get("Flights", [])
+

+ 48 - 0
3.31GK/flight_date_search.py

@@ -0,0 +1,48 @@
+import requests
+
+
+headers = {
+    "accept": "application/json, text/plain, */*",
+    "accept-language": "zh-CN,zh;q=0.9",
+    "cache-control": "no-cache",
+    "culture": "zh-HK",
+    "origin": "https://www.jetstar.com",
+    "pragma": "no-cache",
+    "priority": "u=1, i",
+    "referer": "https://www.jetstar.com/",
+    "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+    "sec-ch-ua-mobile": "?0",
+    "sec-ch-ua-platform": "\"Windows\"",
+    "sec-fetch-dest": "empty",
+    "sec-fetch-mode": "cors",
+    "sec-fetch-site": "same-site",
+    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+}
+
+def search_flight_date():
+    """查询航班日期, 即那天有航班"""
+    url = "https://digitalapi.jetstar.com/v1/farecache/flights/batch/availability-with-fareclasses"
+    params = {
+        "flightCount": "1",
+        "includeSoldOut": "true",
+        "requestType": "StarterOnly",
+        "from": "2025-05-26",  # 采集开始时间
+        "end": "2025-05-27",   # 采集结束时间,可随意写
+        "departures": "CTS",
+        "arrivals": "KOJ",
+        "direction": "outbound",
+        "paxCount": "1",
+        "includeFees": "false"
+    }
+    response = requests.get(url, headers=headers, params=params, verify=False)
+    response.raise_for_status()
+    print(response.json())
+    # for i in response.json():
+    #     print(i)
+    json_data = response.json()[0]['routes']
+    for key, val in json_data.items():
+        flight_dates = list(val.get('flights', {}).keys())
+        print(f'航段: {key} 对应有航班的日期为: {flight_dates}')
+
+
+search_flight_date()

+ 368 - 0
3.31GK/frame_code/clean_gk_flights_datas.py

@@ -0,0 +1,368 @@
+import re
+import time
+import queue
+import threading
+from datetime import datetime
+
+from bson.objectid import ObjectId
+from my_logger import Logger
+from flights_mongodb import mongo_con_parse
+from flights_utils import time_format_conversion, get_now_time, put_data_by_queue, get_data_by_queue
+from settings import CRAWL_DATE_CONF_STATUS_TAB
+from yz_clean_utils import delete_mongodb_clean_datas, save_mongodb_clean_datas, \
+    get_city_code, format_pc_kg_data
+import json
+
+lock = threading.Lock()
+
+
+def convert_time_format(input_str):
+    """"2025年5月30日 (週五) 下午3:35 => 20250530153500 """
+    # 移除括號內的星期信息(如 "週五")
+    cleaned_str = re.sub(r'\s*\(.*?\)', '', input_str)
+
+    # 將中文上午/下午轉換為 AM/PM 標記
+    cleaned_str = cleaned_str.replace("上午", "AM").replace("下午", "PM")
+
+    # 解析日期時間
+    dt = datetime.strptime(cleaned_str, "%Y年%m月%d日 %p%I:%M")
+
+    # 格式化為目標格式,並補齊秒數 (00)
+    return dt.strftime("%Y%m%d%H%M%S")
+
+
+class CleanGKDatas():
+
+    def __init__(self):
+        self.website = "gk"
+        self.logger = Logger('./logs/{}_flights_clear.log'.format(self.website), 'debug').logger
+        self.db = mongo_con_parse()
+        self.info_tab = "flights_{}_info_tab".format(self.website)
+        self.clean_info_tab = "clean_{}".format(self.info_tab)
+        self.thread_numbers = 1
+        self.task_queue = queue.Queue()
+        self.count = 0
+
+    def processing_data_one_search(self, find_info_data, thread_number=0, verify_flag=False):
+        # global has_baggage, baggage
+        from_airport_code = ''
+        to_airport_code = ''
+        search_date = ''
+        end_flight_datas = []
+        try:
+            # print(find_info_data)
+            # 每一次搜索
+            from_city_code = find_info_data.get("search_from_city_code")
+            to_city_code = find_info_data.get("search_to_city_code")
+
+            crawl_date = find_info_data.get("crawl_date")
+            search_date = find_info_data.get("search_date")
+            # 转为保存数据库的出发日期(in_strftime_str:传入时间的格式) 注:为时间
+            search_dep_time_base = time_format_conversion(search_date, in_strftime_str="%Y-%m-%d")
+
+            # 提取网站源数据
+            inner_data = find_info_data.get('inner_data')
+
+            # 取gk航班
+            flights = inner_data.get('Trips', [])[0].get("Flights", [])
+
+            # 处理不同的航班
+            for each_flight in flights:
+
+                mid_flight_datas = []
+
+                DisplayFlightInfo = each_flight.get("DisplayFlightInfo", {})  # 航班信息
+
+                from_airport_code = DisplayFlightInfo.get('OriginAirport')  # 出发机场
+                to_airport_code = DisplayFlightInfo.get('DestinationAirport')  # 到达机场代码
+
+                """提取票价"""
+                # 不同票价的余票 先设置为舱位的余票,因为每个票价没有对应的余票,
+                # 所以先检查经济舱是否有余票,无余票就跳过 (商务舱不用管, 因为查询参数传入的是经济舱/ 而且网站响应商务舱的余票好像一直是 0)
+
+                EconomyFlightInfo = DisplayFlightInfo.get('EconomyFlightInfo')  # 经济舱信息  里面是 余票数
+                if EconomyFlightInfo['RemainingSeats'] == 0:
+                    print('跳过(经济舱)余票数为 0 的航班')
+                    continue
+                # 余票
+                seatCount = EconomyFlightInfo['RemainingSeats']
+
+                Bundles = each_flight['Bundles']  # 票价列表
+                # 税金(手续费)和基本价提取
+                EconomyPriceBreakdown = json.loads(each_flight['EconomyPriceBreakdown'])  # 里面有税金, 是json字符串要转为对象
+                price = EconomyPriceBreakdown['TotalFare']  # 基础票价
+                adult_tax = EconomyPriceBreakdown['TotalCharges']  # 同一个航班是不变的
+
+                # 提取每个票价
+                for each_bundle in Bundles:
+                    # 服务包代码
+                    fareBasis = each_bundle['ServiceBundleCode']
+
+                    #  只采集 最低不带行李的价格 和 一个最低带行李 的票价
+                    # S000: 基本票價   P200: 基本加值套票 行李 + 座位 + 餐膳
+                    if fareBasis not in ['S000', 'P200']:
+                        continue
+
+                    Amount = each_bundle['Amount']  # 增值服务费, 后面算总价要加上这个
+
+                    # 托运行李
+                    if fareBasis == 'S000':
+                        pc_amount, kg_amount = '0', '0'  # 无托运行李
+                        has_baggage, baggage = format_pc_kg_data(pc_amount, kg_amount)
+                    if fareBasis == 'P200':
+                        pc_amount, kg_amount = '1', '20'  # 1件行李20公斤
+                        has_baggage, baggage = format_pc_kg_data(pc_amount, kg_amount)
+
+                    """处理中转"""
+                    segment_elements = []
+                    Legs = DisplayFlightInfo.get('Legs')  # 中转航段信息, 最多有3个
+
+                    # 可能是直达或转乘
+                    for each_leg in Legs:
+                        '''理论航班号信息'''
+                        CarrierCode = each_leg['FlightDesignator']['CarrierCode']    # 航空公司
+                        FlightNumber = each_leg['FlightDesignator']['FlightNumber']  # 航班号
+                        flight_number = f"{CarrierCode}{FlightNumber}"  # 拼接航班号
+
+                        '''实际航班号信息, gk没有这些, ??? 该如何??? => 略 '''
+                        # operatingAirlineCode = each_leg['operatingAirlineCode']  # 营运航空公司代码
+                        # operatingFlightno = each_leg['operatingFlightno']
+                        # operating_flight_number = f"{operatingAirlineCode}{operatingFlightno}"
+
+                        '''机型'''
+                        # 空中巴士 A320-200 (代碼 32J,全經濟艙配置)
+                        aircraftCode = each_leg['Equipment']['Type']
+
+                        '''出发/到达时间'''
+                        departDate = each_leg["DisplayStd"]  # 计划起飞时间  2025年5月31日 (週六) 上午2:15
+                        arrivalDate = each_leg["DisplaySta"]  # 计划到达时间
+
+                        dep_time = convert_time_format(departDate)  # 时间格式转换
+                        arr_time = convert_time_format(arrivalDate)
+
+                        '''出发/到达机场和城市'''
+                        depart_airport = each_leg['DepartureStation']  # 出发机场
+                        depart_city = get_city_code(self.db, self.website, depart_airport)  # 数据库查询对应城市代码
+
+                        arrival_airport = each_leg['ArrivalStation']  # 到达机场
+                        arrival_city = get_city_code(self.db, self.website, arrival_airport)
+
+                        '''舱位类型'''
+                        CabinType = each_leg['CabinType']
+
+                        # 航站楼
+                        # DisplayDepartureAirportTerminal = each_leg['DisplayDepartureAirportTerminal']  # 出发
+                        # DisplayArrivalAirportTerminal = each_leg['DisplayArrivalAirportTerminal']      # 到达
+
+                        segment_element_demo = {
+                            "flight_number": flight_number,  # 航班号
+                            "operating_flight_number": flight_number,  # 实际营运航班号 (gk没有这个和上面写为同一个)
+                            "dep_air_port": depart_airport,  # 出发机场code str
+                            "dep_city_code": depart_city,  # 出发城市code
+                            "dep_time": dep_time,               # 出发时间 格式YYYYMMDDHHMM str
+                            "arr_air_port": arrival_airport,  # 抵达机场code str
+                            "arr_city_code": arrival_city,  # 抵达城市code
+                            "arr_time": arr_time,  # 抵达时间 格式YYYYMMDDHHMM str
+                            "cabin": CabinType,  # 舱位单字母
+                            "carrier": CarrierCode,  # 航空公司代码
+                            "aircraft_code": aircraftCode,  # 机型 str
+                            "cabin_class": 1,  # 舱位等级, 因为捷星日本航空(GK)的航班目前均为全经济舱​​,故这里也可写死
+                            "stop_cities": "",
+                            "has_baggage": has_baggage,  # 是否有托运行李
+                            "baggage": baggage,  # 行李配重 1-23 表示一件行李 23kg
+                            "fareBasis": fareBasis,  # 在同一航班的不同舱位中是唯一值,主要用来区分同一航班的不同舱位,
+                        }
+                        segment_elements.append(segment_element_demo)
+
+                    # 查询出发时间(如果响应没有,可能是入参传递的)
+                    search_dep_time = segment_elements[0].get("dep_time") if segment_elements else search_dep_time_base
+
+                    flight_datas = {
+                        "from_city_code": from_city_code,
+                        "search_dep_time": search_dep_time,
+                        "to_city_code": to_city_code,
+                        "currency": 'JPY',  # 网站默认返回搜索地区的国家货币价格, 采集的全都是 日本的航班 故可写死
+                        "adult_price": int(Amount+price),  # 基本价格
+                        "adult_tax": int(adult_tax),  # 税金
+                        "adult_total_price": int(Amount+price+adult_tax),  # 再加 基本价和税金
+                        "route": "",
+                        "seats_remaining": seatCount,
+                        "segments": segment_elements,
+                        "source_website": self.website,
+                        "crawl_date": crawl_date
+                    }
+
+                    # print(flight_datas)
+
+                    if verify_flag:
+                        flight_datas["verify_date"] = crawl_date  # 验价分支
+                        verify_time = get_now_time()
+                        flight_datas["verify_time"] = verify_time
+                        # print(flight_datas)
+                    mid_flight_datas.append(flight_datas)
+                # 注意:这里已经跳出bundle循环,将当前航班的所有票价记录(mid_flight_datas)扩展到总结果
+                end_flight_datas.extend(mid_flight_datas)
+
+            if verify_flag:
+                # 验价分支,
+                search_from_city_code = find_info_data.get("search_from_city_code")
+                search_to_city_code = find_info_data.get("search_to_city_code")
+                search_date = find_info_data.get("search_date")
+                end_search_date = time_format_conversion(
+                    search_date,
+                    in_strftime_str="%Y-%m-%d",
+                    out_strftime_str="%Y%m%d"
+                )
+
+                delete_many_filter = {
+                    "from_city_code": search_from_city_code,
+                    "to_city_code": search_to_city_code,
+                    "search_dep_time": {"$regex": r"^{}".format(end_search_date)}
+                }
+                delete_mongodb_clean_datas(self.db, self.clean_info_tab, self.logger, delete_many_filter)
+                # 重新保存最新的采集数据
+                if len(end_flight_datas) > 0:
+                    save_mongodb_clean_datas(self.db, self.clean_info_tab, self.website, self.logger, end_flight_datas, thread_number, True)
+
+        except Exception as e:
+            self.logger.error(f"thread_number:{thread_number} clean error: {from_airport_code}---{to_airport_code}---{search_date}---{str(e)}")
+        finally:
+            return end_flight_datas
+
+    def processing_data(self, thread_number):
+        while 1:
+            log_ob_ids = []
+            try:
+                ids = get_data_by_queue(self.task_queue)
+                ob_ids = [ObjectId(i) for i in ids]
+                log_ob_ids = ob_ids
+                # 批量查询 _id 包含在给定列表 ob_ids 中的文档
+                find_info_datas = self.db.get_collection(self.info_tab).find(
+                    {"_id": {"$in": ob_ids}}
+                )
+                final_flight_datas = []
+                # 每一次搜索航线
+                for find_info_data in find_info_datas:
+                    with lock:
+                        self.count += 1
+                    if self.count % 50 == 0:
+                        self.logger.info("thread_number:{0}, clean count: {1}".format(
+                            thread_number, self.count))
+                    # print(find_info_data)
+                    # exit()
+                    end_flight_datas = self.processing_data_one_search(find_info_data, thread_number)
+                    final_flight_datas.extend(end_flight_datas)
+
+                # 更改这些info表里的数据清理状态为1
+                update_result = self.db.get_collection(self.info_tab).update_many(
+                    {"_id": {"$in": ob_ids}},
+                    {"$set": {"clean_status": 1}}
+                )
+                self.logger.info(f"Updated documents: {update_result.modified_count}")
+                if len(final_flight_datas) > 0:
+                    save_mongodb_clean_datas(self.db, self.clean_info_tab, self.website, self.logger, final_flight_datas, thread_number, True)
+
+            except Exception as e:
+                self.logger.error(f"thread_number:{thread_number}, log_ob_ids:{log_ob_ids}, clean unknown err:{str(e)}")
+            finally:
+                self.task_queue.task_done()
+
+    def run_threading(self):
+        for thread_number in range(1, self.thread_numbers+1):
+            t = threading.Thread(
+                target=self.processing_data,
+                args=(thread_number,)
+            )
+            t.daemon = True
+            t.start()
+        self.task_queue.join()
+
+    def split_datas(self, datas, num):
+        return [datas[i:i + num] for i in range(0, len(datas), num)]
+
+    def run(self):
+        # 每次清除之前清洗的数据
+        # 查询已经采集完成但未清洗的数据批次
+        while 1:
+            # 取最新采集的数据
+            find_crawl_conf_datas = self.db.get_collection(
+                CRAWL_DATE_CONF_STATUS_TAB).find(  # find查询
+                {
+                    "website": self.website,
+                    # "crawl_status": 1,  # 反复测试清洗逻辑
+                    "clean_status": 0
+                },  # 查询条件
+                sort=[('_id', -1)]  # 排序规则,按 _id 降序
+            ).limit(1)  # 仅返回第一条记录(即最新的一条文档)
+
+            for find_crawl_conf_data in find_crawl_conf_datas:
+                self.count = 0
+                self.logger.info("start clean crawl_date: {}".format(
+                    find_crawl_conf_data.get("crawl_date")
+                ))
+                find_info_datas = self.db.get_collection(self.info_tab).find(
+                    {
+                        "crawl_date": find_crawl_conf_data.get("crawl_date"),
+                        "website": self.website,
+                        # "clean_status": 1,  # 反复测试清洗逻辑
+                        "clean_status": 0
+                    }
+                )
+                find_id_datas = [str(i.get("_id")) for i in find_info_datas]
+                find_id_datas_splits = self.split_datas(find_id_datas, 5)
+                # 一个队列里最多放5份info数据
+                for find_id_data_split in find_id_datas_splits:
+                    put_data_by_queue(self.task_queue, find_id_data_split)
+
+                self.run_threading()  # 多线程清理
+                self.logger.info("batch clean all counts: {}".format(self.count))
+
+                # 判断整体结束的条件
+                if find_crawl_conf_data.get("crawl_status") == 1:
+                    self.update_clean_date_status(find_crawl_conf_data)
+                    self.logger.info("end clean crawl_date: {}".format(
+                        find_crawl_conf_data.get("crawl_date")
+                    ))
+                    # 整体结束后, 统一计算本次清理的记录数, 为零则告警
+                    clean_crawl_date_counts = self.db.get_collection(
+                        self.clean_info_tab).count_documents(
+                        {
+                            "crawl_date": find_crawl_conf_data.get("crawl_date")
+                        }
+                    )
+                    self.logger.info("crawl_date: {0}, insert counts: {1}".format(
+                        find_crawl_conf_data.get("crawl_date"), clean_crawl_date_counts
+                    ))
+
+                    # 发送钉钉通知
+                    try:
+                        from clean_datas_send_notice import CheckCrawlDatas
+                        C = CheckCrawlDatas()
+                        C.send_website_spiders_crawl_date_counts(
+                            self.website, find_crawl_conf_data.get("crawl_date"),
+                            clean_crawl_date_counts
+                        )
+                    except Exception as e:
+                        self.logger.info("send dingding error: {0}".format(str(e)))
+
+            time.sleep(10 * 1)
+
+    def update_clean_date_status(self, find_crawl_conf_data):
+        # 更新 采集批次时间 状态
+        self.db.get_collection(CRAWL_DATE_CONF_STATUS_TAB).update_one(
+             {
+                 "_id": ObjectId(find_crawl_conf_data.get("_id"))
+             },
+             {
+                 "$set":
+                     {
+                         "clean_status": 1
+                     }
+             }
+        )
+
+
+if __name__ == "__main__":
+    C = CleanGKDatas()
+    C.run()
+

+ 668 - 0
3.31GK/frame_code/gk_flights_spider.py

@@ -0,0 +1,668 @@
+import threading
+import queue
+import time
+import json
+import re
+import execjs
+import random
+import retrying
+from lxml import etree
+
+import datetime
+import requests
+import tls_client
+from urllib.parse import urljoin
+from bson.objectid import ObjectId
+from flights_utils import get_now_time, put_data_by_queue, get_data_by_queue, \
+    time_format_conversion
+from my_logger import Logger
+from flights_mongodb import mongo_con_parse
+from settings import CRAWL_DATE_CONF_STATUS_TAB, FLIGHTS_WEBSITES_ROUTE_CONF_TAB, PROXY_TAIL
+
+
+def generate_date_range(days):
+    """
+    生成 n 天的日期范围
+    """
+    # 获取今天的日期(结束日期)
+    start_date = datetime.datetime.today()
+    # 计算开始日期:结束日期 - (days-1) 天(确保包含今天)
+    end_date = start_date + datetime.timedelta(days=days)
+    # 格式化输出
+    return (
+        start_date.strftime("%Y-%m-%d"),
+        end_date.strftime("%Y-%m-%d")
+    )
+
+
+class GKSpider():
+
+    def __init__(self):
+        self.website = 'gk'  # 网站
+        self.is_proxy = True
+        self.is_online = True  #
+        if self.is_proxy:
+            if self.is_online:
+                # proxies = {
+                #     'http': f'http://B_3351_HK___5_ss-{ip}:ev2pjj@proxy.renlaer.com:7778',
+                #     'https': f'http://B_3351_HK___5_ss-{ip}:ev2pjj@proxy.renlaer.com:7778'
+                # }
+                self.proxy_meta = f"http://B_3351_HK___5_ss-xxxxxxxxxxxx:{PROXY_TAIL}"  # AU / HK
+                self.time_sleep = 0.5
+            else:
+                self.proxy_meta = "http://127.0.0.1:7897"
+                # self.time_sleep = 5.5
+                self.time_sleep = 0.5
+            self.proxies = {
+                "http": self.proxy_meta,
+                "https": self.proxy_meta,
+            }
+        else:
+            self.proxies = None
+            self.time_sleep = 5.5
+
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+        with open('./js_files/akm逆向5.26.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        self.task_queue = queue.Queue()
+        self.cookies_queue = queue.Queue()
+        self.ja3_queue = queue.Queue()  # 要回收可用ja3  频繁切换ja3会增加触发验证码几率
+        self.headers = {
+            "accept": "application/json, text/plain, */*",
+            "accept-language": "zh-CN,zh;q=0.9",
+            "cache-control": "no-cache",
+            "culture": "zh-HK",
+            "origin": "https://www.jetstar.com",
+            "pragma": "no-cache",
+            "priority": "u=1, i",
+            "referer": "https://www.jetstar.com/",
+            "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\"",
+            "sec-fetch-dest": "empty",
+            "sec-fetch-mode": "cors",
+            "sec-fetch-site": "same-site",
+            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+        }
+
+        if self.is_online:
+            self.cookie_thread_num = 3
+            self.thread_numbers = 8
+            self.crawl_days = 10
+        else:
+            self.cookie_thread_num = 3
+            self.thread_numbers = 8
+            self.crawl_days = 5
+
+        self.cookie_queue_num = 4  # 队列中 cookie 的数量
+        self.ja3_queue_num = 4  # 队列中的 ja3 数量
+
+        self.retry_number = 2  # 5
+        self.db = mongo_con_parse()
+        self.info_tab = "flights_{}_info_tab".format(self.website)
+        self.logger = Logger('./logs/{}_flights_spiders.log'.format(self.website), 'debug').logger
+
+    def init_crawl_date_data(self):
+        # 初始化 采集批次时间 状态
+        # 开始采集 创建一个批次时间   采集完成更新采集状态
+        self.db.get_collection(CRAWL_DATE_CONF_STATUS_TAB).insert_one(
+            {
+                "website": self.website,
+                "crawl_date": self.crawl_date,
+                "crawl_status": 0,  # 采集状态
+                "clean_status": 0,  # 数据清洗状态
+                "to_csv_status": 0  # 导出文件状态
+            }
+        )
+
+    def init_crawl_conf(self):
+        # 获取采集时间
+        self.crawl_date = get_now_time()
+        self.logger.info("本次数据采集批次为: {}".format(self.crawl_date))
+        # 初始化 采集批次时间 状态
+        self.init_crawl_date_data()
+
+        # 从航线表里提取记录(提取采集航线表的)
+        search_routes = self.db.get_collection(
+            FLIGHTS_WEBSITES_ROUTE_CONF_TAB
+        ).find(
+            {
+                "source_website": self.website,  # 数据来源网站
+                "website_status": 1,  # 网站是否采集状态
+                "flight_route_status": 1  # 航线是否采集状态
+            },
+            {
+                "_id": 0  # 排除返回结果中的_id字段,因为MongoDB默认会返回_id,但这里用户可能不需要这个字段
+            }
+        )
+        self.logger.info("获取需要采集的航线.并喂给队列")
+        for search_route in search_routes:
+            put_data_by_queue(self.task_queue, search_route)
+
+    def general_proxies(self):
+        if self.is_online:
+            proxy_meta = self.proxy_meta
+            random_no = ''.join(random.choices('0123456789', k=12))
+            # region = ''.join(random.choices(["US"], k=1))
+            # 香港 新加坡 台湾 都能服务  新加坡最快最稳定
+            # proxy_meta_mid = re.sub(r"_(US)_", f"_{region}_", proxy_meta)
+            proxy_meta_new = re.sub(r"-(x+):", f"-{random_no}:", proxy_meta)
+            # print(f"proxy_meta_new: {proxy_meta_new}")
+            proxies = {
+                "http": proxy_meta_new,
+                "https": proxy_meta_new,
+            }
+        else:
+            proxies = self.proxies
+        return proxies
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=3000)
+    def request_new_cookie(self):
+        statusTs = int(time.time() * 1000)
+
+        try:
+            ua, ja3_string = self.ja3_queue.get()
+            proxy = self.general_proxies()
+
+            get_ck_session = tls_client.Session(
+                ja3_string=ja3_string,
+            )
+            headers = {
+                'user-agent': ua,
+                'Accept-Encoding': 'gzip, deflate, br',
+                'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+                'Connection': 'keep-alive',
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'accept-language': 'zh-CN,zh;q=0.9',
+                'cache-control': 'no-cache',
+                'pragma': 'no-cache',
+                'priority': 'u=0, i',
+                'referer': 'https://booking.jetstar.com/',
+                'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+                'sec-ch-ua-mobile': '?0',
+                'sec-ch-ua-platform': '"Windows"',
+                'sec-fetch-dest': 'document',
+                'sec-fetch-mode': 'navigate',
+                'sec-fetch-site': 'same-origin',
+                'sec-fetch-user': '?1',
+                'upgrade-insecure-requests': '1'
+            }
+            # akm js file url
+            akm_url = "https://www.jetstar.com/xsRfGu1Zb-8uTxanFA/9kX3LGbVJLVb/FB0BajANQQk/YE94/HSh0EkU"
+            data = {
+                'sensor_data': self.ctx.call('encrypt1', statusTs)
+            }
+            response1 = get_ck_session.post(akm_url, headers=headers, data=data,
+                                            proxy=proxy
+                                            )
+
+            # print(response1.status_code)
+            # print(response1.text)
+            # print(response1.cookies.get_dict())
+            # print('111', response.headers)
+            bmsz = response1.cookies.get_dict()['bm_sz']
+            # print('bmsz => ', bmsz)
+
+            data2 = {
+                "sensor_data": self.ctx.call('encrypt2', statusTs, bmsz)
+            }
+
+            data2 = json.dumps(data2)
+            response2 = get_ck_session.post(akm_url, headers=headers, data=data2,
+                                            proxy=proxy
+                                            )
+
+            # print(response2.text)
+            # print(response2.cookies.get_dict())
+            if response2.status_code == 201:
+                self.logger.debug('成功获取 cookie bm-sz: {}'.format(bmsz[-16:]))
+
+                # 返回第一次请求响应的cookie
+                return response1.cookies.get_dict()
+            else:
+                self.logger.error('状态码错误{}, {}'.format(response2.status_code, response2.text))
+        except Exception as e:
+            print('request cookie error, 重试中..', e)
+            raise
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=3000)
+    def get_ja3(self):
+        url = "http://8.218.51.130:9003/api/v1/ja3"
+        payload = {}
+        headers = {
+            'cid': '750B5141EDBF7FA6F73A99C768130099'
+        }
+        response = requests.get(url, headers=headers, data=payload, timeout=15)
+        if response.status_code == 200:
+            # print(response.json())
+            res_json = response.json()
+            if res_json.get("code") == 0:
+                ja3 = res_json.get("data").get("ja3_str")
+                ua = res_json.get("data").get("ua")
+                if "--" not in ja3 and ",," not in ja3:
+                    return ua, ja3
+
+    def _refresh_ja3(self):
+        while True:
+            if self.ja3_queue.qsize() < self.ja3_queue_num:
+                try:
+                    ua, ja3 = self.get_ja3()
+                    # logger.debug('获取ja3成功...')
+                    self.ja3_queue.put((ua, ja3))
+                except Exception as e:
+                    self.logger.error(f'ja3接口错误: {e}')
+
+            time.sleep(3)
+
+    def _refresh_cookie(self):
+        while True:
+            if self.cookies_queue.qsize() < self.cookie_queue_num:
+                cookie = self.request_new_cookie()
+                self.cookies_queue.put(cookie)
+
+            time.sleep(3)
+
+    @retrying.retry(stop_max_attempt_number=5, wait_fixed=3000)
+    def tls_client_get_request(self, url, params, max_redirects=3):
+        ua, ja3_string = self.ja3_queue.get()
+        bmsz_cookie = self.cookies_queue.get()
+        proxy = self.general_proxies()
+
+        req_session = tls_client.Session(
+            ja3_string=ja3_string,  # 直接注入自定义指纹
+        )
+        headers = {
+            'user-agent': ua,
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        redirect_count = 0
+        current_url = url
+
+        while redirect_count < max_redirects:
+            response = req_session.get(current_url, headers=headers, cookies=bmsz_cookie, params=params,
+                                       timeout_seconds=15,  # timeout
+                                       proxy=proxy
+                                       )
+
+            # print(response.status_code)
+            # print(response.text)
+
+            # 检查是否为重定向状态码
+            if response.status_code in (301, 302, 303, 307, 308):
+                # 获取 Location 头(需处理相对路径)
+                location = response.headers.get("Location")
+                # if not location:
+                #     break
+                current_url = urljoin(current_url, location)
+                redirect_count += 1
+
+            elif response.status_code == 200:
+                html = etree.HTML(response.text)
+                data = html.xpath("//script[@id='bundle-data-v2']/text()")
+                if data:
+                    json_data = json.loads(data[0])
+                    # 请求成功,归还Cookie 和 ja3
+                    self.cookies_queue.put(bmsz_cookie)  # 成功时放回cookie
+                    self.ja3_queue.put((ua, ja3_string))  # 回收可用ja3  频繁切换ja3会增加触发验证码几率
+
+                    return json_data  # 返回提取后的数据
+                else:
+                    self.logger.warning(f'触发验证码或拒绝访问错误, 重试中... => {response.text}')
+                    raise
+            else:
+                self.logger.error(f'状态码错误, {response.status_code}, 响应内容:{response.text}')
+
+        raise Exception(f"超过最大重定向次数, 检查({max_redirects})")
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=3000)
+    def get_cookie_with_verify_price(self):
+        """验价,刷cookie"""
+        statusTs = int(time.time() * 1000)
+        try:
+            ua, ja3_string = self.get_ja3()
+            proxy = self.general_proxies()
+
+            get_ck_session = tls_client.Session(
+                ja3_string=ja3_string,
+            )
+            headers = {
+                'user-agent': ua,
+                'Accept-Encoding': 'gzip, deflate, br',
+                'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+                'Connection': 'keep-alive',
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'accept-language': 'zh-CN,zh;q=0.9',
+                'cache-control': 'no-cache',
+                'pragma': 'no-cache',
+                'priority': 'u=0, i',
+                'referer': 'https://booking.jetstar.com/',
+                'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+                'sec-ch-ua-mobile': '?0',
+                'sec-ch-ua-platform': '"Windows"',
+                'sec-fetch-dest': 'document',
+                'sec-fetch-mode': 'navigate',
+                'sec-fetch-site': 'same-origin',
+                'sec-fetch-user': '?1',
+                'upgrade-insecure-requests': '1'
+            }
+            # akm js file url
+            akm_url = "https://www.jetstar.com/xsRfGu1Zb-8uTxanFA/9kX3LGbVJLVb/FB0BajANQQk/YE94/HSh0EkU"
+            data = {
+                'sensor_data': self.ctx.call('encrypt1', statusTs)
+            }
+            response1 = get_ck_session.post(akm_url, headers=headers, data=data,
+                                            proxy=proxy
+                                            )
+
+            # print(response1.status_code)
+            # print(response1.text)
+            # print(response1.cookies.get_dict())
+            # print('111', response.headers)
+            bmsz = response1.cookies.get_dict()['bm_sz']
+            # print('bmsz => ', bmsz)
+
+            data2 = {
+                "sensor_data": self.ctx.call('encrypt2', statusTs, bmsz)
+            }
+
+            data2 = json.dumps(data2)
+            response2 = get_ck_session.post(akm_url, headers=headers, data=data2,
+                                            proxy=proxy
+                                            )
+
+            # print(response2.text)
+            # print(response2.cookies.get_dict())
+            if response2.status_code == 201:
+                self.logger.debug('成功获取 cookie bm-sz: {}'.format(bmsz[-16:]))
+
+                # 返回第一次请求响应的cookie
+                return response1.cookies.get_dict()
+            else:
+                self.logger.error('状态码错误{}, {}'.format(response2.status_code, response2.text))
+        except Exception as e:
+            print('request cookie error, 重试中..', e)
+            raise
+
+    @retrying.retry(stop_max_attempt_number=5, wait_fixed=3000)
+    def get_flights_with_verify_price(self, from_city_code, to_city_code, search_date, thread_number=0, max_redirects=3):
+        """验价逻辑, 不用队列"""
+        sleep_r = self.time_sleep if self.time_sleep >= 1 else 1
+        time.sleep(sleep_r)
+
+        params = {
+            "s": "true",
+            "adults": "1",
+            "children": "0",
+            "infants": "0",
+            "selectedclass1": "economy",
+            "currency": "CNY",
+            "mon": "true",
+            "channel": "DESKTOP",
+            "origin1": from_city_code,  # "PVG"
+            "destination1": to_city_code,
+            "departuredate1": search_date
+        }
+        ua, ja3_string = self.get_ja3()
+        bmsz_cookie = self.get_cookie_with_verify_price()
+        proxy = self.general_proxies()
+
+        req_session = tls_client.Session(
+            ja3_string=ja3_string,  # 直接注入自定义指纹
+        )
+        headers = {
+            'user-agent': ua,
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        redirect_count = 0
+        current_url = self.search_flights_api
+
+        while redirect_count < max_redirects:
+            response = req_session.get(current_url, headers=headers, cookies=bmsz_cookie, params=params,
+                                       timeout_seconds=15,  # timeout
+                                       proxy=proxy
+                                       )
+
+            # print(response.status_code)
+            # print(response.text)
+
+            # 检查是否为重定向状态码
+            if response.status_code in (301, 302, 303, 307, 308):
+                # 获取 Location 头(需处理相对路径)
+                location = response.headers.get("Location")
+                # if not location:
+                #     break
+                current_url = urljoin(current_url, location)
+                redirect_count += 1
+
+            elif response.status_code == 200:
+                html = etree.HTML(response.text)
+                data = html.xpath("//script[@id='bundle-data-v2']/text()")
+                if data:
+                    json_data = json.loads(data[0])
+
+                    return {"inner_data": json_data}  # 返回提取后的数据
+                else:
+                    self.logger.warning(f'触发验证码或拒绝访问错误, 重试中... => {response.text}')
+                    raise
+            else:
+                self.logger.error(f'状态码错误, {response.status_code}, 响应内容:{response.text}')
+
+        raise Exception(f"超过最大重定向次数, 检查({max_redirects})")
+
+    def get_flights(self, from_city_code, to_city_code, search_date, thread_number=0):
+        sleep_r = self.time_sleep if self.time_sleep >= 1 else 1
+        time.sleep(sleep_r)
+
+        params = {
+            "s": "true",
+            "adults": "1",
+            "children": "0",
+            "infants": "0",
+            "selectedclass1": "economy",
+            "currency": "CNY",
+            "mon": "true",
+            "channel": "DESKTOP",
+            "origin1": from_city_code,  # "PVG"
+            "destination1": to_city_code,
+            "departuredate1": search_date
+        }
+
+        try:
+            resp_json = self.tls_client_get_request(self.search_flights_api, params)
+
+            self.logger.info(f'获取数据成功 {from_city_code}-{to_city_code} {search_date} => {str(resp_json)[:200]}')
+            return {"inner_data": resp_json}
+
+        except Exception as e:
+            self.logger.error(
+                f"thread_number:{thread_number} {from_city_code}-{to_city_code}-{search_date}- 5次重试全部失败 {str(e)}")
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=3000)
+    def search_flight_date(self, from_city_code, to_city_code, start_date, end_date):
+        """查询航班日期, 即那天有航班"""
+        url = "https://digitalapi.jetstar.com/v1/farecache/flights/batch/availability-with-fareclasses"
+        params = {
+            "flightCount": "1",
+            "includeSoldOut": "true",
+            "requestType": "StarterOnly",
+            "from": start_date,  # 采集开始时间
+            "end": end_date,  # 采集结束时间,可随意写
+            "departures": from_city_code,
+            "arrivals": to_city_code,
+            "direction": "outbound",
+            "paxCount": "1",
+            "includeFees": "false"
+        }
+
+        response = requests.get(url, headers=self.headers, params=params, verify=False)
+        response.raise_for_status()
+        # for i in response.json():
+        #     print(i)
+        json_data = response.json()[0]['routes']
+
+        # 只有一个键值对
+        for key, val in json_data.items():
+            flight_dates = list(val.get('flights', {}).keys())
+            # print(f'航段: {key} 对应有航班的日期为: {flight_dates}')
+            return flight_dates
+
+    def thread_task(self, thread_number):
+        while True:
+            try:
+                # 从队列取出 航线数据
+                queue_data = get_data_by_queue(self.task_queue)
+                search_route = queue_data  # 从队列里取出本次航线
+                from_city_code = search_route['from_city_code']
+                to_city_code = search_route['to_city_code']
+
+                # 生成采集天数的日期范围, 额外多一天
+                start_date, end_date = generate_date_range(self.crawl_days)
+                # 从网站接口查询那天有航班, 日期格式要转换
+                search_date_list = self.search_flight_date(from_city_code, to_city_code, start_date, end_date)
+                if not search_date_list:
+                    self.logger.info(f'{start_date}~{end_date} 内的航段 {from_city_code}-{to_city_code} 无航班')
+                    continue
+
+                for search_date in search_date_list:
+                    self.logger.info(f"线程:{thread_number}, 正在采集 => {search_date} {from_city_code}-{to_city_code}")
+                    # 日期格式转换 20250531 => 2025-05-31
+                    search_date = time_format_conversion(
+                        search_date,
+                        in_strftime_str="%Y%m%d",
+                        out_strftime_str="%Y-%m-%d"
+                    )
+
+                    flights_json = self.get_flights(from_city_code, to_city_code, search_date, thread_number)
+                    if flights_json:
+                        Trips = flights_json.get('inner_data', {}).get("Trips", [])
+                        if not Trips:
+                            self.logger.info(
+                                f'Trip 为空, {search_date}:{from_city_code}-{to_city_code}----{flights_json}')
+                            continue
+
+                        Flights = Trips[0].get('Flights', [])
+                        if len(Flights) > 0:
+                            self.logger.info("获取航班信息{1}:{2}-{3}成功, thread_number:{0}".format(thread_number, search_date,
+                                                                                             from_city_code,
+                                                                                             to_city_code))
+
+                            self.save_mongodb_datas(flights_json, from_city_code, to_city_code, search_date)
+                        else:
+                            self.logger.info(
+                                f"获取航班信息为空 {search_date}:{from_city_code}-{to_city_code},thread_number:{thread_number}")
+
+                    else:
+                        self.logger.info("thread_number:{0},请求航班信息失败/ 该航线数据可能需要重新抓取{1}:{2}-{3}".format(
+                            thread_number, search_date, from_city_code, to_city_code))
+
+            except Exception as e:
+                self.logger.error(f"thread_number:{thread_number}- 未知错误--unknown err:{str(e)}")
+
+            finally:
+                self.task_queue.task_done()
+
+    def run(self):
+        # 往队列上传任务,初始化 采集批次时间状态
+        self.init_crawl_conf()
+
+        thread_list = []
+
+        # 刷cookie
+        for _ in range(1, self.cookie_thread_num + 1):
+            t_get_cookie = threading.Thread(target=self._refresh_cookie)
+            thread_list.append(t_get_cookie)
+
+        # 刷ja3
+        t_get_ja3 = threading.Thread(target=self._refresh_ja3)
+        thread_list.append(t_get_ja3)
+
+        # 采集data
+        for thread_number in range(1, self.thread_numbers + 1):
+            t_get_data = threading.Thread(target=self.thread_task, args=(thread_number,))
+            thread_list.append(t_get_data)
+
+        for t_obj in thread_list:
+            t_obj.setDaemon(True)
+            t_obj.start()
+
+        self.task_queue.join()
+        # 更新采集状态
+        self.update_crawl_date_status()
+        # 关闭数据库连接
+        self.db.client.close()
+
+    def save_mongodb_datas(self, info_data, from_city_code, to_city_code, search_date):
+        # 将采集到的数据入库  并打上相应的数据
+        info_data["website"] = self.website
+        info_data["crawl_date"] = self.crawl_date
+        info_data["search_from_city_code"] = from_city_code
+
+        # info_data["search_from_airport_code"] = from_airport_code
+
+        info_data["search_to_city_code"] = to_city_code
+
+        # info_data["search_to_airport_code"] = to_airport_code
+
+        info_data["search_date"] = search_date  # 搜索日期,采集航班的日期
+        info_data["clean_status"] = 0  # 给每一条数据标记清理状态
+        self.db.get_collection(self.info_tab).insert_one(info_data)
+
+    def update_crawl_date_status(self):
+        # 更新 采集批次时间 状态
+        find_data = self.db.get_collection(CRAWL_DATE_CONF_STATUS_TAB).find_one(
+            {
+                "website": self.website,
+                "crawl_date": self.crawl_date,
+                "crawl_status": 0
+            }
+        )
+        if find_data:
+            self.db.get_collection(CRAWL_DATE_CONF_STATUS_TAB).update_one(
+                {
+                    "_id": ObjectId(find_data.get("_id"))  # 通过文档的 _id 精确匹配
+                },
+                {
+                    "$set":
+                        {
+                            "crawl_status": 1  # 将 crawl_status 设为 1(表示已爬取)
+                        }
+                }
+            )
+
+
+if __name__ == "__main__":
+    spider = GKSpider()
+    spider.run()

+ 107 - 0
3.31GK/frame_code/gk_flights_spider_que_work.py

@@ -0,0 +1,107 @@
+import threading
+import json
+import time
+from my_logger import Logger
+from flights_utils import time_format_conversion
+from flights_redis import RedisHelper
+from flights_mongodb import mongo_con_parse
+from settings import CRAWL_DATE_CONF_STATUS_TAB
+from insert_flights_gk_route import get_airport_data, find_route_airport_pair
+from gk_flights_spider import GKSpider
+from clean_gk_flights_datas import CleanGKDatas
+
+
+class GKSpidersQueWork(GKSpider, CleanGKDatas):
+
+    def __init__(self):
+        super().__init__()
+        self.website = "gk"
+        self.que_key = "flight_website_{}".format(self.website)
+        self.redis_ = RedisHelper()
+        self.db = mongo_con_parse()
+        self.info_tab = "flights_{}_info_tab".format(self.website)
+        self.clean_info_tab = "clean_{}".format(self.info_tab)
+        # self.thread_numbers = 5
+        self.thread_numbers = 1
+        self.logger = Logger(
+            './logs/{}_flights_spiders_que_work.log'.format(self.website),
+            'debug').logger
+
+    def get_clean_1_crawl_date(self):
+        website_crawl_date = None
+        website_crawl_dates = self.db.get_collection(
+                        CRAWL_DATE_CONF_STATUS_TAB).distinct(
+                            "crawl_date",
+                            {
+                                "website": self.website,
+                                "clean_status": 1,
+                                "crawl_status": 1
+                            }
+                        )
+        if len(website_crawl_dates) > 0:
+            website_crawl_dates.sort()
+            website_crawl_date = website_crawl_dates[-1]
+        return website_crawl_date
+
+    def thread_task(self, thread_number):
+        while True:
+            que_data = self.redis_.get_nowait(self.que_key)
+            if que_data:
+                task_json = json.loads(que_data)
+                from_city_code = task_json.get("from_city_code")
+                to_city_code = task_json.get("to_city_code")
+                search_date = task_json.get("search_date")
+                # 查询日期转换
+                search_date = time_format_conversion(
+                    search_date,
+                    in_strftime_str="%Y%m%d",
+                    out_strftime_str="%Y-%m-%d"
+                )
+                self.logger.info(f"正在采集: {search_date}:{from_city_code}-{to_city_code}")
+                
+                # 采集航班数据
+                flights_json = self.get_flights_with_verify_price(from_city_code, to_city_code, search_date, thread_number)
+                if flights_json:
+                    self.logger.info(
+                        "thread_number_name:{0},获取航班信息成功: {1}:{2}-{3}".format(
+                            thread_number, search_date, from_city_code,
+                            to_city_code))
+                    self.logger.info(
+                        "thread_number_name:{0}, {1}:{2}-{3}, resp json: {4}".format(
+                            thread_number, search_date, from_city_code, to_city_code,
+                            json.dumps(flights_json)[:60])
+                    )
+                    # 清洗
+                    flights_json["website"] = self.website
+                    # 获取采集日期
+                    crawl_date = self.get_clean_1_crawl_date()
+                    flights_json["crawl_date"] = crawl_date
+                    flights_json["search_from_city_code"] = from_city_code
+                    flights_json["search_to_city_code"] = to_city_code
+                    flights_json["search_date"] = search_date
+
+                    # 直接进入清洗流程(并开启验价,验价会先删除该航段当天的所有数据,再重新获取)
+                    self.processing_data_one_search(flights_json, thread_number, True)
+
+                    self.logger.info("thread_number_name:{0},保存航班信息结束: {1}:{2}-{3}".format(thread_number, search_date,
+                                                                                           from_city_code,
+                                                                                           to_city_code))
+                else:
+                    self.logger.info("thread_number:{0},获取航班信息{1}:{2}-{3}失败".format(
+                        thread_number, search_date, from_city_code, to_city_code))
+
+            time.sleep(0.5)
+
+    def run_threading(self):
+        for thread_number in range(1, self.thread_numbers+1):
+            t = threading.Thread(
+                target=self.thread_task,
+                args=(thread_number,)
+            )
+            t.start()
+        self.task_queue.join()
+
+
+if __name__ == "__main__":
+    R = GKSpidersQueWork()
+    R.run_threading()

+ 54 - 0
3.31GK/frame_code/insert_flights_gk_city_airport_codes.py

@@ -0,0 +1,54 @@
+import requests
+
+from flights_mongodb import mongo_con_parse
+from settings import FLIGHTS_CITY_AIRPORT_CODE_TAB
+
+
+def insert_codes():
+
+    """
+        网站没有城市代码,得自己找 根据航段找...
+        采集航段中
+            # 能查到的城市码就这几个 
+                城市码和机场码不一致: 'NRT', KIX'
+                存疑 HKO "北海道 (所有机场)", EKK "四國 (所有機場)" 应该是网站为了区分地区自定义的
+                城市机场码一致的 'OKA', 'CTS', 'TAK', 'KMJ', 'KOJ', 'KMI', 'AKJ', 'OIT', 'MYJ', 'NGO', 'FUK', 'NGS' KCZ
+            # 没有的城市/机场码: OSA SPK TYO SHI
+            这里直接手动定义
+    """
+    airport_code_li = [
+        {"city_code": "TYO", "country_code": "JP", "airport_code": "NRT"},
+        {"city_code": "OSA", "country_code": "JP", "airport_code": "KIX"},
+
+        {"city_code": "HKO", "country_code": "JP", "airport_code": "HKO"},
+        {"city_code": "EKK", "country_code": "JP", "airport_code": "EKK"},
+
+        {"country_code": "JP", "city_code": "OKA", "airport_code": "OKA"},
+        {"country_code": "JP", "city_code": "CTS", "airport_code": "CTS"},
+        {"country_code": "JP", "city_code": "TAK", "airport_code": "TAK"},
+        {"country_code": "JP", "city_code": "KMJ", "airport_code": "KMJ"},
+        {"country_code": "JP", "city_code": "KOJ", "airport_code": "KOJ"},
+        {"country_code": "JP", "city_code": "KMI", "airport_code": "KMI"},
+        {"country_code": "JP", "city_code": "AKJ", "airport_code": "AKJ"},
+        {"country_code": "JP", "city_code": "OIT", "airport_code": "OIT"},
+        {"country_code": "JP", "city_code": "MYJ", "airport_code": "MYJ"},
+        {"country_code": "JP", "city_code": "NGO", "airport_code": "NGO"},
+        {"country_code": "JP", "city_code": "FUK", "airport_code": "FUK"},
+        {"country_code": "JP", "city_code": "NGS", "airport_code": "NGS"},
+        {"country_code": "JP", "city_code": "KCZ", "airport_code": "KCZ"},
+
+
+    ]
+    print(len(airport_code_li))
+
+    db = mongo_con_parse()
+    website = "gk"
+    db.get_collection(FLIGHTS_CITY_AIRPORT_CODE_TAB).delete_many({"website": website})  # 清空原来属于该航司的数据
+    for item in airport_code_li:
+        item["website"] = website
+        db.get_collection(FLIGHTS_CITY_AIRPORT_CODE_TAB).insert_one(item)
+    print('insert finish...')
+
+
+if __name__ == "__main__":
+    insert_codes()

+ 391 - 0
3.31GK/frame_code/insert_flights_gk_route.py

@@ -0,0 +1,391 @@
+from flights_mongodb import mongo_con_parse
+from settings import FLIGHTS_WEBSITES_ROUTE_CONF_TAB, FLIGHTS_CITY_AIRPORT_CODE_TAB
+
+db = mongo_con_parse()
+website = "gk"
+
+
+def get_from_to_data(input_str):
+    input_str_list = input_str.split("\t")
+    from_airport_code = input_str_list[0]
+    to_airport_code = input_str_list[1]
+    return {
+        "from_airport_code": from_airport_code,
+        "to_airport_code": to_airport_code
+    }
+
+
+def get_city_data(airport_code):
+    city_code = None
+    find_data = db.get_collection(FLIGHTS_CITY_AIRPORT_CODE_TAB).find_one(  # 这里拿出每个机场码所对应的城市码
+        {
+            "airport_code": airport_code,
+            "website": website
+        }
+    )
+    if find_data:
+        city_code = find_data.get("city_code")
+    return city_code
+
+
+def get_airport_data(city_code):
+    airport_code_list = []
+    find_data = db.get_collection(FLIGHTS_CITY_AIRPORT_CODE_TAB).find(  # 这里要拿出每个城市所有对应的机场码
+        {
+            "city_code": city_code,
+            "website": website
+        }
+    )
+    for item in find_data:
+        airport_code = item.get("airport_code")
+        airport_code_list.append(airport_code)
+    return airport_code_list
+
+
+# 根据城市码获取机场对
+def find_route_airport_pair(from_city_code, to_city_code):
+    airport_code_pair_list = []
+    find_data = db.get_collection(FLIGHTS_WEBSITES_ROUTE_CONF_TAB).find(  # 这里拿出在航线表里定义的城市码对关联的机场码对
+        {
+            "from_city_code": from_city_code,
+            "to_city_code": to_city_code,
+            "source_website": website
+        }
+    )
+    for item in find_data:
+        from_airport_code = item.get("from_airport_code")
+        to_airport_code = item.get("to_airport_code")
+        airport_code_pair_list.append((from_airport_code, to_airport_code))  # 存元组
+    return airport_code_pair_list
+
+
+def insert_routes():
+    # 这里是城市代码, 网站没有的城市/机场码: OSA SPK TYO SHI, 删去它们的航段
+    routes = """
+    KCZ	FUK
+    KCZ	KOJ
+    KCZ	KMJ
+    KCZ	KMI
+    KCZ	OIT
+    KCZ	OKA
+    KCZ	AKJ
+    KCZ	NGS
+    FUK	KCZ
+    FUK	MYJ
+    FUK	NGO
+    FUK	OKA
+    FUK	TAK
+    FUK	AKJ
+    KOJ	KCZ
+    KOJ	KMJ
+    KOJ	MYJ
+    KOJ	NGO
+    KOJ	OKA
+    KOJ	TAK
+    KOJ	AKJ
+    KMJ	KCZ
+    KMJ	KOJ
+    KMJ	MYJ
+    KMJ	NGO
+    KMJ	OKA
+    KMJ	TAK
+    KMJ	AKJ
+    MYJ	FUK
+    MYJ	KOJ
+    MYJ	KMJ
+    MYJ	KMI
+    MYJ	OIT
+    MYJ	OKA
+    MYJ	AKJ
+    MYJ	NGS
+    KMI	KCZ
+    KMI	MYJ
+    KMI	OKA
+    KMI	TAK
+    KMI	AKJ
+    NGO	FUK
+    NGO	KOJ
+    NGO	KMJ
+    NGO	OKA
+    OIT	KCZ
+    OIT	MYJ
+    OIT	OKA
+    OIT	TAK
+    OIT	AKJ
+    OKA	KCZ
+    OKA	FUK
+    OKA	KOJ
+    OKA	KMJ
+    OKA	MYJ
+    OKA	KMI
+    OKA	NGO
+    OKA	OIT
+    OKA	TAK
+    OKA	AKJ
+    OKA	NGS
+    TAK	FUK
+    TAK	KOJ
+    TAK	KMJ
+    TAK	KMI
+    TAK	OIT
+    TAK	OKA
+    TAK	AKJ
+    TAK	NGS
+    AKJ	KCZ
+    AKJ	FUK
+    AKJ	KOJ
+    AKJ	KMJ
+    AKJ	MYJ
+    AKJ	KMI
+    AKJ	OIT
+    AKJ	OKA
+    AKJ	TAK
+    AKJ	NGS
+    NGS	KCZ
+    NGS	MYJ
+    NGS	OKA
+    NGS	TAK
+    NGS	AKJ
+
+    KCZ	FUK
+    KCZ	KOJ
+    KCZ	KMJ
+    KCZ	KMI
+    KCZ	OIT
+    KCZ	OKA
+    KCZ	KIX
+    KCZ	CTS
+    KCZ	NRT
+    KCZ	HKO
+    KCZ	AKJ
+    KCZ	NGS
+    FUK	KCZ
+    FUK	MYJ
+    FUK	NGO
+    FUK	OKA
+    FUK	KIX
+    FUK	CTS
+    FUK	TAK
+    FUK	NRT
+    FUK	HKO
+    FUK	EKK
+    FUK	AKJ
+    KOJ	KCZ
+    KOJ	KMJ
+    KOJ	MYJ
+    KOJ	NGO
+    KOJ	OKA
+    KOJ	CTS
+    KOJ	TAK
+    KOJ	NRT
+    KOJ	HKO
+    KOJ	EKK
+    KOJ	AKJ
+    KMJ	KCZ
+    KMJ	KOJ
+    KMJ	MYJ
+    KMJ	NGO
+    KMJ	OKA
+    KMJ	KIX
+    KMJ	CTS
+    KMJ	TAK
+    KMJ	NRT
+    KMJ	HKO
+    KMJ	EKK
+    KMJ	AKJ
+    MYJ	FUK
+    MYJ	KOJ
+    MYJ	KMJ
+    MYJ	KMI
+    MYJ	OIT
+    MYJ	OKA
+    MYJ	CTS
+    MYJ	NRT
+    MYJ	HKO
+    MYJ	AKJ
+    MYJ	NGS
+    KMI	KCZ
+    KMI	MYJ
+    KMI	OKA
+    KMI	KIX
+    KMI	CTS
+    KMI	TAK
+    KMI	NRT
+    KMI	HKO
+    KMI	EKK
+    KMI	AKJ
+    NGO	FUK
+    NGO	KOJ
+    NGO	KMJ
+    NGO	OKA
+    NGO	CTS
+    NGO	HKO
+    OIT	KCZ
+    OIT	MYJ
+    OIT	OKA
+    OIT	KIX
+    OIT	CTS
+    OIT	TAK
+    OIT	NRT
+    OIT	HKO
+    OIT	EKK
+    OIT	AKJ
+    OKA	KCZ
+    OKA	FUK
+    OKA	KOJ
+    OKA	KMJ
+    OKA	MYJ
+    OKA	KMI
+    OKA	NGO
+    OKA	OIT
+    OKA	KIX
+    OKA	CTS
+    OKA	TAK
+    OKA	NRT
+    OKA	HKO
+    OKA	EKK
+    OKA	AKJ
+    OKA	NGS
+    KIX	KCZ
+    KIX	FUK
+    KIX	KMJ
+    KIX	KMI
+    KIX	OIT
+    KIX	OKA
+    KIX	CTS
+    KIX	NRT
+    KIX	HKO
+    KIX	EKK
+    KIX	AKJ
+    KIX	NGS
+    CTS	KCZ
+    CTS	FUK
+    CTS	KOJ
+    CTS	KMJ
+    CTS	MYJ
+    CTS	KMI
+    CTS	NGO
+    CTS	OIT
+    CTS	OKA
+    CTS	KIX
+    CTS	TAK
+    CTS	NRT
+    CTS	EKK
+    CTS	NGS
+    TAK	FUK
+    TAK	KOJ
+    TAK	KMJ
+    TAK	KMI
+    TAK	OIT
+    TAK	OKA
+    TAK	CTS
+    TAK	NRT
+    TAK	HKO
+    TAK	AKJ
+    TAK	NGS
+    NRT	KCZ
+    NRT	FUK
+    NRT	KOJ
+    NRT	KMJ
+    NRT	MYJ
+    NRT	KMI
+    NRT	OIT
+    NRT	OKA
+    NRT	KIX
+    NRT	CTS
+    NRT	TAK
+    NRT	HKO
+    NRT	EKK
+    NRT	AKJ
+    NRT	NGS
+    HKO	KCZ
+    HKO	FUK
+    HKO	KOJ
+    HKO	KMJ
+    HKO	MYJ
+    HKO	KMI
+    HKO	NGO
+    HKO	OIT
+    HKO	OKA
+    HKO	KIX
+    HKO	TAK
+    HKO	NRT
+    HKO	EKK
+    HKO	NGS
+    EKK	FUK
+    EKK	KOJ
+    EKK	KMJ
+    EKK	KMI
+    EKK	OIT
+    EKK	OKA
+    EKK	KIX
+    EKK	CTS
+    EKK	NRT
+    EKK	HKO
+    EKK	AKJ
+    EKK	NGS
+    AKJ	KCZ
+    AKJ	FUK
+    AKJ	KOJ
+    AKJ	KMJ
+    AKJ	MYJ
+    AKJ	KMI
+    AKJ	OIT
+    AKJ	OKA
+    AKJ	KIX
+    AKJ	TAK
+    AKJ	NRT
+    AKJ	EKK
+    AKJ	NGS
+    NGS	KCZ
+    NGS	MYJ
+    NGS	OKA
+    NGS	KIX
+    NGS	CTS
+    NGS	TAK
+    NGS	NRT
+    NGS	HKO
+    NGS	EKK
+    NGS	AKJ
+       """
+
+    db.get_collection(FLIGHTS_WEBSITES_ROUTE_CONF_TAB).delete_many({"source_website": website})  # 清空之前数据
+    temp_list = [i.strip() for i in routes.split("\n") if i.strip()]
+    routes = list(set(temp_list))  # 去重
+    routes.sort(key=temp_list.index)  # 保持原有顺序
+    # print(len(routes))
+    # total_route = {x for route in routes for x in route.split('\t')}
+    # print(f'一共需要 {len(total_route)} 条航线的城市机场对应信息')
+
+    insert_datas = []  # 列表里准备存字典
+
+    count = 0
+    for route in routes:
+        name_code_datas = get_from_to_data(route)
+        # print(route, name_code_datas)
+        from_city_code = name_code_datas.get("from_airport_code")
+        to_city_code = name_code_datas.get("to_airport_code")
+
+        from_airport_code = get_airport_data(from_city_code)
+        to_airport_code = get_airport_data(to_city_code)
+        dict_mode = {
+            "source_website": website,  # 数据来源网站
+            "website_status": 1,  # 网站是否采集状态
+            "flight_route_status": 1,  # 航线是否采集状态
+            "from_city_code": from_city_code,
+            "from_city_name": "",
+            "from_airport_code": from_airport_code,
+            "to_city_code": to_city_code,
+            "to_city_name": "",
+            "to_airport_code": to_airport_code,
+        }
+        insert_datas.append(dict_mode)
+        count += 1
+        if count % 50 == 0:
+            print("add {} ...".format(count))
+    db.get_collection(FLIGHTS_WEBSITES_ROUTE_CONF_TAB).insert_many(insert_datas)  # 一次录入多条数据
+    print("finish...")
+
+
+if __name__ == "__main__":
+    insert_routes()

+ 304 - 0
3.31GK/other_code/city_pair.py

@@ -0,0 +1,304 @@
+routes = """
+KCZ	FUK
+KCZ	KOJ
+KCZ	KMJ
+KCZ	KMI
+KCZ	OIT
+KCZ	OKA
+KCZ	AKJ
+KCZ	NGS
+FUK	KCZ
+FUK	MYJ
+FUK	NGO
+FUK	OKA
+FUK	TAK
+FUK	AKJ
+KOJ	KCZ
+KOJ	KMJ
+KOJ	MYJ
+KOJ	NGO
+KOJ	OKA
+KOJ	TAK
+KOJ	AKJ
+KMJ	KCZ
+KMJ	KOJ
+KMJ	MYJ
+KMJ	NGO
+KMJ	OKA
+KMJ	TAK
+KMJ	AKJ
+MYJ	FUK
+MYJ	KOJ
+MYJ	KMJ
+MYJ	KMI
+MYJ	OIT
+MYJ	OKA
+MYJ	AKJ
+MYJ	NGS
+KMI	KCZ
+KMI	MYJ
+KMI	OKA
+KMI	TAK
+KMI	AKJ
+NGO	FUK
+NGO	KOJ
+NGO	KMJ
+NGO	OKA
+OIT	KCZ
+OIT	MYJ
+OIT	OKA
+OIT	TAK
+OIT	AKJ
+OKA	KCZ
+OKA	FUK
+OKA	KOJ
+OKA	KMJ
+OKA	MYJ
+OKA	KMI
+OKA	NGO
+OKA	OIT
+OKA	TAK
+OKA	AKJ
+OKA	NGS
+TAK	FUK
+TAK	KOJ
+TAK	KMJ
+TAK	KMI
+TAK	OIT
+TAK	OKA
+TAK	AKJ
+TAK	NGS
+AKJ	KCZ
+AKJ	FUK
+AKJ	KOJ
+AKJ	KMJ
+AKJ	MYJ
+AKJ	KMI
+AKJ	OIT
+AKJ	OKA
+AKJ	TAK
+AKJ	NGS
+NGS	KCZ
+NGS	MYJ
+NGS	OKA
+NGS	TAK
+NGS	AKJ
+
+
+KCZ	FUK
+KCZ	KOJ
+KCZ	KMJ
+KCZ	KMI
+KCZ	OIT
+KCZ	OKA
+KCZ	KIX
+KCZ	CTS
+KCZ	NRT
+KCZ	HKO
+KCZ	AKJ
+KCZ	NGS
+FUK	KCZ
+FUK	MYJ
+FUK	NGO
+FUK	OKA
+FUK	KIX
+FUK	CTS
+FUK	TAK
+FUK	NRT
+FUK	HKO
+FUK	EKK
+FUK	AKJ
+KOJ	KCZ
+KOJ	KMJ
+KOJ	MYJ
+KOJ	NGO
+KOJ	OKA
+KOJ	CTS
+KOJ	TAK
+KOJ	NRT
+KOJ	HKO
+KOJ	EKK
+KOJ	AKJ
+KMJ	KCZ
+KMJ	KOJ
+KMJ	MYJ
+KMJ	NGO
+KMJ	OKA
+KMJ	KIX
+KMJ	CTS
+KMJ	TAK
+KMJ	NRT
+KMJ	HKO
+KMJ	EKK
+KMJ	AKJ
+MYJ	FUK
+MYJ	KOJ
+MYJ	KMJ
+MYJ	KMI
+MYJ	OIT
+MYJ	OKA
+MYJ	CTS
+MYJ	NRT
+MYJ	HKO
+MYJ	AKJ
+MYJ	NGS
+KMI	KCZ
+KMI	MYJ
+KMI	OKA
+KMI	KIX
+KMI	CTS
+KMI	TAK
+KMI	NRT
+KMI	HKO
+KMI	EKK
+KMI	AKJ
+NGO	FUK
+NGO	KOJ
+NGO	KMJ
+NGO	OKA
+NGO	CTS
+NGO	HKO
+OIT	KCZ
+OIT	MYJ
+OIT	OKA
+OIT	KIX
+OIT	CTS
+OIT	TAK
+OIT	NRT
+OIT	HKO
+OIT	EKK
+OIT	AKJ
+OKA	KCZ
+OKA	FUK
+OKA	KOJ
+OKA	KMJ
+OKA	MYJ
+OKA	KMI
+OKA	NGO
+OKA	OIT
+OKA	KIX
+OKA	CTS
+OKA	TAK
+OKA	NRT
+OKA	HKO
+OKA	EKK
+OKA	AKJ
+OKA	NGS
+KIX	KCZ
+KIX	FUK
+KIX	KMJ
+KIX	KMI
+KIX	OIT
+KIX	OKA
+KIX	CTS
+KIX	NRT
+KIX	HKO
+KIX	EKK
+KIX	AKJ
+KIX	NGS
+CTS	KCZ
+CTS	FUK
+CTS	KOJ
+CTS	KMJ
+CTS	MYJ
+CTS	KMI
+CTS	NGO
+CTS	OIT
+CTS	OKA
+CTS	KIX
+CTS	TAK
+CTS	NRT
+CTS	EKK
+CTS	NGS
+TAK	FUK
+TAK	KOJ
+TAK	KMJ
+TAK	KMI
+TAK	OIT
+TAK	OKA
+TAK	CTS
+TAK	NRT
+TAK	HKO
+TAK	AKJ
+TAK	NGS
+NRT	KCZ
+NRT	FUK
+NRT	KOJ
+NRT	KMJ
+NRT	MYJ
+NRT	KMI
+NRT	OIT
+NRT	OKA
+NRT	KIX
+NRT	CTS
+NRT	TAK
+NRT	HKO
+NRT	EKK
+NRT	AKJ
+NRT	NGS
+HKO	KCZ
+HKO	FUK
+HKO	KOJ
+HKO	KMJ
+HKO	MYJ
+HKO	KMI
+HKO	NGO
+HKO	OIT
+HKO	OKA
+HKO	KIX
+HKO	TAK
+HKO	NRT
+HKO	EKK
+HKO	NGS
+EKK	FUK
+EKK	KOJ
+EKK	KMJ
+EKK	KMI
+EKK	OIT
+EKK	OKA
+EKK	KIX
+EKK	CTS
+EKK	NRT
+EKK	HKO
+EKK	AKJ
+EKK	NGS
+AKJ	KCZ
+AKJ	FUK
+AKJ	KOJ
+AKJ	KMJ
+AKJ	MYJ
+AKJ	KMI
+AKJ	OIT
+AKJ	OKA
+AKJ	KIX
+AKJ	TAK
+AKJ	NRT
+AKJ	EKK
+AKJ	NGS
+NGS	KCZ
+NGS	MYJ
+NGS	OKA
+NGS	KIX
+NGS	CTS
+NGS	TAK
+NGS	NRT
+NGS	HKO
+NGS	EKK
+NGS	AKJ
+   """
+# 网站没有的城市/机场码: OSA SPK TYO SHI, 删去它们的航段
+temp_list = [i.strip() for i in routes.split("\n") if i.strip()]
+
+routes = list(set(temp_list))  # 去重
+routes.sort(key=temp_list.index)  # 保持原有顺序
+print(f'一共需要采集 {len(routes)} 条航段')
+
+total_route = {x for route in routes for x in route.split('\t')}
+
+print(f'一共需要 {len(total_route)} 条城市机场对应信息')
+
+
+
+print(total_route)
+
+

+ 169 - 0
3.31GK/other_code/request_curl_cffi.py

@@ -0,0 +1,169 @@
+from datetime import datetime, timedelta
+
+from curl_cffi import requests
+# import requests
+from requests.exceptions import Timeout
+import retrying
+import execjs
+import json
+import time
+from loguru import logger
+
+
+class GK:
+
+    def __init__(self):
+        self.akm_url = 'https://www.jetstar.com/c9NCrswc1aL9a_poKlkL/Y5OpJhrfcSzf/MwUVAg/SE0/adRNiWCo'
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+
+        self.headers = {
+            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+            "accept-language": "zh-CN,zh;q=0.9",
+            "cache-control": "no-cache",
+            "pragma": "no-cache",
+            "priority": "u=0, i",
+            "referer": "https://booking.jetstar.com/",
+            "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\"",
+            "sec-fetch-dest": "document",
+            "sec-fetch-mode": "navigate",
+            "sec-fetch-site": "same-origin",
+            "sec-fetch-user": "?1",
+            "upgrade-insecure-requests": "1",
+            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+        }
+        with open('../akm/akm_5.26.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        self.session = requests.Session()
+
+    def get_cookie(self):
+        # akm js file url
+        akm_url = self.akm_url
+        statusTs = str(int(time.time() * 1000))
+
+        data = {
+            'sensor_data': self.ctx.call('encrypt1', statusTs)
+        }
+        response = self.session.post(akm_url, headers=self.headers, verify=False, data=data,
+                                     impersonate='chrome100',
+                                     # 不指定 impersonate 时,TLS 指纹是 curl 原生的,而非浏览器指纹(依旧过不了检测)。需显式设置该参数以绕过 TLS 指纹检测
+                                     http_version=2
+                                     )
+
+        logger.info(f'第一次请求cookie bmsz 状态吗 {response.status_code}')
+        print('内容 ', response.text)
+        print('响应cookie ', response.cookies.get_dict())
+
+        bmsz = response.cookies.get_dict()['bm_sz']
+        print('bmsz =>', bmsz)
+
+        data2 = {
+            "sensor_data": self.ctx.call('encrypt2', statusTs, bmsz)
+        }
+
+        data2 = json.dumps(data2)
+        response2 = self.session.post(akm_url, headers=self.headers, data=data2, verify=False,
+                                      impersonate='chrome101',
+                                      http_version=2
+                                      )
+        logger.info(f'第2次请求验证 cookie bmsz 状态吗 {response.status_code}')
+        print(response2.text)
+        print(response2.cookies.get_dict())
+
+    @retrying.retry(stop_max_attempt_number=3)
+    def send_get(self, url, params):
+
+        try:
+            response = self.session.get(
+                url,
+                headers=self.headers, params=params,
+                timeout=20,
+                verify=False,
+                # proxies=proxies
+                impersonate='chrome99',
+                http_version=2
+            )
+            response.raise_for_status()
+            print('请求返回cookie', response.cookies.get_dict())
+            return response
+        # 捕获超时请求,可能是cookie不行了,更新后报错触发重试
+        except Timeout as e:
+            print(f"请求超时,重新更换cookie: {e}")
+            # # 清除旧 Cookie
+            # self.session.cookies.clear()
+            # print(self.session.cookies.get_dict())
+            # self.get_cookie()
+            raise
+
+        # except Exception as e:
+        #     logger.error(e)
+        #
+        #     return None
+
+    def get_data(self, datetime_str):
+        params = {
+            "s": "true",
+            "adults": "1",  # 成年人
+            "children": "0",  # 儿童
+            "infants": "0",  # 婴儿
+            "selectedclass1": "economy",  # 选择类型:经济舱
+            "currency": "CNY",  # 货币
+            "mon": "true",
+            "channel": "DESKTOP",
+            "origin1": "PVG",  # 出发地
+            "destination1": "NRT",  # 目的地
+            "departuredate1": datetime_str  # 出发时间
+        }
+
+        response = self.send_get(self.search_flights_api, params)
+        if not response:
+            return
+
+        # print(response.text)
+        print(response)
+
+        from lxml import etree
+        import json
+
+        html = etree.HTML(response.text)
+        data = html.xpath("//script[@id='bundle-data-v2']/text()")
+        if data:
+            json_data = json.loads(data[0])
+            print(datetime_str, ' => ', json_data)
+        else:
+            print(response.text)
+
+    @staticmethod
+    def gen_datetime(start_date, end_date):
+        """生成抓取日期: 2025-03-09 传入这种格式"""
+        # 将字符串转换为 datetime 对象
+        current_date = datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = datetime.strptime(end_date, '%Y-%m-%d')
+
+        # 初始化一个空列表来存储日期
+        date_list = []
+
+        # 使用 timedelta 循环遍历每一天
+        while current_date <= end_date:
+            date_list.append(current_date.strftime('%Y-%m-%d'))  # 转换为字符串格式存储
+            current_date += timedelta(days=1)
+
+        return date_list
+
+    def run(self, start_date, end_date):
+        self.get_cookie()
+        # # 获取采集时间
+        for num, datetime_str in enumerate(self.gen_datetime(start_date, end_date), start=1):
+            # if num % 5 == 0:
+            #     self.session = requests.Session()
+            #     self.get_cookie()
+
+            self.get_data(datetime_str)
+        #     # time.sleep(1)
+
+
+if __name__ == '__main__':
+    gk = GK()
+    gk.run(start_date='2025-05-29', end_date='2025-06-29')

+ 169 - 0
3.31GK/other_code/request_httpx.py

@@ -0,0 +1,169 @@
+import httpx
+
+import time
+from datetime import datetime, timedelta
+
+import retrying
+import execjs
+from lxml import etree
+import json
+from loguru import logger
+
+import threading
+from queue import Queue
+import requests
+
+class GK:
+
+    def __init__(self):
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+
+        with open('../akm/逆向.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        ja3 = "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,5-10-11-17613-45-43-0-23-18-65037-27-35-13-51-65281-16-41,4588-29-23-24,0"
+
+        self.client = httpx.Client(
+            http2=True,
+            verify=False
+        )
+
+
+        # self.session = requests.Session()
+        self.headers = {
+            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+            "accept-language": "zh-CN,zh;q=0.9",
+            "cache-control": "no-cache",
+            "pragma": "no-cache",
+            "priority": "u=0, i",
+            "referer": "https://booking.jetstar.com/",
+            "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\"",
+            "sec-fetch-dest": "document",
+            "sec-fetch-mode": "navigate",
+            "sec-fetch-site": "same-origin",
+            "sec-fetch-user": "?1",
+            "upgrade-insecure-requests": "1",
+            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+        }
+
+    def get_cookie(self):
+        logger.debug('正在获取 cookie bm-sz...')
+
+        # akm js file url
+        akm_url = "https://booking.jetstar.com/MkuYlo/pcp/LD0/PPluEQ/1ik7QcJffXbmL53i/QTcvXmg7/KS5kC3N/VRQcB"
+
+        data = {
+            'sensor_data': self.ctx.call('encrypt1')
+        }
+        response = self.client.post(akm_url, headers=self.headers, data=data,
+                                     # proxies=self.proxies
+                                     )
+        # print(response.http_version)  # 应该显示 "HTTP/2"
+
+        # print(response.text)
+        bmsz = response.cookies.get('bm_sz')
+
+        data2 = {
+            "sensor_data": self.ctx.call('encrypt2', bmsz)
+        }
+
+        data2 = json.dumps(data2)
+        response2 = self.client.post(akm_url, headers=self.headers, data=data2,
+                                      # proxies=self.proxies
+                                      )
+        # print(response2.text)
+        # print(response2.status_code)
+        logger.debug(f'成功获取到 bm-sz :{bmsz}')
+
+
+    @retrying.retry(stop_max_attempt_number=2)
+    def send_get(self, url, params):
+        print(dict(self.client.cookies))
+        response = self.client.get(
+            url,
+            headers=self.headers,
+            params=params,
+        )
+        print(response.status_code)
+        print(response.text)
+        if response.status_code == 302:
+            url = 'https://booking.jetstar.com/hk/zh/booking/select-flights'
+            response = self.client.get(url, headers=self.headers)
+
+        print(response.status_code)
+        print(response.text)
+        return response
+
+    @retrying.retry(stop_max_attempt_number=3)
+    def get_data(self, datetime_str):
+        params = {
+            "s": "true",
+            "adults": "1",  # 成年人
+            "children": "0",  # 儿童
+            "infants": "0",  # 婴儿
+            "selectedclass1": "economy",  # 选择类型:经济舱
+            "currency": "CNY",  # 货币
+            "mon": "true",
+            "channel": "DESKTOP",
+            "origin1": "PVG",  # 出发地
+            "destination1": "NRT",  # 目的地
+            "departuredate1": datetime_str  # 出发时间
+        }
+        logger.info(f'正在采集 {datetime_str} 航班数据...')
+        try:
+            response = self.send_get(self.search_flights_api, params)
+
+            if not response:
+                return
+
+            return datetime_str, response
+            # print(response.text)
+
+        except Exception as e:
+            logger.error(e)
+            # self.ip += 1
+            self.get_cookie()
+            raise
+            # return datetime_str, None
+
+    def parse_data(self, datetime_str, response):
+        if not response:
+            return
+
+        html = etree.HTML(response.text)
+        data = html.xpath("//script[@id='bundle-data-v2']/text()")
+        if data:
+            json_data = json.loads(data[0])
+            print(datetime_str, ' => ', json_data)
+        else:
+            logger.warning(f'{datetime_str} 当天暂无数据 / 触发验证码')
+            print(response.text)
+
+    @staticmethod
+    def gen_datetime(start_date, end_date):
+        current_date = datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = datetime.strptime(end_date, '%Y-%m-%d')
+        date_list = []
+        while current_date <= end_date:
+            date_list.append(current_date.strftime('%Y-%m-%d'))  # 转换为字符串格式存储
+            current_date += timedelta(days=1)
+        return date_list
+
+    def run(self, start_date, end_date):
+        self.get_cookie()
+        # 获取采集时间
+        for num, datetime_str in enumerate(self.gen_datetime(start_date, end_date)):
+            # if num % 4 == 0:
+            #     self.session = requests.Session()
+            #     self.get_cookie()
+
+            datetime_str, response = self.get_data(datetime_str)
+            self.parse_data(datetime_str, response)
+            time.sleep(2)
+
+
+if __name__ == '__main__':
+    gk = GK()
+    gk.run(start_date='2025-05-15', end_date='2025-05-27')

+ 289 - 0
3.31GK/other_code/request_pyhttpx.py

@@ -0,0 +1,289 @@
+import requests
+import pyhttpx
+import time
+from datetime import datetime, timedelta
+
+import retrying
+import execjs
+from lxml import etree
+import json
+from loguru import logger
+
+import threading
+from queue import Queue
+
+
+# import pandas as pd
+
+
+class GK:
+
+    def __init__(self):
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+
+        self.headers = {
+            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
+            # 'user-agent': get_random_user_agent(),
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+
+        with open('../akm/逆向1.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        # self.session = pyhttpx
+        # self.ip = 100000000000
+        # self.proxies = {
+        #     'http': f'http://B_3351_HK___5_ss-{self.ip}:ev2pjj@proxy.renlaer.com:7778',
+        #     'https': f'http://B_3351_HK___5_ss-{self.ip}:ev2pjj@proxy.renlaer.com:7778'
+        # }
+
+        self.lock = threading.Lock()
+        # self.cookies_queue = CookieQueue()
+        self.cookies_queue = Queue()
+        self.ja3_queue = Queue()
+        self.task_queue = Queue()
+        self.resp_data_queue = Queue()
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=4000)
+    def get_ja3_str(self):
+        url = "http://8.218.51.130:9003/api/v1/ja3"
+        payload = {}
+        headers = {
+            'cid': '750B5141EDBF7FA6F73A99C768130099'
+        }
+        while True:
+            if self.ja3_queue.qsize() < 5:
+                response = requests.get(url, headers=headers, data=payload)
+                if response.status_code == 200:
+                    res_json = response.json()
+                    if res_json.get("code") == 0:
+                        ja3 = res_json.get("data").get("ja3_str")
+                        ua = res_json.get("data").get("ua")
+                        if "--" not in ja3 and ",," not in ja3:
+                            end_data = (
+                                ua,
+                                ja3
+                            )
+                            self.ja3_queue.put(end_data)
+
+            time.sleep(3)
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=4000)
+    def request_new_cookie(self):
+        logger.debug('正在获取 cookie bm-sz...')
+
+        ua, ja3 = self.ja3_queue.get()
+        # print(ua, ja3)
+        sess = pyhttpx.HttpSession(
+            ja3=ja3,  # 自定义 JA3 字符串
+            http2=True,  # 启用 HTTP/2
+        )
+        # akm js file url
+        akm_url = "https://booking.jetstar.com/MkuYlo/pcp/LD0/PPluEQ/1ik7QcJffXbmL53i/QTcvXmg7/KS5kC3N/VRQcB"
+
+        data = {
+            'sensor_data': self.ctx.call('encrypt1')
+        }
+        response = sess.post(akm_url, headers=self.headers, verify=False, data=data,
+                             # proxies=self.proxies
+                             )
+
+        # print(response.text)
+        bmsz = response.cookies['bm_sz']
+
+        data2 = {
+            "sensor_data": self.ctx.call('encrypt2', bmsz)
+        }
+
+        data2 = json.dumps(data2)
+        response2 = sess.post(akm_url, headers=self.headers, data=data2, verify=False)
+
+        # print(response2.text)
+        # print(response2.status_code)
+        # print(response2.cookies.get_dict())
+        # with self.lock:
+        logger.debug(f'成功获取到 cookie :{bmsz}')
+
+        return response.cookies
+        # return bmsz
+
+    def _refresh_cookie(self):
+        while True:
+            if self.cookies_queue.qsize() < 2:
+                cookie = self.request_new_cookie()
+
+                self.cookies_queue.put(cookie)
+
+            time.sleep(3)
+
+    def gen_task(self, start_date, end_date):
+        """将每个城市对与日期组合生成独立任务, 上传到任务队列"""
+        # 获取采集城市对
+        # for city_code in self.gen_city():
+        # 获取采集时间
+        for datetime_str in self.gen_datetime(start_date, end_date):
+            # self.task_queue.put((city_code, datetime_str))
+            self.task_queue.put((datetime_str))
+
+    @retrying.retry(stop_max_attempt_number=2)
+    def send_get(self, url, params, bmsz_cookie):
+        ua, ja3_str = self.ja3_queue.get()
+        sess = pyhttpx.HttpSession(
+            ja3=ja3_str,  # 自定义 JA3 字符串
+            http2=True,  # 启用 HTTP/2
+        )
+        headers = {
+            # 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
+            'user-agent': ua,
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        response = sess.get(
+            url,
+            headers=headers, params=params, cookies=bmsz_cookie,
+            timeout=15,
+            verify=False,
+        )
+        self.ja3_queue.put((ua, ja3_str))  # 回收破烂
+        # print(response.text)
+
+        # logger.info(f'')
+        return response
+
+    def get_data(self):
+        while True:
+            datetime_str = self.task_queue.get()
+            bmsz_cookie = self.cookies_queue.get()
+            params = {
+                "s": "true",
+                "adults": "1",  # 成年人
+                "children": "0",  # 儿童
+                "infants": "0",  # 婴儿
+                "selectedclass1": "economy",  # 选择类型:经济舱
+                "currency": "CNY",  # 货币
+                "mon": "true",
+                "channel": "DESKTOP",
+                "origin1": "PVG",  # 出发地
+                "destination1": "NRT",  # 目的地
+                "departuredate1": datetime_str  # 出发时间
+            }
+            logger.info(f'正在采集 {datetime_str} 航班数据...')
+            try:
+                response = self.send_get(self.search_flights_api, params, bmsz_cookie)
+                self.resp_data_queue.put((datetime_str, response))
+                # 请求成功,归还Cookie
+                self.cookies_queue.put(bmsz_cookie)  # 成功时放回cookie
+
+            except Exception as e:
+                logger.error(f"错误发生: {e}")
+
+                self.task_queue.put(datetime_str)
+
+            finally:
+                self.task_queue.task_done()
+
+    def parse_data(self):
+        while True:
+            datetime_str, response = self.resp_data_queue.get()
+
+            html = etree.HTML(response.text)
+            data = html.xpath("//script[@id='bundle-data-v2']/text()")
+            if data:
+                json_data = json.loads(data[0])
+                logger.info(f'获取数据成功 {datetime_str} => {json_data}')
+            else:
+                logger.warning(f'{datetime_str} 当天暂无数据 / 触发验证码')
+                print(response.text)
+
+            self.resp_data_queue.task_done()
+
+    def gen_city(self):
+        """提取Excel表格的城市对信息, 用set去重"""
+        # 只读取Excel的Sheet1的航段信息, 将读取的数据存储在 df(DataFrame 对象)中。
+        df = pd.read_excel(
+            self.excel_path,
+            sheet_name="Sheet1",
+            usecols=["出发机场", "到达机场"]  # 只读取 "出发机场" 和 "到达机场" 两列。
+        )
+
+        segment_info = set()
+        segment_info.add('YNT,XIY')
+
+        # 遍历 DataFrame 的每一行
+        # for row in df.itertuples(index=True, name='Pandas'):
+        #     # 访问行中的数据
+        #     segment_info.add(row.出发机场 + ',' + row.到达机场)
+        # logger.info(f'去重后的航段长度: {len(segment_info)}, {segment_info}')
+        return segment_info
+
+    @staticmethod
+    def gen_datetime(start_date, end_date):
+        current_date = datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = datetime.strptime(end_date, '%Y-%m-%d')
+        date_list = []
+        while current_date <= end_date:
+            date_list.append(current_date.strftime('%Y-%m-%d'))  # 转换为字符串格式存储
+            current_date += timedelta(days=1)
+        return date_list
+
+    def run(self, start_date, end_date):
+        thread_list = []
+
+        self.gen_task(start_date, end_date)
+
+        for _ in range(2):
+            t_get_cookie = threading.Thread(target=self._refresh_cookie)
+            thread_list.append(t_get_cookie)
+
+        t_get_ja3 = threading.Thread(target=self.get_ja3_str)
+        thread_list.append(t_get_ja3)
+
+        for _ in range(6):
+            t_get_data = threading.Thread(target=self.get_data)
+            thread_list.append(t_get_data)
+
+        t_parse_data = threading.Thread(target=self.parse_data)
+        thread_list.append(t_parse_data)
+
+        for t_obj in thread_list:
+            t_obj.setDaemon(True)
+            t_obj.start()
+
+        for q in [self.task_queue, self.resp_data_queue]:
+            q.join()
+
+
+if __name__ == '__main__':
+    gk = GK()
+    gk.run(start_date='2025-06-01', end_date='2025-06-30')

+ 236 - 0
3.31GK/other_code/requests_multi_thread.py

@@ -0,0 +1,236 @@
+import time
+from datetime import datetime, timedelta
+
+import requests
+
+import retrying
+import execjs
+from lxml import etree
+import json
+from loguru import logger
+
+import threading
+from queue import Queue
+
+import pandas as pd
+
+# 禁用SSL相关警告 (推荐)
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+import warnings
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+warnings.filterwarnings("ignore", category=DeprecationWarning)  # 可选:过滤其他警告
+
+
+class GK:
+
+    def __init__(self):
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+
+        self.headers = {
+            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        with open('../akm/逆向1.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        self.session = requests.Session()
+        # self.ip = 100000000000
+        # self.proxies = {
+        #     'http': f'http://B_3351_HK___5_ss-{self.ip}:ev2pjj@proxy.renlaer.com:7778',
+        #     'https': f'http://B_3351_HK___5_ss-{self.ip}:ev2pjj@proxy.renlaer.com:7778'
+        # }
+        # self.proxies = {
+        #     'http': f'127.0.0.1:8888',
+        #     'https': f'127.0.0.1:8888'
+        # }
+        self.lock = threading.Lock()
+        self.cookies_queue = Queue(maxsize=10)
+        self.task_queue = Queue()
+        self.resp_data_queue = Queue()
+
+    @retrying.retry(stop_max_attempt_number=3,  wait_fixed=4000)
+    def get_cookie(self):
+        logger.debug('正在获取 cookie bm-sz...')
+        self.session.cookies.clear()
+        # akm js file url
+        akm_url = "https://booking.jetstar.com/MkuYlo/pcp/LD0/PPluEQ/1ik7QcJffXbmL53i/QTcvXmg7/KS5kC3N/VRQcB"
+
+        data = {
+            'sensor_data': self.ctx.call('encrypt1')
+        }
+        response = self.session.post(akm_url, headers=self.headers, verify=False, data=data,
+                                 # proxies=self.proxies
+                                 )
+
+        print(response.text)
+        bmsz = response.cookies.get_dict()['bm_sz']
+
+        data2 = {
+            "sensor_data": self.ctx.call('encrypt2', bmsz)
+        }
+
+        data2 = json.dumps(data2)
+        response2 = self.session.post(akm_url, headers=self.headers, data=data2, verify=False,
+                                  )
+        print(response2.text)
+        print(response2.status_code)
+        with self.lock:
+            logger.debug(f'成功获取到 bm-sz :{bmsz}')
+
+        return bmsz
+
+    def get_cookie_thread(self):
+        while True:
+            if self.cookies_queue.qsize() < 3:
+                bmsz = self.get_cookie()
+
+                self.cookies_queue.put(bmsz)
+
+            time.sleep(15)
+
+    def gen_city(self):
+        """提取Excel表格的城市对信息, 用set去重"""
+        # 只读取Excel的Sheet1的航段信息, 将读取的数据存储在 df(DataFrame 对象)中。
+        df = pd.read_excel(
+            self.excel_path,
+            sheet_name="Sheet1",
+            usecols=["出发机场", "到达机场"]  # 只读取 "出发机场" 和 "到达机场" 两列。
+        )
+
+        segment_info = set()
+        segment_info.add('YNT,XIY')
+
+        # 遍历 DataFrame 的每一行
+        # for row in df.itertuples(index=True, name='Pandas'):
+        #     # 访问行中的数据
+        #     segment_info.add(row.出发机场 + ',' + row.到达机场)
+        # logger.info(f'去重后的航段长度: {len(segment_info)}, {segment_info}')
+        return segment_info
+
+    def gen_task(self, start_date, end_date):
+        """将每个城市对与日期组合生成独立任务, 上传到任务队列"""
+        # 获取采集城市对
+        # for city_code in self.gen_city():
+        # 获取采集时间
+        for datetime_str in self.gen_datetime(start_date, end_date):
+            # self.task_queue.put((city_code, datetime_str))
+            self.task_queue.put((datetime_str))
+
+    @retrying.retry(stop_max_attempt_number=2)
+    def send_get(self, url, params, bmsz):
+        cookies = {
+            "bm_sz": bmsz
+        }
+        response = requests.get(
+            url,
+            headers=self.headers, params=params, cookies=cookies,
+            timeout=15,
+            verify=False,
+        )
+        # print(response.text)
+        response.raise_for_status()
+        return response
+
+    def get_data(self):
+        while True:
+            datetime_str = self.task_queue.get()
+            bmsz = self.cookies_queue.get()
+            params = {
+                "s": "true",
+                "adults": "1",  # 成年人
+                "children": "0",  # 儿童
+                "infants": "0",  # 婴儿
+                "selectedclass1": "economy",  # 选择类型:经济舱
+                "currency": "CNY",  # 货币
+                "mon": "true",
+                "channel": "DESKTOP",
+                "origin1": "PVG",  # 出发地
+                "destination1": "NRT",  # 目的地
+                "departuredate1": datetime_str  # 出发时间
+            }
+            logger.info(f'正在采集 {datetime_str} 航班数据...')
+            try:
+                response = self.send_get(self.search_flights_api, params, bmsz)
+                ret_bmsz = response.cookies.get_dict()['bm_sz']  # 收集返回的 bmsz,也能用来请求
+                self.cookies_queue.put(ret_bmsz)
+                self.resp_data_queue.put((datetime_str, response))
+
+            except requests.exceptions.Timeout:
+                logger.error(f"请求超时: 更换bmsz ")
+                self.task_queue.put(datetime_str)
+
+            except Exception as e:
+                logger.error(e)
+                self.task_queue.put(datetime_str)
+
+            finally:
+                self.cookies_queue.put(bmsz)
+                self.task_queue.task_done()
+                self.cookies_queue.task_done()
+
+    def parse_data(self):
+        while True:
+            datetime_str, response = self.resp_data_queue.get()
+
+            html = etree.HTML(response.text)
+            data = html.xpath("//script[@id='bundle-data-v2']/text()")
+            if data:
+                json_data = json.loads(data[0])
+                print(datetime_str, ' => ', json_data)
+            else:
+                logger.warning(f'{datetime_str} 当天暂无数据 / 触发验证码')
+                print(response.text)
+
+    @staticmethod
+    def gen_datetime(start_date, end_date):
+        current_date = datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = datetime.strptime(end_date, '%Y-%m-%d')
+        date_list = []
+        while current_date <= end_date:
+            date_list.append(current_date.strftime('%Y-%m-%d'))  # 转换为字符串格式存储
+            current_date += timedelta(days=1)
+        return date_list
+
+    def run(self, start_date, end_date):
+        thread_list = []
+
+        self.gen_task(start_date, end_date)
+
+        t_get_cookie = threading.Thread(target=self.get_cookie_thread)
+        thread_list.append(t_get_cookie)
+
+        for _ in range(1):
+            t_get_data = threading.Thread(target=self.get_data)
+            thread_list.append(t_get_data)
+
+        t_parse_data = threading.Thread(target=self.parse_data)
+        thread_list.append(t_parse_data)
+
+        for t_obj in thread_list:
+            t_obj.setDaemon(True)
+            t_obj.start()
+
+        for q in [self.task_queue, self.resp_data_queue]:
+            q.join()
+
+
+if __name__ == '__main__':
+    gk = GK()
+    gk.run(start_date='2025-06-01', end_date='2025-06-30')

+ 729 - 0
3.31GK/other_code/response_data_parse.py

@@ -0,0 +1,729 @@
+var = {
+    "Trips": [
+        {
+            "TripIndex": 0,
+            # ---
+            "Flights": [
+                {
+                    "IsCjFare": false,
+                    "JourneySellKey": "GK~  36~ ~~PVG~04/21/2025 02:15~NRT~04/21/2025 06:20~~^GK~ 631~ ~~NRT~04/21/2025 12:40~KMI~04/21/2025 14:45~~",
+                    "HighlightProductClass": null,
+                    "Lob": "GKINT",
+                    "JourneyStations": "PVG-NRT-KMI",  # 航班的中转路径
+                    "SelectedServiceBundleCode": null,
+                    "SelectedProductClass": null,
+                    # 这里面是各种票价
+                    "Bundles": [
+                        {
+                            "ProductClass": null,
+                            "ServiceBundleCode": "S000",  # 服务套餐的唯一代码,用于系统内部标识,可用来区分不同的票价
+                            "ServiceBundleCodeType": 0,   #  套餐代码类型,可能为枚举值(如 0=基础套餐,1=促销套餐等)
+                            "Amount": 0,  # 当前套餐的销售价格  附加服务包费用,
+                            "CjAmount": 0,  # 可能为“差价金额”(
+                            "RegularInclusiveAmount": "1219.56",  # 常规包含的总金额(可能是原价或基准价
+                            "CjInclusiveAmount": 0,
+                            "FlightFareKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~",
+                            "CjFlightFareKey": null,
+                            "SavingPercent": 0,
+                            "IsSaleFare": false,  # 是否为促销价格
+                            "HasCjFareBundle": false,
+                            "IsBusinessClassBundle": false,  # 是否为商务舱套餐
+                            "BundleLabel": "基本票",
+                            "BundleName": "基本票",  # 套餐正式名称,
+                            "BundleSubHeader": "我們的基本票價",
+                            "BundleSsrCode": "STRT",
+                            "IsStarterBundle": true,
+                            "BundleProductName": "",
+                            "BundleColorVariant": "bundle-100"
+                        },
+                        {
+                            "ProductClass": null,
+                            "ServiceBundleCode": "P200",
+                            "ServiceBundleCodeType": 0,
+                            "Amount": 413.27,
+                            "CjAmount": 0,
+                            "RegularInclusiveAmount": "1632.83",
+                            "CjInclusiveAmount": 0,
+                            "FlightFareKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~",
+                            "CjFlightFareKey": null,
+                            "SavingPercent": 0,
+                            "IsSaleFare": false,
+                            "HasCjFareBundle": false,
+                            "IsBusinessClassBundle": false,
+                            "BundleLabel": "基本加值套票",
+                            "BundleName": "基本加值套票",
+                            "BundleSubHeader": "行李 + 座位 + 餐膳",  # 套餐子标题,描述包含的核心服务内容
+                            "BundleSsrCode": "STPL",
+                            "IsStarterBundle": false,
+                            "BundleProductName": "基本加值套票",
+                            "BundleColorVariant": "bundle-200"
+                        },
+                        {
+                            "ProductClass": null,
+                            "ServiceBundleCode": "F200",
+                            "ServiceBundleCodeType": 0,
+                            "Amount": 452.65,
+                            "CjAmount": 0,
+                            "RegularInclusiveAmount": "1672.21",
+                            "CjInclusiveAmount": 0,
+                            "FlightFareKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~",
+                            "CjFlightFareKey": null,
+                            "SavingPercent": 0,
+                            "IsSaleFare": false,
+                            "HasCjFareBundle": false,
+                            "IsBusinessClassBundle": false,
+                            "BundleLabel": "Flex",
+                            "BundleName": "Flex",
+                            "BundleSubHeader": "積分與彈性",
+                            "BundleSsrCode": "FLXN",
+                            "IsStarterBundle": false,
+                            "BundleProductName": "Flex 套票",
+                            "BundleColorVariant": "bundle-300"
+                        },
+                        {
+                            "ProductClass": null,
+                            "ServiceBundleCode": "M200",
+                            "ServiceBundleCodeType": 0,
+                            "Amount": 599.3,
+                            "CjAmount": 0,
+                            "RegularInclusiveAmount": "1818.86",
+                            "CjInclusiveAmount": 0,
+                            "FlightFareKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~",
+                            "CjFlightFareKey": null,
+                            "SavingPercent": 0,
+                            "IsSaleFare": false,
+                            "HasCjFareBundle": false,
+                            "IsBusinessClassBundle": false,
+                            "BundleLabel": "彈性加值",
+                            "BundleName": "彈性加值",
+                            "BundleSubHeader": "新增彈性 + 附加項目",
+                            "BundleSsrCode": "FPLS",
+                            "IsStarterBundle": false,
+                            "BundleProductName": "彈性加值套票",
+                            "BundleColorVariant": "bundle-400"
+                        }
+                    ],
+                    "OriginalBundleSsrCode": "",
+                    "EconomyPreSelectedBundleCode": null,
+                    "MobileEcoPreSelectedBundleCode": "S000",
+                    "MobileBizPreSelectedBundleCode": null,
+                    "BusinessPreSelectedBundleCode": null,
+                    "DisplayBundle": "STPL",
+                    "MerchandiseMessagesInfo": [
+                        {
+                            "Message": "超值優惠",
+                            "Bundle": "FPLS",
+                            "Position": "top"
+                        },
+                        {
+                            "Message": "節省附加項目費用!^",
+                            "Bundle": "FPLS",
+                            "Position": "bottom"
+                        }
+                    ],
+                    # 显示航班信息/ 航班数据在这里面
+                    "DisplayFlightInfo": {
+                        "Legs": [  # 有超过2个元素时,是中转航班
+                            {
+                                "Color": "flight-one",
+                                "HasToolTip": false,
+                                "IsConnectingFlight": false,  # 是否转机
+                                "IsInternational": true,  # 国际航班。
+                                "IsDepartingFromInternationalTerminal": false,
+                                "DepartureStation": "PVG",
+                                "ArrivalStation": "NRT",
+                                "DisplayStd": "2025年4月21日 (週一) 上午2:15",
+                                "DisplaySta": "2025年4月21日 (週一) 上午6:20",
+                                "Equipment": {
+                                    "Type": "32J",
+                                    "IsAircraftType": true
+                                },
+                                # 航班号  GK36
+                                "FlightDesignator": {
+                                    "FlightNumber": "  36",
+                                    "CarrierCode": "GK",
+                                    "OpSuffix": " "
+                                },
+                                "DisplayDepartureAirportTerminal": "上海 (浦東) - 2號航站樓",
+                                "DisplayArrivalAirportTerminal": "東京 (成田) - 3號航站樓",
+                                "IsAircraftBusinessCabin": false, # 飞机是商务舱吗?
+                                "DisplayTravelDuration": "3小時 5分鐘",
+                                "DisplayAircraft": "空中巴士 A320-200",
+                                "OperatorName": "NGBE.Global.Carrier.GK",  # 运营商名称
+                                "IsSubjectToGovtApproval": false,
+                                "TransitAirport": "東京 (成田)",
+                                "TransitDuration": "6小時 20分鐘",
+                                "CabinType": "Y",  # 舱位类型
+                                "Lob": "GKINT"
+                            },
+                            {
+                                "Color": "flight-one",
+                                "HasToolTip": false,
+                                "IsConnectingFlight": false,
+                                "IsInternational": false,
+                                "IsDepartingFromInternationalTerminal": false,
+                                "DepartureStation": "NRT",
+                                "ArrivalStation": "KMI",
+                                "DisplayStd": "2025年4月21日 (週一) 下午12:40",
+                                "DisplaySta": "2025年4月21日 (週一) 下午2:45",
+                                "Equipment": {
+                                    "Type": "32J",
+                                    "IsAircraftType": true
+                                },
+                                "FlightDesignator": {
+                                    "FlightNumber": " 631",
+                                    "CarrierCode": "GK",
+                                    "OpSuffix": " "
+                                },
+                                "DisplayDepartureAirportTerminal": "東京 (成田) - 3號航站樓",
+                                "DisplayArrivalAirportTerminal": "宮崎",
+                                "IsAircraftBusinessCabin": false,
+                                "DisplayTravelDuration": "2小時 5分鐘",
+                                "DisplayAircraft": "空中巴士 A320-200",
+                                "OperatorName": "NGBE.Global.Carrier.GK",
+                                "IsSubjectToGovtApproval": false,
+                                "TransitAirport": null,
+                                "TransitDuration": null,
+                                "CabinType": "Y",
+                                "Lob": "GKDOM"
+                            }
+                        ],
+                        # 商务航班信息
+                        "BusinessFlightInfo": {
+                            "IsPreselected": false,
+                            "IsSpecialFare": false,
+                            "IsLowOnSeats": false,
+                            "HasAlert": false,
+                            # ​机票状态
+                            "TicketsAvailability": "NotAvailable",  # 不可用
+                            "RemainingSeats": 0,  # 剩余座位
+                            "ShouldShowSeatCountdownLabel": false,
+                            "IsFlexSameDayChange": false
+                        },
+                        # 经济航班信息
+                        "EconomyFlightInfo": {
+                            "IsPreselected": false,
+                            "IsSpecialFare": true,
+                            "IsLowOnSeats": true,
+                            "HasAlert": false,
+                            "TicketsAvailability": "Available",
+                            "RemainingSeats": 4,
+                            "ShouldShowSeatCountdownLabel": true,
+                            "IsFlexSameDayChange": false
+                        },
+                        "FareAlertInfo": {
+                            "IsVtlFlight": false,
+                            "IsDomesticFlightDepartFromInternationalTerminal": false,
+                            "IsAnyLegsSubjectToGovtApproval": false,
+                            "IsNotEnoughInfantsSSR": false,
+                            "IsNoWheelChairAvailable": false,
+                            "IsInValidPassengerCountForQf": false,
+                            "IsRoundTrip": false,
+                            "IsNotAllAircraftWithBusinessCabin": true,
+                            "IsEconomyFlightDiscounted": false,
+                            "IsBusinessFlightDiscounted": false,
+                            "ShouldShowWaiveFlightChangeFeeTip": false,
+                            "IsMultiAirportCity": false,
+                            "TransitCustomMessageType": null
+                        },
+                        "OriginAirport": "上海 (浦東)",
+                        "DestinationAirport": "宮崎",
+                        "StandardTimeOfDeparture": "2025-04-21T02:15:00",  # ​​计划起飞时间​
+                        "StandardTimeOfArrival": "2025-04-21T14:45:00",    # 计划到达时间​
+                        "TravelTime": "11:30:00",
+                        "RouteName": "上海 (浦東) 至 宮崎"
+                    },
+                    "EconomyPriceBreakdown": "{\r\n  \"JourneyType\": \"OutBound\",\r\n  \"TotalAmountDue\": 1219.5600000000000,\r\n  \"TotalFare\": 1110.6600000000000,\r\n  \"TotalCharges\": 108.9000000000000,\r\n  \"TotalPaxCount\": 1.0,\r\n  \"TotalBusinessMaxFare\": 0.0,\r\n  \"BusinessMaxFare\": 0.0,\r\n  \"PriceBreakdown\": [\r\n    {\r\n      \"PaxType\": \"ADT\",\r\n      \"PaxTypeCount\": 1,\r\n      \"PerPaxAmount\": 1110.6600000000000,\r\n      \"Label\": \"成人\",\r\n      \"Fees\": [\r\n        {\r\n          \"ChargeCode\": \"HJ\",\r\n          \"PerPaxAmount\": 18.9000000000000,\r\n          \"Label\": \"成人 - 乘客安檢費\",\r\n          \"PaxTypeCount\": 1\r\n        },\r\n        {\r\n          \"ChargeCode\": \"CN\",\r\n          \"PerPaxAmount\": 90.00,\r\n          \"Label\": \"成人 - 機場服務費\",\r\n          \"PaxTypeCount\": 1\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}",
+                    "BusinessPriceBreakdown": null,
+                    "EconomyMemberPriceBreakdown": null,
+                    "BusinessMemberPriceBreakdown": null,
+                    "IsEconomyFarePreselected": false,
+                    "IsBusinessFarePreselected": false,
+                    "EconomyClassSalePillInfo": {
+                        "IsShow": false,
+                        "Variant": null,
+                        "PromoText": null,
+                        "Source": null
+                    },
+                    "BusinessClassSalePillInfo": {
+                        "IsShow": false,
+                        "Variant": null,
+                        "PromoText": null,
+                        "Source": null
+                    },
+                    "FareClassOfServices": [
+                        "E1",
+                        "EP1",
+                        "EB1",
+                        "EY1"
+                    ]
+                }
+            ],
+            "Lobs": [
+                {
+                    "Lob": "GKINT",
+                    "JourneyStations": "PVG-NRT-KMI",
+                    "Bundles": [
+                        {
+                            "BundleLabel": "基本票",
+                            "ProductClass": null,
+                            "BundleCode": "S000",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "基本加值套票",
+                            "ProductClass": null,
+                            "BundleCode": "P200",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "Flex",
+                            "ProductClass": null,
+                            "BundleCode": "F200",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "彈性加值",
+                            "ProductClass": null,
+                            "BundleCode": "M200",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "Flex",
+                            "ProductClass": null,
+                            "BundleCode": "F000",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "加值",
+                            "ProductClass": null,
+                            "BundleCode": "P000",
+                            "Inclusions": null
+                        },
+                        {
+                            "BundleLabel": "頂級",
+                            "ProductClass": null,
+                            "BundleCode": "M000",
+                            "Inclusions": null
+                        }
+                    ],
+                    "Disclaimers": {
+                        "CombinedBundleTC": "NGBE.FlightSelect.Bundles.FareRules.Disclaimer",
+                        "BusinessClassCombinedBundleTC": "NGBE.Bundles.BusinessClassLegalDisclaimer",
+                        "QFFPointsTC": "NGBE.Bundles.BundleRules.Qantas",
+                        "EKPointsTC": "NGBE.Bundles.BundleRules.Emirates",
+                        "JALPointsTC": "NGBE.Bundles.BundleRules.JAL",
+                        "SavingBundleTC": "NGBE.FlightSelect.BundlesMobile.InclusionsExplained.BundleSavingsDisclaimer"
+                    }
+                }
+            ],
+            "IsServiceBundle": true,
+            "CityPair": "PVGKMI",
+            "SelectedFareKey": null,
+            "IsReturningTrip": false
+        }
+    ],
+    "BookingInfo": null,
+    "DynamicBundleOnFlight": {
+        "BundlesInFlights": [
+            {
+                "JourneySellKey": "GK~  36~ ~~PVG~04/21/2025 02:15~NRT~04/21/2025 06:20~~^GK~ 631~ ~~NRT~04/21/2025 12:40~KMI~04/21/2025 14:45~~",
+                "Bundles": [
+                    {
+                        "Id": {
+                            "ServiceBundleCode": "F200",
+                            "BundleSsrCode": "FLXN",
+                            "FareSellKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~"
+                        },
+                        "InclusionRefIds": [
+                            "PgmDQDA29lNhh1x6eWEP+w==",
+                            "WAljjBUGWn1F/4Aux51Nmw==",
+                            "hUqf5jNQtcK/cAonxhTSJg==",
+                            "NINy80MZbJIY9F/X7EAatA==",
+                            "paelhZ9HXNaMCj/1OcPGBw==",
+                            "zEV7981MJBEmNT22hu1jkA==",
+                            "Iq3TUCj8Mj8YSjJCVTaqWw==",
+                            "/0QKU8uPz3OjR17NBllGWQ==",
+                            "fiiEljeP/MrsA9ZoTTibTg==",
+                            "CaqcKnF27ciyIsRVivcm5A=="
+                        ],
+                        "IsMemberFare": false,
+                        "CabinType": "Economy",
+                        "Title": "Flex",
+                        "SubTitle": "積分與彈性",
+                        "JclColorVariant": "bundle-300"
+                    },
+                    {
+                        "Id": {
+                            "ServiceBundleCode": "M200",
+                            "BundleSsrCode": "FPLS",
+                            "FareSellKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~"
+                        },
+                        "InclusionRefIds": [
+                            "PgmDQDA29lNhh1x6eWEP+w==",
+                            "m/I9bLk4RlkrSeWhHf9HiQ==",
+                            "qJ/pbMNV8DS5A2IOHNaXkg==",
+                            "hcU/sG5cwAw7wXHHktvRlg==",
+                            "paelhZ9HXNaMCj/1OcPGBw==",
+                            "zEV7981MJBEmNT22hu1jkA==",
+                            "Iq3TUCj8Mj8YSjJCVTaqWw==",
+                            "4hM7b5KEA7sSPm3cj+dbIw==",
+                            "Eb+ydtDs/t1mHDDfn2dv3Q==",
+                            "CaqcKnF27ciyIsRVivcm5A=="
+                        ],
+                        "IsMemberFare": false,
+                        "CabinType": "Economy",
+                        "Title": "彈性加值",
+                        "SubTitle": "新增彈性 + 附加項目",
+                        "JclColorVariant": "bundle-400"
+                    },
+                    {
+                        "Id": {
+                            "ServiceBundleCode": "P200",
+                            "BundleSsrCode": "STPL",
+                            "FareSellKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~"
+                        },
+                        "InclusionRefIds": [
+                            "PgmDQDA29lNhh1x6eWEP+w==",
+                            "m/I9bLk4RlkrSeWhHf9HiQ==",
+                            "p2ZbfTFp93on6OZEmvSM+A==",
+                            "hcU/sG5cwAw7wXHHktvRlg==",
+                            "HxyP2sgcEoNPJG/Pew60bQ==",
+                            "T9DWQCiTqlVfNkxThie8Vw==",
+                            "Gf5F7nNl9bVlMhfu+mYTaA==",
+                            "/0QKU8uPz3OjR17NBllGWQ==",
+                            "skjgAOyXx+yXe71m2AFbrg==",
+                            "CaqcKnF27ciyIsRVivcm5A=="
+                        ],
+                        "IsMemberFare": false,
+                        "CabinType": "Economy",
+                        "Title": "基本加值套票",
+                        "SubTitle": "行李 + 座位 + 餐膳",
+                        "JclColorVariant": "bundle-200"
+                    },
+                    {
+                        "Id": {
+                            "ServiceBundleCode": "S000",
+                            "BundleSsrCode": "STRT",
+                            "FareSellKey": "2~E1~~JQ~ELECOE1~7200~~0~1~~X^1~H~~JQ~HLECOH~7000~~0~6~~"
+                        },
+                        "InclusionRefIds": [
+                            "PgmDQDA29lNhh1x6eWEP+w==",
+                            "WAljjBUGWn1F/4Aux51Nmw==",
+                            "hfmW7rDTv6BA5r35PZqXeQ==",
+                            "NINy80MZbJIY9F/X7EAatA==",
+                            "HxyP2sgcEoNPJG/Pew60bQ==",
+                            "T9DWQCiTqlVfNkxThie8Vw==",
+                            "Gf5F7nNl9bVlMhfu+mYTaA==",
+                            "/0QKU8uPz3OjR17NBllGWQ==",
+                            "yKllSRBGWvGXMjRuYK87Bw==",
+                            "CaqcKnF27ciyIsRVivcm5A=="
+                        ],
+                        "IsMemberFare": false,
+                        "CabinType": "Economy",
+                        "Title": "基本票",
+                        "SubTitle": "我們的基本票價",
+                        "JclColorVariant": "bundle-100"
+                    }
+                ],
+
+
+                "Disclaimers": {
+                    "QFFPointsTC": "NGBE.Bundles.BundleRules.Qantas",
+                    "EKPointsTC": "NGBE.Bundles.BundleRules.Emirates",
+                    "JALPointsTC": "NGBE.Bundles.BundleRules.JAL",
+                    "SavingBundleTC": "NGBE.FlightSelect.BundlesMobile.InclusionsExplained.BundleSavingsDisclaimer"
+                },
+                "MerchandiseMessagesInfo": [
+                    {
+                        "Message": "超值優惠",
+                        "Bundle": "FPLS",
+                        "Position": "top"
+                    },
+                    {
+                        "Message": "節省附加項目費用!^",
+                        "Bundle": "FPLS",
+                        "Position": "bottom"
+                    }
+                ],
+                "BundleInFocusSsrCode": "STPL",
+                "BundlePreselections": [
+                    {
+                        "PreselectedBundleSsrCode": "STRT",
+                        "CabinType": "Economy",
+                        "DeviceType": "mobile"
+                    }
+                ]
+            }
+        ],
+
+        # 航班包含的服务
+        "Inclusions": [
+            {
+                # 标识行李类型(如手提行李、托运行李)
+                "ProductName": "CarryOnBaggage",  #  随身行李
+                "IsIncluded": true,
+                "DisplayText": "7 公斤",  # 行李额度
+                "Title": "手提行李",    # 行李类型名称
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 1,
+                "CabinType": "Economy", # 舱位等级
+                "RefId": "PgmDQDA29lNhh1x6eWEP+w==",
+                "IconVariant": "economy-carry-on-baggage"
+            },
+            {
+                "ProductName": "CheckedBaggage",  # 托运行李
+                "IsIncluded": false,
+                "DisplayText": "稍後添加",          # 行李额度
+                "Title": "託運行李",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 2,
+                "CabinType": "Economy",
+                "RefId": "WAljjBUGWn1F/4Aux51Nmw==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "Seat",
+                "IsIncluded": true,
+                "DisplayText": "前艙或標準",
+                "Title": "選位",
+                "SubTitle": null,
+                "SsrCode": "FXS2",
+                "SortOrder": 3,
+                "CabinType": "Economy",
+                "RefId": "hUqf5jNQtcK/cAonxhTSJg==",
+                "IconVariant": "economy-seat"
+            },
+            {
+                "ProductName": "Meals",
+                "IsIncluded": false,
+                "DisplayText": "稍後添加",
+                "Title": "機上餐飲",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 4,
+                "CabinType": "Economy",
+                "RefId": "NINy80MZbJIY9F/X7EAatA==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "DateTimeChange",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "日期和時間更改*",
+                "SubTitle": "我們或會收取票價差額",
+                "SsrCode": "DCHG",
+                "SortOrder": 5,
+                "CabinType": "Economy",
+                "RefId": "paelhZ9HXNaMCj/1OcPGBw==",
+                "IconVariant": "economy-date-time-change"
+            },
+            {
+                "ProductName": "SameDayChange",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "免費即日更改*",
+                "SubTitle": null,
+                "SsrCode": "SDCH",
+                "SortOrder": 6,
+                "CabinType": "Economy",
+                "RefId": "zEV7981MJBEmNT22hu1jkA==",
+                "IconVariant": "same-day-change"
+            },
+            {
+                "ProductName": "CancelFlight",
+                "IsIncluded": true,
+                "DisplayText": "消費額現金券",
+                "Title": "取消你的航班*",
+                "SubTitle": null,
+                "SsrCode": "CVHR",
+                "SortOrder": 7,
+                "CabinType": "Economy",
+                "RefId": "Iq3TUCj8Mj8YSjJCVTaqWw==",
+                "IconVariant": "economy-cancel-flight"
+            },
+            {
+                "ProductName": "OriginDestinationChange",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "出發地/目的地更改*",
+                "SubTitle": "我們或會收取票價差額",
+                "SsrCode": null,
+                "SortOrder": 8,
+                "CabinType": "Economy",
+                "RefId": "/0QKU8uPz3OjR17NBllGWQ==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "FrequentFlyer",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "忠誠獎勵",
+                "SubTitle": null,
+                "SsrCode": "LOYT",
+                "SortOrder": 9,
+                "CabinType": "Economy",
+                "RefId": "fiiEljeP/MrsA9ZoTTibTg==",
+                "IconVariant": "economy-loyalty-points"
+            },
+            {
+                "ProductName": "QantasBusinessReward",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "澳航商務獎勵",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 10,
+                "CabinType": "Economy",
+                "RefId": "CaqcKnF27ciyIsRVivcm5A==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "CheckedBaggage",
+                "IsIncluded": true,
+                "DisplayText": "20 公斤",
+                "Title": "託運行李",
+                "SubTitle": null,
+                "SsrCode": "BG20",
+                "SortOrder": 2,
+                "CabinType": "Economy",
+                "RefId": "m/I9bLk4RlkrSeWhHf9HiQ==",
+                "IconVariant": "economy-checked-baggage"
+            },
+            {
+                "ProductName": "Seat",
+                "IsIncluded": true,
+                "DisplayText": "任何適用座位",
+                "Title": "選位",
+                "SubTitle": null,
+                "SsrCode": "FXS3",
+                "SortOrder": 3,
+                "CabinType": "Economy",
+                "RefId": "qJ/pbMNV8DS5A2IOHNaXkg==",
+                "IconVariant": "economy-seat"
+            },
+            {
+                "ProductName": "Meals",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "機上餐飲",
+                "SubTitle": null,
+                "SsrCode": "ML01",
+                "SortOrder": 4,
+                "CabinType": "Economy",
+                "RefId": "hcU/sG5cwAw7wXHHktvRlg==",
+                "IconVariant": "economy-meals"
+            },
+            {
+                "ProductName": "OriginDestinationChange",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "出發地/目的地更改*",
+                "SubTitle": "我們或會收取票價差額",
+                "SsrCode": "OCHG",
+                "SortOrder": 8,
+                "CabinType": "Economy",
+                "RefId": "4hM7b5KEA7sSPm3cj+dbIw==",
+                "IconVariant": "economy-origin-destination-change"
+            },
+            {
+                "ProductName": "FrequentFlyer",
+                "IsIncluded": true,
+                "DisplayText": "包括",
+                "Title": "忠誠獎勵",
+                "SubTitle": null,
+                "SsrCode": "LOYT",
+                "SortOrder": 9,
+                "CabinType": "Economy",
+                "RefId": "Eb+ydtDs/t1mHDDfn2dv3Q==",
+                "IconVariant": "economy-loyalty-points"
+            },
+            {
+                "ProductName": "Seat",
+                "IsIncluded": true,
+                "DisplayText": "一般",
+                "Title": "選位",
+                "SubTitle": null,
+                "SsrCode": "FXS1",
+                "SortOrder": 3,
+                "CabinType": "Economy",
+                "RefId": "p2ZbfTFp93on6OZEmvSM+A==",
+                "IconVariant": "economy-seat"
+            },
+            {
+                "ProductName": "DateTimeChange",
+                "IsIncluded": false,
+                "DisplayText": "需支付更改費用",
+                "Title": "日期和時間更改*",
+                "SubTitle": "我們或會收取票價差額",
+                "SsrCode": null,
+                "SortOrder": 5,
+                "CabinType": "Economy",
+                "RefId": "HxyP2sgcEoNPJG/Pew60bQ==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "SameDayChange",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "免費即日更改*",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 6,
+                "CabinType": "Economy",
+                "RefId": "T9DWQCiTqlVfNkxThie8Vw==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "CancelFlight",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "取消你的航班*",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 7,
+                "CabinType": "Economy",
+                "RefId": "Gf5F7nNl9bVlMhfu+mYTaA==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "FrequentFlyer",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "忠誠獎勵",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 9,
+                "CabinType": "Economy",
+                "RefId": "skjgAOyXx+yXe71m2AFbrg==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "Seat",
+                "IsIncluded": false,
+                "DisplayText": "稍後添加或獲得免費分配座位",
+                "Title": "選位",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 3,
+                "CabinType": "Economy",
+                "RefId": "hfmW7rDTv6BA5r35PZqXeQ==",
+                "IconVariant": "clear-circle"
+            },
+            {
+                "ProductName": "FrequentFlyer",
+                "IsIncluded": false,
+                "DisplayText": "不適用",
+                "Title": "忠誠獎勵",
+                "SubTitle": null,
+                "SsrCode": null,
+                "SortOrder": 9,
+                "CabinType": "Economy",
+                "RefId": "yKllSRBGWvGXMjRuYK87Bw==",
+                "IconVariant": "clear-circle"
+            }
+        ]
+    }
+}

+ 82 - 0
3.31GK/other_code/控制流平坦化-状态机为变量/ast.js

@@ -0,0 +1,82 @@
+const fs = require("fs");//文件读写
+const parse = require("@babel/parser"); //解析为ast
+const traverse = require('@babel/traverse').default;//遍历节点
+const t = require('@babel/types');//类型
+const generator = require('@babel/generator').default;//ast解析为代码
+
+
+//读取混淆js文件
+const jsCode = fs.readFileSync('./encode.js', {encoding: 'utf-8'});
+
+
+const visitor = {
+    WhileStatement(path) {                // 处理 While 循环语句
+        const {test, body} = path.node;   // 解构循环条件和循环体
+
+        // 硬编码状态变量名和初始值(需后续从代码动态提取)
+        let init_name = 'index';           // 状态变量名(示例值)
+        let init_value = 0;                // 初始状态值(示例值)
+
+        const switch_body = body.body[0];  // 提取循环体内的第一个()语句
+
+        // 验证是否为 Switch 语句,否则终止处理
+        if (!t.isSwitchStatement(switch_body)) return;
+
+        const {discriminant, cases} = switch_body;  // 解构 Switch 的条件和分支列表
+
+        // 验证 Switch 条件是否为指定状态变量
+        if (!t.isIdentifier(discriminant, {name: init_name})) return;
+
+        const ret_body = [];               // 存储最终生成的代码块集合
+        let end_flag = false;              // 终止循环处理标志
+
+        while (true) {                     // 循环处理所有状态分支
+            if (end_flag) break;            // 检测终止条件
+
+            for (const each_case of cases) { // 遍历 Switch 的所有 case 分支
+                const {test, consequent} = each_case;
+
+                // 跳过非当前状态的 case 分支
+                if (init_value !== test.value) continue;
+
+                // 移除分支末尾的 continue 语句
+                if (t.isContinueStatement(consequent[consequent.length - 1])) {
+                    consequent.pop();
+                }
+
+                // 处理状态变量更新逻辑
+                if (t.isExpressionStatement(consequent[consequent.length - 1])) {
+                    const {expression} = consequent[consequent.length - 1];
+
+                    // 解析状态变量赋值表达式
+                    if (t.isAssignmentExpression(expression)) {
+                        const {left, right} = expression;
+
+                        // 更新状态变量值并移除赋值语句
+                        if (t.isIdentifier(left, {name: init_name})) {
+                            init_value = right.value;
+                            consequent.pop();
+                        }
+                    }
+                }
+
+                // 检测 return 语句作为终止信号
+                if (t.isReturnStatement(consequent[consequent.length - 1])) {
+                    end_flag = true;
+                }
+
+                ret_body.push(...consequent);  // 合并处理后的代码块
+                break;                         // 退出当前分支处理循环
+            }
+        }
+
+        path.replaceInline(ret_body);      // 用线性代码替换原循环结构
+    }
+};
+
+let ast = parse.parse(jsCode);//js转ast
+traverse(ast, visitor)  // 处理控制流平坦化
+
+let {code} = generator(ast)
+
+console.log(code)

+ 33 - 0
3.31GK/other_code/控制流平坦化-状态机为变量/encode.js

@@ -0,0 +1,33 @@
+function test() {
+    var index = 0;
+    while (1) {
+        switch (index) {
+            case 0:
+                console.log('this is case-block 0');
+                index = 3
+                continue
+            case 1:
+                console.log('this is case-block 1');
+                return;
+                index = 5
+                continue
+            case 2:
+                console.log('this is case-block 2');
+                index = 1
+                continue
+            case 3:
+                console.log('this is case-block 3');
+                index = 4
+                continue
+            case 4:
+                console.log('this is case-block 4');
+                index = 2
+                continue
+        }
+    }
+}
+
+test()
+
+
+

+ 100 - 0
3.31GK/other_code/控制流平坦化-状态机为变量/test.js

@@ -0,0 +1,100 @@
+const parser = require('@babel/parser');
+const traverse = require('@babel/traverse').default;
+const fs = require('fs');
+
+const code = `
+let state = 0, x = 5;
+while (1) {
+    switch (state) {
+        case 0:
+            console.log('执行块 0 操作')
+            state = 1;
+            break;
+        case 1:
+            state = (x > 0) ? 2 : 3;
+            console.log('执行块 1 操作')
+            break;
+        case 2:
+            console.log('执行块 2 操作')
+            state = 4;
+            break;
+        case 3:
+            console.log('执行块 3 操作')
+            state = 4;
+            break;
+        case 4:
+            console.log('程序执行完毕');
+    }
+}
+`;
+
+const ast = parser.parse(code, { sourceType: 'module' });
+
+let initialState = null;
+let switchCases = {};
+
+traverse(ast, {
+  VariableDeclarator(path) {
+    if (path.node.id.name === 'state') {
+      initialState = path.node.init.value;
+    }
+  },
+  SwitchCase(path) {
+    const test = path.node.test.value;
+    const body = path.node.consequent;
+    let logs = [];
+    let nextState = null;
+
+    body.forEach(node => {
+      if (
+        node.type === 'ExpressionStatement' &&
+        node.expression.type === 'CallExpression' &&
+        node.expression.callee.object.name === 'console'
+      ) {
+        logs.push(node.expression.arguments[0].value);
+      }
+
+      if (
+        node.type === 'ExpressionStatement' &&
+        node.expression.type === 'AssignmentExpression' &&
+        node.expression.left.name === 'state'
+      ) {
+        nextState = node.expression.right;
+      }
+    });
+
+    switchCases[test] = { logs, nextState };
+  }
+});
+
+function formatNextState(nextState) {
+  if (nextState.type === 'ConditionalExpression') {
+    return `if (${generateCode(nextState.test)}) {
+  ${switchCases[nextState.consequent.value].logs.map(log => `console.log('${log}')`).join('\n  ')}
+} else {
+  ${switchCases[nextState.alternate.value].logs.map(log => `console.log('${log}')`).join('\n  ')}
+}`;
+  } else if (nextState.type === 'NumericLiteral') {
+    const logs = switchCases[nextState.value]?.logs || [];
+    return logs.map(log => `console.log('${log}')`).join('\n');
+  } else {
+    return '// 未识别的跳转';
+  }
+}
+
+function generateCode(node) {
+  if (node.type === 'BinaryExpression') {
+    return `${node.left.name} ${node.operator} ${node.right.value}`;
+  }
+  return '// 未识别表达式';
+}
+
+// 🌳 生成结构化代码
+let outputCode = '';
+outputCode += switchCases[0].logs.map(log => `console.log('${log}')`).join('\n') + '\n';
+
+outputCode += formatNextState(switchCases[1].nextState) + '\n';
+outputCode += switchCases[4].logs.map(log => `console.log('${log}')`).join('\n');
+
+console.log('\n✅ 结构化还原后的代码:\n');
+console.log(outputCode);

+ 432 - 0
3.31GK/req_tls_client.py

@@ -0,0 +1,432 @@
+import threading
+import time
+from queue import Queue
+
+import requests
+from lxml import etree
+import json
+
+import random
+from datetime import datetime, timedelta
+
+import execjs
+from loguru import logger
+import tls_client
+import retrying
+
+from urllib.parse import urljoin
+
+
+# requests = requests.Session()
+
+
+class GK:
+
+    def __init__(self):
+        self.search_flights_api = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+        self.headers = {
+            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+            "Accept-Language": "zh-CN,zh;q=0.9",
+            "Connection": "keep-alive",
+            "Referer": "https://www.jetstar.com/",
+            "Sec-Fetch-Dest": "document",
+            "Sec-Fetch-Mode": "navigate",
+            "Sec-Fetch-Site": "same-site",
+            "Sec-Fetch-User": "?1",
+            "Upgrade-Insecure-Requests": "1",
+            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
+            "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\""
+        }
+        with open('akm/akm_5.26.js', encoding='utf-8') as f:
+            js = f.read()
+        self.ctx = execjs.compile(js)
+        self.proxies_url = 'http://B_3351_HK___5_ss-{}:ev2pjj@proxy.renlaer.com:7778'
+
+        self.cookies_queue = Queue()
+        self.ja3_queue = Queue()
+        self.task_queue = Queue()
+        self.resp_data_queue = Queue()
+
+        # 使用本地代理软件
+        self.proxies = {
+            "http": "http://127.0.0.1:7897",
+            "https": "http://127.0.0.1:7897"
+        }
+
+    def get_ja3_str(self):
+        url = "http://8.218.51.130:9003/api/v1/ja3"
+        payload = {}
+        headers = {
+            'cid': '750B5141EDBF7FA6F73A99C768130099'
+        }
+        while True:
+            if self.ja3_queue.qsize() < 3:
+                try:
+                    response = requests.get(url, headers=headers, data=payload, timeout=15)
+                    if response.status_code == 200:
+                        # print(response.json())
+                        res_json = response.json()
+                        if res_json.get("code") == 0:
+                            ja3 = res_json.get("data").get("ja3_str")
+                            ua = res_json.get("data").get("ua")
+                            if "--" not in ja3 and ",," not in ja3:
+                                end_data = (
+                                    ua,
+                                    ja3
+                                )
+                                # logger.debug('获取ja3成功...')
+                                self.ja3_queue.put(end_data)
+                except Exception as e:
+                    logger.error(f'ja3接口错误: {e}')
+
+            time.sleep(3)
+
+    @retrying.retry(stop_max_attempt_number=3, wait_fixed=3000)
+    def request_new_cookie(self):
+        statusTs = int(time.time() * 1000)
+        # ua, ja3_string = self.ja3_queue.get()
+        ua, ja3_string = self.ja3_queue.get()
+        # ip = ''.join(random.choices('0123456789', k=12))
+        # proxies = {
+        #     'http': self.proxies_url.format(ip),
+        #     'https': self.proxies_url.format(ip)
+        # }
+        # print(proxies)
+        # browser = random.choice(self.BROWSER)
+        """
+        注意:ja3 和 client_identifier、 random_tls_extension_order不能同时使用
+        否则存在 潜在冲突,可能导致 JA3 指纹不符合预期,
+        具体原因如下:
+            参数优先级冲突
+                ja3_string 是直接定义 JA3 指纹的 完整参数,会覆盖 client_identifier 的默认配置。
+                若同时设置 ja3_string 和 client_identifier,client_identifier 的浏览器预置参数会被忽略,仅 ja3_string 生效。
+            随机扩展顺序干扰
+                random_tls_extension_order=True 会打乱 TLS 扩展的顺序,导致 JA3 指纹动态变化。
+                虽然增强了匿名性,但若目标网站检测 JA3 的稳定性(如固定指纹校验),此配置会触发反爬机制。       
+        """
+        get_ck_session = tls_client.Session(
+            ja3_string=ja3_string,
+        )
+        headers = {
+            # 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
+            'user-agent': ua,
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        # akm js file url
+        akm_url = "https://www.jetstar.com/w6N0DejiHPXeE_PZTkeSCCzH/3XiJLNQzXNpfYO/EA1mYRtQBw/LV/Y8ahE0KUo"
+
+        data = {
+            'sensor_data': self.ctx.call('encrypt1', statusTs)
+        }
+        response1 = get_ck_session.post(akm_url, headers=headers, data=data, timeout_seconds=15,
+                                        proxy=self.proxies
+                                        )
+
+        # print(response1.status_code)
+        # print(response1.text)
+        # print(response1.cookies.get_dict())
+        # print('111', response.headers)
+        bmsz = response1.cookies.get_dict()['bm_sz']
+        # print('bmsz => ', bmsz)
+
+        data2 = {
+            "sensor_data": self.ctx.call('encrypt2', statusTs, bmsz)
+        }
+
+        data2 = json.dumps(data2)
+        response2 = get_ck_session.post(akm_url, headers=headers, data=data2, timeout_seconds=15,
+                                        proxy=self.proxies
+                                        )
+        logger.debug('成功获取 cookie bm-sz: {}'.format(bmsz[10:]))
+
+        # print(response2.text)
+        # print(response2.cookies.get_dict())
+        if response2.status_code == 201:
+            # print('响应cookie1', response1.cookies.get_dict())
+            # 返回第一次请求响应的cookie
+            return response1.cookies.get_dict()
+        else:
+            logger.error('状态码错误{}'.format(response2.status_code))
+            print(response2.text)
+
+    def _refresh_cookie(self):
+        while True:
+            if self.cookies_queue.qsize() < 3:
+                cookie = self.request_new_cookie()
+
+                self.cookies_queue.put(cookie)
+
+            time.sleep(3)
+
+    @retrying.retry(stop_max_attempt_number=5, wait_fixed=3000)
+    def request_with_redirect(self, url, params, bmsz_cookie, max_redirects=3):
+        """"""
+        ua, ja3_string = self.ja3_queue.get()
+        # ip = ''.join(random.choices('0123456789', k=12))
+        # proxies = {
+        #     'http': self.proxies_url.format(ip),
+        #     'https': self.proxies_url.format(ip)
+        # }
+
+        req_session = tls_client.Session(
+            ja3_string=ja3_string,  # 直接注入自定义指纹
+        )
+        headers = {
+            'user-agent': ua,
+            'Accept-Encoding': 'gzip, deflate, br',
+            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+            'Connection': 'keep-alive',
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'pragma': 'no-cache',
+            'priority': 'u=0, i',
+            'referer': 'https://booking.jetstar.com/',
+            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"Windows"',
+            'sec-fetch-dest': 'document',
+            'sec-fetch-mode': 'navigate',
+            'sec-fetch-site': 'same-origin',
+            'sec-fetch-user': '?1',
+            'upgrade-insecure-requests': '1'
+        }
+        redirect_count = 0
+        current_url = url
+
+        while redirect_count < max_redirects:
+            response = req_session.get(current_url, headers=headers, cookies=bmsz_cookie, params=params,
+                                       timeout_seconds=15,  # timeout
+                                       proxy=self.proxies
+                                       )
+
+            print(response.status_code)
+            # print(response.text)
+
+            # 检查是否为重定向状态码
+            if response.status_code in (301, 302, 303, 307, 308):
+                # 获取 Location 头(需处理相对路径)
+                location = response.headers.get("Location")
+                # if not location:
+                #     break
+                current_url = urljoin(current_url, location)
+                redirect_count += 1
+                # print(f"Redirecting to: {current_url}")
+
+                # 可选:继承原始请求的特定 Headers(如 Referer)
+                # headers["Referer"] = response.url
+            else:
+                # 请求成功,归还Cookie
+                # print('请求成功的cookie', req_session.cookies.get_dict())
+                # print('请求响应的cookie', response.cookies.get_dict())
+                self.cookies_queue.put(bmsz_cookie)  # 成功时放回cookie
+                return response  # 返回最终响应
+
+        raise Exception(f" ({max_redirects})")
+
+    def get_data(self):
+        while True:
+            city_code, datetime_str = self.task_queue.get()
+            origin1, destination1 = city_code.split('\t')
+            bmsz_cookie = self.cookies_queue.get()
+
+            params = {
+                "s": "true",
+                "adults": "1",
+                "children": "0",
+                "infants": "0",
+                "selectedclass1": "economy",
+                # "currency": "CNY",
+                "mon": "true",
+                "channel": "DESKTOP",
+                "origin1": origin1,
+                "destination1": destination1,
+                "departuredate1": datetime_str
+            }
+            # # print(params1)
+            # params = {
+            #     "s": "true",
+            #     "adults": "1",
+            #     "children": "0",
+            #     "infants": "0",
+            #     "selectedclass1": "economy",
+            #     "currency": "CNY",
+            #     "mon": "true",
+            #     "channel": "DESKTOP",
+            #     "origin1": "CTS",
+            #     "destination1": "KOJ",
+            #     "departuredate1": "2025-05-30"  # !!!
+            # }
+            # print(params)
+            logger.info(f'正在采集{city_code} {datetime_str} 航班数据...')
+
+            try:
+                response = self.request_with_redirect(self.search_flights_api, params, bmsz_cookie)
+                self.resp_data_queue.put((city_code, datetime_str, response))
+
+            except Exception as e:
+                logger.error(e)
+                # 失败时重新上传任务
+                # self.task_queue.put(datetime_str)
+
+            finally:
+                self.task_queue.task_done()
+
+    def parse_data(self):
+        while True:
+            city_code, datetime_str, response = self.resp_data_queue.get()
+
+            html = etree.HTML(response.text)
+            data = html.xpath("//script[@id='bundle-data-v2']/text()")
+            if data:
+                json_data = json.loads(data[0])
+                print('获取数据成功', city_code, datetime_str, ' => ', json_data)
+                # print(response.text)
+            else:
+                logger.warning(f'{datetime_str} 触发验证码或拒绝访问错误, => {response.text}')
+
+            self.resp_data_queue.task_done()
+
+    @staticmethod
+    def gen_datetime(start_date, end_date):
+        current_date = datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = datetime.strptime(end_date, '%Y-%m-%d')
+        date_list = []
+        while current_date <= end_date:
+            date_list.append(current_date.strftime('%Y-%m-%d'))  # 转换为字符串格式存储
+            current_date += timedelta(days=1)
+        return date_list
+
+    def gen_city(self):
+        routes = """
+          
+          
+           CTS	KOJ
+           """
+
+        temp_list = [i.strip() for i in routes.split("\n") if i.strip()]
+        routes = list(set(temp_list))  # 去重
+        print(routes)
+
+        return routes
+
+    @staticmethod
+    def gen_date_format(date_str):
+        """20250531 => 2025-05-31 """
+        original_date = datetime.strptime(date_str, "%Y%m%d")
+        return original_date.strftime("%Y-%m-%d")
+
+    def gen_task(self, start_date, end_date):
+        """将每个城市对与日期组合生成独立任务, 上传到任务队列"""
+        # 获取采集城市对
+        for city_code in self.gen_city():
+
+            # 获取采集时间
+            flight_date_list = self.search_flight_date(city_code, start_date, end_date)
+            if not flight_date_list:
+                print(city_code, start_date, end_date, '无航班')
+                continue
+            for datetime_str in flight_date_list:
+                # 日期格式转为 20250531 => 2025-05-31
+                self.task_queue.put((city_code, self.gen_date_format(datetime_str)))
+            # self.task_queue.put((datetime_str))
+
+    def search_flight_date(self, city_pair, start_date, end_date):
+        """查询航班日期, 即那天有航班"""
+        departures, arrivals = city_pair.split('\t')
+        url = "https://digitalapi.jetstar.com/v1/farecache/flights/batch/availability-with-fareclasses"
+        params = {
+            "flightCount": "1",
+            "includeSoldOut": "true",
+            "requestType": "StarterOnly",
+            "from": start_date,  # 采集开始时间
+            "end": end_date,  # 采集结束时间,可随意写  后面写完实例属性
+            "departures": departures,
+            "arrivals": arrivals,
+            "direction": "outbound",
+            "paxCount": "1",
+            "includeFees": "false"
+        }
+        headers = {
+            "accept": "application/json, text/plain, */*",
+            "accept-language": "zh-CN,zh;q=0.9",
+            "cache-control": "no-cache",
+            "culture": "zh-HK",
+            "origin": "https://www.jetstar.com",
+            "pragma": "no-cache",
+            "priority": "u=1, i",
+            "referer": "https://www.jetstar.com/",
+            "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\"",
+            "sec-fetch-dest": "empty",
+            "sec-fetch-mode": "cors",
+            "sec-fetch-site": "same-site",
+            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+        }
+        response = requests.get(url, headers=headers, params=params, verify=False)
+        response.raise_for_status()
+        # for i in response.json():
+        #     print(i)
+        json_data = response.json()[0]['routes']
+
+        # 只有一个键值对
+        for key, val in json_data.items():
+            flight_dates = list(val.get('flights', {}).keys())
+            print(f'航段: {key} 对应有航班的日期为: {flight_dates}')
+            return flight_dates
+
+    def main(self, start_date, end_date):
+        thread_list = list()
+
+        self.gen_task(start_date, end_date)
+
+        for _ in range(1):
+            t_get_cookie = threading.Thread(target=self._refresh_cookie)
+            thread_list.append(t_get_cookie)
+
+        t_get_ja3 = threading.Thread(target=self.get_ja3_str)
+        thread_list.append(t_get_ja3)
+
+        for _ in range(1):
+            t_get_data = threading.Thread(target=self.get_data)
+            thread_list.append(t_get_data)
+
+        t_parse_data = threading.Thread(target=self.parse_data)
+        thread_list.append(t_parse_data)
+
+        for t_obj in thread_list:
+            t_obj.setDaemon(True)
+            t_obj.start()
+
+        for q in [self.task_queue, self.resp_data_queue]:
+            q.join()
+
+
+if __name__ == '__main__':
+    gk = GK()
+    gk.main(start_date='2025-05-30', end_date='2025-06-05')
+    # gk.gen_city()
+
+# http://B_3351_AU___5_ss-XXXXXXXXXXXX:ev2pjj@proxy.renlaer.com:7778
+# curl -x http://B_3351_SG___5_ss-XXXXXXXXXXXX:ev2pjj@proxy.renlaer.com:7778 cip.cc
+# curl -x http://B_3351_TW___5_ss-XXXXXXXXXXXX:ev2pjj@proxy.renlaer.com:7778 cip.cc
+# curl -x http://B_3351_HK___5_ss-115511111111:ev2pjj@proxy.renlaer.com:7778 cip.cc

+ 255 - 0
3.31GK/request_test.py

@@ -0,0 +1,255 @@
+from urllib.parse import urljoin
+
+import requests
+import json
+
+import execjs
+# from curl_cffi import requests
+import retrying
+# import pyhttpx
+#
+# ja3 = "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-45-43-5-23-35-13-65281-16-65037-18-51-10-11-17513-27,29-23-24,0"  # 自定义指纹字符串
+# sess = pyhttpx.HttpSession(
+#     # ja3=ja3,
+#     http2=True
+# )
+import tls_client
+
+headers = {
+    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+    "accept-language": "zh-CN,zh;q=0.9",
+    "cache-control": "no-cache",
+    "pragma": "no-cache",
+    "priority": "u=0, i",
+    "referer": "https://booking.jetstar.com/",
+    "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
+    "sec-ch-ua-mobile": "?0",
+    "sec-ch-ua-platform": "\"Windows\"",
+    "sec-fetch-dest": "document",
+    "sec-fetch-mode": "navigate",
+    "sec-fetch-site": "same-origin",
+    "sec-fetch-user": "?1",
+    "upgrade-insecure-requests": "1",
+    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
+}
+
+
+def get_cookie():
+    with open('akm/逆向.js', encoding='utf-8') as f:
+        js = f.read()
+    ctx = execjs.compile(js)
+    session = requests.Session()
+
+    # akm js file url
+    akm_url = "https://booking.jetstar.com/Yxz-/5uZW/r/LS/v0HpqZQ/cY1w2bLGSLDp/UEtAAzo/SgIr/ED9jfEw"
+
+    data = {
+        'sensor_data': ctx.call('encrypt1')
+    }
+    response = session.post(akm_url, headers=headers, verify=False, data=data,
+                            # proxies=self.proxies
+                            )
+
+    # print(response.text)
+    bmsz = response.cookies.get_dict()['bm_sz']
+
+    data2 = {
+        "sensor_data": ctx.call('encrypt2', bmsz)
+    }
+
+    data2 = json.dumps(data2)
+    response2 = session.post(akm_url, headers=headers, data=data2, verify=False,
+                             # proxies=self.proxies
+                             )
+    print('bmsz => ', bmsz)
+    print(response2.text)
+    print(response2.status_code)
+
+
+@retrying.retry(stop_max_attempt_number=2)
+def req(bmsz_cookie):
+    params = {
+        "s": "true",
+        "adults": "1",
+        "children": "0",
+        "infants": "0",
+        "selectedclass1": "economy",
+        "currency": "CNY",
+        "mon": "true",
+        "channel": "DESKTOP",
+        "origin1": "CTS",
+        "destination1": "KOJ",
+        "departuredate1": f"2025-05-27"
+    }
+    cookie = {
+        'bm_sz': bmsz_cookie
+    }
+    response = requests.get("https://booking.jetstar.com/hk/zh/booking/search-flights", headers=headers, cookies=bmsz_cookie, params=params,
+                        timeout=15,
+                        verify=False,
+                        # impersonate='chrome99',
+                        # http_version=2
+
+                        proxies=proxies
+                        # allow_redirects=False
+
+                        )
+
+    print(response.text)
+    print(response)
+
+    from lxml import etree
+    import json
+
+    html = etree.HTML(response.text)
+    data = html.xpath("//script[@id='bundle-data-v2']/text()")[0] if html.xpath(
+        "//script[@id='bundle-data-v2']/text()") else '{}'
+    json_data = json.loads(data)
+    print(json_data)
+
+
+@retrying.retry(stop_max_attempt_number=3, wait_fixed=4000)
+def get_ja3_str( ):
+    url = "http://8.218.51.130:9003/api/v1/ja3"
+    payload = {}
+    headers = {
+        'cid': '750B5141EDBF7FA6F73A99C768130099'
+    }
+    response = requests.get(url, headers=headers, data=payload)
+    if response.status_code == 200:
+        res_json = response.json()
+        if res_json.get("code") == 0:
+            ja3 = res_json.get("data").get("ja3_str")
+            ua = res_json.get("data").get("ua")
+            if "--" not in ja3 and ",," not in ja3:
+                end_data = (
+                    ua,
+                    ja3
+                )
+                return end_data
+
+def tls_req(bmsz_cookie):
+    ua, ja3_string = get_ja3_str()
+
+    # current_cookie = cookies
+    # browser = random.choice(self.BROWSER)
+    req_session = tls_client.Session(
+        ja3_string=ja3_string,  # 直接注入自定义指纹
+    )
+    headers = {
+        'user-agent': ua,
+        'Accept-Encoding': 'gzip, deflate, br',
+        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+        'Connection': 'keep-alive',
+        'Content-Type': 'application/x-www-form-urlencoded',
+        'accept-language': 'zh-CN,zh;q=0.9',
+        'cache-control': 'no-cache',
+        'pragma': 'no-cache',
+        'priority': 'u=0, i',
+        'referer': 'https://booking.jetstar.com/',
+        'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
+        'sec-ch-ua-mobile': '?0',
+        'sec-ch-ua-platform': '"Windows"',
+        'sec-fetch-dest': 'document',
+        'sec-fetch-mode': 'navigate',
+        'sec-fetch-site': 'same-origin',
+        'sec-fetch-user': '?1',
+        'upgrade-insecure-requests': '1'
+    }
+    params = {
+        "s": "true",
+        "adults": "1",
+        "children": "0",
+        "infants": "0",
+        "selectedclass1": "economy",
+        "currency": "CNY",
+        "mon": "true",
+        "channel": "DESKTOP",
+        "origin1": "PVG",
+        "destination1": "SYD",
+        "departuredate1": f"2025-05-31"
+    }
+    redirect_count = 0
+    max_redirects = 2
+    cookie = {
+        'bm_sz': bmsz_cookie
+    }
+    proxies_url = 'http://B_3351_HK___5_ss-{}:ev2pjj@proxy.renlaer.com:7778'
+    ip = 100000000000
+    proxies = {
+        'http': proxies_url.format(ip),
+        'https': proxies_url.format(ip)
+    }
+
+    current_url = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+    while redirect_count < max_redirects:
+        response = req_session.get(current_url, headers=headers, cookies=cookie, params=params,
+                                   proxy=proxies
+                                   )
+
+        print(response.status_code)
+        # print(response.text)
+
+        # 检查是否为重定向状态码
+        if response.status_code in (301, 302, 303, 307, 308):
+            # 获取 Location 头(需处理相对路径)
+            location = response.headers.get("Location")
+            # if not location:
+            #     break
+            current_url = urljoin(current_url, location)
+            redirect_count += 1
+            # print(f"Redirecting to: {current_url}")
+
+            # 可选:继承原始请求的特定 Headers(如 Referer)
+            # headers["Referer"] = response.url
+        else:
+            from lxml import etree
+            import json
+
+            html = etree.HTML(response.text)
+            data = html.xpath("//script[@id='bundle-data-v2']/text()")[0] if html.xpath(
+                "//script[@id='bundle-data-v2']/text()") else '{}'
+            json_data = json.loads(data)
+            print(json_data)
+            return
+
+    raise Exception(f" ({max_redirects})")
+
+# cookies = {
+#     # "bm_sz": "C0B44D1663A1D1DF531263C2737158CA~YAAQuqzbF+g+wsyWAQAAXYayzRvvCtO5B+WPIETQGca8PO//hOzF1VgQ8UWaX5jQZqjBtJddRoQiBEhF8Kt/yAAy1PYYdaau8jE40Cfj3UxUpLerF6Bx7lMVhelxsPpebDhexd9epZ869ZHXU7vr1aD5waP/ryM62ifQhwkZhoZYCUTM+l4o3i6heEfjohv20aoENno39gXuJ62b7Pw/fWIV1iZsg9ZXz6cytoOozhB86c7SYg8S4F2uHmdRJCQwAle44prjdlu+Dr4InbJIQNhYP+vlzTTuJjMBQWidZncw+5SeoYGHGVhzONc4K8ojnfu1RdhhFfv1vWbT6jLa7pKh3sxzSOHYh7c3zXJsEp7bKFRcqWNO0dqt5NvlLXmOgdiQuOk=~4604742~4342593",
+#     "bm_sz": "D0FDE91D248039D3BCB85401C9E89EBC~YAAQEmrRF7aHJueWAQAA7oY75xu9AR2ykLW/ST7t+Z/9dlMdB5y12d+59GyysxUFJhQNz3GORFkJm0LXmIRyzA3UQ1toCGCp7oib9mXRbovqxMUbdHe3FzPkg6P+Qkz523VsaKzS40mf4sPhh33AU7BTBLYYoTeOii1NKDjkX7xXK0UQQTmi3DLjkLHTXNYuvzNpRZLjMXsMqDOp0e7mm8altPSk8GMJPnA0mMcokJ348UHk9vL92QLq9FmvD0Oi6PQwgPlec/qRjN2rQBH/hYe0KETY6RwKtaVbti5MVbR84d2VWVkKZyQwmmcibWMSrwdxp+oZjCJ3kguEctLgrQlOUfFiQwXprYugwZM=~3159352~4407875",
+# }
+# url = "https://booking.jetstar.com/hk/zh/booking/search-flights"
+#
+# # get_cookie()
+# req(30)
+
+bmsz = cookies = {
+    "bm_sz": "92BF676C3DD979E01C44FB2085A5EB17~YAAQHWrRFzRVgemWAQAAVomREBt8eEC//siHZ6ZbSARsn9I5LSlX4gu9JMhr+mKMUqle7mBkR+FaMJRD6bqONlFEMRiSPt47mEXkvqd2XdhB42MWCDbDyBL4Kq5AQAUstK/W5QCqigla+wVukC/J8BCF4uUXQeC8RLQFt7nELruQ95bcwOZ0dcK+DLBJqhl7tbFUBXwJzTg59ijCrpx/3KIPbsVb8fOnX5+3HE1bgrwurqAVOB7ZLwDy2rz0MR4TZegpmu1LroCHjdbOC97Sr8Bm0vwycXsd3P67jSVGKtsjMkBWFXPYAnnZEfVYFWkCvq33qY4VAMwP0+liJr7oV/OtUSxJNKOXMARY6g==~3683381~3421233",
+}
+try:
+    proxies = {
+        "http": "http://127.0.0.1:7897",
+        "https": "http://127.0.0.1:7897"
+    }
+    req(bmsz)
+    tls_req(bmsz_cookie=bmsz)
+
+except Exception as e:
+    #
+    print(e)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Some files were not shown because too many files changed in this diff