JavaScript中的JSON转为Python可读取

目录

  • 创作背景
  • 问题再现
  • 解决办法
    • 形参与实参对应关系
  • 格式化 JSON
    • 给键加上双引号
    • 识别且替换值
    • 总代码
  • 不足

    创作背景 最近本菜鸡在爬一个网站的时候,意外发现所需 JSON 数据在页面前端,但是不易提取,写下此篇博客以记录解决方案。

    问题再现 数据是通过 JS 代码传递的,大致格式(仅 作举例说明 ,方便查看层次,实际 在同一行 )如下:
    function (a, b, c, d){return {title: a,data: [{data: b},{data: c},{data: d}]}}('title', 2, 3, 4)

    我要提取的是整个 JSON 格式的数据。
    如果要直接提取,可以使用
    re.findall('return (\{.*?\})\}\(', content)

    得到结果,但如果要解析数据,会报以下的错:
    JavaScript中的JSON转为Python可读取
    文章图片

    意思是:键值对中的键需要被双引号包含 。
    所以我们要完成任务的话,需要解决以下问题:
    • 键需要用 "" 包含。
    • 需要将形式参数 a, b, c, d 转化为实际参数 "title", 2, 3, 4

    解决办法 形参与实参的对应关系容易解决,所以先解决这个问题。

    形参与实参对应关系
    可以使用以下代码得到 形式参数 。
    ''.join(re.findall('function\((.*?)\)\{', content)).split(',')

    使用下述代码获得 实际参数 。
    ''.join(re.findall('}\((.*?)\)', content)).split(',')

    因为 形式参数 和 实际参数 的 个数 一样,所以可以根据 列表索引 建立对应关系,使用 np.c_ 可以进行 列表横向合并 。
    代码如下:
    # 获得实际参数Argument = ''.join(re.findall('}\((.*?)\)', content)).split(',')# 获得形式参数Formal_parameter = ''.join(re.findall('function\((.*?)\)\{', content)).split(',')# 建立对应关系mapping = pd.DataFrame(np.c_[Formal_parameter, Argument])# 以形参作为索引mapping.set_index(0, drop=True, inplace=True)

    结果如下:
    JavaScript中的JSON转为Python可读取
    文章图片


    格式化 JSON 要解决这个问题,我们需要先提取出 JSON 字符串,代码如下:
    string = ''.join(re.findall('return (\{.*?\})\}\(', content))

    结果如下:
    JavaScript中的JSON转为Python可读取
    文章图片

    然后,我们需要有个思路,如下:
    • {, 后边,: 之前的部分是 键 ,需要加上双引号。
    • :[{ 后边,], 之前的部分都是 值 ,需要识别且替换。

    给键加上双引号
    因为涉及到 插入元素 ,Python 中只有 列表 能担此重任,所以我们需要先 将字符串转为列表 ,代码如下:
    string = list(''.join(re.findall('return (\{.*?\})\}\(', content)))

    然后,我们需要设置一个变量,当识别出是 键 的时候就 置 1 ,否则为 0
    key_flag = 0

    如果我们按照上文中的规则识别出 键 ,就要从当前位开始,一直到 : 之前的这部分都用 双引号 包含。
    还有些特殊情况,比如
    • 嵌套字典 ,比如一个列表中值均为字典 。
    • 空字典 ,{ 后边是 }
    考虑到特殊情况,代码如下:
    index = 0while True:# 如果索引超出范围,就跳出循环if index >= len(string):break# 给键加双引号if key_flag:# 在当前位插入一个双引号string.insert(index, '"')index += 1# 循环读取 while True:# 直到出现 ":",循环读取的部分为 键# 在 键 的最后添加一个双引号if string[index] == ':':string.insert(index, '"')# 重置 key_flagkey_flag = 0# 终止循环break# 读取下一位index += 1# 当前字符为 "{" 或 ",",则后边的为 键if string[index] in '{,':key_flag = 1# 嵌套时,则将索引移向下一位if string[index+1] in '{':index += 1# 如果为空字典,则重置 key_valueif string[index+1] in '}':key_flag = 0index += 1

    结果如下(因篇幅限制,代码无法截全):
    JavaScript中的JSON转为Python可读取
    文章图片

    可以看到,已经将所有的键用双引号包含。

    识别且替换值
    这一部分还是小有难度的。
    首先,和上边一样,我们还是需要一个变量,记录当前识别 值 的状态,1 代表识别出来了,0 代表没有。
    value_flag = 0

    不过也是有特殊情况:
    • 值已经是字符串 ,但 字符串中有 :
    • 值是 js 语句 ,不过其不是我们要提取的数据。
    考虑到特殊情况,代码如下:
    while True:if index >= len(string):break# 检测到 值if value_flag:# 取出当前字符value = https://www.it610.com/article/string.pop(index)# 如果字符是数字、"[" 和 "{" 或者已经是字符串if value in '"1234567890[{' or is_value:value_flag = 0string.insert(index, value)index += is_value# 不符合上述情况else:# 循环取出 值 字符串while True:# 如果为 "," 或 "}",则代表已取完if string[index] in ',}':breakvalue += string.pop(index)# 如果 值 字符串在对应关系中,就替换if value in mapping.index:# 因为是在当前位不断插入,所以要将数据反向trans = mapping.loc[value][::-1]# 如果不在,则直接替换成空字符串else:trans = '""'# 计算索引要移动几位length = len(trans)# 插入对应的数据for c in trans:string.insert(index, c)# 索引移动index += lengthvalue_flag = 0continue# 如果识别到 ":" 且该 ":" 不在值字符串中if string[index] in ':' and not is_value:value_flag = 1# 如果是值是字符串,则设置 is_valueif string[index+1] in '"':is_value = https://www.it610.com/article/1# 识别值字符串结束,并重置 is_valueelif string[index] in'"' and is_value:is_value = https://www.it610.com/article/0index += 1

    结果如下:
    JavaScript中的JSON转为Python可读取
    文章图片

    可以看到,转换的还是挺成功的。

    总代码
    content = '''function(a,b,c,d){return {title:a,data:[{data:b},{data:c},{data:d}]}}("title",2,3,4)'''string = list(''.join(re.findall('return (\{.*?\})\}\(', content)))is_value = https://www.it610.com/article/0key_flag = 0value_flag = 0index = 0while True:if index>= len(string):breakif key_flag:string.insert(index, '"')index += 1while True:if string[index] == ':':string.insert(index, '"')key_flag = 0breakindex += 1elif value_flag:value = https://www.it610.com/article/string.pop(index)if value in'"1234567890[{' or is_value:value_flag = 0string.insert(index, value)index += is_valueelse:storage = indexwhile True:if string[index] in ',}':breakvalue += string.pop(index)if value in mapping.index:trans = mapping.loc[value][::-1]else:trans = '""'length = len(trans)for c in trans:string.insert(index, c)index += lengthvalue_flag = 0continueif string[index] in '{,':key_flag = 1if string[index+1] in '{':index += 1if string[index+1] in '}':key_flag = 0elif string[index] in ':' and not is_value:value_flag = 1if string[index+1] in '"':is_value = https://www.it610.com/article/1elif string[index] in'"' and is_value:is_value = https://www.it610.com/article/0index += 1''.join(string)

    结果如下:
    JavaScript中的JSON转为Python可读取
    文章图片

    这时候就可以使用 json.loads 来提取数据了。
    结果如下:
    JavaScript中的JSON转为Python可读取
    文章图片


    不足 其实上述代码是有不足的地方的。因为这段 js 代码是特殊的,是 在一行 ,且 没有多余的空格 。
    不过也是有解决办法的:
    如果 有空格 怎么办? —> 在 提取形参 和 使用形参转换实参 时使用 strip 去除两侧的空格即可。
    代码不在一行 怎么办? —> 使用 .replace('\t', '').replace('\n', '') 去除 换行符 和 制表符 ,然后再进行格式化工作。
    其实如果只是 代码不在一行 的问题的话,js.loads 会帮我们去除 \n 之类的,直接提取重要部分。
    不过如果要使用我的代码的话,目前只支持 在一行 的 js 代码。
    还有就是有些 将键值对的值设置成逻辑运算式 ,比如 a || "" 这种,也不太好提取,还得根据问题调整。
    这些都只是思路,大家可以自行尝试,如果有问题也及时提出来。
    【JavaScript中的JSON转为Python可读取】到此这篇关于JavaScript中的JSON转为Python可读取的文章就介绍到这了,更多相关JavaScript JSON转为Python内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

      推荐阅读