术→技巧, 研发

Python JSON/JSONP数据解析

钱魏Way · · 512 次浏览

JSON简介

JSON 即 JavaScript Object Natation,它是一种轻量级的数据交换格式,非常适合于服务器与 JavaScript 的交互。

在普通的 Web 应用中,开发者经常为 XML 的解析伤透了脑筋,无论是服务器端生成或处理 XML,还是客户端用 JavaScript 解析 XML,都常常导致复杂的代码,极低的开发效率。实际上,对于大多数 Web 应用来说,许多 AJAX 应用甚至直接返回 HTML 片段来构建动态 Web 页面。和返回 XML 并解析它相比,返回 HTML 片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。

JSON 为 Web 应用开发者提供了另一种数据交换格式。和 XML 一样,JSON 也是基于纯文本的数据格式。由于 JSON 天生是为 JavaScript 准备的,因此,JSON 的数据格式非常简单,您可以用 JSON 传输一个简单的 String,Number,Boolean,也可以传输一个数组,或者一个复杂的 Object 对象。

JSON的结构基于下面两点

  • 名称/值对的集合 不同语言中,它被理解为对象(object),记录(record),结构(struct),字典(dictionary),哈希表(hash table),键列表(keyed list)等
  • 值的有序列表 多数语言中被理解为数组(array)

JSON以一种特定的字符串形式来表示 JavaScript 对象。如果将具有这样一种形式的字符串赋给任意一个 JavaScript 变量,那么该变量会变成一个对象引用,而这个对象就是字符串所构建出来的。

这里假设我们需要创建一个User对象,并具有以下属性:

  • 用户ID
  • 用户名
  • 网站

您可以使用以下JSON形式来表示User对象:

{"UserID":100, "Name":"biaodianfu", "website":"https://www.biaodianfu.com"};

然后如果把这一字符串赋予一个JavaScript变量,那么就可以直接使用对象的任一属性了。

<script>
    var User = {"UserID": 100, "Name": "biaodianfu", "Website": "https://www.biaodianfu.com"};
    alert(User.Name);
</script>

实际使用时可能更复杂一点,比如我们为Name定义更详细的结构,使它具有EnglishName和ChineseName:

{"UserID":100, "Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"}, "Website":"https://www.biaofianfu.com"};

完整代码:

<script>
    var User = {
        "UserID": 100,
        "Name": {"EnglishName": "biaodianfu", "ChineseName": "标点符"},
        "Website": "https://www.biaofianfu.com"
    };
    alert(User.Name.ChineseName);
</script>

如果某个页面需要一个用户列表,而不仅仅是一个单一的用户信息,那么就需要创建一个用户列表数组。下面代码演示了使用JSON形式定义这个用户列表:

<script>
    var UserList = [
        {
            "UserID": 100,
            "Name": {"EnglishName": "biaodianfu", "ChineseName": "标点符"},
            "Website": "https://www.biaofianfu.com "
        },
        {
            "UserID": 101,
            "Name": {"EnglishName": "biaodianfu", "ChineseName": "标点符"},
            "Website": "https://www.biaofianfu.com "
        },
        {
            "UserID": 102,
            "Name": {"EnglishName": "biaodianfu", "ChineseName": "标点符"},
            "Website": "https://www.biaofianfu.com "
        },
    ];
    alert(UserList[0].Name.ChineseName);
</script>

除了使用”.”引用属性外,还可以使用下面语句:

alert(UserList[0]["Name"]["ChineseName"]);
alert(UserList[0].Name["ChineseName"]);

JSON的一些规则:

  • 对象是属性、值对的集合。一个对象的开始于“{”,结束于“}”。每一个属性名和值间用“:”提示,属性间用“,”分隔。
  • 数组是有顺序的值的集合。一个数组开始于”[“,结束于”]”,值之间用”,”分隔。
  • 值可以是引号里的字符串、数字、true、false、null,也可以是对象或数组。这些结构都能嵌套。
  • 字符串和数字的定义和C或Java基本一致。

JSON的一些优点:

  • JSON 提供了一种优秀的面向对象的方法,以便将元数据缓存到客户机上。
  • JSON 帮助分离了验证数据和逻辑。
  • JSON 帮助为 Web 应用程序提供了 Ajax 的本质

JSON与Python字典的区别

JSON与Python中的dict非常类似,都是以key-value的形式存储数据,而且json、dict也可以非常方便的通过dumps、loads进行格式的相互转换。

既然都是key-value格式,为啥还需要进行格式转换呢?

  • JSON(JavaScript Object Notation):json是一种数据格式,是纯字符串。可以被解析成Python的dict或者其他形式。
  • dict(dictionary):字典是一个完整的数据结构,是对HashTable这一数据结构的一种实现,是一套从存储到提取都封装好了的方案。它使用内置的哈希函数来规划key对应value的存储位置,从而获得较快的数据读取速度。

JSON和dict的区别:

  • json的key只能是字符串,python的dict可以是任何可hash对象。
  • json的key可以是有序、重复的,python的dict的key不可以重复。
  • json的key存在默认值undefined,dict没有默认值。
  • json的value只能是字符串、浮点数、布尔值或者null,或者它们构成的数组或者对象。
  • json访问方式可以是[],也可以是.,遍历方式分in、of,dict的value仅可以通过下标[]访问。
  • json的字符串强制双引号,dict字符串可以单引号、双引号。
  • json里只有数组,dict可以嵌套tuple。
  • json中的中文必须是unicode编码,如“你好”在json中应为”\u4f60\u597d”。
  • json的数据类型是字符串(str),字典的数据类型是字典(dict)。
  • json定义布尔值和空值:true、false、null。
  • Python定义布尔值和空值:True、False、None。

Python 标准库JSON模块

Python 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数:

JSON字符串转Python字典

# Import the module
import json

# String with JSON format
data_JSON = """
{
    "size": "Medium",
    "price": 15.67,
    "toppings": ["Mushrooms", "Extra Cheese", "Pepperoni", "Basil"],
    "client": {
        "name": "Jane Doe",
        "phone": "455-344-234",
        "email": "janedoe@email.com"
    }
}
"""

# Convert JSON string to dictionary
data_dict = json.loads(data_JSON)
print(type(data_dict))
print(data_dict["size"])
print(data_dict["price"])
print(data_dict["toppings"])
print(data_dict["client"])

输出内容:

<class 'dict'>
Medium
15.67
['Mushrooms', 'Extra Cheese', 'Pepperoni', 'Basil']
{'name': 'Jane Doe', 'phone': '455-344-234', 'email': 'janedoe@email.com'}

JSON转Python字典过程中的数据类型转换:

Python字典转JSON字符串

# Import the module
import json

# Python Dictionary
client = {
    "name": "Nora",
    "age": 56,
    "id": "45355",
    "eye_color": "green",
    "wears_glasses": False
}

# Get a JSON formatted string
client_JSON = json.dumps(client)
print(type(client_JSON))
print(client_JSON)

输出内容:

<class 'str'>
{"name": "Nora", "age": 56, "id": "45355", "eye_color": "green", "wears_glasses": false}

Python字典转JSON时的数据类型转换:

Python JSON数据美化

client_JSON = json.dumps(client, indent=4)
print(client_JSON)

输出内容:

{
    "name": "Nora",
    "age": 56,
    "id": "45355",
    "eye_color": "green",
    "wears_glasses": false
}

JSON文件的读取与写入

# Open the orders.json file
with open("orders.json") as file:
    # Load its content and make a new dictionary
    data = json.load(file)

    # Delete the "client" key-value pair from each order
    for order in data["orders"]:
        del order["client"]

# Open (or create) an orders_new.json file 
# and store the new version of the data.
with open("orders_new.json", 'w') as file:
    json.dump(data, file)

json与simplejson的区别

我们看到许多项目使用了simplejson模块而不是标准库中的json模块。为什么要使用这些替代方法而不是标准库中的替代方法?

Python标准库中的json模块其实就是simplejson,只不过更新频率相比独立的simplejson要慢一些。两者功能相似,性能上simplejson要更好一些。

建议这样使用:

try:
    import simplejson as json
except ImportError:
    import json

其他JSON解析工具

  • python-rapidjson: RapidJSON是腾讯开源的一个高效的C++ JSON解析器及生成器。目前被包装为Python库。
  • ultrajson:用C写的json解析库
  • pysimdjson:simdjson项目的Python绑定,这是SIMD加速的JSON解析器. 如果没有SIMD指令,则使用后备解析器,从而使pysimdjson在任何地方都可以安全使用。
  • orjson:orjson 是 Rust 语言写的,本地没有 Rust 环境他会提示你需要 Rust 环境,安装起来比较麻烦,据说性能非常的快。
  • marshmallow:marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes.

PythonJSONP

JSONP(JSON with Padding)是JSON的一种“使用模式”,可以让网页从别的网域要数据。

由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com 的服务器沟通,而HTML的<script>元素是一个例外。利用<script>元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的 JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

为了理解这种模式的原理,先想像有一个回传 JSON 文件的 URL,而 Javascript 程式可以用XMLHttpRequest跟这个 URL 要资料。假设我们的 URL 是http://server2.example.com/RetrieveUser?UserId=xxx。假设小明的 UserId 是1234,且当浏览器透过 URL 传小明的 UserId,也就是抓取http://server2.example.com/RetrieveUser?UserId=1234,得到:

{"Name": "小明", "Id": 1823, "Rank": 7}

这个 JSON 资料可能是依据传过去 URL 的查询参数动态产生的。

这个时候,把<script>元素的src属性设成一个回传 JSON 的 URL 是可以想像的,这也代表从 HTML 页面透过 script 元素抓取 JSON 是可能的。

然而,一份 JSON 文件并不是一个JavaScript 程序。为了让浏览器可以在<script>元素执行,从 src 里 URL 回传的必须是可执行的 JavaScript。在JSONP的使用模式里,该 URL 回传的是由函数呼叫包起来的动态生成 JSON,这就是JSONP 的“填充(padding)”或是“前辍(prefix)”的由来。

惯例上浏览器提供回调函数的名称当作送至服务器的请求中命名查询参数的一部份,例如:

<script type="text/javascript" src="http://server2.example.com/RetrieveUser?UserId=1234&jsonp=parseResponse"></script>

服务器会在传给浏览器前将 JSON 数据填充到回调函数(parseResponse)中。浏览器得到的回应已不是单纯的资料叙述而是一个脚本。在本例中,浏览器得到的是:

parseResponse({"Name": "Cheeso", "Id": 1823, "Rank": 7})

虽然这个填充(前辍)“通常”是浏览器执行背景中定义的某个回调函数,它也可以是变量赋值、if 叙述或者是其他 JavaScript 叙述。JSONP 要求(也就是使用 JSONP 模式的请求)的回应不是 JSON 也不被当作 JSON 解析——回传内容可以是任意的运算式,甚至不需要有任何的 JSON,不过惯例上填充部份还是会触发函数调用的一小段 JavaScript 片段,而这个函数呼叫是作用在 JSON 格式的资料上的。

另一种说法,典型的 JSONP 就是把既有的 JSON API 用函数请求包起来以达到跨域存取的解法。为了要启动一个 JSONP请求(或者说,使用这个模式),你需要一个 script 元素。因此,浏览器必须为每一个JSONP 要求加(或是重用)一个新的、有所需 src 值的 <script> 元素到 HTML DOM 里—或者说是“注入”这个元素。浏览器执行该元素,抓取 src 里的 URL,并执行回传的 JSON。也因为这样,JSON 被称作是一种“让使用者利用 script 元素注入的方式绕开同源策略”的方法。

使用远端网站的 script 标签会让远端网站得以注入任何的内容至网站里。如果远端的网站有 JavaScript 注入漏洞,原来的网站也会受到影响.现在有一个正在进行计划在定义所谓的 JSON-P 严格安全子集,使浏览器可以对 MIME 类别是“application/json-p”请求做强制处理。如果回应不能被解析为严格的 JSON-P,浏览器可以丢出一个错误或忽略整个回应。

粗略的 JSONP 部署很容易受到跨网站的伪造要求(CSRF/XSRF)的攻击。因为 HTML <script> 标签在浏览器里不遵守同源策略,恶意网页可以要求并取得属于其他网站的 JSON 资料。当使用者正登入那个其他网站时,上述状况使得该恶意网站得以在恶意网站的环境下操作该 JSON 资料,可能泄漏使用者的密码或是其他敏感资料。

只有在该 JSON 资料含有不该泄漏给第三方的隐密资料,且服务器仅靠浏览器的同源策略阻挡不正常要求的时候这才会是问题。若服务器自己决定要求的专有性,并只在要求正常的情况下输出资料则没有问题。只靠 Cookie 并不够决定要求是合法的,这很容易受到跨网站的伪造要求攻击。

JSONP的客户端实现

我们知道,哪怕跨域js文件中的代码(当然指符合web脚本安全策略的),web页面也是可以无条件执行的。

远程服务器remoteserver.com根目录下有个remote.js文件代码如下:

alert('我是远程文件');

本地服务器localserver.com下有个jsonp.html页面代码如下:

<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>

毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。

现在我们在jsonp.html页面定义一个函数,然后在远程remote.js中传入数据进行调用。

jsonp.html页面代码如下:

<script type="text/javascript">
    var localHandler = function (data) {
        alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
    };
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>

remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});

运行之后查看结果,页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。很欣喜,跨域远程获取数据的目的基本实现了,但是又一个问题出现了,我怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。

聪明的开发者很容易想到,只要服务端提供的js脚本是动态生成的就行了呗,这样调用者可以传一个参数过去告诉服务端“我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来生成js脚本并响应了。

看jsonp.html页面的代码:

<script type="text/javascript">
    // 得到航班信息查询结果后的回调函数
    var flightHandler = function (data) {
        alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
    };
    // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script);
</script>

这次的代码变化比较大,不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。

我们看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。

OK,服务器很聪明,这个叫做flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html(服务端的实现这里就不演示了,与你选用的语言无关,说到底就是拼接字符串):

flightHandler({
        "code": "CA1998",
        "price": 1780,
        "tickets": 5
    });

我们看到,传递给flightHandler函数的是一个json,它描述了航班的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!

到这里为止的话,相信你已经能够理解jsonp的客户端实现原理了吧?剩下的就是如何把代码封装一下,以便于与用户界面交互,从而实现多次和重复调用。

jQuery对JSONP的实现

我们依然沿用上面那个航班信息查询的例子,假定返回jsonp结果不变:

<script type="text/javascript" src=jquery.min.js"></script>
<script type="text/javascript">
    jQuery(document).ready(function () {

        $.ajax({
            type: "get",
            async: false,
            url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
            dataType: "jsonp",
            jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
            jsonpCallback: "flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
            success: function (json) {
                alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。');
            },
            error: function () {
                alert('fail');
            }
        });
    });
</script>

为什么我这次没有写flightHandler这个函数呢?而且竟然也运行成功了!这就是jQuery的功劳了,jquery在处理jsonp类型的ajax时自动帮你生成回调函数并把数据取出来供success属性方法来调用。

ajax与jsonp的异同

ajax和jsonp这两种技术在调用方式上“看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;

但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。

还有就是,jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

总而言之,jsonp不是ajax的一个特例,哪怕jquery等巨头把jsonp封装进了ajax,也不能改变这一点!

Python与JSONP

Python生成JSONP数据:

def dumps_jsonp(request):
    funcname = request.GET.get('callback')
    # print(funcname)
    content = '%s(%s)' % (funcname, data())
    # print(content)
    return HttpResponse(content)

Python读取JSONP数据:

import re
import json

def loads_jsonp(_jsonp):
    try:
        return json.loads(re.match(".*?({.*}).*",_jsonp,re.S).group(1))
    except:
    raise ValueError('Invalid Input')

参考链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注