网页表格抓取(本文以爬取东方财富网CPI数据[1],讲解如何使用Stata)
优采云 发布时间: 2021-10-15 15:15网页表格抓取(本文以爬取东方财富网CPI数据[1],讲解如何使用Stata)
本文以东方财富网[1]的CPI数据为例,讲解如何使用Stata抓取web表数据。
Stata虽然不是数据爬取工具,但是可以轻松解决一些小的数据爬取任务。数据爬取的本质无非是数据请求和数据处理,所以熟练使用Stata进行数据爬取往往是良好数据处理能力的标志。在实际应用中,我们经常需要爬取一些公共数据。显示这些数据的一种常见方式是通过 HTML 呈现。一个简单的 HTML 表格示例如下:
1
2
3
1
2
3
东方财富网CPI数据表是这样的:
下面我将一步步解释如何爬取这张表。
准备工作Stata14.0或以上;
Chrome浏览器;
想要爬下数据的你。
网络分析
先解释一下如何爬取一个页面的表单。这个页面的网址是:
在页面上右击,选择显示网页的源代码。很多浏览器都有查看网页源代码的功能,但我还是最喜欢谷歌浏览器。点击跳转到页面源码界面:
也就是说,你刚才看到的网页的本质其实就是源代码。我们之所以能看到各种五颜六色的页面,是因为浏览器已经为我们翻译了源代码。
接下来我们要做的就是找出这张表在源码中的位置,然后分析这张表的特征,方便后面Stata处理源码的时候过滤。
一种经常用于查找目标的方法是使用搜索功能。Ctr+F(Mac 的 Command + F)可以打开搜索框。我们注意到表中有两个词,所以我们用月份来搜索。
我们第一次发现这个词是在1987行,仔细一看,这里附近的代码就是表的代码。
下一步就是分析这部分代码的特点:表中的所有数据都位于源代码中的一行,所以我们不能从它所在的行开始;
表数据的前一行要么有一个字符串
经过上面的网页分析后,我们就可以开始爬取网页表单了。
开始爬行
总的来说,Stata 抓取网页表单分为 3 个步骤:请求:下载收录所需数据的源代码;
转码:许多网页不使用 UTF-8 编码。直接读入Stata会造成乱码,所以可以提前进行UTF-8转码;
处理:主要是处理字符串。常见的操作包括拆分、转置(sxpose)、提取(正则表达式或直接字符串提取)等。
问
由于本网页没有防爬机制,可以直接使用copy命令下载。copy命令不仅可以下载网页,还可以下载文件(当然,网页其实就是一个html文件)。更多的使用可以帮助复制。这里我们将要抓取的页面保存为一个名为temp.txt的txt文件。为什么会有这个名字,因为爬完之后会被删除,所以它只是一个临时文件。其实Stata也可以创建临时文件,运行结束会自动删除,不过我觉得没有这个必要,因为通常需要打开temp.txt文件分析一下内容。
清除所有
* 设置工作目录
cd'你自己的工作目录(文件夹的路径)'
复制'#39; temp.txt,替换
转码
在我的Stata命令package-finance包中,我写了一个简单的转码命令。这个命令包的安装方法是: * 首先需要安装github命令,这个命令用于在GitHub上安装命令
* net install github, from('#39;)
* 然后你可以安装这个命令
* github 安装 czxa/finance,替换
安装成功后,使用以下命令直接对temp.txt文件进行转码:
utrans temp.txt
如果返回的结果是转码成功,说明转码成功!(感觉像是在胡说八道……)
如果由于各种原因不幸安装这个小命令失败,可以直接使用以下三个命令进行转码:unicode encoding set gb18030
unicode 翻译 temp.txt
unicode 擦除备份,badidea
读
接下来我们将temp.txt文件读入Stata进行处理。一个很常见的读取方法是使用中缀命令:
使用 temp.txt 中缀 strL v 1-20000,清除
* 将变量 v 的显示格式更改为 %60s(使其看起来更宽)
格式 v %60s
这个命令的意思是创建一个strL格式的变量v,然后读取temp.txt文件每一行的前1-20000个字符(因为我们注意到temp.txt的每一行不超过20000字符)输入变量 v 的每个观测值,读入后如下所示:
处理
首先,根据我们在上面网页分析中发现的结论,我们保留上一行匹配表数据或有字符串'
* 再次删除空观察
删除如果 v ==''
* 删除一些明显不是表中数据的观察,这些无用的观察都收录斜线
删除如果索引(v,'/')
这一步的结果:
其实到这里,我们已经把表格组织的很干净了,但是这不像一个表格,我们需要进行这样的操作:我们注意到第1-13行实际上是表格的第一行,14- 26 该行实际上是表的第二行。我们怎样才能完成这样的操作呢?一个非常有用的命令是 post 命令。这个命令的功能就像它的名字-邮局一样。它可以将v的每个观察值发送到我们想要获取的表的指定位置。
* 第一步是在postfile中创建一个“邮局”,并设置“收件人”(变量日期,v1-v12):
postfile mypost str20 日期 str20 v1 str20 v2 str20 v3 ///
str20 v4 str20 v5 str20 v6 str20 v7 str20 v8 str20 v9 ///
str20 v10 str20 v11 str20 v12 使用 cpi.dta,替换
* 然后循环发送v的值给每个接收者
forval i = 1(13)`=_N'{
post mypost (v[`i']) (v[`i' + 1]) (v[`i' + 2]) (v[`i' + 3]) ///
(v[`i' + 4]) (v[`i' + 5]) (v[`i' + 6]) (v[`i' + 7]) ///
(v[`i' + 8]) (v[`i' + 9]) (v[`i' + 10]) (v[`i' + 11]) ///
(v[`i' + 12])
}
* 关闭邮局 mypost
关闭我的帖子
* 打开 cpi.dta 查看整理后的数据
使用 cpi,清除
在这个“邮局操作”之后,数据现在是这样的:
至此,我们其实已经完成了对这张表的爬取,但是接下来我们会将数据组织成更正规的Stata数据。* 组织日期变量
替换日期 = subinstr(date,'year','', .)
替换日期 = subinstr(date,'month','', .)
* date() 函数将字符串日期转换为Stata日期
gen date1 = date(date,'YM')
* 格式很容易让我们人类理解
格式日期1 %tdCY-N
* 将 date1 变量放在第一列
订单日期1
* 删除日期
下车日期
* 将 date1 重命名为 date
ren date1 日期
* 循环所有变量并删除%
foreach i 的 varlist _all{
cap 替换 `i' = subinstr(`i','%','', .)
}
* 将所有可以转换为数字变量的字符串变量转换为数字变量
解串,替换
* 循环所有变量,如果变量名不是日期,将显示格式改为%6.2f
foreach i 的 varlist _all{
如果'`i'' !='date' {
格式`i' %6.2f
}
}
* 添加变量标签
标签 var date'month'
标签 var v1'National CPI'
标签 var v2'全国 CPI 年率'
标签 var v3'全国 CPI 月率'
标签 var v4'National CPI Cumulative'
标签 var v5'City CPI'
标签 var v6'城市 CPI 年率'
标签 var v7'城市 CPI 月率'
label var v8'City CPI Cumulative'
标签 var v9'农村居民消费价格指数'
label var v10'农村CPI年率'
label var v11'农村CPI月率'
标签 var v12'农村 CPI 累积'
* 数据集标签
标签数据'消费者价格指数'
* 变量重命名
ren v1 cpi_all
ren v2 cpi_all_year_rate
ren v3 cpi_all_month_rate
ren v4 cpi_all_accum
ren v5 cpi_city
ren v6 cpi_city_year_rate
ren v7 cpi_city_month_rate
ren v8 cpi_city_accum
ren v9 cpi_village
ren v10 cpi_village_year_rate
ren v11 cpi_village_month_rate
ren v12 cpi_village_accum
保存 CPI_final,替换
排序后的数据集如下所示:
就这样,我们爬上了单页表单。此外,我们还注意到完整的表格有 8 页。我们可以点击下一页,看到第二页的网址是:
显然url中的最后一个参数是页数。每个页面的结构都是一样的,所以你可以循环刚才的代码,爬下剩下的6个页面的表格,合并它们。
多页面抓取
垂直拼接示例
举个例子,我们试着用刚才的代码爬取第二页:clear
复制'#39; temp.txt,替换
utrans temp.txt
使用 temp.txt 中缀 strL v 1-20000,清除
保持如果索引(v [_n-1],'
删除如果 v ==''
删除如果索引(v,'/')
postfile mypost str20 日期 str20 v1 str20 v2 str20 v3 ///
str20 v4 str20 v5 str20 v6 str20 v7 str20 v8 str20 v9 ///
str20 v10 str20 v11 str20 v12 使用 CPI_temp.dta,替换
forval i = 1(13)`=_N'{
post mypost (v[`i']) (v[`i' + 1]) (v[`i' + 2]) (v[`i' + 3]) ///
(v[`i' + 4]) (v[`i' + 5]) (v[`i' + 6]) (v[`i' + 7]) ///
(v[`i' + 8]) (v[`i' + 9]) (v[`i' + 10]) (v[`i' + 11]) ///
(v[`i' + 12])
}
关闭我的帖子
使用 CPI_temp,清除
替换日期 = subinstr(date,'year','', .)
替换日期 = subinstr(date,'month','', .)
gen date1 = date(date,'YM')
格式日期1 %tdCY-N
订单日期1
下车日期
ren date1 日期
foreach i 的 varlist _all{
cap 替换 `i' = subinstr(`i','%','', .)
}
解串,替换
foreach i 的 varlist _all{
如果'`i'' !='date' {
格式`i' %6.2f
}
}
ren v1 cpi_all
ren v2 cpi_all_year_rate
ren v3 cpi_all_month_rate
ren v4 cpi_all_accum
ren v5 cpi_city
ren v6 cpi_city_year_rate
ren v7 cpi_city_month_rate
ren v8 cpi_city_accum
ren v9 cpi_village
ren v10 cpi_village_year_rate
ren v11 cpi_village_month_rate
ren v12 cpi_village_accum
运行这些代码后,可以得到一个类似于第一页爬取结果的表,然后可以使用append命令将该表垂直拼接到第一页爬取的数据集CPI_final.dta上:
使用 CPI_final 追加
保存 CPI_final,替换
环缝
为了一致性,我重写了上面的代码,因为有些代码可以在循环后运行,比如变量标签、变量名等。 *==================== ============*
* 东方财富消费者物价指数爬行
*================================*
* 下载第一页
清除所有
cd'~/桌面'
复制'#39; temp.txt,替换
utrans temp.txt
使用 temp.txt 中缀 strL v 1-20000,清除
保持如果索引(v [_n-1],'
删除如果 v ==''
删除如果索引(v,'/')
postfile mypost str20 日期 str20 v1 str20 v2 str20 v3 ///
str20 v4 str20 v5 str20 v6 str20 v7 str20 v8 str20 v9 ///
str20 v10 str20 v11 str20 v12 使用 CPI_temp.dta,替换
forval i = 1(13)`=_N'{
post mypost (v[`i']) (v[`i' + 1]) (v[`i' + 2]) (v[`i' + 3]) ///
(v[`i' + 4]) (v[`i' + 5]) (v[`i' + 6]) (v[`i' + 7]) ///
(v[`i' + 8]) (v[`i' + 9]) (v[`i' + 10]) (v[`i' + 11]) ///
(v[`i' + 12])
}
关闭我的帖子
使用 CPI_temp,清除
保存 CPI_final,替换
* 下一个循环从第 2 页到第 8 页并垂直缝合每一页
forval i = 2/8{
清除
复制'`i'' temp.txt,替换
utrans temp.txt
使用 temp.txt 中缀 strL v 1-20000,清除
保持如果索引(v [_n-1],'
删除如果 v ==''
删除如果索引(v,'/')
postfile mypost str20 日期 str20 v1 str20 v2 str20 v3 ///
str20 v4 str20 v5 str20 v6 str20 v7 str20 v8 str20 v9 ///
str20 v10 str20 v11 str20 v12 使用 CPI_temp.dta,替换
forval i = 1(13)`=_N'{
post mypost (v[`i']) (v[`i' + 1]) (v[`i' + 2]) (v[`i' + 3]) ///
(v[`i' + 4]) (v[`i' + 5]) (v[`i' + 6]) (v[`i' + 7]) ///
(v[`i' + 8]) (v[`i' + 9]) (v[`i' + 10]) (v[`i' + 11]) ///
(v[`i' + 12])
}
关闭我的帖子
使用 CPI_temp,清除
使用 CPI_final 追加
保存 CPI_final,替换
}
使用 CPI_final,清除
替换日期 = subinstr(date,'year','', .)
替换日期 = subinstr(date,'month','', .)
gen date1 = date(date,'YM')
格式日期1 %tdCY-N
订单日期1
下车日期
ren date1 日期
foreach i 的 varlist _all{
cap 替换 `i' = subinstr(`i','%','', .)
}
解串,替换
foreach i 的 varlist _all{
如果'`i'' !='date' {
格式`i' %6.2f
}
}
* 添加变量标签
标签 var date'month'
标签 var v1'National CPI'
标签 var v2'全国 CPI 年率'
标签 var v3'全国 CPI 月率'
标签 var v4'National CPI Cumulative'
标签 var v5'City CPI'
标签 var v6'城市 CPI 年率'
标签 var v7'城市 CPI 月率'
label var v8'City CPI Cumulative'
标签 var v9'农村居民消费价格指数'
label var v10'农村CPI年率'
label var v11'农村CPI月率'
标签 var v12'农村 CPI 累积'
* 数据集标签
标签数据'消费者价格指数'
* 变量重命名
ren v1 cpi_all
ren v2 cpi_all_year_rate
ren v3 cpi_all_month_rate
ren v4 cpi_all_accum
ren v5 cpi_city
ren v6 cpi_city_year_rate
ren v7 cpi_city_month_rate
ren v8 cpi_city_accum
ren v9 cpi_village
ren v10 cpi_village_year_rate
ren v11 cpi_village_month_rate
ren v12 cpi_village_accum
保存 CPI_final,替换
爬取结果:
至此,我们就完成了这个爬取任务。下面我们进行一个简单的应用-数据展示。
数据呈现
如果我想观察CPI的走势,我需要画一个折线图:
使用 CPI_final,清除
排序日期
* 画画
* 推荐使用我最喜欢的绘画主题plotplain
* 安装方法
ssc 安装百叶窗方案,替换所有
* 永久设置情节主题为plotplain
设置计划情节,永久
* 查看2018年6月对应的Stata日期
di date('2018-06','YM')
* /// 表示代码换行,注意下面几行绘制代码一起运行
tw ///
行 cpi_all 日期, ///
lc(blue*0.6) lp(solid) ///
xline(21336) || ///
行 cpi_city 日期, lc(dkorange) ///
lp(实心) || ///
行 cpi_village 日期, lc(orange_red) ||, ///
ti('图:消费物价指数走势', size(*1.2)) || ///
scatteri 101.90 21336 (12)'June 2018' ||, ///
leg(pos(6) row(1) order(1'全国CPI' 2'城市CPI' 3'农村CPI'))
* 以png格式导出图片
gr 导出 20181004a1.png,替换
以上一些选项的含义: ||:用于分隔图层。
line:用于绘制折线图
tw:全称twoway,用于组合多个图层
lc:全称lcolor(),控制折线图的颜色
lp:全称lpattern(),控制线型
yline:在指定位置画一条水平线
xline:在指定位置画一条垂直线
ti:全称title(),控制标题,size用来控制标题文字的大小,这里是1.2次
leg:全称是legend(),控制图例。pos 用于控制图例的位置。这是一个 6 点方向,排列成一行。
scatteri:用于在指定坐标处绘制一个点。'June 2018'是这个点的标签,(12)用于指定这个标签在该点所在的方向,指定为12点钟方向。❝
欢迎加入 TidyFriday 知识星球,获取更多学习资源:❞
参考文献[1]
东方财富网CPI数据: