用Python筛选国考职位表

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.matchre.searchre.splitpandas.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.Are.ASCII预定义字符类\w,\W,\b,\B,\d,\D,\s,\S仅匹配ASCII字符。Python3默认匹配Unicode字符。
re.Ire.IGNORECASE忽略大小写
re.Lre.LOCALE预定义字符类\w,\W,\b,\B和忽略大小写匹配依赖于当前语言环境
re.Mre.MULTILINE元字符^$可匹配每一行开头与结尾
re.Sre.DOTALL元字符.匹配任意字符(包括换行符)
re.Xre.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):返回一个以所有命名匹配表达式patternkey的字典。例如:

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查询包括SeriesDataFrame查询,又可归结为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)

参考文献

后记

滴答,滴答,滴答...
老狗睁开了今天的睡眼——
刷牙、洗脸、长裤、防晒衣和那沾满泥土的球鞋...
电缆线、PVC管、铁捁、螺母、扳手、地电阻测试仪...
数了一遍又一遍,仿佛数着卡上那几个可怜的数字
冲下楼——老板:来个4元的土豆大饼!
然后,爬上那辆泥土厚垢的汽车驶向了山路尽头...

云南的天很怪
常常在太阳中下起小雨来
射得眼睛一晃一晃,浸湿了泪水
似乎
"亚洲第一、世界第三"
才配得上这北纬26°的紫外线

世界很残酷
留下了一段狗延残喘的距离
又能怎么样呢
敢去填填万人坑的基础么?
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C_xxy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值