查看原文
其他

Stata 网络数据爬取:JSON篇

RStata RStata 2022-05-17

本文来自 RStata 线上培训班课程「Stata网络数据爬取:JSON篇」,如果你想获取本课程的讲义材料和视频讲解,可以在公众号后台回复 获取资料 了解如何获取。

其实之前在「中国的工业企业都在哪里?—— Stata、高德接口与地理编码」(RStata 线上培训班课程)课程中就已经介绍过 Stata 处理 json 格式的数据的方法了,因为高德地图接口返回的数据就是 json 格式的。今天我将通过最近我搜集的一些案例来演示如何进行网页分析以及使用 Stata 处理 JSON 数据。

注意:由于 tssc 已经下架,大家可以通过附件中的离线安装包安装本文提到的 Stata 外部命令。

案例一:新冠肺炎疫情事件脉络

这个网站在这里:http://xgml.zhiweidata.net/

我们要爬取这个图的数据。

通过网页分析我们可以看着这个网页中的图的数据来源于两个 json 格式的数据:

  1. http://xgml.zhiweidata.net/data.json
  2. http://xgml.zhiweidata.net/line.json

使用 R 语言可以很方便的将 JSON 格式的文件读取为 list 处理(对 R 不了解的小伙伴可以跳过):

library(jsonlite)
library(lubridate)
library(tidyverse)
# http://xgml.zhiweidata.net/data.json
download.file('http://xgml.zhiweidata.net/data.json''data.json')

fromJSON('data.json') %>% 
  as_tibble() %>% 
  mutate(startTime = case_when(
    str_detect(startTime, " ") ~ startTime,
    T ~ paste0(startTime, " 00:00")
  )) %>% 
  mutate(startTime = ymd_hm(startTime)) %>% 
  select(1:8) %>% 
  select(-endTime, -id) %>% 
  mutate(keyword = if_else(
    str_length(keyword) != 24 | str_detect(keyword, "\\+"),
    keyword, ""
  )) %>% 
  type_convert() %>% 
  writexl::write_xlsx("data.xlsx")

# http://xgml.zhiweidata.net/line.json
download.file('http://xgml.zhiweidata.net/line.json''line.json')

fromJSON("line.json") %>% 
  as_tibble() %>% 
  type_convert() %>% 
  writexl::write_xlsx("line.xlsx")

上面的代码就是将这两个数据处理成了 xlsx 文件。

那么我们的 Stata 用户如何处理这个数据呢?使用 insheetjson 即可,这个命令很强大的!

首先是安装:

ssc install insheetjson
ssc install libjson

如果你无法使用 ssc 安装 insheetjson 和 libjson 也没关系,我已经在附件里准备好了这两个命令的离线安装包,可以使用 net install 安装:

net install insheetjson.pkg, from("insheetjson文件夹的路径")
net install libjson.pkg, from("libjson文件夹的路径")

使用 Stata 开头两句:

clear all
cd "工作目录的路径"

我们首先把 data.json 和 line.json 下载下来:

copy "http://xgml.zhiweidata.net/data.json" "data.json", replace
copy "http://xgml.zhiweidata.net/line.json" ., replace

首先处理 data.json:

* 查看响应:
insheetjson using "data.json", showresponse flatten

可以看到,一共有 346 个观测值,我们先把第一个观测值读入 Stata:

clear
gen str1000 startTime = ""
gen str1000 name = ""
gen str1000 keyword = ""
gen str1000 type = ""
gen str1000 index = ""
gen str1000 value = ""
insheetjson startTime name keyword type index value using "data.json", table(1) col("startTime" "name" "keyword" "type" "index" "value") replace

然后再循环把剩下的 345 个逐个读入,使用 offset 选项即可设置读入存放到第几个观测值:

forval i = 2/346 {
local j = `i' - 1
qui insheetjson startTime name keyword type index value using "data.json", table(`i') col("startTime" "name" "keyword" "type" "index" "value") replace offset(`j')
}

下面再处理下:

compress
drop if startTime == ""
replace startTime = startTime + " 0:00" if !ustrregexm(startTime, " ")
replace startTime = subinstr(startTime, substr(startTime, -5, .), "", .)
gen date = date(startTime, "YMD")
order date
format date %tdCY-N-D
drop startTime
format name %20s
format keyword %20s
replace keyword = "" if ustrregexm(keyword, "100")
replace index = "" if index == `""""'
destring, replace
gsort date
save data, replace

得到的 data.dta 是这样的:

同样的方法处理 line:

copy "http://xgml.zhiweidata.net/line.json" ., replace
insheetjson using "line.json", showresponse flatten
clear
gen str100 time = ""
gen str100 voice = ""
gen str100 heat = ""
gen str100 case = ""
gen str100 allCase = ""
insheetjson time voice heat case allCase using "line.json", table(1) col("time" "voice" "heat" "case" "allCase") replace
forval i = 2/105 {
local j = `i' - 1
qui insheetjson time voice heat case allCase using "line.json", table(`i') col("time" "voice" "heat" "case" "allCase") replace offset(`j')
}
compress
replace voice = "" if voice == `""""'
destring, replace
save line, replace

得到的 data.dta 是这样的:

案例二:获取 VINSIGHT 网站上的系统性风险数据

这个网站在这里:http://vinsight.shanghai.nyu.edu/srisk.php

我们要爬取这个图里面的数据。

通过网页分析可以发现这个图的数据在这里:http://vinsight.shanghai.nyu.edu/core/get_srisk.php?assetid=china&number=10000

这个数据更好处理了,这是个 JSON 数组!

clear all
* http://vinsight.shanghai.nyu.edu/srisk.php
copy "http://vinsight.shanghai.nyu.edu/core/get_srisk.php?assetid=china&number=10000" "srisk.json", replace

insheetjson using "srisk.json", showresponse flatten
clear
gen str100 datetemp = ""
gen str100 value = ""
insheetjson datetemp value using "srisk.json", col("1" "2") replace aoa(1)
compress
destring, replace
gen date = date(datetemp, "YMD")
format date %tdCY-N-D
drop datetemp
order date
save 系统性风险, replace

JSON 数据不需要循环处理,可以直接读入。

这个网页下面还有一个图:

这个图的数据不用网页分析查看在哪里,因为它在源代码里面,我们把数据部分复制保存下:

* 先手动把网页中数据对应的部分保存为 `系统性风险排名.json` 文件
clear
insheetjson using "系统性风险排名.json", showresponse flatten
gen str100 asset_id = ""
gen str100 english = ""
gen str100 chinese = ""
gen str100 srisk = ""
insheetjson using "系统性风险排名.json", table(1) col("asset_id" "english" "chinese" "srisk") replace
forval i = 2/72 {
local j = `i' - 1
qui insheetjson using "系统性风险排名.json", table(`i') col("asset_id" "english" "chinese" "srisk") replace offset(`j')
}
compress
destring, replace
* Unicode 字符串转换:
replace chinese = ustrunescape(chinese)
save 系统性风险排名, replace

系统性风险排名.dta 是这样的:

这个的处理和案例一是一样的。

案例三:沪深 300 VIX 与 上证 50 VIX

这个网页在这里:http://vinsight.shanghai.nyu.edu/implied_moments.php

从两个图里面可以爬取到沪深 300 波动率指数和 上证 50 波动率指数,同样它们的数据在源代码里,这个的处理和上面的案例二中的 系统性风险排名 的爬取类似,所以我把这个作为作业留给大家。

案例四:爬取和讯网的AB股上市公司信息列表

这个数据在这里:

http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=5000&titType=null&page=1&callback=hxbase_json15

大概没有比这个更加丧心病狂的了!这其实不是一个 JSON 数据,而是个 JS 对象(带回掉函数的),关于 JSON 与 JS 对象的关系,百度百科里面给出了一个比较:

var obj = {a'Hello'b'World'}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = '{"a": "Hello", "b": "World"}'//这是一个 JSON 字符串,本质是一个字符串

但是我们现在手头上的工具是 insheetjson,如何把这个 JS 对象转换为 JSON 呢?

先下载下来再说吧!

copy "http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=5000&titType=null&page=1&callback=hxbase_json15" "temp.json", replace

这个 temp.json 文件里面只有一行,245 万个字符,这样的文件是不能直接使用 infix 之类的命令读入 Stata 的,我们可以使用 mata 程序处理它,处理的模板就是将它变成一个 json:

mata:
fin = fopen("temp.json", "r")
line = fread(fin, 3000000)
line = subinstr(line, `"'"', `"""', .)
line = subinstr(line, `""_blank""', `"'_blank'"', .)
line = subinstr(line, `"openshowd(this,""', "", .)
line = subinstr(line, `"","1")"', "", .)
line = subinstr(line, `""Closed(this)""', "", .)
line = subinstr(line, `"<img alt="" src=""', "", .)
line = subinstr(line, `""/>"', "", .)
line = subinstr(line, "sum", `""sum""', .)
line = subinstr(line, "list", `""list""', .)
line = subinstr(line, "Number", `""Number""', .)
line = subinstr(line, "StockNameLink", `""StockNameLink""', .)
line = subinstr(line, "Stockname", `""Stockname""', .)
line = subinstr(line, "Pricelimit", `""Pricelimit""', .)
line = subinstr(line, "lootchips", `""lootchips""', .)
line = subinstr(line, "shareholders", `""shareholders""', .)
line = subinstr(line, "Institutional", `""Institutional""', .)
line = subinstr(line, "Iratio", `""Iratio""', .)
line = subinstr(line, "deviation", `""deviation""', .)
line = subinstr(line, "maincost", `""maincost""', .)
line = subinstr(line, "district", `""district""', .)
line = subinstr(line, "Cprice", `""Cprice""', .)
line = subinstr(line, "Stockoverview", `""Stockoverview""', .)
line = subinstr(line, "hyLink", `""hyLink""', .)
line = subinstr(line, "dyLink", `""dyLink""', .)
line = subinstr(line, "gnLink", `""gnLink""', .)
line = subinstr(line, "StockLink", `""StockLink""', .)
line = subinstr(line, "Addoptional", `""Addoptional""', .)
line = subinstr(line, "hxbase_json15(", "", .)
line = subinstr(line, "}]})", "}]}", .)
fclose(fin)
mata stata cap erase temp2.json
fout = fopen("temp2.json", "w")
fwrite(fout, line)
fclose(fout)
end

处理之后我们得到了一个 temp2.json 文件,这个文件就是一个 JSON 数据了:

我们先给这个文件转下码,因为我注意到里面有乱码:

utrans temp2.json

utrans 是我写的一个非常简单的小命令:

*! utf-8中文转码
*! utrans 文件名.后缀名
*! 示例:utrans temp.do
cap prog drop utrans
prog define utrans
version 14.0
syntax anything
cap preserve
clear
cap qui{
unicode encoding set gb18030
unicode translate "`anything'"
unicode erasebackups, badidea
unicode analyze "`anything'"
unicode erasebackups, badidea
}
if r(N_needed) == 0 di in yellow "转码完成"
if r(N_needed) != 0 di in red "转码失败"
end

查看响应:

insheetjson using "temp2.json", showresponse flatten

存储为 Stata 数据:

clear
gen str100 Stockname = ""
gen str100 Pricelimit = ""
gen str100 lootchips = ""
gen str100 shareholders = ""
gen str100 Institutional = ""
gen str100 Iratio = ""
gen str100 district = ""
gen str100 Cprice = ""
gen str200 maincost = ""
insheetjson Stockname Pricelimit lootchips shareholders Institutional Iratio district Cprice maincost using "temp2.json", table(list) col("Stockname" "Pricelimit" "lootchips" "shareholders" "Institutional" "Iratio" "district" "Cprice" "maincost")
replace district = ustrregexs(1) if ustrregexm(district, ">(.*)<")
replace maincost = ustrregexs(1) if ustrregexm(maincost, `"'>(.*)</a"')
foreach i of varlist _all {
replace `i' = "" if `i' == "--"
}
compress
destring, replace
save stocklist, replace

stocklist.dta 数据是这样的:

是不是感觉超酷,因为我们知道爬虫俱乐部写了个 cnstock 命令:

* 安装 cnstock
ssc install cnstock

不过这个命令只能获取股票的代码和名称,我们这段程序呢,可以获取股票的详细信息!那我们不编一个命令是不是可惜了?

*! TidyFriday
*! 2020 年 5 月 22 日
cap prog drop cnstock2
prog def cnstock2
version 7.0
di in yellow "下载中..."
qui{
clear
copy "http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=5000&titType=null&page=1&callback=hxbase_json15" "temp.json", replace
cap erase temp2.json
mata: file()
* 处理 json 格式的数据
* 转码
unicode encoding set gb18030
unicode translate "temp2.json"
unicode erasebackups, badidea
gen str100 Stockname = ""
gen str100 Pricelimit = ""
gen str100 lootchips = ""
gen str100 shareholders = ""
gen str100 Institutional = ""
gen str100 Iratio = ""
gen str100 district = ""
gen str100 Cprice = ""
gen str200 maincost = ""
insheetjson Stockname Pricelimit lootchips shareholders Institutional Iratio district Cprice maincost using "temp2.json", table(list) col("Stockname" "Pricelimit" "lootchips" "shareholders" "Institutional" "Iratio" "district" "Cprice" "maincost")
replace district = ustrregexs(1) if ustrregexm(district, ">(.*)<")
replace maincost = ustrregexs(1) if ustrregexm(maincost, `"'>(.*)</a"')
foreach i of varlist _all {
replace `i' = "" if `i' == "--"
}
compress
destring, replace
cap erase temp.json
}
di in green "获取成功..."
end

mata:
void file() {
fin = fopen("temp.json", "r")
line = fread(fin, 3000000)
line = subinstr(line, `"'"', `"""', .)
line = subinstr(line, `""_blank""', `"'_blank'"', .)
line = subinstr(line, `"openshowd(this,""', "", .)
line = subinstr(line, `"","1")"', "", .)
line = subinstr(line, `""Closed(this)""', "", .)
line = subinstr(line, `"<img alt="" src=""', "", .)
line = subinstr(line, `""/>"', "", .)
line = subinstr(line, "sum", `""sum""', .)
line = subinstr(line, "list", `""list""', .)
line = subinstr(line, "Number", `""Number""', .)
line = subinstr(line, "StockNameLink", `""StockNameLink""', .)
line = subinstr(line, "Stockname", `""Stockname""', .)
line = subinstr(line, "Pricelimit", `""Pricelimit""', .)
line = subinstr(line, "lootchips", `""lootchips""', .)
line = subinstr(line, "shareholders", `""shareholders""', .)
line = subinstr(line, "Institutional", `""Institutional""', .)
line = subinstr(line, "Iratio", `""Iratio""', .)
line = subinstr(line, "deviation", `""deviation""', .)
line = subinstr(line, "maincost", `""maincost""', .)
line = subinstr(line, "district", `""district""', .)
line = subinstr(line, "Cprice", `""Cprice""', .)
line = subinstr(line, "Stockoverview", `""Stockoverview""', .)
line = subinstr(line, "hyLink", `""hyLink""', .)
line = subinstr(line, "dyLink", `""dyLink""', .)
line = subinstr(line, "gnLink", `""gnLink""', .)
line = subinstr(line, "StockLink", `""StockLink""', .)
line = subinstr(line, "Addoptional", `""Addoptional""', .)
line = subinstr(line, "hxbase_json15(", "", .)
line = subinstr(line, "}]})", "}]}", .)
fclose(fin)
fout = fopen("temp2.json", "w")
fwrite(fout, line)
fclose(fout)
}
end

注意 Stata 命令里面的 ado 代码和 mata 代码要分开,不能直接在 ado 代码里面嵌入 mata 代码块,因为 ado 命令是以 end 为识别标志结束的。

然后运行:

cnstock2
*> 下载中...
*> File temp2.json (text file)
*> 获取成功...

获取的结果:

是不是超酷!

作业

案例三就是大家要完成的作业,不要忘记哈!

获取该课程讲义材料

本文来自 RStata 线上培训班课程「Stata网络数据爬取:JSON篇」,如果你想获取本课程的讲义材料和视频讲解,可以在公众号后台回复 获取资料 了解如何获取。

其它数据分享

工企位置(1998~2007) // 工企位置(2008~2013) // 行政村区划(2019) // 县域年鉴(2015~2019) // 县域年鉴(2014) // 乡镇区划(2018~2019) // 区县行政区划(2009~2019) // 新冠疫情数据

推荐阅读


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存