利用正则表达式从统计公报中爬取信息形成结构化统计数据
Posted python和R数据分析流程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用正则表达式从统计公报中爬取信息形成结构化统计数据相关的知识,希望对你有一定的参考价值。
1 引言
现在是毕业季,相信很多同学都在忙着毕业论文,而毕业论文中最关键的问题之一就是数据来源,正所谓巧妇难为无米之炊,即使再厉害的文笔,没有一份好的数据也很难写出漂亮的论文。而在如今信息化的时代,数据来源的渠道有很多种,比如现成的统计年鉴、各种机构出的调查数据等等。但有时候,我们需要用到一些县级层面的数据时,只有去统计公报中找,当然我们大可以受到复制粘贴去抽取出我们需要的信息,在需要的数据量很少时,不妨这样做,但当我们需要的数据量比较大时,就可以考虑利用计算机程序语言来完成了。因此,本文基于收集数据的目的,讲述如何用正则表达式从连片的文字信息中抽取出我们需要的数据,并形成结构化的数据表,而正则表达式的实现则用如今炙手可热的python,python中有个re库是实现正则表达式的。
本文的内容安排如下:第2章简单概括正则表达式的基本语法(说明:包括三段代码快,待匹配文本、匹配模式和匹配结果,匹配到的字符串用粗体表示),第3章讲用python的re库实现正则表达式,第4章进行实战操作,第5章注意事项。
2 正则表达式的基本语法
2.1 匹配单个字符
待匹配文本{1}
Hello, my name is JicYoum. my WehChat public number is Python__R
匹配模式
JicYoum
匹配结果Hello, my name is
JicYoum
. my WehChat public number is Python__R
注意:正则表达式区分大小写,因此jicyoum不能匹配JicYoum
多个结果的情况
{1}
匹配模式
my
匹配结果Hello,
my
name is JicYoum.
my
WehChat public number is Python__R
2.1.1 匹配任意字符
待匹配文本{2-11}
sales.xlssales1.xlsorders3.xlssales2.xlssales3.xlsapac1.xlseurope2.xlsna1.xlsna2.xlssa1.xls
匹配模式
sales.
. 可以匹配任何单个字符包括本身,不能匹配换行符。(计算机中有换行符和回车符,可自行百度了解)
匹配结果sales.
xlssales1
.xls
orders3.xlssales2
.xlssales3
.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls
2.1.2 匹配特殊字符
{2-11}
匹配模式
.a.\.xls
当我们要匹配“.”字符时,我们要告诉正则表达式,我们要匹配字符本身而不是其代表的“任何字符”,因此我们使用元字符 \ (转义字符,表示“这个字符有特殊的含义,而不是字符本身的含义”),因此 . 代表一个普通的点字符。
sales.xls
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xlsna1.xls
na2.xls
sa1.xls
2.2 匹配一组字符
2.2.1 匹配一组字符中的一个
{2-11}
匹配模式
[ns]a.\.xls
[和]不匹配任何字符,用来定义一个字符集合,此处匹配任意以n或s开题的字符串
思考:如何去除一个字符变量a的空白字符和换行符,a.replace('\s','').replace('\n', '') 在学正则表达式前我一般这样做,利用正则表达式re.sub(patter,relacement,sting)有更好的处理方式。
匹配结果
sales.xls
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xlsna1.xls
na2.xls
sa1.xls
2.2.2 利用字符集合区间
{2-11}
匹配模式
[ns]a[0123456789]\.xls 或者 [ns]a[0-9]\.xls
只有在[]里才是具有特殊意愿的连字符,否则只是普通字符,尾字符不能比首字符小,否则就没意义,常用字符集合还有[A-Z], [a-z]
匹配结果
sales.xls
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xlsna1.xls
na2.xls
sa1.xls
2.2.3 取非匹配
待匹配文本
sales.xlssales1.xlsorders3.xlssales2.xlssales3.xlsapac1.xlseurope2.xlssam.xlsna1.xlsna2.xlssa1.xlsca1.xls
匹配模式
[ns]a[^0-9]\.xls
匹配结果
sales.xls
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xlssam.xls
na1.xls
na2.xls
sa1.xls
ca1.xls
^ 只有在字符集合里才表示取非,否则表示一个匹配模式的开头
2.3 使用元字符
空白元字符
元字符 | 说明 |
---|---|
[\b] | 回退(并删除)一个字符(Backspace键) |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符 |
\v | 垂直制表符 |
空白字符元字符
元字符 | 说明 |
---|---|
\s | 任何一个空白字符(等价于[\f\n\r\t\v]) |
\S | 任何一个非空白字符(等价于[^\f\n\r\t\v]) |
数字字母元字符
元字符 | 说明 |
---|---|
\d | 任何一个数字字符(等价于[0-9]) |
\D | 任何一个非数字字符(等价于[^0-9]) |
\w | 任何一个数字字母字符(大小均可)或下划线字符(等价于[a-zA-Z0-9]) |
\W | 任何一个非数字字母字符或下划线字符(等价于[^a-zA-Z0-9]) |
2.4 重复匹配
元字符 | 说明 |
---|---|
* | 匹配前一个字符0次或无穷次 |
+ | 匹配前一个字符1次或无穷次 |
? | 匹配前一个字符0次或1次 |
{m}/{m,n} | 匹配前一个字符m次或m到n次,m或n可以省略一个 |
*?/+? | 非贪婪版本,尽可能少的匹配字符 |
贪婪匹配与非贪婪匹配?
2.5 位置匹配
元字符 | 说明 |
---|---|
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
\A / \Z | 指定的字符串必须出现在开头 / 结尾 |
2.6 子表达式
定义:子表达式是一个更大的表达式的一部分,把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当做独立元素来使用。
待匹配文本
微信号:Python__R。在2018年2月24日发出了第一篇推文,是关于如何抓取网页中表格,以后会一直慢慢更新的,请有大家关注下微信号:Python__R,谢谢各位的支持。
匹配模式
微信号:(\w+){2,}
匹配结果
子表达式
元字符 | 说明 |
---|---|
| | 匹配左右任意一个表达式 |
(ab) | 括号中表达式作为一个分组 |
(?P<name>) | 给分组起一个别名 |
\<number> | 引用编号为number的分组匹配到的字符串 |
(?P=name) | 引用别名为name的分组匹配到的字符串 |
正则表达式还有引用、前后查找、嵌套条件等内容,推荐看《正则表达式必知必会》
3 正则表达式的python实现:re模块
python正则表达式基本语法与第二章所述基本一致,有补充之处可以参考官方文档python regular expression operation
re模块中处理字符串的方法主要有:re.match(),re.search(),re.findall(),re.sub(),re.split()
前3个方法是用来搜寻匹配字符串。
re.match()和re.search()主要区别是re.match()需要从字符串的开头匹配,而re.search()可以从整个文本中搜寻匹配,两个方法返回的都是一个match对象可以进一步对这个match对象采用group方法
match.group(0),返回的是匹配结果本身,match.group(1-),返回的匹配结果中的子匹配,也可以用match.groups(),以元组的形式返回所有子匹配结果re.findall()和前连个方法的区别是,它返回的是一个match对象的列表。
re.sub()和re.split()分别用正则表达式对字符串进行替换和切割操作。
4 用正则表达式爬取统计公报数据
4.1 确定爬取目标
我们以中国最有名的县之一——凤凰县为例,来说说明正则表达式的应用,首先我们需要去凤凰县的政府网站确定其统计公报的发布网址如下:
urls = {
2017: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201803/t20180316_871446.html',
2016: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201706/t20170614_706524.html',
2015: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201609/t20160924_311672.html',
}
作为研究案例,就选取凤凰县2015-2017年三年的统计年鉴作为爬虫目标,而需要爬取的指标如下表所示,都是一些常规的经济指标。
年份 | 生产总值 | 财政总收入 | 农林牧渔业总产值 | 工业增加值 | 全县总人口 | 城镇居民人均可支配收入 | 农村居民人均可支配收入 |
---|
4.2 爬取实战
直接先上代码
import reimport requestsimport numpy as npimport pandas as pdfrom bs4 import BeautifulSoup
USER_AGENT_LIST = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]
urls = { 2017: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201803/t20180316_871446.html', 2016: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201706/t20170614_706524.html', 2015: 'http://www.fhzf.gov.cn/zwgk/xzfxxgkml/tjxx/201609/t20160924_311672.html',
}
headers = { 'User-Agent': np.random.choice(USER_AGENT_LIST)
}def get_info(year, url):
res = requests.get(url, headers=headers)
res.encoding = 'UTF-8'
html = res.text
soup = BeautifulSoup(html, 'lxml')
info_txt = soup.select('.con1_text')[0].text.strip()
pattern = re.compile('生产总值为?([\d.\w]+?)元.*?财政总收入)?([\d.\w]+?)元.*?农林牧渔业总产值([\d.\w]+?)元'
+ '.*?工业增加值([\d.\w]+?)元.*?全县总人口([\d.\w]+?)人.*?城镇居民人均可支配收入([\d.\w]+?)元.*?农村居民人均可支配收入([\d.\w]+?)元', re.S)
result = re.search(pattern, info_txt) return { '年份': year, '生产总值': result.group(1), '财政总收入': result.group(2), '农林牧渔业总产值': result.group(3), '工业增加值': result.group(4), '全县总人口': result.group(5), '城镇居民人均可支配收入': result.group(6), '农村居民人均可支配收入': result.group(7)
}
result_list = []for year, url in urls.items():
info = get_info(year, url)
result_list.append(info)
df = pd.DataFrame(result_list)
最终爬取的结果如下:
凤凰县指标.png
代码解读:代码的主体是定义了一个获取指标数据的函数,然后利用循环爬取三年的数据,用到的模块是实现正则表达式的re模块、模拟浏览器获取网站信息的requests模块、解析网页的BeautifulSoup模块,以及用于数据处理的numpy和pandas模块。
头部信息:由于政府网站可能会有些限制,因此加入浏览器头伪装成浏览器访问,浏览器头部信息存在USER_AGENT_LIST中,每次访问随机从中取一个。
获取网页内容并解析:用requests获取网页内容,并用BeautifulSoup解析取得统计公报的完整内容。
用正则表达获取指标数据:正则表达式中有7个用括号括起来的子表达式,代表需要爬取的7个指标数据。([\d.\w]+?),其中\d.代表一个数字,由于可能是小数所以家里''.'',用\w把单位也爬下来+?表示非贪婪匹配。
把结果返回到一个字典中。
利用循环把爬取的各年的字典结构的数据加入列表中,然后用pandas把其转化成数据框。
5 注意事项
本文的例子是相对比较简单和理想的情况,在实际应用中,特别是要爬取的年份比较多的时候,可能每年统计公报的差异会表较大,这时候需要获取数据就要编写逻辑更复杂的正则表达式。但有时候如果,正则表达式编写的难度太大,需要花费太多精力,还不如放弃那一两个指标,把很难解决的问题直接用复制粘贴替代,有时候反而能节约时间精力。因为,正则表达式本身的目的就是使复杂问题简单、高效,有的时候为了写出一个“高大上”的正则表达式而浪费大量时间是得不偿失的。
下期预告:
以上是关于利用正则表达式从统计公报中爬取信息形成结构化统计数据的主要内容,如果未能解决你的问题,请参考以下文章