目录
1 前言
2021国考在即,你报名了吗?如果你的office耍得好,三下五除二便能筛选出符合自己的心仪职位;但若是耍得差,那可就……今天,咱们聊聊如何用Python筛选国考职位表——包括Excel文件读取、条件查询等。
2 Python读写Excel
Python读写Excel的方法其实很多,比如:
xlrd,xlwt,xlutils模块
openpyxl模块
pandas.read_excel,pandas.to_excel等函数
......
其中,最高效、最简洁的方法莫过于pandas
提供的各种Excel操作函数。
(1)读取Excel:
import pandas as pd
'''
pd.read_excel(io, sheetname=0,header=0,skiprows=None,index_col=None,names=None,
arse_cols=None,date_parser=None,na_values=None,thousands=None,
convert_float=True,has_index_names=None,converters=None,dtype=None,
true_values=None,false_values=None,engine=None,squeeze=False,**kwds)
'''
# sheet_name: 指定表
df1=pd.read_excel("test.xlsx") #default: Sheet1
df2=pd.read_excel("test.xlsx",sheet_name='Sheet1') #Sheet1
df3=pd.read_excel("test.xlsx",sheet_name=1) #Sheet2
# header:指定作为columns的行(default=0),数据为列名行以下的数据(不含列名可设header=None)
df4=pd.read_excel("test.xlsx ",sheet_name=0,header=2)
# index_col: 指定作为index的列
df5=pd.read_excel("test.xlsx ",sheet_name=0,index_col=1)
# names: 以list重新指定columns
df6=pd.read_excel("test.xlsx ",sheet_name=0,names=["列一","列二","列三","列四","列五"])
# skiprows:省略指定的行(columns的序号=0)
df7=pd.read_excel("test.xlsx ",sheet_name=0,skiprows=[1,3,4])
# usecols: 指定导入的columns
df8=pd.read_excel("test.xlsx ",sheet_name=0,usecols=[0,3])
df9=pd.read_excel("test.xlsx ",sheet_name=0,usecols=["学历","应届"])
(2)写入Excel:
import pandas as pd
din=pd.DataFrame([("张三","301",370),("李四","302",500),("王五","303",600),("赵六","302",580),("郭七 ","303",700)],
columns=["name","class","scores"])
din.to_excel("stu_scores.xlsx",index=False) #忽略行标index保存
3 Python正则表达式
正则表达式是一个能匹配字符串的模板,它作为某些方法(如re.match
、re.search
、re.split
、pandas.Series.str.contains
)的参数。正则表达式=普通字符+特殊字符(元字符)。
3.1 基础知识
元字符清单见下表:
元字符 | 含义 |
---|---|
. | 匹配除换行符外的任何字符。在DOTALL模式中也能匹配换行符 |
\ | 转义字符(使用后改变字符原义) |
* | 匹配前一字符n≥0 次 |
+ | 匹配前一字符n≥1 次 |
? | 匹配前一字符n≤1 次 |
^ | 匹配字符串开头,在多行模式中匹配每行的开头 |
$ | 匹配字符串结尾,在多行模式中匹配每行的末尾 |
| | 从左到右匹配 |左右任一 表达式。若 | 没包含在()内,则其范围是整个表达式 |
(...) | 将正则表达式分组,编号从1开始。(a|b) #a or b 、(c){3} #ccc |
[...] | 字符集合,匹配此集合内任一元素。特殊字符于集合中均变成普通字符。可用- 构成范围,如[a-c] 等价于[abc] |
{,} | {m},{m,n},{m,},{,n} 分别匹配前一个字符m,[m,n],[m,∞],[0,n] 次 |
正则表达式预定义字符见下表:
预定义字符 | 含义 |
---|---|
\d | 匹配0~9 中任一数字字符,即[0-9] |
\D | 匹配任一非数字字符,即[^0-9] |
\s | 匹配空白字符,即[\f\n\r\t\v] (走纸换页、换行、回车、横向制表、竖向跳格) |
\S | 匹配非空白字符,即[^\f\n\r\t\v] |
\w | 匹配任一单词字符,即[a-zA-Z0-9_] |
\W | 匹配任一非单词字符,即[^a-zA-Z0-9_] |
\A | 匹配字符串开头,即^ |
\Z | 匹配字符串结尾,即$ |
\b | 匹配字母数字与非字母数字的边界(异质之边界) |
\B | 匹配字母数字与字母数字、非字母数字与非字母数字的边界(同质之边界) |
备注:①\d
应写成\\d
(防转义)或r"\d"
(Python原生字符串);②Python标识符可表示为r"[a-zA-Z_][\w$]*"
(以字母或下划线打头,后接[0,∞]个单词字符);③\b,\B
是单词边界,不匹配任何实际字符(是间隙),且\B
是\b
的非(补)。深入了解可参考博文:正则表达式里\b
和\B
,Python实例。
正则表达式的特殊分组见下表:
特殊分组 | 含义 |
---|---|
(?P<name>...) | 将正则表达式分组。除默认分组编号外附加一个别名name |
\number | 引用分组编号为number 的分组(默认编号) |
(?P=name) | 引用别名为name 的分组 |
备注:①r"\b(?P<my_group1>\w+)\b\s+(?P=my_group1)\b"
中'\\w+'
(或写成r'\w+'
)编号为1,别名为my_group1
,后面的(?P=my_group1)
是对'\\w+'
的引用。②r"\b(\w+)\b\s+\1\b"
中'\\w+'
编号为1,后面的\1
是对'\\w+'
的引用。
一般说来,re
模块方法的参数有:①正则表达式pattern
、②需匹配的字符串string
、③匹配模式flags
(多个flag可用|
连接)。其中匹配模式见下表:
匹配模式 | 意义 |
---|---|
re.A 或re.ASCII | 预定义字符类\w,\W,\b,\B,\d,\D,\s,\S 仅匹配ASCII字符。Python3默认匹配Unicode字符。 |
re.I 或re.IGNORECASE | 忽略大小写 |
re.L 或re.LOCALE | 预定义字符类\w,\W,\b,\B 和忽略大小写匹配依赖于当前语言环境 |
re.M 或re.MULTILINE | 元字符^ 和$ 可匹配每一行开头与结尾 |
re.S 或re.DOTALL | 元字符. 匹配任意字符(包括换行符) |
re.X 或re.VERBOSE | 正则表达式可为多行,忽略空白字符,并可以加入注释 |
3.2 实例分析
(1)re.match(pattern,string,flags=0):
仅在string
起始位置匹配pattern
,否则返回None。例如:
import re
str1="To Python, or not to Python: this is a question."
index=re.match('to',str1) #None
(2)re.search(pattern,string,flags=0):
查询string
中首次出现pattern
的位置,否则返回None。例如:
import re
str1="To Python, or not to Python: this is a question."
index=re.search('Python',str1) #<re.Match object; span=(3, 9), match='Python'>
(3)re.fullmatch(pattern,string,flags=0):
从string
整个地匹配pattern
,否则返回None。例如:
import re
#str1="To Python, or not to Python: this is a question."
index=re.fullmatch("Python is Fun","Python is fun") #None
(4)re.findall(pattern,string,flags=0):
返回string
中所有匹配pattern
且不重叠的子串(列表),否则返回空列表。例如:
import re
str1="To Python, or not to Python: this is a question."
s=re.findall('Python',str1) #['Python', 'Python']
(5)re.finditer(pattern,string,flags=0):
返回string
中所有匹配pattern
且不重叠的子串(迭代器),否则返回空迭代器。例如:
import re
str1="To Python, or not to Python: this is a question."
it=re.finditer('Python',str1) #<callable_iterator at 0x19afa872790>
(6).group([group1,...]):
返回匹配group1...
的一个或多个子串(re.Match object
变现为具体字符串)。其中,group1可为别名或编号,0代表整个匹配表达式pattern
(此时.group()=.group(0)
);指定多个参数.group(0,1,2,...)
将返回一个匹配对应表达式的元组。例如:
import re
# mathch仅在起始位置匹配;.group()变现子串;
index=re.match(r'(\w+)(\w+)(\w+)',"Guido von Rossum, Programmer") #<re.Match object; span=(0, 5), match='Guido'>
s=re.match(r'(\w+)(\w+)(\w+)',"Guido von Rossum, Programmer").group() #'Guido'
s2=re.match('(\\w+)(\\w+)(\\w+)',"Guido von Rossum, Programmer").group(0) #'Guido'
# 尽可能保证前面的pattern匹配更多
s3=re.match(r'(\w+)(\w+)(\w+)',"Guido von Rossum, Programmer").group(1) #'Gui'
s4=re.match(r'(\w+)(\w+)(\w+)',"Guido von Rossum, Programmer").group(2) #'d'
s5=re.match(r'(\w+)(\w+)(\w+)',"Guido von Rossum, Programmer").group(3) #'o'
s6=re.match(r'(\w+) (\w+) (\w+)',"Guido von Rossum, Programmer").group(0) #'Guido von Rossum'
s7=re.match(r'(\w+) (\w+) (\w+)',"Guido von Rossum, Programmer").group(1) #'Guido'
s8=re.match(r'(\w+) (\w+) (\w+)',"Guido von Rossum, Programmer").group(2) #'von'
s9=re.match(r'(\w+) (\w+) (\w+)',"Guido von Rossum, Programmer").group(3) #'Rossum'
s10=re.match(r'(\w+) (\w+) (\w+)',"Guido von Rossum, Programmer").group(1,2,3) #('Guido', 'von', 'Rossum')
s11=re.match(r'(\w+) (\w+)(\w+)',"Guido von Rossum, Programmer").group(0) #'Guido von'
s12=re.match(r'(\w+) (\w+)(\w+)',"Guido von Rossum, Programmer").group(1) #'Guido'
s13=re.match(r'(\w+) (\w+)(\w+)',"Guido von Rossum, Programmer").group(2) #'vo'
s14=re.match(r'(\w+) (\w+)(\w+)',"Guido von Rossum, Programmer").group(3) #'n'
# "写时"即能用,"引用"也可用
index=re.match(r'(?P<name>\w+ \w+ \w+),(?P<job>\w+)',"xi yuan yang,Programmer") #<re.Match object; span=(0, 23), match='xi yuan yang,Programmer'>
index[0] #'xi yuan yang,Programmer'
index[1] #'xi yuan yang'
index[2] #'Programmer'
index['name'] #'xi yuan yang'
index['job'] #'Programmer'
(7).groups(default=None):
返回一个包含所有匹配表达式pattern
的元组。例如:
import re
s1=re.match(r'(\w+) (\w+) (\w+)',"xi yuan yang,Programmer").groups() # ('xi', 'yuan', 'yang')
s2=re.match(r'(\w+) (\w+) (\w+)',"xi yuan yang,Programmer").group(1,2,3) # ('xi', 'yuan', 'yang')
(8).groupdict(default=None):
返回一个以所有命名匹配表达式pattern
为key
的字典。例如:
import re
# 像极了C/C++/C#/Python的格式输出......
s=re.match(r"(?P<Stu_name>\w+),(?P<job>\w+)",'XiyuanYang,Programmer').groupdict() #{'Stu_name': 'XiyuanYang', 'job': 'Programmer'}
s2=re.match(r"(?P<Stu_name>\w+), (?P<job>\w+)",'XiyuanYang,Programmer').groupdict() #None
(9).start()/.end()/.span():
返回匹配子串(string
匹配的pattern
)于原字符串中的开始/结束/范围。例如:
import re
str1="To Python, or not to Python: this is a question."
start=re.search("Python",str1).start() #3
span=re.search("Python",str1).span() #(3,9)
(10)re.split(pattern,string,maxsplit=0,flags=0)
分割字符串(返回列表),maxsplit用于指定分割次数(默认整体分割)。例如:
re.split(r"\d+","0C1C++2Java3Python4Rust") #['', 'C', 'C++', 'Java', 'Python', 'Rust']
# 裁剪去掉 "123及其后的异质间隙"
re.split('123\\b','==123!! abc123. 123. 123abc. 123') #['==', '!! abc', '. ', '. 123abc. ', '']
# 裁剪去掉 "123及其前后的异质间隙"
re.split('\\b123\\b','123 ==123!! abc123.123.123abc.123') #['', ' ==', '!! abc123.', '.123abc.', '']
# 裁剪去掉 "py=及其后的同质间隙"
re.split(r'py=\B','1py=cthon py5 2py=342 py==1py2py4 pyp3 3py= pyabc') #['1py=cthon py5 2py=342 ', '=1py2py4 pyp3 3', ' pyabc']
# 裁剪去掉 "123=及其前的异质间隙与后的同质间隙"
re.split('\\b123=\\B','==123!! abc123,123,123==abc,123') #['==123!! abc123,123,', '=abc,123']
(11)若某匹配表达式pattern
需多次使用,可将其预编译(re.compile(pattern,flags=0)
)为正则表达式对象。使用方法则同上。
import re
regex=re.compile(r'(\w+) (\w+) (\w+)')
s1=regex.match('xi yuan yang,Programmer').groups() #('xi', 'yuan', 'yang')
s2=regex.match('mu dian yang,Teacher').groups() #('mu', 'dian', 'yang')
4 pandas查询
pandas
查询包括Series
和DataFrame
查询,又可归结为Series
查询——因为DataFrame
查询可视为前者查询之组合(使用逻辑运算符and,or,not
或位运算符&,|,~
)。
4.1 Series查询
(1)数值型查询:
import pandas as pd
s=pd.Series([10.0,24,300.0,4,55],index=['a','b','c','d','e'])
# Series使用
# =============================================================================
# s[0];s[1:3];s[[0,2,3,4]]
# s['a'];s['b':'c'];s[['a','c','d','e']]
# s*2;s**2
# =============================================================================
# s[同维度的一个bool型Series]
a=s[(s>5)&(s<150)|(~(s<300))] #type(s>5):pandas.core.series.Series
c=s[s.isin([300.0,4.0])]
(2)字符串型查询:
①pandas.Series.str.startswith(pat,na=NaN):
匹配以pat
开头的字符串(精准),空值na
默认设置为NaN
。此函数不支持正则表达式。
import numpy as np
import pandas as pd
s=pd.Series(['41.0','24','484.0',np.nan,'cat','cgdog'],index=range(10,16))
a=s[s.str.startswith('4',na=True)]
b=s[s.str.startswith('cg',na=False)]
②pandas.Series.str.endswith(pat,na=NaN):
匹配以pat
结尾的字符串(精准),空值na
默认设置为NaN
。此函数不支持正则表达式。
import numpy as np
import pandas as pd
s=pd.Series(['41.0','24','484.0',np.nan,'cat','cgdog'],index=range(10,16))
a=s[s.str.endswith('.0',na=True)]
b=s[s.str.endswith('g',na=False)]
③pandas.Series.str.contains(pat,case=True,flags=0,na=NaN,regex=True):
以正则表达式(regex=True
;模糊倾向)或非正则表达式(regex=False
;精准倾向)进行匹配。
import numpy as np
import pandas as pd
s=pd.Series(['41.0','24','484.0',np.nan,'Cat','cgdog'],index=range(10,16))
# not regex
a=s[s.str.contains('C',case=True,na=False,regex=False)] #精准匹配
b=s[s.str.contains('.0',case=False,na=True,regex=False)]
# regex
c=s[s.str.contains('4',case=False,na=True,regex=True)] #包含'4'的
d=s[s.str.contains('^4',case=False,na=True,regex=True)] #以'4'开头的
e=s[s.str.contains(r'\w*\.\w*',case=False,na=False,regex=True)] #包含'.'的
import numpy as np
import pandas as pd
s=pd.Series(['41','24(999) ','0(x)123',np.nan,' (aa)xyz','str()68'],index=range(10,16))
#提取含"()"的项——两边非空白字符≥0,中间()有或无字符
a=s[s.str.contains(r'(\s*|\w*)\((\w+|\s*)\)(\w*|\s*)',na=False,regex=True)]
b=s[s.str.contains(r'\S*\(\S*\)\S*',na=False,regex=True)] #\S*=(\s*|\w+)
c=s[s.str.contains(r'(\(|\))',na=False,regex=True)]
#提取"()"不靠边的项——两边非空白字符≥1,中间()内有或无字符
d=s[s.str.contains(r'\S+\((\w+|\s*)\)\S+',na=False,regex=True)]
e=s[s.str.contains(r'\S+\((\S*)\)\S+',na=False,regex=True)]
4.2 DataFrame查询
DataFrame可看成Series对象构成的字典(每一列相当于一个Series),因此它的查询即组合各个DataFrame.columns
的查询。基本写法如下:
(1)df[(df.列一查询)&(df.列二查询)|(df.列三查询)&(~df列四查询)]
,例如:
import pandas as pd
df=pd.DataFrame({'one':[1,2,3,4,5],
'two':['a','b','c','d','e'],
'three':['100.0','24.314','56','980','0.45'],
'four':[45.0,78.04,4.5,6890,0.012]})
result=df[(df.one>=2)&(df.two<='d')&(df.three.str.contains(r'\S*\.\S*'))&(df.four<560)] #regex
(2)df.query('列一查询 and 列二查询 or 列三查询 not 列四查询')
,例如:
import pandas as pd
df=pd.DataFrame({'one':[1,2,3,4,5],
'two':['a','b','c','d','e'],
'three':['100.0','24.314','56','980','0.45'],
'four':[45.0,78.04,4.5,6890,0.012]})
param=560
result=df.query('one>=2 & two<="d" and( not three in ["56","980"]) and four<@param')
5 国考职位表筛选
点击2021年度招考简章下载国考职位表。解压后,须将表首行——“招考职位由招录机关编报,招录专业、学历等与职位资格条件相关的问题,由招录机关负责解释。” 删除。筛选条件应按照自身情况进行修改。
import pandas as pd
# <文件路径>
fileName="E:\\file home\\CivilServant\\中央机关及其直属机构2021年度考试录用公务员招考简章.xls"
SheetNames=["中央党群机关","中央国家行政机关(本级)","中央国家行政机关省级以下直属机构","中央国家行政机关参照公务员法管理事业单位"]
# <筛选条件>
optMajor='(测绘|测量|导航|遥感|工学类)' #专业
optEducation='(大专及以上|本科及以上|本科或硕士研究生|仅限硕士研究生|硕士研究生及以上)' #学历
optPolity=['不限','中共党员或共青团员'] #政治面貌
optWorkyears="无限制" #基层工作最低年限
optWorkExperience="无限制" #服务基层项目工作经历
'''
【一些特殊备注】
(1)限2020年和2021年应届毕业生(√)
(2)2021年毕业的高校毕业生(高等教育各阶段均需取得相应学历学位)(×)
(3)高校应届毕业生(高等教育各阶段均需取得相应学历学位)(×)
(4)2021年应届高校毕业生,本单位不提供宿舍,在本单位最低服务年限为5年(×)
(5)限普通高等学校2021年应届毕业生;2.各职位明确的专业类目录按教育部《普通高等学校本科专业目录(2020年版)》确定,报考人员最高学历专业须为相应职位明确的专业类目录下设专业(毕业证书、学位证书均为同一专业,含研究生专业对应的一级学科专业)(×)
【排除条件】
(1)有"2021"+有"应届"+无"2020"→→→不要
(2)有"2021"+有"应届"+有"2020年版"→→→不要
(3)有"2021"+无"应届"→→→不要
(4)无"2021"+有"应届"→→→不要
'''
# <文件读取及刷选>
resultData=pd.DataFrame()
print("【考试类别】 "+"【招考人数】 "+"【专业】 "+"【学历】 "+"【政治面貌】 "+"【基层工作最低年限】 "+"【服务基层项目工作经历】")
for sheet_name in SheetNames:
ExcelData=pd.read_excel(fileName,sheet_name) #文件读取
# 没有'备注'(na)是一定要的(无限制)——一次取反:na=False;二次取反:na=True
optMark1=(ExcelData['备注'].str.contains('2021',na=False))&(ExcelData['备注'].str.contains('应届',na=False))&(~(ExcelData['备注'].str.contains('2020',na=True)))
optMark2=(ExcelData['备注'].str.contains(r'\S*2021\S*',na=False))&(ExcelData['备注'].str.contains(r'\S*应届\S*',na=False))&(ExcelData['备注'].str.contains(r'\S*2020年版\S*',na=False))
optMark3=(ExcelData['备注'].str.contains('2021',na=False))&(~(ExcelData['备注'].str.contains('应届',na=True)))
optMark4=(ExcelData['备注'].str.contains('应届',na=False))&(~(ExcelData['备注'].str.contains('2021',na=True)))
AppJob=ExcelData[(ExcelData['专业'].str.contains(optMajor))&(ExcelData['学历'].str.contains(optEducation))&(ExcelData['政治面貌'].isin(optPolity))
&(ExcelData['基层工作最低年限']==optWorkyears)&(ExcelData['服务基层项目工作经历']==optWorkExperience)
&((~optMark1)&(~optMark2)&(~optMark3)&(~optMark4))]
if len(resultData.columns)==0:
resultData=AppJob
else:
resultData=resultData.append(AppJob,ignore_index=False,sort=False) #拼接
#resultData=pd.concat([resultData,AppJob],axis=0,join='outer',ignore_index=False,sort=False)
for n in range(AppJob.shape[0]): #展示
RowItem=AppJob.iloc[n]
print( RowItem["考试类别"]+" "+ str(RowItem["招考人数"])+" "+ RowItem["专业"]+" "+ RowItem["学历"]+" "+ RowItem["政治面貌"]+" "+
RowItem["基层工作最低年限"]+" "+ RowItem["服务基层项目工作经历"])
# <储存结果>
resultData.to_excel("E:\\file home\\CivilServant\\满足条件的职位表(2021).xlsx",index=False)
参考文献
- 虞歌.Python程序设计基础[M].北京:中国铁道出版社,2018.
- Pandas官方文档.
- 正则表达式里
\b
和\B
,Python实例.
后记
滴答,滴答,滴答...
老狗睁开了今天的睡眼——
刷牙、洗脸、长裤、防晒衣和那沾满泥土的球鞋...
电缆线、PVC管、铁捁、螺母、扳手、地电阻测试仪...
数了一遍又一遍,仿佛数着卡上那几个可怜的数字
冲下楼——老板:来个4元的土豆大饼!
然后,爬上那辆泥土厚垢的汽车驶向了山路尽头...
云南的天很怪
常常在太阳中下起小雨来
射得眼睛一晃一晃,浸湿了泪水
似乎
"亚洲第一、世界第三"
才配得上这北纬26°的紫外线
世界很残酷
留下了一段狗延残喘的距离
又能怎么样呢
敢去填填万人坑的基础么?