浏览代码

中国招标投标公共服务平台[未按规范]采集爬虫;

dzr 2 月之前
父节点
当前提交
185235b0c9

+ 474 - 0
lzz_theme/zgzbtbggfwpt_wagf/des.py

@@ -0,0 +1,474 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-07
+---------
+@summary:
+    通过按 4 个字符一组对输入数据进行处理,对每组数据依次使用三个密钥进行异或加密操作,
+    最后将加密后的字节数组转换为十六进制字符串
+"""
+
+# 扩展置换函数
+def expandPermute(rightData):
+    epByte = [0] * 48
+    for i in range(8):
+        if i == 0:
+            epByte[i * 6 + 0] = rightData[31]
+        else:
+            epByte[i * 6 + 0] = rightData[i * 4 - 1]
+        epByte[i * 6 + 1] = rightData[i * 4 + 0]
+        epByte[i * 6 + 2] = rightData[i * 4 + 1]
+        epByte[i * 6 + 3] = rightData[i * 4 + 2]
+        epByte[i * 6 + 4] = rightData[i * 4 + 3]
+        if i == 7:
+            epByte[i * 6 + 5] = rightData[0]
+        else:
+            epByte[i * 6 + 5] = rightData[i * 4 + 4]
+    return epByte
+
+
+# 异或操作函数
+def xor(byteOne, byteTwo):
+    return [a ^ b for a, b in zip(byteOne, byteTwo)]
+
+
+# S 盒置换函数
+def sBoxPermute(expandByte):
+    sBoxByte = [0] * 32
+    s1 = [
+        [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
+        [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
+        [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
+        [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]
+    ]
+    s2 = [
+        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
+        [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
+        [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
+        [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]
+    ]
+    s3 = [
+        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
+        [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
+        [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
+        [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]
+    ]
+    s4 = [
+        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
+        [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
+        [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
+        [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]
+    ]
+    s5 = [
+        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
+        [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
+        [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
+        [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]
+    ]
+    s6 = [
+        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
+        [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
+        [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
+        [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]
+    ]
+    s7 = [
+        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
+        [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
+        [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
+        [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]
+    ]
+    s8 = [
+        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
+        [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
+        [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
+        [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]
+    ]
+    s_tables = [s1, s2, s3, s4, s5, s6, s7, s8]
+    for m in range(8):
+        i = expandByte[m * 6 + 0] * 2 + expandByte[m * 6 + 5]
+        j = expandByte[m * 6 + 1] * 8 + expandByte[m * 6 + 2] * 4 + expandByte[m * 6 + 3] * 2 + expandByte[m * 6 + 4]
+        binary = getBoxBinary(s_tables[m][i][j])
+        sBoxByte[m * 4 + 0] = int(binary[0])
+        sBoxByte[m * 4 + 1] = int(binary[1])
+        sBoxByte[m * 4 + 2] = int(binary[2])
+        sBoxByte[m * 4 + 3] = int(binary[3])
+    return sBoxByte
+
+
+# P 盒置换函数
+def pPermute(sBoxByte):
+    pBoxPermute = [0] * 32
+    pBoxPermute[0] = sBoxByte[15]
+    pBoxPermute[1] = sBoxByte[6]
+    pBoxPermute[2] = sBoxByte[19]
+    pBoxPermute[3] = sBoxByte[20]
+    pBoxPermute[4] = sBoxByte[28]
+    pBoxPermute[5] = sBoxByte[11]
+    pBoxPermute[6] = sBoxByte[27]
+    pBoxPermute[7] = sBoxByte[16]
+    pBoxPermute[8] = sBoxByte[0]
+    pBoxPermute[9] = sBoxByte[14]
+    pBoxPermute[10] = sBoxByte[22]
+    pBoxPermute[11] = sBoxByte[25]
+    pBoxPermute[12] = sBoxByte[4]
+    pBoxPermute[13] = sBoxByte[17]
+    pBoxPermute[14] = sBoxByte[30]
+    pBoxPermute[15] = sBoxByte[9]
+    pBoxPermute[16] = sBoxByte[1]
+    pBoxPermute[17] = sBoxByte[7]
+    pBoxPermute[18] = sBoxByte[23]
+    pBoxPermute[19] = sBoxByte[13]
+    pBoxPermute[20] = sBoxByte[31]
+    pBoxPermute[21] = sBoxByte[26]
+    pBoxPermute[22] = sBoxByte[2]
+    pBoxPermute[23] = sBoxByte[8]
+    pBoxPermute[24] = sBoxByte[18]
+    pBoxPermute[25] = sBoxByte[12]
+    pBoxPermute[26] = sBoxByte[29]
+    pBoxPermute[27] = sBoxByte[5]
+    pBoxPermute[28] = sBoxByte[21]
+    pBoxPermute[29] = sBoxByte[10]
+    pBoxPermute[30] = sBoxByte[3]
+    pBoxPermute[31] = sBoxByte[24]
+    return pBoxPermute
+
+
+# 最终置换函数
+def finallyPermute(endByte):
+    fpByte = [0] * 64
+    fpByte[0] = endByte[39]
+    fpByte[1] = endByte[7]
+    fpByte[2] = endByte[47]
+    fpByte[3] = endByte[15]
+    fpByte[4] = endByte[55]
+    fpByte[5] = endByte[23]
+    fpByte[6] = endByte[63]
+    fpByte[7] = endByte[31]
+    fpByte[8] = endByte[38]
+    fpByte[9] = endByte[6]
+    fpByte[10] = endByte[46]
+    fpByte[11] = endByte[14]
+    fpByte[12] = endByte[54]
+    fpByte[13] = endByte[22]
+    fpByte[14] = endByte[62]
+    fpByte[15] = endByte[30]
+    fpByte[16] = endByte[37]
+    fpByte[17] = endByte[5]
+    fpByte[18] = endByte[45]
+    fpByte[19] = endByte[13]
+    fpByte[20] = endByte[53]
+    fpByte[21] = endByte[21]
+    fpByte[22] = endByte[61]
+    fpByte[23] = endByte[29]
+    fpByte[24] = endByte[36]
+    fpByte[25] = endByte[4]
+    fpByte[26] = endByte[44]
+    fpByte[27] = endByte[12]
+    fpByte[28] = endByte[52]
+    fpByte[29] = endByte[20]
+    fpByte[30] = endByte[60]
+    fpByte[31] = endByte[28]
+    fpByte[32] = endByte[35]
+    fpByte[33] = endByte[3]
+    fpByte[34] = endByte[43]
+    fpByte[35] = endByte[11]
+    fpByte[36] = endByte[51]
+    fpByte[37] = endByte[19]
+    fpByte[38] = endByte[59]
+    fpByte[39] = endByte[27]
+    fpByte[40] = endByte[34]
+    fpByte[41] = endByte[2]
+    fpByte[42] = endByte[42]
+    fpByte[43] = endByte[10]
+    fpByte[44] = endByte[50]
+    fpByte[45] = endByte[18]
+    fpByte[46] = endByte[58]
+    fpByte[47] = endByte[26]
+    fpByte[48] = endByte[33]
+    fpByte[49] = endByte[1]
+    fpByte[50] = endByte[41]
+    fpByte[51] = endByte[9]
+    fpByte[52] = endByte[49]
+    fpByte[53] = endByte[17]
+    fpByte[54] = endByte[57]
+    fpByte[55] = endByte[25]
+    fpByte[56] = endByte[32]
+    fpByte[57] = endByte[0]
+    fpByte[58] = endByte[40]
+    fpByte[59] = endByte[8]
+    fpByte[60] = endByte[48]
+    fpByte[61] = endByte[16]
+    fpByte[62] = endByte[56]
+    fpByte[63] = endByte[24]
+    return fpByte
+
+
+# 获取 4 位二进制字符串
+def getBoxBinary(i):
+    return '{:04b}'.format(i)
+
+
+# 初始置换函数
+def initPermute(originalData):
+    ipByte = [0] * 64
+    for i in range(4):
+        m = 2 * i + 1
+        n = 2 * i
+        for j in range(8):
+            k = 7 - j
+            ipByte[i * 8 + j] = originalData[k * 8 + m]
+            ipByte[i * 8 + j + 32] = originalData[k * 8 + n]
+    return ipByte
+
+
+# 生成密钥函数
+def generateKeys(keyByte):
+    key = [0] * 56
+    keys = [[] for _ in range(16)]
+    loop = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
+    for i in range(7):
+        for j in range(8):
+            k = 7 - j
+            key[i * 8 + j] = keyByte[8 * k + i]
+    for i in range(16):
+        for _ in range(loop[i]):
+            tempLeft = key[0]
+            tempRight = key[28]
+            for k in range(27):
+                key[k] = key[k + 1]
+                key[28 + k] = key[29 + k]
+            key[27] = tempLeft
+            key[55] = tempRight
+        tempKey = [0] * 48
+        tempKey[0] = key[13]
+        tempKey[1] = key[16]
+        tempKey[2] = key[10]
+        tempKey[3] = key[23]
+        tempKey[4] = key[0]
+        tempKey[5] = key[4]
+        tempKey[6] = key[2]
+        tempKey[7] = key[27]
+        tempKey[8] = key[14]
+        tempKey[9] = key[5]
+        tempKey[10] = key[20]
+        tempKey[11] = key[9]
+        tempKey[12] = key[22]
+        tempKey[13] = key[18]
+        tempKey[14] = key[11]
+        tempKey[15] = key[3]
+        tempKey[16] = key[25]
+        tempKey[17] = key[7]
+        tempKey[18] = key[15]
+        tempKey[19] = key[6]
+        tempKey[20] = key[26]
+        tempKey[21] = key[19]
+        tempKey[22] = key[12]
+        tempKey[23] = key[1]
+        tempKey[24] = key[40]
+        tempKey[25] = key[51]
+        tempKey[26] = key[30]
+        tempKey[27] = key[36]
+        tempKey[28] = key[46]
+        tempKey[29] = key[54]
+        tempKey[30] = key[29]
+        tempKey[31] = key[39]
+        tempKey[32] = key[50]
+        tempKey[33] = key[44]
+        tempKey[34] = key[32]
+        tempKey[35] = key[47]
+        tempKey[36] = key[43]
+        tempKey[37] = key[48]
+        tempKey[38] = key[38]
+        tempKey[39] = key[55]
+        tempKey[40] = key[33]
+        tempKey[41] = key[52]
+        tempKey[42] = key[45]
+        tempKey[43] = key[41]
+        tempKey[44] = key[49]
+        tempKey[45] = key[35]
+        tempKey[46] = key[28]
+        tempKey[47] = key[31]
+        keys[i] = tempKey
+    return keys
+
+
+# 加密函数
+def enc(dataByte, keyByte):
+    keys = generateKeys(keyByte)
+    ipByte = initPermute(dataByte)
+    ipLeft = ipByte[:32]
+    ipRight = ipByte[32:]
+    for i in range(16):
+        tempLeft = ipLeft[:]
+        ipLeft = ipRight[:]
+        key = keys[i]
+        tempRight = xor(pPermute(sBoxPermute(xor(expandPermute(ipRight), key))), tempLeft)
+        ipRight = tempRight
+    finalData = ipRight + ipLeft
+    return finallyPermute(finalData)
+
+
+# 字符串转字节数组
+def strToBt(str):
+    leng = len(str)
+    bt = [0] * 64
+    if leng < 4:
+        for i in range(leng):
+            k = ord(str[i])
+            for j in range(16):
+                pow_val = 2 ** (15 - j)
+                bt[16 * i + j] = (k // pow_val) % 2
+        for p in range(leng, 4):
+            k = 0
+            for q in range(16):
+                pow_val = 2 ** (15 - q)
+                bt[16 * p + q] = (k // pow_val) % 2
+    else:
+        for i in range(4):
+            k = ord(str[i])
+            for j in range(16):
+                pow_val = 2 ** (15 - j)
+                bt[16 * i + j] = (k // pow_val) % 2
+    return bt
+
+
+# 获取密钥字节数组
+def getKeyBytes(key):
+    keyBytes = []
+    leng = len(key)
+    iterator = leng // 4
+    remainder = leng % 4
+    for i in range(iterator):
+        keyBytes.append(strToBt(key[i * 4:(i * 4 + 4)]))
+    if remainder > 0:
+        keyBytes.append(strToBt(key[iterator * 4:]))
+    return keyBytes
+
+
+# 4 位二进制转十六进制
+def bt4ToHex(binary):
+    hex_map = {
+        "0000": "0",
+        "0001": "1",
+        "0010": "2",
+        "0011": "3",
+        "0100": "4",
+        "0101": "5",
+        "0110": "6",
+        "0111": "7",
+        "1000": "8",
+        "1001": "9",
+        "1010": "A",
+        "1011": "B",
+        "1100": "C",
+        "1101": "D",
+        "1110": "E",
+        "1111": "F"
+    }
+    return hex_map[binary]
+
+
+# 64 位二进制转十六进制
+def bt64ToHex(byteData):
+    hex_str = ""
+    for i in range(16):
+        bt = ''.join(map(str, byteData[i * 4:(i * 4 + 4)]))
+        hex_str += bt4ToHex(bt)
+    return hex_str
+
+
+# 字符串加密函数
+def strEnc(data, firstKey, secondKey, thirdKey):
+    leng = len(data)
+    encData = ""
+    if firstKey:
+        firstKeyBt = getKeyBytes(firstKey)
+        firstLength = len(firstKeyBt)
+    if secondKey:
+        secondKeyBt = getKeyBytes(secondKey)
+        secondLength = len(secondKeyBt)
+    if thirdKey:
+        thirdKeyBt = getKeyBytes(thirdKey)
+        thirdLength = len(thirdKeyBt)
+    if leng > 0:
+        if leng < 4:
+            bt = strToBt(data)
+            if firstKey and secondKey and thirdKey:
+                tempBt = bt
+                for x in range(firstLength):
+                    tempBt = enc(tempBt, firstKeyBt[x])
+                for y in range(secondLength):
+                    tempBt = enc(tempBt, secondKeyBt[y])
+                for z in range(thirdLength):
+                    tempBt = enc(tempBt, thirdKeyBt[z])
+                encByte = tempBt
+            elif firstKey and secondKey:
+                tempBt = bt
+                for x in range(firstLength):
+                    tempBt = enc(tempBt, firstKeyBt[x])
+                for y in range(secondLength):
+                    tempBt = enc(tempBt, secondKeyBt[y])
+                encByte = tempBt
+            elif firstKey:
+                tempBt = bt
+                for x in range(firstLength):
+                    tempBt = enc(tempBt, firstKeyBt[x])
+                encByte = tempBt
+            encData = bt64ToHex(encByte)
+        else:
+            iterator = leng // 4
+            remainder = leng % 4
+            for i in range(iterator):
+                tempData = data[i * 4:(i * 4 + 4)]
+                tempByte = strToBt(tempData)
+                if firstKey and secondKey and thirdKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    for y in range(secondLength):
+                        tempBt = enc(tempBt, secondKeyBt[y])
+                    for z in range(thirdLength):
+                        tempBt = enc(tempBt, thirdKeyBt[z])
+                    encByte = tempBt
+                elif firstKey and secondKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    for y in range(secondLength):
+                        tempBt = enc(tempBt, secondKeyBt[y])
+                    encByte = tempBt
+                elif firstKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    encByte = tempBt
+                encData += bt64ToHex(encByte)
+            if remainder > 0:
+                remainderData = data[iterator * 4:]
+                tempByte = strToBt(remainderData)
+                if firstKey and secondKey and thirdKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    for y in range(secondLength):
+                        tempBt = enc(tempBt, secondKeyBt[y])
+                    for z in range(thirdLength):
+                        tempBt = enc(tempBt, thirdKeyBt[z])
+                    encByte = tempBt
+                elif firstKey and secondKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    for y in range(secondLength):
+                        tempBt = enc(tempBt, secondKeyBt[y])
+                    encByte = tempBt
+                elif firstKey:
+                    tempBt = tempByte
+                    for x in range(firstLength):
+                        tempBt = enc(tempBt, firstKeyBt[x])
+                    encByte = tempBt
+                encData += bt64ToHex(encByte)
+    return encData
+
+
+if __name__ == '__main__':
+    print(strEnc('0025-ZB1062900000000', '2025-05-06 14', 'cebpubservice', 'iiss'))

+ 8 - 0
lzz_theme/zgzbtbggfwpt_wagf/render.js

@@ -0,0 +1,8 @@
+const jsdom = require("jsdom");
+const { JSDOM } = jsdom;
+
+
+function renderHtml(html) {
+    const dom = new JSDOM(html, {runScripts: "dangerously"});
+    return dom.window.document.documentElement.outerHTML
+}

+ 600 - 0
lzz_theme/zgzbtbggfwpt_wagf/showDetails.js

@@ -0,0 +1,600 @@
+function timeZH(time) {
+    var reg = /\d+/g;
+    if (time == null || typeof (time) == undefined) {
+        return "";
+    }
+    var time_array = time.toString().match(reg);
+    var tm = "";
+    if (time_array.length > 1) {
+        for (var i = 0; i < time_array.length; i++) {
+            switch (i) {
+                case 0:
+                    tm += time_array[i];
+                    tm += "年";
+                    break;
+                case 1:
+                    tm += time_array[i];
+                    tm += "月";
+                    break;
+                case 2:
+                    tm += time_array[i];
+                    tm += "日 ";
+                    break;
+                case 3:
+                    tm += time_array[i];
+                    break;
+                case 4:
+                    tm += ":";
+                    tm += time_array[i];
+                    break;
+                case 5:
+                    tm += ":";
+                    tm += time_array[i];
+                    break;
+                default:
+                    break;
+            }
+        }
+        return tm;
+    } else {
+        for (var i = 0; i < time.length; i++) {
+            if (i == 3) {
+                tm += time.substring(0, 4);
+                tm += "年";
+            }
+            if (i == 5) {
+                tm += time.substring(4, 6);
+                tm += "月";
+            }
+            if (i == 7) {
+                tm += time.substring(6, 8);
+                tm += "日";
+            }
+            if (i == 9) {
+                tm += " ";
+                tm += time.substring(8, 10);
+                tm += ":";
+            }
+            if (i == 11) {
+                tm += time.substring(10, 12);
+                tm += ":";
+            }
+            if (i == 13) {
+                tm += time.substring(12, 14);
+            }
+        }
+        return tm;
+    }
+}
+
+
+function getHtml(dataNullName, response){
+    ////////    获取查询的详细内容开始    ////////
+    var mapb
+    var mapbFlag = response.object != null ? 1 : 0
+    var keyList = []
+    if (response.object != null) {
+        mapb = response.object;
+        var i = 0;
+        for (var key in mapb) {
+            keyList[i] = key;
+            i++;
+        }
+    }
+
+    //判断从地址栏接收到的四种类型,转换成中文字符
+    var dataNullNameCN = "";
+    switch (dataNullName) {
+        case "tenderBulletin": {
+            dataNullNameCN = "招标公告";
+            break;
+        }
+        case "openBidRecord": {
+            dataNullNameCN = "开标记录";
+            break;
+        }
+        case "winCandidateBulletin": {
+            dataNullNameCN = "评标公示";
+            break;
+        }
+        case "winBidBulletin": {
+            dataNullNameCN = "中标公告";
+            break;
+        }
+        case "tenderProject": {
+            dataNullNameCN = "招标项目";
+            break;
+        }
+    }
+    // 组合成页面正文
+    switch (dataNullName) {
+        case "tenderProject": //招标项目
+            var arry = mapb.tenderProject;
+            var htmlA = "";
+            if (arry != null && arry != undefined) {
+                if (arry.length == 0) {
+                    htmlA += "<div class='wBox'>";
+                    htmlA += "<div class='borderBlue'></div>";
+                    htmlA += "<div class='wrap_box'>";
+                    htmlA += "    <div class='topTit3 fix' style='height: 40px;'>";
+                    htmlA += "        <h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                    htmlA += "<div class='inner_con'>";
+                    htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                    htmlA += "暂无详细数据";
+                    htmlA += "</div>";
+                    htmlA += "<div style='height:20px;'></div>";
+                    htmlA += "</div></div></div>";
+                }
+                htmlA += "<div class='wBox'>" +
+                    "<div class='borderBlue'></div>" +
+                    "<div class='wrap_box'>" +
+                    "<div class='topTit3 fix' style='height: 40px;'>" +
+                    "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4>" +
+                    "</div>";
+                for (var i = 0; i < arry.length; i++) {
+                    htmlA += "<div class='inner_con'>" +
+                        "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                    if (i > 0) {
+                        htmlA += "<div class='title fix closed'>" +
+                            "<h4 class ='fleft'>" + arry[i].bulletinName + "</h4>" +
+                            "<div class='ocBtn fright'></div>" +
+                            "</div>" +
+                            "<div class='inner display_none'>";
+                    } else {
+                        htmlA += "<div class='title fix open'>" +
+                            "<h4 class ='fleft'>" + arry[i].bulletinName + "</h4>" +
+                            "<div class='ocBtn fright'></div>" +
+                            "</div>" +
+                            "<div class='inner'>";
+                    }
+                    htmlA += "<div style='margin:0 auto 10px auto;text-align:center;font-size:14px;color:#333;font-family:微软雅黑;'>发布日期:" + timeZH(arry[i].bulletinssueTime) + "" + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>" +
+                        "<table class='table_tenderProject' border='0px' bordercolor='#999'>" +
+                        "<tr><td class ='even'><p>所属行业:<span>" + (arry[i].industriesType == undefined ? '' : arry[i].industriesType) + "</span></p></td><td><p>所属地区:<span>" + (arry[i].regionCode == undefined ? '' : arry[i].regionCode) + "</span></p></td></tr>" +
+                        "<tr><td class ='even'><p>招标项目建立时间:<span>" + timeZH(arry[i].bulletinssueTime) + "</span></p></td><td><p><span></span></p></td></tr>" +
+                        "<tr><td class ='even'><p>招标代理机构代码:<span title ='" + (arry[i].tenderAgencyCode == undefined ? '' : arry[i].tenderAgencyCode) + "'>" + (arry[i].tenderAgencyCode == undefined ? '' : arry[i].tenderAgencyCode) + "</span></p></td><td><p>招标代理机构名称:<span title ='" + (arry[i].tenderAgencyName == undefined ? '' : arry[i].tenderAgencyName) + "'>" + (arry[i].tenderAgencyName == undefined ? '' : arry[i].tenderAgencyName) + "</span></p></td></tr>" +
+                        "<tr><td class ='even'><p>招标人名称:<span title ='" + (arry[i].tendererName == undefined ? '' : arry[i].tendererName) + "'>" + (arry[i].tendererName == undefined ? '' : arry[i].tendererName) + "</span></p></td><td><p>招标组织方式:<span>" + (arry[i].tenderOrganizeForm == undefined ? '' : arry[i].tenderOrganizeForm) + "</span></p></td></tr>" +
+                        "<tr><td class ='even'><p>行政监督部门代码:<span>" + (arry[i].superviseDeptCode == undefined ? '' : arry[i].superviseDeptCode) + "</span></p></td><td><p>行政监督部门名称:<span>" + (arry[i].superviseDeptName == undefined ? '' : arry[i].superviseDeptName) + "</span></p></td></tr>" +
+                        "<tr><td class ='even'><p>行政审核部门代码:<span>" + (arry[i].approveDeptCode == undefined ? '' : arry[i].approveDeptCode) + "</span></p></td><td><p>行政审核部门名称:<span>" + (arry[i].approveDeptName == undefined ? '' : arry[i].approveDeptName) + "</span></p></td></tr>" +
+                        /* "<tr><td class ='even'><p>所属行业:<span>医疗器械</span></p></td><td><p>所属行业:<span>医疗器械</span></p></td></tr>"+ */
+                        "</table>" +
+                        "<div class ='div_tenderProject'>" +
+                        "<h5>招标内容与范围及招标方案说明:</h5>";
+                    if (typeof (arry[i].bulletinContent) != "undefined" && arry[i].bulletinContent != null) {
+                        htmlA += "<span>" + arry[i].bulletinContent + "</span>";
+                    } else {
+                        htmlA += "<span>无</span>";
+                    }
+                    htmlA += "</div>" +
+                        "</div>" +
+                        "</div>" +
+                        "<div style='height:20px;'></div>" +
+                        "</div>";
+                }
+                htmlA += "</div>" +
+                    "</div>";
+            }
+            return htmlA
+        case "openBidRecord": //开标记录
+            var htmlA = "";
+            if (mapbFlag) {
+                var mapList = [];
+                mapList = mapb.openBidRecord;
+                var openBidRecord; //开标记录信息列表
+                var goodsOpenBidList = []; //开标记录货物信息列表
+                var serviceOpenBidList = []; //开标记录服务信息列表
+                var projectOpenBidList = []; //开标记录工程信息列表
+                if (mapList.length == 1) {
+                    openBidRecord = mapList[0].openBidRecord;
+                    goodsOpenBidList = mapList[0].goodsOpenBidList0;
+                    serviceOpenBidList = mapList[0].serviceOpenBidList0;
+                    projectOpenBidList = mapList[0].projectOpenBidList0;
+                    if (openBidRecord == null) {
+                        htmlA += "<div class='wBox'>";
+                        htmlA += "<div class='borderBlue'></div>";
+                        htmlA += "<div class='wrap_box'>";
+                        htmlA += "<div class='topTit3 fix' style='height: 40px;'>";
+                        htmlA += "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                        htmlA += "<div class='inner_con'>";
+                        htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                        htmlA += "暂无详细数据";
+                        htmlA += "</div>";
+                        htmlA += "<div style='height:20px;'></div>";
+                        htmlA += "</div></div></div>";
+                    } else {
+                        htmlA += "<div class='wBox'>";
+                        htmlA += "<div class='borderBlue'></div>"
+                        htmlA += "<div class='wrap_box'>"
+                        htmlA += "<div class='topTit3 fix' style='height: 40px;'>";
+                        htmlA += "<h4 style='color: #387fcc;'>开标记录</h4>  </div>";
+                        htmlA += "<div class='inner_con'><div class='openCloseWrap'><div class='title fix open'>";
+                        htmlA += "<h4 class='fleft'>" + openBidRecord.openBidRecordName + "</h4>";
+                        htmlA += "<div class='ocBtn fright'></div></div><div class='inner'>";
+
+                        // htmlA += "<div style='margin:0 auto 10px auto;text-align:center;font-size:14px;color:#333;font-family:微软雅黑;'>开标时间:";
+                        // if (typeof (openBidRecord.bidOpeningTime) != "undefined" && openBidRecord.bidOpeningTime != null) {
+                        //     openBidRecord.bidOpeningTime = timeZH(openBidRecord.bidOpeningTime);
+                        //     htmlA += openBidRecord.bidOpeningTime;
+                        // }
+                        // htmlA += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a target='_blank' href='";
+                        // if (typeof (openBidRecord.sourceUrl) == "undefined" || openBidRecord.sourceUrl == null || openBidRecord.sourceUrl == "") {
+                        //     htmlA += "#";
+                        // } else {
+                        //
+                        //     if (openBidRecord.sourceUrl.indexOf("http://") > -1) {
+                        //         htmlA += openBidRecord.sourceUrl;
+                        //     } else if (openBidRecord.sourceUrl.indexOf("https://") > -1) {
+                        //         htmlA += openBidRecord.sourceUrl;
+                        //     } else {
+                        //         htmlA += "http://" + openBidRecord.sourceUrl;
+                        //     }
+                        // }
+                        // htmlA += "'>原文链接地址</a></div>";
+
+                        htmlA += "<table class='table_1' border='1px' bordercolor='#999' >";
+                        if (goodsOpenBidList != null && goodsOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + goodsOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标价格</td><td>交货期</td></tr>";
+                            for (var i = 0; i < goodsOpenBidList.length; i++) {
+                                htmlA += "    <tr><td>";
+                                if (typeof (goodsOpenBidList[i].bidderName) != "undefined" && goodsOpenBidList[i].bidderName != null) {
+                                    htmlA += goodsOpenBidList[i].bidderName
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (goodsOpenBidList[i].bidAmount) != "undefined" && goodsOpenBidList[i].bidAmount != null) {
+                                    htmlA += goodsOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (goodsOpenBidList[i].verifyTime) != "undefined" && goodsOpenBidList[i].verifyTime != null) {
+                                    htmlA += goodsOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                        if (serviceOpenBidList != null && serviceOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + serviceOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标价格</td><td>交货期</td></tr>";
+                            for (var i = 0; i < serviceOpenBidList.length; i++) {
+                                htmlA += "<tr><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].bidderName != null) {
+                                    htmlA += serviceOpenBidList[i].bidderName;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].bidAmount != null) {
+                                    htmlA += serviceOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].verifyTime != null) {
+                                    htmlA += serviceOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                        if (projectOpenBidList != null && projectOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + projectOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标报价</td><td>交货期</td></tr>";
+                            for (var i = 0; i < projectOpenBidList.length; i++) {
+                                htmlA += "<tr><td>";
+                                if (typeof (projectOpenBidList[i].bidderName) != "undefined" && projectOpenBidList[i].bidderName != null) {
+                                    htmlA += projectOpenBidList[i].bidderName;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (projectOpenBidList[i].bidAmount) != "undefined" && projectOpenBidList[i].bidAmount != null) {
+                                    htmlA += projectOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (projectOpenBidList[i].verifyTime) != "undefined" && projectOpenBidList[i].verifyTime != null) {
+                                    htmlA += projectOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                        htmlA += "</table>";
+                        if (typeof (openBidRecord.attachmentCode) != "undefined" && openBidRecord.attachmentCode != null && openBidRecord.attachmentCode != "") {
+                            htmlA += "<div class='downloadbut'><span class='downloadbutton'>附件下载<span class='attachmentCode' style='display:none'>" + openBidRecord.attachmentCode + "</span><span class='openBidRecordName' style='display:none'>" + openBidRecord.openBidRecordName + "</span></span></div>";
+                        }
+                        htmlA += "</div></div><div style='height: 20px;'></div></div></div></div>";
+                    }
+                } else {
+                    openBidRecord = mapList[0].openBidRecord;
+                    if (openBidRecord == null) {
+                        htmlA += "<div class='wBox'>";
+                        htmlA += "<div class='borderBlue'></div>";
+                        htmlA += "<div class='wrap_box'>";
+                        htmlA += "<div class='topTit3 fix' style='height: 40px;'>";
+                        htmlA += "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                        htmlA += "<div class='inner_con'>";
+                        htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                        htmlA += "暂无详细数据";
+                        htmlA += "</div>";
+                        htmlA += "<div style='height:20px;'></div>";
+                        htmlA += "</div></div></div>";
+                    } else {
+                        htmlA += "<div class='wBox'>";
+                        htmlA += "<div class='borderBlue'></div>"
+                        htmlA += "<div class='wrap_box'>"
+                        htmlA += "<div class='topTit3 fix' style='height: 40px;'>";
+                        htmlA += "<h4 style='color: #387fcc;'>开标记录</h4>  </div>";
+                        htmlA += "<div class='inner_con'><div class='openCloseWrap'><div class='title fix open'>";
+                        htmlA += "<h4 class='fleft'>" + openBidRecord.openBidRecordName + "</h4>";
+                        htmlA += "<div class='ocBtn fright'></div></div><div class='inner'>";
+
+                        // htmlA += "<div style='margin:0 auto 10px auto;text-align:center;font-size:14px;color:#333;font-family:微软雅黑;'>开标时间:";
+                        // if (typeof (openBidRecord.bidOpeningTime) != "undefined" && openBidRecord.bidOpeningTime != null) {
+                        //     openBidRecord.bidOpeningTime = timeZH(openBidRecord.bidOpeningTime);
+                        //     htmlA += openBidRecord.bidOpeningTime;
+                        // }
+                        // htmlA += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a target='_blank' href='";
+                        // if (typeof (openBidRecord.sourceUrl) == "undefined" || openBidRecord.sourceUrl == null || openBidRecord.sourceUrl == "") {
+                        //     htmlA += "#";
+                        // } else {
+                        //
+                        //     if (openBidRecord.sourceUrl.indexOf("http://") > -1) {
+                        //         htmlA += openBidRecord.sourceUrl;
+                        //     } else if (openBidRecord.sourceUrl.indexOf("https://") > -1) {
+                        //         htmlA += openBidRecord.sourceUrl;
+                        //     } else {
+                        //         htmlA += "http://" + openBidRecord.sourceUrl;
+                        //     }
+                        // }
+                        // htmlA += "'>原文链接地址</a></div>";
+
+                        htmlA += "<table class='table_1' border='1px' bordercolor='#999' >";
+                    }
+                    var json = {
+                        arr: []
+                    }
+
+                    function modifyJosnKey(json, oddkey, newkey, key) {
+                        var val = json[oddkey];
+                        delete json[oddkey];
+                        json[newkey] = key;
+                    }
+
+                    for (var j = 0; j < mapList.length; j++) {
+                        modifyJosnKey(json, json.arr, j, mapList[j]);
+                        goodsOpenBidList = json[j]["goodsOpenBidList" + j];
+                        serviceOpenBidList = json[j]["serviceOpenBidList" + j];
+                        projectOpenBidList = json[j]["projectOpenBidList" + j];
+                        if (goodsOpenBidList != null && goodsOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + goodsOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标价格</td><td>交货期</td></tr>";
+                            for (var i = 0; i < goodsOpenBidList.length; i++) {
+                                htmlA += "    <tr><td>";
+                                if (typeof (goodsOpenBidList[i].bidderName) != "undefined" && goodsOpenBidList[i].bidderName != null) {
+                                    htmlA += goodsOpenBidList[i].bidderName
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (goodsOpenBidList[i].bidAmount) != "undefined" && goodsOpenBidList[i].bidAmount != null) {
+                                    htmlA += goodsOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (goodsOpenBidList[i].verifyTime) != "undefined" && goodsOpenBidList[i].verifyTime != null) {
+                                    htmlA += goodsOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                        if (serviceOpenBidList != null && serviceOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + serviceOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标价格</td><td>交货期</td></tr>";
+                            for (var i = 0; i < serviceOpenBidList.length; i++) {
+                                htmlA += "<tr><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].bidderName != null) {
+                                    htmlA += serviceOpenBidList[i].bidderName;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].bidAmount != null) {
+                                    htmlA += serviceOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (serviceOpenBidList[i].verifyTime) != "undefined" && serviceOpenBidList[i].verifyTime != null) {
+                                    htmlA += serviceOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                        if (projectOpenBidList != null && projectOpenBidList.length > 0) {
+                            htmlA += "<tr><td colspan='3' style='text-align:left;'>" + projectOpenBidList[0].bidSectionName + "</td></tr>";
+                            htmlA += "<tr><td>投标人名称</td><td>投标报价</td><td>交货期</td></tr>";
+                            for (var i = 0; i < projectOpenBidList.length; i++) {
+                                htmlA += "<tr><td>";
+                                if (typeof (projectOpenBidList[i].bidderName) != "undefined" && projectOpenBidList[i].bidderName != null) {
+                                    htmlA += projectOpenBidList[i].bidderName;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (projectOpenBidList[i].bidAmount) != "undefined" && projectOpenBidList[i].bidAmount != null) {
+                                    htmlA += projectOpenBidList[i].bidAmount;
+                                }
+                                htmlA += "</td><td>";
+                                if (typeof (projectOpenBidList[i].verifyTime) != "undefined" && projectOpenBidList[i].verifyTime != null) {
+                                    htmlA += projectOpenBidList[i].verifyTime;
+                                }
+                                htmlA += "</td></tr>";
+                            }
+                        }
+                    }
+                    htmlA += "</table>";
+                    if (typeof (openBidRecord.attachmentCode) != "undefined" && openBidRecord.attachmentCode != null && openBidRecord.attachmentCode != "") {
+                        htmlA += "<div class='downloadbut'><span class='downloadbutton'>附件下载<span class='attachmentCode' style='display:none'>" + openBidRecord.attachmentCode + "</span><span class='openBidRecordName' style='display:none'>" + openBidRecord.openBidRecordName + "</span></span></div>";
+                    }
+                    htmlA += "</div></div><div style='height: 20px;'></div></div></div></div>";
+                }
+            } else {
+                htmlA += "<div class='wBox'>";
+                htmlA += "<div class='borderBlue'></div>";
+                htmlA += "<div class='wrap_box'>";
+                htmlA += "<div class='topTit3 fix' style='height: 60px;'>";
+                htmlA += "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                htmlA += "<div class='inner_con'>";
+                htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                htmlA += "暂无详细数据";
+                htmlA += "</div>";
+                htmlA += "<div style='height:20px;'></div>";
+                htmlA += "</div></div></div>";
+            }
+            return htmlA
+        default:
+            var htmlA = "";
+            if (mapbFlag) {
+                var mapList = [];
+                var keyCN = [];
+                for (var i = 0; i < keyList.length; i++) {
+                    switch (keyList[i]) {
+                        case "tenderBulletin": {
+                            mapList[i] = mapb.tenderBulletin;
+                            keyCN[i] = "招标公告";
+                            break;
+                        }
+                        case "qualifyBulletin": {
+                            mapList[i] = mapb.qualifyBulletin;
+                            keyCN[i] = "资格预审公告";
+                            break;
+                        }
+                        case "amendBulletin": {
+                            mapList[i] = mapb.amendBulletin;
+                            keyCN[i] = "变更公告";
+                            break;
+                        }
+                        case "openBidRecord": {
+                            mapList[i] = mapb.openBidRecord;
+                            keyCN[i] = "开标记录";
+                            break;
+                        }
+                        case "goodsOpenBidList": {
+                            mapList[i] = mapb.goodsOpenBidList;
+                            keyCN[i] = "开标明细货物";
+                            break;
+                        }
+                        case "serviceOpenBidList": {
+                            mapList[i] = mapb.serviceOpenBidList;
+                            keyCN[i] = "开标明细服务";
+                            break;
+                        }
+                        case "projectOpenBidList": {
+                            mapList[i] = mapb.projectOpenBidList;
+                            keyCN[i] = "开标明细工程";
+                            break;
+                        }
+                        case "winCandidateBulletin": {
+                            mapList[i] = mapb.winCandidateBulletin;
+                            keyCN[i] = "中标候选人公示";
+                            break;
+                        }
+                        case "winBidBulletin": {
+                            mapList[i] = mapb.winBidBulletin;
+                            keyCN[i] = "中标结果公告";
+                            break;
+                        }
+                        case "tenderProject": {
+                            mapList[i] = mapb.tenderProject;
+                            keyCN[i] = "招标项目";
+                            break;
+                        }
+                    }
+                }
+                var htmlNotNullFlag = false; //接收到的所有关键字中list
+                for (var i = 0; i < mapList.length; i++) {
+                    var ArrayA = [];
+                    ArrayA = mapList[i];
+                    if (ArrayA.length != 0) {
+                        htmlNotNullFlag = true;
+                    }
+                }
+                if (htmlNotNullFlag) {
+                    for (var i = 0; i < mapList.length; i++) {
+                        var ArrayA = [];
+                        ArrayA = mapList[i];
+                        if (ArrayA.length > 0) {
+                            htmlA += "<div class='wBox'>";
+                            htmlA += "<div class='borderBlue'></div>";
+                            htmlA += "<div class='wrap_box'>";
+                            htmlA += "<div class='topTit3 fix'>";
+                            htmlA += "<h4 style='color: #387fcc;'>" + keyCN[i] + "</h4></div>";
+                            htmlA += "<div class='inner_con'>";
+                            for (var j = 0; j < ArrayA.length; j++) {
+                                if (j > 0) {
+                                    // 防止跟标题无关数据展示
+                                    break
+                                }
+
+                                htmlA += "<div class='openCloseWrap'>";
+                                if (j == 0) {
+                                    htmlA += "<div class='title fix open'>";
+                                } else {
+                                    htmlA += "<div class='title fix closed'>";
+                                }
+                                htmlA += " <h4 class='fleft'>";
+                                if (typeof (ArrayA[j].bulletinName) != "undefined" && ArrayA[j].bulletinName != null && ArrayA[j].bulletinName != "") {
+                                    htmlA += ArrayA[j].bulletinName;
+                                }
+                                htmlA += "</h4>";
+                                htmlA += "<div class='ocBtn fright'></div>";
+                                htmlA += "</div>";
+                                if (j == 0) {
+                                    htmlA += "<div class='inner' style='width:668px;overflow-x:auto;font-size:14px;color:#333;font-family:微软雅黑;line-height:2;'>";
+                                } else {
+                                    htmlA += "<div class='inner display_none' style='width:668px;overflow-x:auto;font-size:14px;color:#333;font-family:微软雅黑;line-height:2;'>";
+                                }
+
+                                // htmlA += "<div style='margin:0 auto 10px auto;text-align:center;font-size:14px;color:#333;font-family:微软雅黑;'>发布日期:";
+                                // if (typeof (ArrayA[j].bulletinssueTime) != "undefined" && ArrayA[j].bulletinssueTime != null && ArrayA[j].bulletinssueTime != "") {
+                                //     ArrayA[j].bulletinssueTime = timeZH(ArrayA[j].bulletinssueTime);
+                                //     htmlA += ArrayA[j].bulletinssueTime;
+                                // }
+                                // htmlA += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a target='_blank' href='";
+                                // if (typeof (ArrayA[j].sourceUrl) != "undefined" && ArrayA[j].sourceUrl != null && ArrayA[j].sourceUrl != "") {
+                                //     htmlA += ArrayA[j].sourceUrl;
+                                //     if (ArrayA[j].sourceUrl.indexOf("http://") > -1) {
+                                //         htmlA += ArrayA[j].sourceUrl;
+                                //     } else if (ArrayA[j].sourceUrl.indexOf("https://") > -1) {
+                                //         htmlA += ArrayA[j].sourceUrl;
+                                //     } else {
+                                //         htmlA += "http://" + ArrayA[j].sourceUrl;
+                                //     }
+                                // } else {
+                                //     htmlA += "#";
+                                // }
+                                // htmlA += "' >原文链接地址</a></div>";
+
+                                if (typeof (ArrayA[j].bulletinContent) != "undefined" && ArrayA[j].bulletinContent != null) {
+                                    htmlA += ArrayA[j].bulletinContent.replace("<![CDATA[", "").replace("]]>", "");
+                                }
+                                if (typeof (ArrayA[j].attachmentCode) != "undefined" && ArrayA[j].attachmentCode != null && ArrayA[j].attachmentCode != "") {
+                                    htmlA += "<div class='downloadbut'><span class='downloadbutton'>附件下载<span class='attachmentCode' style='display:none'>" + ArrayA[j].attachmentCode + "</span><span class='openBidRecordName' style='display:none'>" + ArrayA[j].bulletinName + "</span></span></div>";
+                                }
+                                htmlA += "</div></div>";
+                                htmlA += "<div style='height:20px;'></div>";
+                            }
+                            htmlA += "</div>";
+                            htmlA += "</div></div></div>";
+                        }
+                    }
+                } else {
+                    htmlA += "<div class='wBox'>";
+                    htmlA += "<div class='borderBlue'></div>";
+                    htmlA += "<div class='wrap_box'>";
+                    htmlA += "<div class='topTit3 fix' style='height: 60px;'>";
+                    htmlA += "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                    htmlA += "<div class='inner_con'>";
+                    htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                    htmlA += "暂无详细数据";
+                    htmlA += "</div>";
+                    htmlA += "<div style='height:20px;'></div>";
+                    htmlA += "</div></div></div>";
+                }
+            } else {
+                htmlA += "<div class='wBox'>";
+                htmlA += "<div class='borderBlue'></div>";
+                htmlA += "<div class='wrap_box'>";
+                htmlA += "<div class='topTit3 fix' style='height: 60px;'>";
+                htmlA += "<h4 style='color: #387fcc;'>" + dataNullNameCN + "</h4></div>";
+                htmlA += "<div class='inner_con'>";
+                htmlA += "<div class='openCloseWrap' style='heigth:40px; font-size:16px;'>";
+                htmlA += "暂无详细数据";
+                htmlA += "</div>";
+                htmlA += "<div style='height:20px;'></div>";
+                htmlA += "</div></div></div>";
+            }
+            return htmlA
+    }
+}

+ 470 - 0
lzz_theme/zgzbtbggfwpt_wagf/spider_detail.py

@@ -0,0 +1,470 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-07
+---------
+@summary:  中国招标投标公共服务平台 - 详情页[未按规范]
+
+"""
+import datetime
+import html as htmlparser
+import io
+import sys
+import time
+from concurrent.futures import ThreadPoolExecutor, wait
+from enum import Enum
+from functools import partial
+from pathlib import Path
+
+import execjs
+import requests
+from lxml import etree
+from lxml.html import HtmlElement
+from lxml.html import fromstring
+from requests.models import Response
+
+import utils.tools as tools
+from des import strEnc
+from tools import get_json, repair_json, get_info
+from tools import rsub
+from utils.clean_html import cleaner
+from utils.log import logger
+from utils.tools import Mongo_client
+from utils.tools import get_QGIP, get_proxy
+
+MEGABYTES = 1024 * 1024  # 1M(兆字节),单位:M
+
+
+def show_html(business_name, response):
+    with open('./showDetails.js', encoding='utf-8') as rp:
+        js_script = rp.read()
+        ctx = execjs.compile(js_script)
+        return ctx.call('getHtml', business_name, response)
+
+
+def modify_node(element):
+    temp_element = etree.Element('html')
+    head = etree.Element('head')
+    body = etree.Element('body')
+    for node in list(element):
+        if node.tag == 'meta':
+            head.append(node)
+
+        elif node.tag == 'title':
+            for sub_node in list(node):
+                if sub_node.tag not in ['meta', 'style', 'script', 'link']:
+                    body.append(sub_node)
+        else:
+            body.append(node)
+
+    temp_element.append(head)
+    temp_element.append(body)
+    element.append(temp_element)
+
+
+def remove_node(element_or_html):
+    if isinstance(element_or_html, HtmlElement):
+        element = element_or_html
+    else:
+        element = fromstring(element_or_html)
+
+    title = element.xpath('//title[contains(string(), "纸张大小")]')
+    fj_nodes = element.xpath('//div[@title="附件信息"]/..')  # 附件信息
+    pt_nodes = element.xpath("|".join([
+        '//div[@title="平台信息"]/..',
+        '//div[@title="来源交易平台信息"]/..'
+    ]))  # 平台信息
+    remove_nodes = [*fj_nodes, *pt_nodes, *title]
+    for node in remove_nodes:
+        parent = node.getparent()
+        if parent is not None:
+            parent.remove(node)
+
+
+def handle_html(html, default=None):
+
+    def element2html(elem):
+        remove_node(elem)
+        for node in elem.xpath('//div[@role="accordion"]'):
+            # 这里仅匹配第一个 div 元素内容
+            _html = etree.tostring(node, encoding='utf-8', method='html').decode()
+            return execute('renderHtml', _html)
+
+        _html = etree.tostring(elem, encoding='utf-8', method='html').decode()
+        return execute('renderHtml', _html)
+
+    with open('./render.js') as fp:
+        js = fp.read()
+        ctx = execjs.compile(js)
+        execute = ctx.call
+        ret = execute('renderHtml', html)
+        ret = htmlparser.unescape(ret)  # 特殊符号解码
+
+        root = fromstring(ret)
+        for tag in root.xpath('//span[contains(@title, "!important")]'):
+            modify_node(tag)  # 修改部分节点顺序
+            html = element2html(root)
+            return html
+
+        if html is not None:
+            root = fromstring(html)
+            html = element2html(root)
+            return html
+
+        return default
+
+
+def check_text(html, with_tail=False):
+    elem = fromstring(html)
+
+    count = 0
+    nodes = elem.xpath('//div[@class="form-row"][1]/div')
+    for tag in nodes:
+        if 'label' in tag.attrib:
+            child = next(iter(tag.getchildren()), None)
+            if child is not None and child.tag == 'span':
+                itertext = child.itertext(with_tail=with_tail)
+                text = ''.join(''.join(itertext).split())
+                if len(text) > 0:
+                    count += 1
+
+    assert count > 0
+
+
+class DataStreamReadStatus(Enum):
+    """数据流读取状态"""
+    NORMAL = 2  # 数据正常接收
+    NULL = 3  # 暂无详情数据
+    NOMATCH = 4  # 没有符合的数据
+    EMPTY = 5  # 非结构化数据内容为空
+    LOSE = 10086  # 文件内容不全
+
+
+class Spider:
+
+    def __init__(self, sizes=100, threads=1, interval=0.5):
+        self.db_table = Mongo_client()['py_spider']
+        self.theme_list = self.db_table['theme_list']
+        self.data_bak = self.db_table['data_bak']
+
+        self._data_trans_limit = 1  # 数据传输内容接收上限,单位:M, 建议不要超过3M
+
+        self._max_retries = 3
+        self._interval = interval  # 请求延时间隔
+        self._sizes = sizes  # 任务条数
+
+        thread_name = Path(sys.argv[0]).name.replace('.py', '')
+        self._executor = ThreadPoolExecutor(max_workers=threads,
+                                            thread_name_prefix=thread_name)
+        self._executor_wait = wait
+        self._fs = []
+
+    def add_task(self, fn, *args, **kwargs):
+        self._fs.append(self._executor.submit(fn, *args, **kwargs))
+
+    def wait(self):
+        self._executor_wait(self._fs)
+        self._fs = []
+
+    def shutdown_spider(self):
+        self._executor.shutdown(wait=True)
+
+    def unpack_large_content(self, response):
+        if self._data_trans_limit <= 3:
+            # 数据的内容越大(3M以上)首次解码耗时越长,且解码时会将无法识别的字符转换成替换字符
+            text = response.text
+        else:
+            text = response.content.decode(errors='ignore')
+        return text
+
+    def unpack_large_json(self, response):
+        if self._data_trans_limit <= 3:
+            resp_json = response.json()
+        else:
+            body = response.content.decode(errors='ignore')
+            resp_json = get_json(body)
+        return resp_json
+
+    def extract_html(self, business_keyword, response):
+        content_length_limit = self._data_trans_limit * MEGABYTES
+        upper_limit = response.headers['content-length'] > content_length_limit
+        if not upper_limit:
+            # 情况2.1:结构化数据,直接提取数据
+            resp_json = self.unpack_large_json(response)
+            try:
+                data_lst = resp_json['object'][business_keyword]
+                if isinstance(data_lst, list) and len(data_lst) == 0:
+                    # 暂无详情的数据,调用js拼接时因json中不存在关键的分类名称而导致TypeError错误;
+                    # 置空分类名称不影响js拼接,js脚本会自动处理内容分类
+                    business_keyword = ''
+            except Exception:
+                # 该项目发生变更会导致分类名称发生变更或者返回没有符合的数据
+                pass
+        else:
+            # 情况2.2:非结构化数据
+            if business_keyword == 'openBidRecord':
+                return None, DataStreamReadStatus.LOSE
+
+            html = self.unpack_large_content(response)
+            # 模糊查询结果,返回的数据内容是按照时间降序排列
+            content = get_info(html, '\"object\":({.*?}),', fetch_one=True)
+            content = ":".join(content.split(':')[1:])[1:]  # [{...} -> {...}
+            if not content:
+                return None, DataStreamReadStatus.NULL
+            elif not content.endswith('}'):
+                # raise EOFError('content 不是以"}"结尾,文件内容不全,丢弃')
+                return None, DataStreamReadStatus.LOSE
+            else:
+                ret = repair_json(content)
+                resp_json = {
+                    "message": "",
+                    "success": True,
+                    "object": {business_keyword: [ret]}
+                }
+
+        html = show_html(business_keyword, resp_json)
+        return html, DataStreamReadStatus.NORMAL
+
+    def __recv_data(self, url, response):
+        """
+            接收响应体,并设置响应体大小["content-length"]。若超过数据上限则熔断接收流程
+        """
+        ibytes = io.BytesIO()
+
+        content_length = 0  # 单位:字节
+        limit = self._data_trans_limit * MEGABYTES  # 接收数据的大小,单位:M
+        for r in response.iter_content(chunk_size=MEGABYTES):  # chunk_size 单位:字节
+            n = ibytes.write(r)
+            content_length += n
+            if content_length >= limit:
+                # 接收的数据内容超过上限时,影响后续流程处理,因此添加此熔断条件
+                break
+
+        resp = Response()
+        resp.__dict__.update({
+            "url": url,
+            "cookies": response.cookies,
+            "_content": ibytes.getvalue(),
+            "status_code": response.status_code,
+            "elapsed": response.elapsed,
+            "headers": {
+                **response.headers,
+                "content-length": content_length
+            },
+            "_content_consumed": True,
+        })
+        return resp
+
+    def download(self, method, url, headers, proxies=None, **kwargs):
+        time.sleep(self._interval)
+        response = requests.request(method,
+                                    url,
+                                    headers=headers,
+                                    proxies=proxies,
+                                    timeout=kwargs.pop('timeout', 60),
+                                    **kwargs)
+        response.raise_for_status()
+        return response
+
+    def fetch(self, item, request_params, max_retries=None):
+        max_retries = max_retries or self._max_retries
+        proxies = get_QGIP()
+        title = item['title']
+
+        schemaVersion = request_params['schemaVersion']
+        businessKeyWord = request_params['businessKeyWord']
+        tenderProjectCode = request_params['tenderProjectCode']
+        businessId = request_params['businessId']
+
+        url = 'http://www.cebpubservice.com/ctpsp_iiss/SecondaryAction/findDetails.do'
+        headers = {
+            'Accept': 'application/json, text/javascript, */*; q=0.01',
+            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
+            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+            'Origin': 'http://www.cebpubservice.com',
+            'Referer': 'http://www.cebpubservice.com/ctpsp_iiss/searchbusinesstypebeforedooraction/showDetails.do',
+            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
+            'X-Requested-With': 'XMLHttpRequest'
+        }
+        _send = partial(self.download, 'post', url, headers)
+
+        key2 = 'cebpubservice'
+        key3 = 'iiss'
+        for i in range(max_retries):
+            key1 = datetime.datetime.now().strftime('%Y-%m-%d %H')
+            data = {
+                'schemaVersion': schemaVersion,
+                'businessKeyWord': businessKeyWord,
+                'tenderProjectCode': strEnc(tenderProjectCode, key1, key2, key3),
+                'businessObjectName': title,
+                'businessId': strEnc(businessId, key1, key2, key3),
+            }
+            try:
+                response = _send(stream=True, data=data, proxies=proxies, verify=False)
+                response = self.__recv_data(url, response)
+                return response
+            except IndexError as e:
+                logger.error(f'网络请求|{title}|{type(e).__name__}|重试..{i + 1}')
+
+    def parse(self, response, item, request_params):
+        tenderProjectCode = request_params['tenderProjectCode']
+        businessId = request_params['businessId']
+
+        uuid = businessId + tenderProjectCode
+        href = f'http://www.cebpubservice.com/ctpsp_iiss/searchbusinesstypebeforedooraction/showDetails.do#uuid={uuid}'
+        item['href'] = href
+
+        business_keyword = request_params['businessKeyWord']
+        contenthtml, state = self.extract_html(business_keyword, response)
+        # 删除页面中的图片
+        contenthtml = rsub('data:image/(.*?)["|\']', '', contenthtml) if contenthtml else ''
+        # 清洗源码
+        special = {
+            '<\!\[cdata[^>]*>|<?cdata [^>]*>': '',
+            '</body[^>]*>|]]>': '',
+        }
+        detail = cleaner(contenthtml, special=special)
+        # 检查汉字数量
+        item['sendflag'] = 'false' if tools.text_search(detail).total >= 20 else 'true'
+        item['contenthtml'] = contenthtml
+        item['detail'] = detail
+        tools.clean_fields(item)  # 入库字段清理
+        item['comeintime'] = tools.ensure_int64(tools.get_current_timestamp())
+        self.data_bak.insert_one(item)
+        logger.info(f"采集成功|{item['title']}")
+
+    def splash(self, item, timeout=3, max_retries=None):
+        max_retries = max_retries or self._max_retries
+
+        # splash 教程 https://splash-cn-doc.readthedocs.io/zh_CN/latest/scripting-tutorial.html
+        url = 'http://splash.spdata.jianyu360.com/render.json'
+        headers = {
+            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
+        }
+        _send = partial(self.download, 'get', url, headers)
+
+        title = item['title']
+        href = item['href']
+        for i in range(max_retries):
+            try:
+                proxies = get_proxy()
+                proxy = proxies.get("http", "").strip("http://")
+                params = {
+                    'iframes': 1,
+                    'html': 1,
+                    'wait': timeout,  # 等待时长
+                    'proxy': proxy,
+                    'url': href,
+                }
+                return _send(params=params)
+            except IOError as e:
+                logger.error(f'网络请求|{title}|{type(e).__name__}|重试..{i + 1}')
+
+    def parse_html(self, response, item):
+        resp_json = response.json()
+        res_html = resp_json.get("html") or ''
+
+        child_frames = resp_json.get("childFrames") or []
+        for frame in child_frames:
+            res_html += frame.get("html")
+
+        element = fromstring(res_html)
+        if element is None:
+            return
+
+        html = ''
+        htmls = element.xpath("|".join([
+            '//div[@class="fui-accordions"]',
+            '//div[@class="mini-panel-viewport mini-grid-viewport"]'
+        ]))
+        if len(htmls) > 0:
+            html = '\n'.join([etree.tounicode(html) for html in htmls])
+
+        # 处理源码
+        html = handle_html(html, '')
+        # 清洗图片
+        html = rsub('data:image/(.*?)["|\']', '', html)
+        # 清洗源码
+        detail = cleaner(html)
+        if tools.text_search(detail).total == 0:
+            # 无正文内容时,该内容直接标记true, 不在被统计
+            item["sendflag"] = "true"
+
+        # 检查文本内容是否有效(可能未渲染成功,会导致页面数据未加载)
+        check_text(html)
+
+        item["contenthtml"] = html
+        item["detail"] = detail
+        tools.clean_fields(item)
+        item['comeintime'] = tools.ensure_int64(tools.get_current_timestamp())
+        self.data_bak.insert_one(item)
+        logger.info(f'splash|采集成功|{item["title"]}')
+
+    def deal_request(self, item, max_retries=3):
+        request_params = item.pop('request_params')
+        for i in range(max_retries):
+            try:
+                if item['classify'] == '0':
+                    response = self.fetch(item, request_params)
+                    if response is None:
+                        return False
+
+                    content_type = response.headers.get('Content-Type', '')
+                    if 'text/html' in content_type:
+                        # with open('index.html', 'w', encoding='utf-8') as f:
+                        #     f.write(response.content.decode())
+                        return False
+
+                    self.parse(response, item, request_params)
+                else:
+                    response = self.splash(item, timeout=10, max_retries=5)
+                    if response is None:
+                        return False
+
+                    self.parse_html(response, item)
+                return True
+            except Exception as e:
+                logger.error(f"采集失败|{item['title']}|{type(e).__name__}|重试..{i + 1}")
+
+    def spider(self, task):
+        _id = task['_id']
+        retry = task['retry']
+
+        ret = self.deal_request(task, max_retries=3)
+        if ret is True:
+            update = {'is_crawl': True, 'failed': False}
+        else:
+            retry += 1
+            update = {'failed': True, 'retry': retry}
+
+        self.theme_list.update_one({'_id': _id}, {'$set': update})
+
+    def get_tasks(self, sizes, show_debug=False):
+        results = []
+        query = {
+            'parser_name': 'ztpc_zgzbtbggfwpt_wagf',
+            'is_crawl': False,
+            'failed': False,
+        }
+        with self.theme_list.find(query, limit=sizes) as cursor:
+            for item in cursor:
+                if show_debug:
+                    logger.debug(item)
+
+                results.append(item)
+
+        yield from results
+
+    def start(self):
+        logger.debug("********** 任务开始 **********")
+        try:
+            for task in self.get_tasks(self._sizes):
+                self.add_task(self.spider, task)
+
+            self.wait()
+        finally:
+            self.shutdown_spider()
+            logger.debug("********** 任务结束 **********")
+
+
+if __name__ == '__main__':
+    Spider(sizes=100, threads=2).start()

+ 284 - 0
lzz_theme/zgzbtbggfwpt_wagf/spider_list.py

@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-04-30 
+---------
+@summary:  中国招标投标公共服务平台 - 列表页[未按规范]
+"""
+import html
+import math
+import re
+import sys
+import time
+from collections import namedtuple
+from concurrent.futures import ThreadPoolExecutor, wait
+from datetime import datetime, timedelta
+from pathlib import Path
+
+import requests
+
+import utils.tools as tools
+from utils.RedisDB import RedisFilter
+from utils.log import logger
+from utils.tools import Mongo_client
+from utils.tools import get_QGIP
+
+
+class Spider:
+
+    def __init__(self, menus, page_sizes=15, threads=1, interval=0.4, max_retries=3, date=None):
+        self.theme_list = Mongo_client()['py_spider']['theme_list']
+        self.RDS = RedisFilter()
+
+        self.menus = menus
+        self.kwargs = {'date': date}
+        self._page_sizes = page_sizes
+
+        self._max_retries = max_retries
+        thread_name = Path(sys.argv[0]).name.replace('.py', '')
+        self._executor = ThreadPoolExecutor(max_workers=threads,
+                                            thread_name_prefix=thread_name)
+        self._interval = interval  # 请求延时间隔
+        self._executor_wait = wait
+        self._fs = []
+
+    def add_task(self, fn, *args, **kwargs):
+        self._fs.append(self._executor.submit(fn, *args, **kwargs))
+
+    def wait(self):
+        self._executor_wait(self._fs)
+        self._fs = []
+
+    def shutdown_spider(self):
+        self._executor.shutdown(wait=True)
+
+    @staticmethod
+    def extract_address(region):
+        city = ''
+        if not region:
+            area = '全国'
+        else:
+            args = region.split(' ')
+            if len(args) == 2:
+                area, city = args
+            elif len(args) == 1:
+                area = args
+            else:
+                area, city, *_ = args
+
+        area = area[0] if isinstance(area, list) else area
+        area = re.sub('省|市', '', area.strip())
+        city = city.strip()
+        return area, city
+
+    def download(self, menu, page, date=None, proxies=None):
+        url = 'http://www.cebpubservice.com/ctpsp_iiss/searchbusinesstypebeforedooraction/getStringMethod.do'
+        headers = {
+            'Accept': 'application/json, text/javascript, */*; q=0.01',
+            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
+            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+            'Origin': 'http://www.cebpubservice.com',
+            'Referer': 'http://www.cebpubservice.com/ctpsp_iiss/searchbusinesstypebeforedooraction/getSearch.do',
+            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
+            'X-Requested-With': 'XMLHttpRequest',
+        }
+
+        data = {
+            'searchName': '',
+            'searchArea': '',
+            'searchIndustry': '',
+            'centerPlat': '',
+            'businessType': menu.category,
+            'searchTimeStart': '',
+            'searchTimeStop': '',
+            'timeTypeParam': '',
+            'bulletinIssnTime': '',
+            'bulletinIssnTimeStart': '',
+            'bulletinIssnTimeStop': '',
+            'pageNo': page,
+            'row': self._page_sizes
+        }
+        if date is not None:
+            start, stop = date
+            stop = (datetime.strptime(stop, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d')
+            if menu.category in ['开标记录', '评标公示', '中标公告']:
+                data['searchTimeStart'] = start
+                data['searchTimeStop'] = stop
+            else:
+                data['bulletinIssnTimeStart'] = start
+                data['bulletinIssnTimeStop'] = stop
+
+        time.sleep(self._interval)  # 增加并发延迟时间
+        response = requests.post(
+            url,
+            headers=headers,
+            data=data,
+            proxies=proxies,
+            timeout=20,
+            verify=False
+        )
+        response.raise_for_status()
+        return response
+
+    def fetch_total_count(self, menu, date=None, max_retries=None):
+        max_retries = max_retries or self._max_retries
+        for i in range(max_retries):
+            try:
+                response = self.fetch_list(menu, 1, date, max_retries=1, show_log=False)
+                if response is not None:
+                    json_data = response.json()
+                    total = json_data['object']['page']['totalCount']
+                    logger.info(f"查询|{menu.channel}|数据总量|{total}")
+                    return (
+                        total,
+                        json_data['object']['page']['totalPage'],
+                        json_data['object']['page']['pageNo'],
+                        json_data['object']['page']['rowNo'],
+                    )
+            except Exception as e:
+                logger.error(f"网络请求|{menu.channel}|{type(e).__name__}|重试..{i + 1}")
+        else:
+            return 0, 0, 0, 0
+
+    def fetch_list(self, menu, page, date=None, show_log=True, max_retries=None):
+        max_retries = max_retries or self._max_retries
+        for i in range(max_retries):
+            # proxies = get_proxy()
+            proxies = get_QGIP()
+            try:
+                return self.download(menu, page, date, proxies)
+            except IOError as e:
+                if show_log:
+                    logger.error(f"网络请求|{menu.channel}|第{page}页|{type(e).__name__}|重试..{i + 1}")
+
+    def parse(self, data_items, menu, page):
+        count = 0
+        for item in data_items:
+            title = item.get("businessObjectName")
+            if not title or title == 'null':
+                continue
+
+            # 详情请求参数
+            request_params = {
+                # "businessObjectName": title,
+                "schemaVersion": item["schemaVersion"],
+                "businessKeyWord": menu.businessKeyWord,
+                "tenderProjectCode": item["tenderProjectCode"],
+                "businessId": item["businessId"],
+                "platformName": html.unescape(item["transactionPlatfName"]),
+                "platformCode": item["transactionPlatfCode"],
+            }
+            _type = item.get("type", "0")
+            if _type == "1":
+                row_guid = item.get("rowGuid")
+                href = f"http://connect.cebpubservice.com/PSPFrame/infobasemis/socialpublic/publicyewu/Frame_yewuDetail?rowguid={row_guid}"
+            else:
+                href = "&&".join([request_params['businessKeyWord'],
+                                  request_params['platformCode'],
+                                  request_params['businessId'],
+                                  request_params['tenderProjectCode']])
+
+            dedup = [href]
+            if not self.RDS.data_filter(dedup):
+                receiveTime = item['receiveTime']
+                time_str = receiveTime if receiveTime not in ['1970-01-01', 'null'] else tools.get_current_date('%Y-%m-%d')
+                timestamp = tools.date_to_timestamp(time_str, '%Y-%m-%d')
+
+                region = item.get('regionName') or ''
+                area, city = self.extract_address(region)
+
+                # 详情采集关键参数
+                task_kwargs = {
+                    "parser_name": "ztpc_zgzbtbggfwpt_wagf",
+                    "is_crawl": False,
+                    "failed": False,
+                    "retry": 0,
+                }
+
+                data = {
+                    "title": title,
+                    "href": href,
+                    "site": "中国招标投标公共服务平台",
+                    "channel": menu.channel,
+                    "spidercode": menu.code,
+                    "area": area,
+                    "city": city,
+                    "district": "",
+                    "comeintime": tools.ensure_int64(tools.get_current_timestamp()),
+                    "publishtime": time_str,
+                    "l_np_publishtime": tools.ensure_int64(timestamp),
+                    "is_mixed": False,
+                    "is_theme": True,
+                    "T": "bidding",
+                    "iscompete": True,
+                    "_d": "comeintime",
+                    "publishdept": "",
+                    "infoformat": 1,
+                    "type": "",
+                    "classify": _type,  # 源数据分类 '0'|'1'
+                    "request_params": request_params,
+                    **task_kwargs,
+                }
+                self.theme_list.insert_one(data)
+                self.RDS.data_save_redis(dedup)
+                count += 1
+
+        logger.info(f'采集成功|{menu.channel}|第{page}页|发布{len(data_items)}条|入库{count}条')
+
+    def _spider(self, menu, page, date=None):
+        try:
+            response = self.fetch_list(menu, page, date)
+            if response is None:
+                return False
+
+            content_type = response.headers.get("Content-Type", "")
+            if "text/html" in content_type:
+                # with open('index.html', 'w', encoding='utf-8') as f:
+                #     f.write(response.content.decode())
+                return False
+
+            json_data = response.json()
+            lst_items = json_data["object"]["returnlist"]
+            self.parse(lst_items, menu, page)
+            return True
+        except requests.exceptions.JSONDecodeError:
+            logger.warning(f"代理ip被禁|{menu.category}|第{page}页")
+        except Exception as why:
+            logger.error(f"采集失败|{menu.category}|第{page}页|原因|{type(why).__name__}")
+
+    def start(self):
+        logger.debug("********** 任务开始 **********")
+        date = self.kwargs['date']
+
+        try:
+            for menu in self.menus:
+                auto_paginate = getattr(menu, 'auto_paginate', False)
+                if not auto_paginate:
+                    max_page = menu.crawl_page
+                else:
+                    total_count, *_ = self.fetch_total_count(menu, date)
+                    max_page = math.ceil(total_count / self._page_sizes)  # 向上取整
+
+                max_page = max(max_page, 1)
+                for page in range(1, max_page + 1):
+                    self.add_task(self._spider, menu, page, date=date)
+
+            self.wait()
+        finally:
+            self.shutdown_spider()
+            logger.debug("********** 任务结束 **********")
+
+
+if __name__ == '__main__':
+    Menu = namedtuple(
+        'Menu',
+        ['channel', 'code', 'category', 'businessKeyWord', 'crawl_page']
+    )
+
+    target_menus = [
+        Menu('未按数据规范-招标公告', 'a_zgzbtbggfwpt_wasjgf_zbgg', '招标公告', 'tenderBulletin', 1),
+        Menu('未按数据规范-开标记录', 'a_zgzbtbggfwpt_wasjgf_kbjl', '开标记录', 'openBidRecord', 1),
+        Menu('未按数据规范-评标公示', 'a_zgzbtbggfwpt_wasjgf_pbgs', '评标公示', 'winCandidateBulletin', 1),
+        Menu('未按数据规范-中标公告', 'a_zgzbtbggfwpt_wasjgf_zhbgg', '中标公告', 'winBidBulletin', 1),
+    ]
+
+    Spider(target_menus, date=('2025-05-06', '2025-05-06'), threads=1).start()

+ 141 - 0
lzz_theme/zgzbtbggfwpt_wagf/tools.py

@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-07 
+---------
+@summary:  
+---------
+@author: Dzr
+"""
+import ast
+import json
+import re
+
+from utils.log import logger
+
+_regexs = {}
+
+
+def get_info(html, regexs, allow_repeat=True, fetch_one=False, split=None):
+    regexs = isinstance(regexs, str) and [regexs] or regexs
+
+    infos = []
+    for regex in regexs:
+        if regex == "":
+            continue
+
+        if regex not in _regexs.keys():
+            _regexs[regex] = re.compile(regex, re.S)
+
+        if fetch_one:
+            infos = _regexs[regex].search(html)
+            if infos:
+                infos = infos.groups()
+            else:
+                continue
+        else:
+            infos = _regexs[regex].findall(str(html))
+
+        if len(infos) > 0:
+            # print(regex)
+            break
+
+    if fetch_one:
+        infos = infos if infos else ("",)
+        return infos if len(infos) > 1 else infos[0]
+    else:
+        infos = allow_repeat and infos or sorted(set(infos), key=infos.index)
+        infos = split.join(infos) if split else infos
+        return infos
+
+
+def get_json(json_str):
+    """
+    @summary: 取json对象
+    ---------
+    @param json_str: json格式的字符串
+    ---------
+    @result: 返回json对象
+    """
+
+    try:
+        return json.loads(json_str) if json_str else {}
+    except Exception as e1:
+        try:
+            json_str = json_str.strip()
+            json_str = json_str.replace("'", '"')
+            keys = get_info(json_str, "(\w+):")
+            for key in keys:
+                json_str = json_str.replace(key, '"%s"' % key)
+
+            return json.loads(json_str) if json_str else {}
+
+        except Exception as e2:
+            logger.error(
+                """
+                e1: %s
+                format json_str: %s
+                e2: %s
+                """
+                % (e1, json_str, e2)
+            )
+
+        return {}
+
+
+def repair_json(data, limit=10):
+    """
+        检查json字符串格式,对错误的json格式在规定次数中尝试进行修复
+    @param str data: json_str
+    @param int limit: json_str 结构修正次数上限
+    """
+    retries = 0
+    try:
+        data = ast.literal_eval(data)
+        return json.loads(json.dumps(data, ensure_ascii=False))
+    except SyntaxError:
+        while retries < limit:
+            try:
+                parsed_data = json.loads(data)
+                # print("字符串符合JSON格式")
+                # print(parsed_data)
+                return parsed_data
+            except json.JSONDecodeError as e:
+                retries += 1
+                error_pos = e.pos
+                err_msg = e.msg
+                # print("字符串不符合JSON格式")
+                # print("错误位置:", error_pos)
+                # print("错误原因:", err_msg)
+                data = data[:-1] if data.endswith(",") else data
+                # 补全'[{...}]' 缺失符号
+                missing_bracket_count = data.count('[{') - data.count('}]')
+                if missing_bracket_count > 0:
+                    for i in range(missing_bracket_count):
+                        delimiter = ']' if i == 0 and data.endswith("}") else '}]'
+                        data += delimiter
+                    continue
+                # 补全缺失闭合符号
+                missing_closing_bracket_count = data.count('{') - data.count('}')
+                if missing_closing_bracket_count > 0:
+                    for i in range(missing_closing_bracket_count):
+                        data += '}'
+                    continue
+                # 双引号替换单引号
+                if 'Expecting property name enclosed in double quotes' == err_msg:
+                    data = data.replace("'", '"')
+                    try:
+                        data = ast.literal_eval(data)
+                        data = json.dumps(data, ensure_ascii=False)
+                    except SyntaxError:
+                        pass
+                    continue
+                # 缺失符号补全
+                if 'delimiter' in err_msg:
+                    ret = re.search('Expecting(.*?)delimiter', err_msg).group(1)
+                    delimiter = ret.strip().replace("'", '')
+                    data = "".join([data[:error_pos], delimiter, data[error_pos:]])
+                    continue
+
+
+def rsub(pattern, repl, string, count=0, flags=0):
+    return re.sub(pattern, repl, string, count=count, flags=flags)

+ 11 - 0
lzz_theme/zgzbtbggfwpt_wagf/zgzbtbggfwpt_wagf_details.py

@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-06
+---------
+@summary: 中国招标投标公共服务平台 - 详情[未按规范]
+"""
+from spider_detail import Spider
+
+
+if __name__ == '__main__':
+    Spider(sizes=2000, threads=50, interval=0.65).start()

+ 27 - 0
lzz_theme/zgzbtbggfwpt_wagf/zgzbtbggfwpt_wagf_list_b.py

@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-06
+---------
+@summary: 中国招标投标公共服务平台 - 列表页[未按规范] - 大周期
+"""
+
+from collections import namedtuple
+from datetime import datetime, timedelta
+
+from spider_list import Spider
+
+if __name__ == '__main__':
+    Menu = namedtuple(
+        'Menu',
+        ['channel', 'code', 'category', 'businessKeyWord', 'auto_paginate']
+    )
+
+    target_menus = [
+        Menu('未按数据规范-招标公告', 'a_zgzbtbggfwpt_wasjgf_zbgg', '招标公告', 'tenderBulletin', True),
+        Menu('未按数据规范-开标记录', 'a_zgzbtbggfwpt_wasjgf_kbjl', '开标记录', 'openBidRecord', True),
+        Menu('未按数据规范-评标公示', 'a_zgzbtbggfwpt_wasjgf_pbgs', '评标公示', 'winCandidateBulletin', True),
+        Menu('未按数据规范-中标公告', 'a_zgzbtbggfwpt_wasjgf_zhbgg', '中标公告', 'winBidBulletin', True),
+    ]
+    now = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
+    date = (now, now)
+    Spider(target_menus, date=date, page_sizes=100, threads=10).start()

+ 28 - 0
lzz_theme/zgzbtbggfwpt_wagf/zgzbtbggfwpt_wagf_list_f.py

@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+Created on 2025-05-06
+---------
+@summary:  中国招标投标公共服务平台 - 列表页[未按规范] - 小周期
+"""
+
+import datetime
+from collections import namedtuple
+
+from spider_list import Spider
+
+if __name__ == '__main__':
+    Menu = namedtuple(
+        'Menu',
+        ['channel', 'code', 'category', 'businessKeyWord', 'crawl_page']
+    )
+
+    target_menus = [
+        Menu('未按数据规范-招标公告', 'a_zgzbtbggfwpt_wasjgf_zbgg', '招标公告', 'tenderBulletin', 1),
+        Menu('未按数据规范-开标记录', 'a_zgzbtbggfwpt_wasjgf_kbjl', '开标记录', 'openBidRecord', 1),
+        Menu('未按数据规范-评标公示', 'a_zgzbtbggfwpt_wasjgf_pbgs', '评标公示', 'winCandidateBulletin', 1),
+        Menu('未按数据规范-中标公告', 'a_zgzbtbggfwpt_wasjgf_zhbgg', '中标公告', 'winBidBulletin', 1),
+    ]
+
+    today = datetime.datetime.now().strftime('%Y-%m-%d')
+    date = (today, today)
+    Spider(target_menus, date=date, page_sizes=60, threads=4).start()