Vehs Results Exporter

Collects search results from Baidu and exports as Excel

// ==UserScript==
// @name         Vehs Results Exporter
// @license MIT
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  Collects search results from Baidu and exports as Excel
// @author       Your Name
// @match        https://*.122.gov.cn/views/memfyy/vehinfo.html?index=7
// @match        https://*.122.gov.cn/views/memrent/vehlist.html
// @grant        GM_addStyle
// @require      https://unpkg.com/xlsx@0.18.5/dist/xlsx.full.min.js
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// ==/UserScript==
(function() {
    'use strict';
    const KEY = "asdji^!~jiaiosnd";
    let COOKIE_KEY = "CLICKDAY";
    let DOMAIN = "https://gd.122.gov.cn";
    let API_VEHS = "/user/m/userinfo/vehs";
    let hpzlArr = ["02", "52"];
    let retryTimes = 3;
    const API_VEHS_MEMRENT = "/user/m/rentveh/vehlist";
    const API_GETCERT = "/m/electronicCertificate/getCert?lx=1&xh=";
    const API_SURIQUERY = "/user/m/uservio/suriquery";
    const API_SURIQUERY_DETAIL = "/user/m/tsc/vio/querySurvielDetail";

    async function postDataToAPI(apiUrl, formData) {
        for (let i = 0; i < retryTimes; i++) {
            try {
                await randomSleep(5000, 15000);
                const response = await fetch(apiUrl, {
                    method: 'POST',
                    body: formData, // 直接使用 FormData 作为请求体
                    credentials: 'include' // 设置 credentials 为 'include' 以携带 cookie
                });
                if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }
                const responseData = await response.json();
                if (responseData.code != 200) {
                    throw new Error(`HTTP error! Status: ${responseData}`);
                }
                return responseData;
            } catch (error) {
                if (i >= retryTimes) {
                    alert("网络请求异常,请稍后再重试!")
                    return null;
                }
            }
        }
    }

    async function getDataFromAPI(apiUrl) {
        for (let i = 0; i < retryTimes; i++) {
            try {
                await randomSleep(5000, 15000);
                const response = await fetch(apiUrl, {
                    method: 'GET',
                    credentials: 'include' // 设置 credentials 为 'include' 以携带 cookie
                });
                if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }
                const data = await response.json();
                return data;
            } catch (error) {
                if (i >= retryTimes) {
                    alert("网络请求异常,请稍后再重试!")
                    return null;
                }
            }
        }
    }

    // 加密函数
    function encryptData(data) {
        const secretKey = CryptoJS.enc.Utf8.parse(KEY); // 密钥
        const encrypted = CryptoJS.AES.encrypt(data, secretKey, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        }).toString();
        return encrypted;
    }

    function createVehsFormData(page, hpzl) {
        const formData = new FormData();
        formData.append('page', page);
        formData.append('size', '10');
		formData.append('hpzl', hpzl);
		formData.append('hphm', '');
		formData.append('status', 'null');
        return formData;
    }

    // URL编码
    function encodeUrl(str) {
        return encodeURIComponent(str);
    }

    function createBreakFormData(hphm, page, hpzl) {
        const formData = new FormData();
        formData.append('startDate', formatDateSelf(subtractMonths(new Date(), 12), "yyyyMMdd"));
        formData.append('endDate', formatDateSelf(new Date(), "yyyyMMdd"));
        formData.append('hpzl', hpzl);
		formData.append('hphm', hphm);
		formData.append('page', page);
        formData.append('type', '0');
        return formData;
    }

    function createBreakDetailFormData(hphm, hpzl, xh, cjjg) {
        const formData = new FormData();
        formData.append('hpzl', hpzl);
		formData.append('hphm', hphm);
		formData.append('xh', xh);
        formData.append('cjjg', cjjg);
        return formData;
    }


    function displayButton() {
        COOKIE_KEY = $(".user-center").parent().text();
        const $dataElement = $("#btnSearch");
        var clickDay = getCookie(encodeUrl(COOKIE_KEY));
        if($dataElement && clickDay && clickDay == formatDate(new Date())){
            $dataElement.after('<div id="downDiv" style="padding-bottom: 0px;padding-right: 20px;" class="pull-right"><a class="btn btn-success pull-right c3" style="background-image: linear-gradient(to bottom,gray,gray);border: 1px solid gray;background-color: gray;" href="javascript:;"> <i class="icon-download"></i>今日分析次数已用完</a></div>');
        }else if ($dataElement) {
            $dataElement.after('<div id="downDiv" style="padding-bottom: 0px;padding-right: 20px;" class="pull-right"><a class="btn btn-success pull-right c3" style="background-image: linear-gradient(to bottom,#FF5722,#FF5722);border: 1px solid #FF5722;background-color: #FF5722;" id="downVehicle" href="javascript:;"> <i class="icon-download"></i>分析机动车状态</a></div>');
        } else {
            console.error('No element with id "data-display" found.');
        }

        let area = $("#district-name").text();
        if(area == '北京' || area == '北京市' ){
            DOMAIN = "https://bj.122.gov.cn";
        } else if(area == '浙江省'){
            DOMAIN = "https://zj.122.gov.cn";
        }else if(area == '广东省'){
            DOMAIN = "https://gd.122.gov.cn";
        }else if(area == '天津'  || area == '天津市' ){
            DOMAIN = "https://tj.122.gov.cn";
        }else if(area == '河北省'){
            DOMAIN = "https://he.122.gov.cn";
        }else if(area == '山西省'){
            DOMAIN = "https://sx.122.gov.cn";
        }else if(area == '内蒙古省'){
            DOMAIN = "https://nm.122.gov.cn";
        }else if(area == '辽宁省'){
            DOMAIN = "https://ln.122.gov.cn";
        }else if(area == '吉林省'){
            DOMAIN = "https://jl.122.gov.cn";
        }else if(area == '黑龙江省'){
            DOMAIN = "https://hl.122.gov.cn";
        }else if(area == '上海'  || area == '上海市' ){
            DOMAIN = "https://sh.122.gov.cn";
        }else if(area == '江苏省'){
            DOMAIN = "https://js.122.gov.cn";
        }else if(area == '安徽省'){
            DOMAIN = "https://ah.122.gov.cn";
        }else if(area == '福建省'){
            DOMAIN = "https://fj.122.gov.cn";
        }else if(area == '江西省'){
            DOMAIN = "https://jx.122.gov.cn";
        }else if(area == '山东省'){
            DOMAIN = "https://sd.122.gov.cn";
        }else if(area == '河南省'){
            DOMAIN = "https://ha.122.gov.cn";
        }else if(area == '湖北省'){
            DOMAIN = "https://hb.122.gov.cn";
        }else if(area == '湖南省'){
            DOMAIN = "https://hn.122.gov.cn";
        }else if(area == '广西壮族自治区' || area == '广西省'){
            DOMAIN = "https://gx.122.gov.cn";
        }else if(area == '海南省'){
            DOMAIN = "https://hi.122.gov.cn";
        }else if(area == '重庆' || area == '重庆市'){
            DOMAIN = "https://cq.122.gov.cn";
        }else if(area == '四川省'){
            DOMAIN = "https://sc.122.gov.cn";
        }else if(area == '贵州省'){
            DOMAIN = "https://gz.122.gov.cn";
        }else if(area == '云南省'){
            DOMAIN = "https://yn.122.gov.cn";
        }else if(area == '西藏省'){
            DOMAIN = "https://xz.122.gov.cn";
        }else if(area == '陕西省'){
            DOMAIN = "https://sn.122.gov.cn";
        }else if(area == '甘肃省'){
            DOMAIN = "https://gs.122.gov.cn";
        }else if(area == '青海省'){
            DOMAIN = "https://qh.122.gov.cn";
        }else if(area == '宁夏省'){
            DOMAIN = "https://nx.122.gov.cn";
        }else if(area == '新疆省'){
            DOMAIN = "https://xj.122.gov.cn";
        }
    }

    function displayButtonOnRental() {
        const $dataElement = $("#exportveh");
        if($dataElement && $dataElement.length>0){
            API_VEHS = DOMAIN+"/user/m/rentveh/vehlist";
            hpzlArr = ["02"];
        }
        var clickDay = getCookie(COOKIE_KEY);
        if($dataElement && clickDay && clickDay == formatDate(new Date())){
            $dataElement.after('<div id="downDiv" style="padding-bottom: 0px;padding-right: 20px;" class="pull-right"><a class="btn btn-success pull-right c3" style="background-image: linear-gradient(to bottom,gray,gray);border: 1px solid gray;background-color: gray;" href="javascript:;"> <i class="icon-download"></i>今日分析次数已用完</a></div>');
        }else if ($dataElement) {
            $dataElement.after('<div id="downDiv" style="padding-bottom: 0px;padding-right: 20px;" class="pull-right"><a class="btn btn-success pull-right c3" style="background-image: linear-gradient(to bottom,#FF5722,#FF5722);border: 1px solid #FF5722;background-color: #FF5722;" id="downVehicle" href="javascript:;"> <i class="icon-download"></i>分析机动车状态</a></div>');
        } else {
            console.error('No element with id "data-display" found.');
        }
    }

    function downVehicle(){
        $("#downVehicle").click(function(){
            let $this = $(this);
            $this.prop('disabled', true);
            $this.text("数据分析中,请稍后......")
            spiderVehs().then(data =>{
                $("#downDiv").replaceWith('<div id="downDiv" style="padding-bottom: 0px;padding-right: 20px;" class="pull-right"><a class="btn btn-success pull-right c3" style="background-image: linear-gradient(to bottom,gray,gray);border: 1px solid gray;background-color: gray;" href="javascript:;"> <i class="icon-download"></i>今日分析次数已用完</a></div>')
            }).catch(error =>{
                $this.prop('disabled', false);
                $this.attr("style", 'background-image: linear-gradient(to bottom,#FF5722,#FF5722);border: 1px solid #FF5722;background-color: #FF5722;')
                $this.html("<i class=\"icon-download\"></i>分析机动车状态")
            });
        })
    }

    /**
     * 休眠函数,防止执行过快
     * @param ms
     * @returns {Promise<unknown>}
     */
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function randomSleep(min, max) {
        return new Promise((resolve) => {
            var randomTime = Math.floor(Math.random() * (max - min + 1)) + min;
            setTimeout(resolve, randomTime);
        });
    }

    function formatDateSelf(date, format) {
        const pad = (num, size) => num.toString().padStart(size, '0');
        const day = pad(date.getDate(), 2);
        const month = pad(date.getMonth() + 1, 2); // 月份从0开始,所以加1
        const year = date.getFullYear();
        const hours = pad(date.getHours(), 2);
        const minutes = pad(date.getMinutes(), 2);
        const seconds = pad(date.getSeconds(), 2);

        // 构建日期字符串
        return format
            .replace('yyyy', year)
            .replace('MM', month)
            .replace('dd', day)
            .replace('HH', hours)
            .replace('mm', minutes)
            .replace('ss', seconds);
    }

    function subtractMonths(date, months) {
        const newDate = new Date(date);
        newDate.setMonth(newDate.getMonth() - months);
        return newDate;
    }
    
    function createExcelFile(data1, data2) {
        const ws1 = XLSX.utils.json_to_sheet(data1);
        const ws2 = XLSX.utils.json_to_sheet(data2);
        ws1['!cols'] = [
            { wch: 30 }, // 第一列宽度为20个字符宽度
            { wch: 30 },
            { wch: 30 },
            { wch: 50 }
        ];

        ws2['!cols'] = [
            { wch: 20 }, // 第一列宽度为20个字符宽度
            { wch: 20 },
            { wch: 40 },
            { wch: 65 },
            { wch: 20 },
            { wch: 20 },
            { wch: 20 },
            { wch: 20 },
            { wch: 20 },
            { wch: 50 }
        ];

        const wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws1, 'vehs');
        XLSX.utils.book_append_sheet(wb, ws2, 'break');
        // XLSX.utils.book_append_sheet(wb, ws2, '机动车违章信息');

        XLSX.writeFile(wb, formatDate(new Date())+'机动车状态信息.xlsx');
    }

    function formatDate(date) {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以加1
        const day = String(date.getDate()).padStart(2, '0');
        return `${year}-${month}-${day}`;
    }

    function setCookie(name, value, days) {
        var expires = "";
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toUTCString();
        }
        document.cookie = encodeUrl(name) + "=" + (value || "") + expires + "; path=/";
    }

    function getCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
        }
        return null;
    }


    /**
     * 获取机动车列表信息
     */
    async function spiderVehs(){
        const sheet1Data = [];
        const sheet2Data = [];
        for (let i = 0; i < hpzlArr.length; i++) {
            let hpzl = hpzlArr[i];
            let vehsPage = 1;
            let totalPages = 1;
            while (true){
                const data = await postDataToAPI(DOMAIN+API_VEHS, createVehsFormData(vehsPage, hpzl));
                if (!data || data.code != 200) {
                    throw new Error(`HTTP error! Status: ${data}`);
                }

                if (data) {
                    let result = data.data.content;
                    totalPages = data.data.totalPages;
                    if (result.length == 0) {
                        break;
                    }
                    $("#downVehicle").html('<i class=\"icon-download\"></i>分析第'+vehsPage+'页总共'+totalPages+'页');
                    for (let index = 0; index < result.length; index++) {
                        var vehsElement = result[index];
                        let breakPage = 1;
                        while (true){
                            let breakData = await postDataToAPI(DOMAIN+API_SURIQUERY, createBreakFormData(vehsElement.hphm, breakPage, vehsElement.hpzl))
                            if (!breakData.data || !breakData.data.content || breakData.data.content.length == 0) {
                                break;
                            }
                            let breakResult = breakData.data.content;
                            for (let index = 0; index < breakResult.length; index++) {
                                sheet2Data.push(breakResult[index]);
                            }

                            // await randomSleep(10000, 15000);
                            if(breakData.data.totalPages == breakPage){
                                break;
                            }
                            breakPage++;
                        }
                        sheet1Data.push(vehsElement);
                    }
                } else {
                    break;
                }

                if (totalPages == vehsPage){
                    break;
                }
                vehsPage++;
                // await randomSleep(5000, 10000);
            }
        }

        let data1 = [];
        for (let index = 0; index < sheet1Data.length; index++) {
            const element = sheet1Data[index];
            data1.push({"RXbv5Dz0l2dVNxWchTQfUQ==": encryptData(element.hphm), "qVOojSv50bNZOeBHecVtPg==": encryptData(element.hpzl), "xvHm4pbKAO0GpLLWj8/BxQ==": encryptData(element.zt), "1WLdT09J2/ASx+/SH3ftatPQhDEy41/jPTfuZ6QEaI0=":encryptData(element.yxqz)});
        }

        let data2 = [];
        for (let index = 0; index < sheet2Data.length; index++) {
            const element = sheet2Data[index];
            // 交款状态 0未交款  1已交款 9无需交款
            // let jkbjStr = element.jkbj == "0"?"未交款":(element.jkbj == "1"?"已交款":(element.jkbj == "9"?"无需交款":""));
            // let clbjStr = element.clbj == "0"?"未处理":(element.clbj == "1"?"已处理":"");
            data2.push({"RXbv5Dz0l2dVNxWchTQfUQ==": encryptData(element.hphm), "b95apzt5yO+e5EsCTN2dQg==": encryptData(element.wfsj), "+Wa6gljDcjYiL8jLVOCmMQ==": encryptData(element.wfdz), "VRcOnwqy1xBOHegTLlgJRQ==": encryptData(element.wfms), "gff3Rolt3CTyCvJbrgydww==": encryptData(element.clbj), "7cZx8saTA2HhVc2UmmVkTQ==": encryptData(element.clsj), "93mJjnE/5UbBOMTFTMG5rI4KyaFGro8kRjpPrqMvNbM=": encryptData(element.fkje), "toi8XW6fS5GGd6bmwSfr5g==": encryptData(element.jkbj)});
        }

        if(data1.length > 0){
            createExcelFile(data1, data2);
            setCookie(COOKIE_KEY, formatDate(new Date()), 1);
        }
    }

    displayButton();
    displayButtonOnRental();
    downVehicle();
})();