如何解析不规范的JSON数据?

回复列表(45|隐藏机器人聊天)
  • @Ta / 2022-06-06 / /

    @老虎会游泳,试过了,客户端打开那本书,目录一片空白……是不是哪个实习生干的。。

  • @Ta / 2022-06-06 / /

    @无名啊,所以我认为社会工程学(提交Bug报告)是最佳解决方案,正则表达式可以作为备用方案。

  • @Ta / 2022-06-06 / /

    @老虎会游泳,他们会不会想“大部分书籍的目录都能正常生成,干嘛要改?。。。方便你爬我数据是吧?”

  • @Ta / 2022-06-06 / /

    @无名啊,你为什么不假装你是想看那本书的用户呢?让自己表现得不像一个程序员,只展示错误的现象,不要展示错误细节。

  • @Ta / 2022-06-06 / /

    @老虎会游泳,有没有容错性强的json解析库?

  • @Ta / 2022-06-06 / /

    @老虎会游泳

    你为什么不假装你是想看那本书的用户呢?

    有点道理,可以一试

  • @Ta / 2022-06-06 / /

    @无名啊,有,该工具通常叫做PCRE,也就是“Perl兼容正则表达式”,它允许你自行编写规则,解析任意格式的数据。

    https://php.net/pcre


    对于 http://indexinfo.cdn.bcebos.com/BookFiles/Html/421/420161/index.html 采用

    /\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s
    

    <?php
    $json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/421/420161/index.html');
    
    $regexp = '/\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s';
    
    preg_match_all($regexp, $json, $results, PREG_SET_ORDER);
    
    foreach ($results as $obj) {
        echo "$obj[1], $obj[2], $obj[3]\n";
    }
    

    Screenshot_20220606_131957.jpg

  • @Ta / 2022-06-06 / /

    @老虎会游泳,卷和章节的所属关系信息也能完好保留嘛?还是解析出[{章节1, 章节2, ...}]

  • @Ta / 2022-06-06 / /

    @无名啊,你可以先解析出卷(/\[(.*?)\]/),然后再解析出章节(/\{"xxx":"(.*)",……\}/)啊。

  • @Ta / 2022-06-06 / /

    @无名啊,JSON是嵌套格式,所以也要使用嵌套解析。先匹配最外层,然后逐层深入。

  • @Ta / 2022-06-06 / /

    @无名啊,我给你写个嵌套匹配的例子,你等等。

  • @Ta / 2022-06-06 / /

    @老虎会游泳,总觉得这样干,通用性不强,这网站的其他页面也要对应写规则。。

    希望是能实现如下函数功能:

    • 输入:畸形json
    • 输出:尽最大可能矫正后的、一定能正常解析的json

    我在考虑能否用正则逐json元素进行矫正

  • @Ta / 2022-06-06 / /

    @无名啊

    <?php
    $json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/545/544490/index.html');
    
    $卷表达式 = '/\{"name":"?(.*?)"?,"list":\[(.*?)\]\},/s';
    
    $章表达式 = '/\{"id":(\d+),"name":"?(.*?)"?,"hasContent":(\d+)\},/s';
    
    preg_match_all($卷表达式, $json, $卷组, PREG_SET_ORDER);
    
    foreach ($卷组 as $卷) {
        echo "卷: $卷[1]\n";
        preg_match_all($章表达式, $卷[2], $章组, PREG_SET_ORDER);
        foreach ($章组 as $章) {
            echo "章:$章[1], $章[2], $章[3]\n";
        }
        echo "\n";
    }
    

    Screenshot_20220606_133842_com.termux.jpg

  • @Ta / 2022-06-06 / /

    @无名啊,可以使用preg_replace_callback进行修复,但依然没有通用解决方案,因为你必须扩大匹配范围,才能防止错误匹配。

    /"([^"]+)":"?(.*?)"?,/ 这不安全,如果标题中出现半角逗号,就会错误匹配。

    /"name":"?(.*?)"?,"hasContent":/ 这样才安全,通过锚定前面的name和后面的hasContent,才能避免匹配到错误的地方。

  • @Ta / 2022-06-06 / /

    潜在的通用解决方案,但不成功,原因待查:

    <?php
    $json = file_get_contents('http://indexinfo.cdn.bcebos.com/BookFiles/Html/545/544490/index.html');
    
    function 修复函数($arr) {
        $k = $arr[1];
        $v = $arr[2];
        var_dump($arr);die;
        $v = html_entity_decode($v); // 处理&转义
        $v = preg_replace('/[\x00-\x19]/', '', $v); // 删除不允许出现的字符
        $v = trim($v); // 删除前后空白
        return json_encode($k).':'.json_encode($v);
    }
    
    function 修复字符串字段($json, $要修复的字段) {
        $修复表达式 = '/"$要修复的字段":"?(.*?)",/s';
        return preg_replace_callback($修复表达式, '修复函数', $json);
    }
    
    function 删除数组结尾逗号($json) {
        return str_replace(',]', ']', $json);
    }
    
    function 修复JSON($json, $要修复的字符串字段列表) {
        foreach ($要修复的字符串字段列表 as $要修复的字段) {
            $json = 修复字符串字段($json, $要修复的字段);
        }
        $json = 删除数组结尾逗号($json);
        return $json;
    }
    
    //var_dump(json_decode($json));
    
    $json = 修复JSON($json, ['name']);
    echo /*"\n\n\n",*/ $json/*, "\n\n\n"*/;
    ini_set('display_errors', true);
    error_reporting(E_ALL);
    var_dump(json_decode($json));
    
  • @Ta / 2022-06-06 / /
    @老虎会游泳,缺点是正则效率有点低,你永远不知道得到的是什么数据。
    我就遇到过 他自己拼接的json,先是json里面包含换行符 还包含emoji 还包含"号 还包含html代码
  • @Ta / 2022-06-06 / /

    @老虎会游泳,初步过滤成功,但仍有很多局限性

    <?php
    
    $s = '
    [
        {"name": "第五卷:"世家"子弟"},         # 这种情况出现最多,没转义『"』
        {"name": "天下大乱(上)' . chr(6) . '"},    # 出现了 ASCII 0-31 的字符,json 规范是不允许的
        {"name": "\&quot;小白兔\&quot;少爷"},  # 转义了不该转义的字符
        {"name": "番外篇~\(≧▽≦)/~"},        # 同上
        {"name": "权力的Chun\药"},             # 同上
        {"name": 饮马江湖"},                   # 搞不明白这是怎么出现的。是标题含有“退格符”(\x08),吃掉上一个『"』吗?
        {"name": style="color:Gray;"},         # ???
        {"name": ""},                          # 最后一项末尾不能有『,』
    ]
    ';
    
    $has_comma = false;
    
    $r = preg_replace_callback(
        '/
        (?<ws>\s+) | # 空白字符
        (?<ks>[],:[{}]) | # 符号关键字
        (?<cm>\#.*?(?:\n|$)) | # 注释
        (?<kw>(?:true|false|null)(?=[]},\s]|$)) | # 关键字
        (?<num>-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(?=[]},\s]|$)) | # 数字
        (?<str>"?(?:\\\\["\\bfnrt]?|.)*?"(?=[]},:\s]|$)) # 字符串
        /sxDSu',
        function ($m) use (&$has_comma) {
    
            // 跳过 空白字符 或 注释
            if (!(is_null($m['ws']) && is_null($m['cm']))) {
                return '';
            }
    
            // 记录逗号,右中/大括号 前不输出逗号
            elseif (!is_null($m['ks'])) {
                if ($m[0] === ',') {
                    $has_comma = true;
                }
                else {
                    $r = ($has_comma && $m[0] !== ']' && $m[0] !== '}' ? ',' : '') . $m[0];
                    $has_comma = false;
                    return $r;
                }
            }
    
            // 原样输出 关键字、数字(若记录了逗号,则先输出)
            elseif (!(is_null($m['kw']) && is_null($m['num']))) {
                $r = ($has_comma ? ',' : '') . $m[0];
                $has_comma = false;
                return $r;
            }
    
            // 都不匹配的,认为是字符串
            else {
                $is_begin_quoted = false;
                $r = ($has_comma ? ',' : '') . '"' . preg_replace_callback(
                    '/(?<bg>^")|(?<esc>\\\\["\\bfnrt])|(?<bs>\\\\)|(?<qe>"$)|(?<q>")|(?<ctrl>[\x00-\x1f])/Su',
                    function ($m) use (&$is_begin_quoted) {
                        if (!is_null($m['bg'])) $is_begin_quoted = true;  // 开头引号
                        elseif (!is_null($m['q'])) return '\\"';  // 转义 "
                        elseif (!is_null($m['bs'])) return '\\\\';  // 转义 \
                        elseif (!is_null($m['esc'])) return $m[0];  // 原样输出转义字符(暂不考虑 \uxxxx)
                        elseif (!is_null($m['qe'])) return $is_begin_quoted ? '' : '\\"';   // 开头若有引号,末尾的引号不认为是内容之一
                        elseif (!is_null($m['ctrl'])) return '【净化】';  // ASCII 0-31 的字符,可以考虑 \uxxxx 回去
                    },
                    $m[0],
                    flags: PREG_UNMATCHED_AS_NULL,
                ) . '"';
                $has_comma = false;
                return $r;
            }
        },
        $s,
        flags: PREG_UNMATCHED_AS_NULL,
    );
    
    echo $r . "\n";
    var_dump(json_decode($r));
    
    [{"name":"第五卷:\"世家\"子弟"},{"name":"天下大乱(上)【净化】"},{"name":"\\&quot;小白兔\\&quot;少爷"},{"name":"番外篇~\\(≧▽≦)/~"},{"name":"权力的Chun\\药"},{"name":"饮马江湖\""},{"name":"style=\"color:Gray;\""},{"name":""}]
    
  • @Ta / 2022-06-06 / /

    @淡然,体会到你的心情,已经开始生气了

    ™的,不好好用现成json库,偏要自己手动输出,还不好好转义,不处理异常结果

  • @Ta / 2022-06-07 / /

    @无名啊,人要有感激之情,你爬对方数据没有经过对方同意吧。不告而取谓之窃,别人没有批评你,你却批评起对方,不是很奇怪吗?升米恩,斗米仇,别人就不该暴露接口给你提供这么多数据。

  • @Ta / 2022-06-07 / /

    @老虎会游泳,嗯,感谢对面大大方方暴露接口,大部分接口也没有加密

    其实,对面盗版站,也是不告而取(除非真的是起点养的)

    前几天也遭到全网500多大神作家和各地协会联名抵制来着

添加新回复
回复需要登录