为了让大家更好的理解上面的内容,欢迎各位培训班会员参加明晚 8 点的直播课 「Stata 网络数据爬取:JSON 篇」
该课程是「Stata 编程导论」的第 7 课时,课程主页(点击文末的阅读原文即可跳转):
#/brief/course/57571646805e4786b622867cfd5ad1d9
其实之前在 使用 Stata 进行地理编码:地址解析经纬度、坐标转换 & 根据经纬度判断所处的省市区县 课程中就已经介绍过 Stata 处理 json 格式的数据的方法了,因为高德地图接口返回的数据就是 json 格式的。今天我将通过最近我搜集的一些案例来演示如何进行网页分析以及使用 Stata 处理 JSON 数据。
使用 Stata 进行地理编码:地址解析经纬度、坐标转换 & 根据经纬度判断所处的省市区县:#/brief/course/537300af1a9947edb758789785c600f3
案例一:新冠肺炎疫情事件脉络
这个网站在这里:
我们要爬取这个图的数据。
通过网页分析我们可以看着这个网页中的图的数据来源于两个 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
同样的方法处理 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
案例二:获取 VINSIGHT 网站上的系统性风险数据
这个网站在这里:
我们要爬取这个图里面的数据。
通过网页分析可以发现这个图的数据在这里:
这个数据更好处理了,这是个 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
*- 该网站如果挂了,可以直接处理附件中的 srisk.json 数据
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
replace value = value / 100
tw area value date, xla(#6) xti("") yti("系统性风险(亿美元)") ///
ti("中国历年系统性风险(亿美元)") ///
subti("数据爬取 & 绘图:微信公众号 RStata") ///
caption("数据来源:http://vinsight.shanghai.nyu.edu/srisk.php")
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
use 系统性风险排名, clear
*- ssc install sencode
sencode chinese, gen(country) gsort(-srisk)
sum country
keep if country <= 10
replace srisk = srisk / 1000
tw bar srisk country, hori yla(1(1)10, val noticks) ///
base(100) barwidth(0.8) ///
ysc(reverse noline) yti("") xti("系统风险-SRISK(亿美元)") ///
ti("系统风险排名 - SRISK Ranking") ///
subti("数据爬取 & 绘图:微信公众号 RStata") ///
caption("数据来源:http://vinsight.shanghai.nyu.edu/srisk.php")
这个的处理和案例一是一样的。
案例三:沪深 300 VIX 与 上证 50 VIX
这个网页在这里:
从两个图里面可以爬取到沪深 300 波动率指数和 上证 50 波动率指数,同样它们的数据在源代码里数组转json,这个的处理和上面的案例二中的 系统性风险排名 的爬取类似,所以我把这个作为作业留给大家。
案例四:爬取和讯网的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, `", "", .)
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,这个文件就是一个 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
是不是感觉超酷,因为我们知道爬虫俱乐部写了个 cnstock 命令(改命令爬取的就是上次课的最后一个案例,最近这个命令挂了):
*- 安装 cnstock
ssc install cnstock
不过这个命令只能获取股票的代码和名称,我们这段程序呢,可以获取股票的详细信息!那我们不编一个命令是不是可惜了?
*! 微信公众号 RStata
*! 2023 年 1 月 27 日
cap prog drop cnstock3
prog def cnstock3
version 7.0
di in yellow "欢迎使用微信公众号 RStata 开发的 cnstock3 命令,该命令可以实时下载中国上市公司的详细信息。"
di in yellow "下载中..."
qui{
clear
tempfile filein fileout
copy "http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=5000&titType=null&page=1&callback=hxbase_json15" `filein', replace
mata: file("`filein'", "`fileout'")
* 处理 json 格式的数据
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 "`fileout'", 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
}
di in green "获取成功..."
end
mata:
void file(string scalar filein, string scalar fileout) {
fin = fopen(filein, "r")
line = fread(fin, 3000000)
line = ustrfrom(line, "GBK", 4)
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, `", "", .)
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(fileout, "w")
fwrite(fout, line)
fclose(fout)
}
end
注意 Stata 命令里面的 ado 代码和 mata 代码要分开,不能直接在 ado 代码里面嵌入 mata 代码块,因为 ado 命令是以 end 为识别标志结束的。
另外注意这里的代码和前面的代码有几处不同:
ado 里面尽量不要下载文件到本地,而应该使用临时文件:tempfile filein fileout;
unicode encoding set gb18030、unicode translate “文件名”、unicode erasebackups, badidea 或者 utrans 命令只能转码工作目录下面的文件,因此这里使用的是 line = ustrfrom(line, “GBK”, 4) 进行转码。
把这部分代码保存为 cnstock3.ado 即可使用。
然后运行:
cnstock3
获取的结果:
是不是超酷!
作业
案例三就是大家要完成的作业,不要忘记哈!
直播信息
为了让大家更好的理解上面的内容,欢迎各位培训班会员参加明晚 8 点的直播课 「Stata 网络数据爬取:JSON 篇」
直播地址:腾讯会议(需要报名 RStata 培训班参加)
讲义材料:需要报名 RStata 培训班,详情可阅读:
更多关于 RStata 会员的更多信息可添加微信号 r_stata 咨询:
限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688