BlankerL / DXY-COVID-19-Crawler

2019新型冠状病毒疫情实时爬虫及API | COVID-19/2019-nCoV Realtime Infection Crawler and API
https://lab.isaaclin.cn/nCoV/
MIT License
1.99k stars 400 forks source link

用Python如何解决嵌套(list)的问题读取地区(area)的数据? #67

Closed yijunwang0805 closed 4 years ago

yijunwang0805 commented 4 years ago

已知用以下python代码能获取全国的数据

import requests
import pandas as pd

url = 'https://lab.isaaclin.cn/nCoV/api/all?latest=0'
r = requests.request('GET', url)

data = r.json()
df = pd.DataFrame.from_records(data['results'])
df

但当我们需要读取城市/区域的数据将all改成area

import requests
import pandas as pd

url = 'https://lab.isaaclin.cn/nCoV/api/area?latest=0'
r = requests.request('GET', url)

data = r.json()
df = pd.DataFrame.from_records(data['results'])
df

输出嵌套着list,不知道如何解决? image

BlankerL commented 4 years ago

你好,由于不同省份的城市数量不定,所以丁香园和我都是选择了在cities中储存列表来保证城市长度的可变性,但在pandas读取数据时确实不太方便。

在数据挖掘过程中,载入数据必定会碰到类似的问题,大部分的数据都不是可以直接载入的,所以我下面的内容可能不止针对这一个问题,而是针对类似的问题的一个解决方案。

事实上,这个返回数据中的results也是一个list,这个list中的每一个元素都是一个字典,pandas在读取results时,会直接将list中的每一个字典作为一行或一列。然而,其中cities又是一个字典的列表,而pandas无法直接对这个数据进行解析了。

大致的数据结构如下:

"results": [
    {
        "cities": [
            {
                ...  # 这个城市内各种参数
            },
            ...  # 其他城市的字典
        ],
        ...  # 这个省份的其他变量
    },
    ...  # 其他省份
]

因此,如果希望能够解析这个数据,需要从最底层的字典列表开始逐层往上展开,即先考虑读取cities

读取cities

如果希望能够读取cities中的列表,将cities面的每一个字典都变成单独的一行,可以直接使用

temp = pd.DataFrame(r.json()['results'][0]['cities'])

这样的方法由于在results处取[0],仅能够载入第一个省份字典中的cities数据,无法载入该省份除cities以外的数据,但是已经能够将这个省份内的所有城市数据载入一个DataFrame。

读取province

在载入了cities之后,已经有一个基础的DataFrame,其中每一行是一个省份的所有城市数据,但是并不包括这个省份的其他数据,因此,需要在这个DataFrame里面添加其他列,来填充这个省份的数据。

# 其他变量也可以通过此方法处理
temp['province_confirmedCount'] = r.json()['results'][0]['confirmedCount']

读取所有省份

在上述操作完成之后,现在的这个DataFrame里面已经包括了这个省份的所有信息。只需要使用一个循环,针对results里面的每一个字典都进行上述的操作,就能载入所有省份的信息,最后将结果汇总在一张大表中即可。

大致的逻辑如下:

data = pd.DataFrame()
for province in r.json()['results']:
    temp = pd.DataFrame(province['cities'])
    temp['province_confirmedCount'] = province['confirmedCount']
    ...  # 载入其他变量
    data = data.concat([data, temp], ignore_index=True)  # 将每一个省份的数据合并到总数据中

最终即可完成数据载入工作。

BlankerL commented 4 years ago

另外,可以参考数据仓库的script.py文件,里面的csv_dumper()函数就是上述过程的完整代码。

这份代码仅包括了对cities的解析,没有对国家和港澳台地区的解析(因为这些地区没有cities,我在代码中直接剔除了所有不带cities的数据),如果能理解上面的内容,再加上其他国家和港澳台地区的数据应该也不太困难。

yijunwang0805 commented 4 years ago

你好, 我用上述方法提取了数据,现在发现似乎少了时间变量。

import pandas as pd
import os
import numpy
import requests
url = 'https://lab.isaaclin.cn/nCoV/api/area?latest=1'
r = requests.request('GET', url)
data = r.json()
df = pd.DataFrame.from_records(data['results'])
from datetime import datetime
df
data = pd.DataFrame()
temp = pd.DataFrame(r.json()['results'][0]['cities'])
# # 其他变量也可以通过此方法处理
temp['province_confirmedCount'] = r.json()['results'][0]['confirmedCount']
# data = pd.DataFrame()
from pandas import concat
for province in r.json()['results']:
    temp = pd.DataFrame(province['cities'])
    temp['province_confirmedCount'] = province['confirmedCount']
    data = pd.concat([data, temp], ignore_index=True)  # 将每一个省份的数据合并到总数据中

Anaconda 警告了

C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:9: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.

To accept the future behavior, pass 'sort=False'.

To retain the current behavior and silence the warning, pass 'sort=True'.

  if __name__ == '__main__':

最后提出来才424行,

                         cityEnglishName cityName  confirmedCount  curedCount  \
0                                Chengdu       成都           143.0        93.0   
1    Garzê Tibetan Autonomous Prefecture      甘孜州            78.0        53.0   
2                                 Dazhou       达州            42.0        28.0   
3                               Nanchong       南充            39.0        30.0   
4                                Bazhong       巴中            24.0        18.0   
..                                   ...      ...             ...         ...   
419                               Anshun       安顺             4.0         4.0   
420                            Qianxinan     黔西南州             4.0         4.0   
421                                Lhasa       拉萨             1.0         1.0   
422                               Xining       西宁            15.0        15.0   
423                               Haibei      海北州             3.0         3.0   

     currentConfirmedCount  deadCount  locationId  province_confirmedCount  \
0                     47.0        3.0    510100.0                      538   
1                     25.0        0.0    513300.0                      538   
2                     14.0        0.0    511700.0                      538   
3                      9.0        0.0    511300.0                      538   
4                      6.0        0.0    511900.0                      538   
..                     ...        ...         ...                      ...   
419                    0.0        0.0    520400.0                      146   
420                    0.0        0.0    522300.0                      146   
421                    0.0        0.0    540100.0                        1   
422                    0.0        0.0    630100.0                       18   
423                    0.0        0.0    632200.0                       18   

     suspectedCount  
0               0.0  
1               0.0  
2               0.0  
3               0.0  
4               0.0  
..              ...  
419             0.0  
420             0.0  
421             0.0  
422             0.0  
423             0.0  

[424 rows x 9 columns]

目前没找到有效的提取方法,一直在用手动输入城市名字url = 'https://lab.isaaclin.cn/nCoV/api/area?latest=0&province=北京市 一个一个城市地提取

BlankerL commented 4 years ago

@yijunwang0805

  1. 第一个代码块,url = 'https://lab.isaaclin.cn/nCoV/api/area?latest=0'才能够提取到所有时间序列数据,否则只能提取到目前最新数据;
  2. 第一个代码块,不需要使用data['results']建立DataFrame,一开始直接从cities来处理比较合适,从最下层级开始往上拆解数据;
  3. 你发的这个内容不是报错,而是pandas的concat方法在未来的版本会修改sort参数的默认属性,因此建议用户直接在函数内定义sort变量的值,否则有可能会因为未来的版本迭代而影响以前的代码,具体的内容可以参考StackOverflow,此处sort设为True或者False均可,不会影响结果。

如果方便的话可以发一下完整的代码,我可以来看看。或者也可以参考我的script.py文件。

yijunwang0805 commented 4 years ago

Hi, 现在好像有个不一样的报错了。(其实script.py文件我看了好几次,但我的代码理解能力还是很有限的)

import pandas as pd
import os
import numpy
import requests
url = 'https://lab.isaaclin.cn/nCoV/api/area?latest=0'
r = requests.request('GET', url)
data = r.json()
#df = pd.DataFrame.from_records(data['results'])
from datetime import datetime
#data = pd.DataFrame()
temp = pd.DataFrame(r.json()['results'][0]['cities'])
# # 其他变量也可以通过此方法处理
temp['province_confirmedCount'] = r.json()['results'][0]['confirmedCount']
# data = pd.DataFrame()
from pandas import concat
for province in r.json()['results']:
    temp = pd.DataFrame(province['cities'])
    temp['province_confirmedCount'] = province['confirmedCount']
    data = pd.concat([data, temp], ignore_index=True)  # 将每一个省份的数据合并到总数据中
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-58c98792dc3b> in <module>
     17     temp = pd.DataFrame(province['cities'])
     18     temp['province_confirmedCount'] = province['confirmedCount']
---> 19     data = pd.concat([data, temp], ignore_index=True)  # 将每一个省份的数据合并到总数据中

C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\reshape\concat.py in concat(objs, axis, join, join_axes, ignore_index, keys, levels, names, verify_integrity, sort, copy)
    253         verify_integrity=verify_integrity,
    254         copy=copy,
--> 255         sort=sort,
    256     )
    257 

C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\reshape\concat.py in __init__(self, objs, axis, join, join_axes, keys, levels, names, ignore_index, verify_integrity, copy, sort)
    330                     " only Series and DataFrame objs are valid".format(type(obj))
    331                 )
--> 332                 raise TypeError(msg)
    333 
    334             # consolidate

TypeError: cannot concatenate object of type '<class 'dict'>'; only Series and DataFrame objs are valid
BlankerL commented 4 years ago

Hi, 现在好像有个不一样的报错了。(其实script.py文件我看了好几次,但我的代码理解能力还是很有限的)

你的代码中由于data = r.json(),导致data是字典的格式,temp是DataFrame的格式,无法使用concat方法链接的。 我的示例代码里面最后一个代码块有这部分的内容,其中有一句data = pd.DataFrame(),就是为了解决这个问题的。

你如果需要用data变量存放r.json(),则可以使用其他变量(如_data)初始化一个DataFrame,并且concat的时候使用_data = pd.concat([_data, temp], ignore_index=True)