Stata 网络数据爬取:JSON篇
本文来自 RStata 线上培训班课程「Stata网络数据爬取:JSON篇」,如果你想获取本课程的讲义材料和视频讲解,可以在公众号后台回复
获取资料
了解如何获取。
其实之前在「中国的工业企业都在哪里?—— Stata、高德接口与地理编码」(RStata 线上培训班课程)课程中就已经介绍过 Stata 处理 json 格式的数据的方法了,因为高德地图接口返回的数据就是 json 格式的。今天我将通过最近我搜集的一些案例来演示如何进行网页分析以及使用 Stata 处理 JSON 数据。
注意:由于 tssc 已经下架,大家可以通过附件中的离线安装包安装本文提到的 Stata 外部命令。
案例一:新冠肺炎疫情事件脉络
这个网站在这里:http://xgml.zhiweidata.net/
我们要爬取这个图的数据。
通过网页分析我们可以看着这个网页中的图的数据来源于两个 json 格式的数据:
http://xgml.zhiweidata.net/data.json 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) // 新冠疫情数据
推荐阅读
Stata 专题课程 | 我们分析了 65.8 万个村名,找到了中国地名的秘密 超级细致、超级好用的新冠疫情数据来啦!免费分享! 独家!合并整理好的 Stata + Excel 格式的中国县域统计年鉴数据来了!2015 ~ 2019 年合集 Stata 中的数据管理 如何摆脱庸俗的商务范 PPT?交互式的幻灯片你玩的转么?RMarkdown、幻灯片和交互式图表 县域统计年鉴市如何被整理好的?——以2016年的为例 R 语言和 RStudio 的安装、R Profile 的配置及初识 R 语言数据爬取与可视化 我的微信公众号有什么隐藏的功能? Stata 爬取新三板挂牌数据 Stata VS. R:如何绘制填充地图 使用 R 语言绘制交互式席位分摊图 Stata 如何绘制如何获得具有不同箱宽的直方图? 使用 R 语言绘制交互式可变宽度柱形图 Stata 中如何解决散点重叠问题? 如何在几秒钟内完成 Stata 外部命令的安装? 使用 ggplot2 绘制填充地图 使用 Stata 绘制填充地图