지난번 포스팅에서는 Python의 json.dumps() 를 사용해서 JSON 포맷 데이터를 쓰거나, json.loads()를 사용해서 JSON 포맷 데이터를 python으로 읽어오는 방법(https://rfriend.tistory.com/474)을 소개하였습니다. 


이번 포스팅에서는 이어서 웹에 있는 JSON 포맷 데이터를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법(How to read JSON formate data from WEB API and convert it to pandas DataFrame in python)을 소개하겠습니다. 





JSON 포맷 파일을 가져올 수 있는 사이트로 "Awesome JSON Datasets (https://github.com/jdorfman/awesome-json-datasets)" 를 예로 들어서 설명해보겠습니다. 


여러개의 JSON Datasets 이 올라가 있는데요, 이중에서 'Novel Prize' JSON 포맷 데이터(http://api.nobelprize.org/v1/prize.json)를 읽어와서 DataFrame으로 만들어보겠습니다. 




 (1) API 웹 사이트에서 JSON 포맷 자료를 Python으로 읽어오기



이제 urllib 모듈의 rulopen 함수를 사용해서 JSON 데이터가 있는 URL로 요청(request)을 보내서 URL을 열고 JSON 데이터를 읽어와서, python의 json.loads() 를 사용하여 novel_prize_json 이라 이름의 Python 객체로 만들어보겠습니다.  



# parse a JSON string using json.loads() method : returns a dictionary

import json

import urllib

import pandas as pd


# API request to the URL

import sys


if sys.version_info[0] == 3:

    from urllib.request import urlopen # for Python 3.x

else:

    from urllib import urlopen           # for Python 2.x


with urlopen("http://api.nobelprize.org/v1/prize.json") as url:

    novel_prize_json_file = url.read()




urllib 모듈의 (web open) request 메소드를 불러오 때 Python 2.x 버전에서는 from urllib import urlopen 을 사용하는 반면, Python 3.x 버전에서는 from urllib.request import urlopen 을 사용합니다. 따라서 만약 Python 3.x 사용자가 아래처럼 (Python 2.x 버전에서 사용하는) from urllib import urlopen 이라고 urlopen을 importing 하려고 하면 ImportError: cannot import name 'urlopen' 이라는 에러가 납니다. 


# ImportError: cannot import name 'urlopen' at python 3.x

from urllib import urlopen # It's only for Python 2.x. It's not working at Python 3.x


with urlopen("http://api.nobelprize.org/v1/prize.json") as url:

    novel_prize_json_file = url.read()


---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-81c8fae1a1fd> in <module>()
      1 # API request to the URL
----> 2 from urllib import urlopen
      3 
      4 with urlopen("http://api.nobelprize.org/v1/prize.json") as url:
      5     novel_prize_json_file = url.read()

ImportError: cannot import name 'urlopen'



다음으로, 위에서 읽어온 JSON 포맷 데이터를 Python의 json.loads() 메소드를 이용해서 decoding 해보겠습니다. 이때 decode('utf-8') 로 설정해주었습니다. 


# decoding to python object

novel_prize_json = json.loads(novel_prize_json_file.decode('utf-8'))



decoding을 할 때 'utf-8' 을 설정을 안해주니 아래처럼 TypeError 가 나네요. (TypeError: the JSON object must be str, not 'builtin_function_or_method')


# decoding TypeError. decode using decode('utf-8')

novel_prize_json = json.loads(novel_prize_json_file.decode)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-1fda68be6386> in <module>()
      1 # decoding TypeError
----> 2 novel_prize_json = json.loads(novel_prize_json_file.decode)

C:\Users\admin\Anaconda3\lib\json\__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    310     if not isinstance(s, str):
    311         raise TypeError('the JSON object must be str, not {!r}'.format(
--> 312                             s.__class__.__name__))
    313     if s.startswith(u'\ufeff'):
    314         raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",

TypeError: the JSON object must be str, not 'builtin_function_or_method'




Novel Prize JSON 파일을 Python 객체로 읽어왔으니, keys() 메소드로 키를 확인해보겠습니다. 그리고 'prizes' 키의 첫번째 데이터(novel_prize_json['prizes'][0])인 물리학(Physics) 분야 노벨상 수상자 정보를 인쇄해보겠습니다. 



novel_prize_json.keys()

dict_keys(['prizes'])


novel_prize_json['prizes'][0].keys()

dict_keys(['laureates', 'year', 'overallMotivation', 'category'])


novel_prize_json['prizes'][0]

{'category': 'physics',
 'laureates': [{'firstname': 'Arthur',
   'id': '960',
   'motivation': '"for the optical tweezers and their application to biological systems"',
   'share': '2',
   'surname': 'Ashkin'},
  {'firstname': 'Gérard',
   'id': '961',
   'motivation': '"for their method of generating high-intensity, ultra-short optical pulses"',
   'share': '4',
   'surname': 'Mourou'},
  {'firstname': 'Donna',
   'id': '962',
   'motivation': '"for their method of generating high-intensity, ultra-short optical pulses"',
   'share': '4',
   'surname': 'Strickland'}],
 'overallMotivation': '"for groundbreaking inventions in the field of laser physics"', 

'year': '2018'} 




가독성을 높이기 위해서 json.dumps(obj, indent=4) 를 사용해서 4칸 들여쓰기 (indentation)을 해보겠습니다. 



print(json.dumps(novel_prize_json['prizes'][0], indent=4))

{
    "laureates": [
        {
            "share": "2",
            "id": "960",
            "surname": "Ashkin",
            "motivation": "\"for the optical tweezers and their application to biological systems\"",
            "firstname": "Arthur"
        },
        {
            "share": "4",
            "id": "961",
            "surname": "Mourou",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "firstname": "G\u00e9rard"
        },
        {
            "share": "4",
            "id": "962",
            "surname": "Strickland",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "firstname": "Donna"
        }
    ],
    "year": "2018",
    "overallMotivation": "\"for groundbreaking inventions in the field of laser physics\"",
    "category": "physics"
}

 



키(keys)를 기준으로 정렬하는 것까지 포함해서 다시 한번 프린트를 해보겠습니다. 



print(json.dumps(novel_prize_json['prizes'][0], indent=4, sort_keys=True))

{
    "category": "physics",
    "laureates": [
        {
            "firstname": "Arthur",
            "id": "960",
            "motivation": "\"for the optical tweezers and their application to biological systems\"",
            "share": "2",
            "surname": "Ashkin"
        },
        {
            "firstname": "G\u00e9rard",
            "id": "961",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "share": "4",
            "surname": "Mourou"
        },
        {
            "firstname": "Donna",
            "id": "962",
            "motivation": "\"for their method of generating high-intensity, ultra-short optical pulses\"",
            "share": "4",
            "surname": "Strickland"
        }
    ],
    "overallMotivation": "\"for groundbreaking inventions in the field of laser physics\"",
    "year": "2018"
}

 




 (2) JSON 포맷 데이터를 pandas DataFrame으로 만들기


다음으로 Python으로 불러온 JSON 포맷의 데이터 중의 일부분을 indexing하여 pandas DataFrame으로 만들어보겠습니다. 


예로, ['prizes'][0] 은 물리학(physics) 노벨상이며, ['prizes'][0]['laureates'] 로 물리학 노벨상 수상자 정보만 선별해서 pd.DataFrame() 으로 DataFrame을 만들어보겠습니다. 


novel_prize_physics = pd.DataFrame(novel_prize_json['prizes'][0]["laureates"])


novel_prize_physics

firstnameidmotivationsharesurname
0Arthur960"for the optical tweezers and their applicatio...2Ashkin
1Gérard961"for their method of generating high-intensity...4Mourou
2Donna962"for their method of generating high-intensity...4Strickland



DataFrame을 만들 때 칼럼 순서를 columns 로 지정을 해줄 수 있습니다. 


novel_prize_physics = pd.DataFrame(novel_prize_json['prizes'][0]["laureates"]

                                   columns = ['id', 'firstname', 'surname', 'share', 'motivation'])


novel_prize_physics

idfirstnamesurnamesharemotivation
0960ArthurAshkin2"for the optical tweezers and their applicatio...
1961GérardMourou4"for their method of generating high-intensity...
2962DonnaStrickland4"for their method of generating high-intensity...


많은 도움이 되었기를 바랍니다 .


다음 포스팅에서는 웹에서 XML 포맷 데이터를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법을 소개하겠습니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

json.org의 JSON 소개 내용에 따르면, JSON (JavaScript Object Notation) 은 XML, YAML 과 함께 효율적으로 데이터를 저장하고 교환(exchange data)하는데 사용하는 텍스트 데이터 포맷 중의 하나입니다. JSON은 사람이 읽고 쓰기에 쉬우며, 또한 기계가 파싱하고 생성하기도에 쉽습니다. JSON은 그 이름에서 유추할 수 있듯이 JavaScript의 프로그래밍 언어의 부분에 기반하고 있으며, C-family 프로그램밍 언어 (C, C++, C#, Java, JavaScript, Perl, Python 등)의 규약을 따르고 있어서 C-family 프로그래밍 언어 간 데이터를 교환하는데 적합합니다. 

JSON은 아래의 두개의 구조로 이루어져 있습니다. 

  • 이름/값 쌍의 집합 (A collection of name/value pairs): object, record, struct, dictionary, hash table, keyed list, associative array
  • 정렬된 값의 리스트 (An ordered list of values): array, vector, list, sequence

홍길동이라는 이름의 학생에 대한 정보를 포함하고 있는 JSON 데이터 포맷의 예를 들어보겠습니다.

{

    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20,
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ],
            "Minor": "ComputerScience"
        }
    ]
}


그러면, 이번 포스팅에서는 

(1) Python 객체를 JSON 데이터로 쓰기, 직렬화, 인코딩 (Write Python object to JSON, Serialization, Encoding)

(2) JSON 포맷 데이터를 Python 객체로 읽기, 역직렬화, 디코딩 (Read JSON to Python, Deserialization, Decoding)

하는 방법을 소개하겠습니다. 


위의 Python - JSON 간 변환 표(conversion table b/w python and JSON)에서 보는 바와 같이, python의 list, tuple 이 JSON의 array로 변환되며, JSON의 array는 pythonhon의 list로 변환됩니다. 따라서 Python의 tuple을 JSON으로 변환하면 JSON array가 되며, 이를 다시 Python으로 재변환하면 이땐 python의 tuple이 아니라 list로 변환된다는 점은 인식하고 사용하기 바랍니다. 


 (1) Python 객체를 JSON 데이터로 쓰기, 직렬화, 인코딩: json.dumps()
      (Write Python object to JSON, Serialization, Encoding)

python 객체를 JSON 데이터로 만들어서 쓰기 위해서는 파이썬의 내장 json 모듈이 필요합니다. 

 import json


아래와 같은 홍길동 이라는 학생의 정보를 담고 있는 사전형 자료(dictionary)를 json.dump()와 json.dumps() 의 두가지 방법으로 JSON 포맷 데이터로 만들어보겠습니다. 

student_data = {
    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20, 
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Major": "Statistics", 
            "Classes": ["Probability", 
                        "Generalized Linear Model", 
                        "Categorical Data Analysis"]
        }, 
        {
            "Minor": "ComputerScience", 
            "Classes": ["Data Structure", 
                        "Programming", 
                        "Algorithms"]
        }
    ]
} 


(2-1) with open(): json.dump() 를 사용해서 JSON 포맷 데이터를 디스크에 쓰기

with open("student_file.json", "w") 로 "student_file.json" 이름의 파일을 쓰기("w") 모드로 열어놓고, json.dump(student_data, json_file) 로 직렬화해서 JSON으로 내보내고자 하는 객체 student_data를, 직렬화된 데이터가 쓰여질 파일 json_file 에 쓰기를 해주었습니다. 

import json

with open("student_file.json", "w") as json_file:

    json.dump(student_data, json_file)


그러면 아래의 화면캡쳐에서 보는 바와 같이 'student_file.json'이라는 이름의 JSON 포맷 데이터가 새로 생성되었음을 알 수 있습니다.



(2-2) json.dumps()를 사용해서 JSON 포맷 데이터를 메모리에 만들기

만약 메모리 상에 JSON 포맷 데이터를 만들어놓고 python에서 계속 작업을 하려면 json.dumps() 를 사용합니다.

import json

st_json = json.dumps(student_data)

print(st_json)

{"5.Courses": [{"Classes": ["Probability", "Generalized Linear Model", "Categorical Data Analysis"], "Major": "Statistics"}, {"Minor": "ComputerScience", "Classes": ["Data Structure", "Programming", "Algorithms"]}], "3.Age": 20, "2.LastName": "Hong", "4.University": "Yonsei University", "1.FirstName": "Gildong"}


이때 만약 json.dumps()가 아니라 json.dump() 처럼 's'를 빼먹으면 TypeError가 발생하므로 주의하세요. 

# use json.dumps() instead of json.dump()

st_json = json.dump(student_data)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ac881d00bbcb> in <module>()
----> 1 st_json = json.dump(student_data)

TypeError: dump() missing 1 required positional argument: 'fp'


json.dumps()로 파이썬 객체를 직렬화해서 JSON으로 쓸 때 사람이 보기에 좀더 쉽도록 'indent = int' 로 들여쓰기(indentation) 옵션을 설정할 수 있습니다. 아래 예시는 indent=4 로 설정한건데요, 한결 보기에 가독성이 좋아졌습니다. 


import json

st_json2 = json.dumps(student_data, indent=4)

print(st_json2)

{

    "3.Age": 20,
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Minor": "ComputerScience",
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ]
        }
    ],
    "1.FirstName": "Gildong",
    "4.University": "Yonsei University",
    "2.LastName": "Hong"
}


'sort_keys=True' 를 설정해주면 키(keys)를 기준으로 정렬해서 직렬화하여 내보낼 수도 있습니다. 

import json

st_json3 = json.dumps(student_data, indent=4, sort_keys=True)

print(st_json3)

{
    "1.FirstName": "Gildong",
    "2.LastName": "Hong",
    "3.Age": 20,
    "4.University": "Yonsei University",
    "5.Courses": [
        {
            "Classes": [
                "Probability",
                "Generalized Linear Model",
                "Categorical Data Analysis"
            ],
            "Major": "Statistics"
        },
        {
            "Classes": [
                "Data Structure",
                "Programming",
                "Algorithms"
            ],
            "Minor": "ComputerScience"
        }
    ]
}




 (2) JSON 포맷 데이터를 Python 객체로 읽기, 역직렬화, 디코딩: json.loads()
      (Read JSON to Python, Deserialization, Decoding)


(2-1) 디스크에 있는 JSON 포맷 데이터를 json.load()를 사용하여 Python 객체로 읽어오기 (역직렬화, 디코딩 하기)

이어서, (1)번에서 with open(): json.dump() 로 만들어놓은 JSON 포맷의 데이터 "student_file.json" 를 Python 으로 역질렬화(deserialization)해서 읽어와 보겠습니다. with open("student_file.json", "r") 로 읽기 모드("r")로 JSON파일을 열어 후에, json.load(st_json)으로 디코딩하였습니다. 

import json

with open("student_file.json", "r") as st_json:

    st_python = json.load(st_json)


st_python

{'1.FirstName': 'Gildong',
 '2.LastName': 'Hong',
 '3.Age': 20,
 '4.University': 'Yonsei University',
 '5.Courses': [{'Classes': ['Probability',
    'Generalized Linear Model',
    'Categorical Data Analysis'],
   'Major': 'Statistics'},
  {'Classes': ['Data Structure', 'Programming', 'Algorithms'],
   'Minor': 'ComputerScience'}]}


이때 json.loads() 처럼 's'를 붙이면 TypeError: the JSON object must be str, not 'TextIOWrapper'가 발생합니다. (json.loads()가 아니라 json.load() 를 사용해야 함)

# use json.load() instead of json.loads()

with open("student_json_file.json", "r") as st_json:

    st_python = json.loads(st_json)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-c39634419df6> in <module>()
      1 with open("student_json_file.json", "r") as st_json:
----> 2     st_python = json.loads(st_json)

C:\Users\admin\Anaconda3\lib\json\__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    310     if not isinstance(s, str):
    311         raise TypeError('the JSON object must be str, not {!r}'.format(
--> 312                             s.__class__.__name__))
    313     if s.startswith(u'\ufeff'):
    314         raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",

TypeError: the JSON object must be str, not 'TextIOWrapper'



(2-2) 메모리에 있는 JSON 포맷 데이터를 json.loads()로 Python 객체로 읽기 (역직렬화, 디코딩하기)

import json

st_python2 = json.loads(st_json3)

st_python2

{'1.FirstName': 'Gildong',
 '2.LastName': 'Hong',
 '3.Age': 20,
 '4.University': 'Yonsei University',
 '5.Courses': [{'Classes': ['Probability',
    'Generalized Linear Model',
    'Categorical Data Analysis'],
   'Major': 'Statistics'},
  {'Classes': ['Data Structure', 'Programming', 'Algorithms'],
   'Minor': 'ComputerScience'}]}


이때 만약 json.loads() 대신에 's'를 빼고 json.load()를 사용하면 AttributeError: 'str' object has no attribute 'read' 가 발생하니 주의하기 바랍니다. 

# use json.loads() instead of json.load()

st_python2 = json.load(st_json3)

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)
<ipython-input-54-9de49903fef6> in <module>()
----> 1 st_python2 = json.load(st_json3)

C:\Users\admin\Anaconda3\lib\json\__init__.py in load(fp, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    263 
    264     """
--> 265     return loads(fp.read(),
    266         cls=cls, object_hook=object_hook,
    267         parse_float=parse_float, parse_int=parse_int,

AttributeError: 'str' object has no attribute 'read'


많은 도움이 되었기를 바랍니다. 

다음번 포스팅에서는 '웹(API)으로 부터 JSON 포맷 자료를 Python으로 읽어와서 pandas DataFrame으로 만드는 방법(https://rfriend.tistory.com/475)을 소개하겠습니다. 

Python으로 XML 파일 읽기, 쓰기는 https://rfriend.tistory.com/477 를 참고하세요. 

Python으로 YAML 파일 읽기, 쓰기는 https://rfriend.tistory.com/540 를 참고하세요. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.  :-)


728x90
반응형
Posted by Rfriend
,