Python纪实:用爬虫明确求职路上的技能树(一)

来源:互联网 发布:淘宝新手入门教程视频 编辑:程序博客网 时间:2024/06/06 07:40

一直在关注各大招聘网站的招聘信息,这样做的目的是明确市场要什么样的我,怎样学习才能满足市场的需求,毕竟要在低头拉车的同时不时地抬头看路嘛!
在浏览职位列表中各个职位的时候,如果想知道具体的招聘要求,就需要点击进去,进入二级页面,查看具体的职位信息和技术要求。这给我们带来了十足的不便,一遍遍地进入返回,很容易造成疲惫,并且不能给出总的市场要求最多的技能。这时我们可以是用爬虫,爬取每个相关职位的要求技能,并统计出市场上对于某一职位大致要求。
在爬取时,我选择前程无忧和 python 为分析实例。
先观察页面结构,右键查看网页源码发现以下信息:
这里写图片描述

1.每个职位都包裹在 class="el"div2.每个职位的职称存在于包裹在 class="t1" 的 p 中的 a3.每个职位的对应具体描述页面的链接既是2a 的 href4.每个职位的其他信息(公司、地区、薪资等)分别在类名为"t2""t3"等的 span 中5.发出请求后得到的是完整的页面,职位列表就存在于 html 中,并不像有些网站是动态加载的,所以我们要抓取的是一个静态页面

这样就可以大致确定 main 函数要做的事:

def main():    jobList = downPage(searchUrl)    #下载页面并解析得到职位列表    for job in jobList[1:len(jobList)-1]:        jD = parseJob(job)        #解析每一个职位

函数具体实现如下:

def downPage(url):    html = requests.get(url,headers = headers).content    bsObj = BeautifulSoup(html, "html.parser")    #这里使用 requests 和 BeautifulSoup 库解析网页    searchResult = bsObj.find("div", {"id": "resultList"})    jobList = searchResult.findAll("div", {"class": "el"})    return jobListdef parseJob(job):    jobPosition = job.find("p").find("a").getText()    jobPosition = re.sub(r'\s+', '', jobPosition)    companyName = job.find("span", {"class": "t2"}).find("a").get("title")    jobLocation = job.find("span", {"class": "t3"}).getText()    jobPrice = job.find("span", {"class": "t4"}).getText()    jobDate = job.find("span", {"class": "t5"}).getText()    jD = [jobPosition, companyName, jobLocation, jobPrice, jobDate]    return jD

要讲一下 parseJob 中的

jobPosition = re.sub(r'\s+', '', jobPosition)

这句话是得到职位职称之后进行文本过滤,在源码中是这样的:

<a target="_blank" title="python开发工程师(Odoo)" href="http://jobs.51job.com/shanghai-xhq/62630612.html?s=01&t=0" onmousedown="">                                    python开发工程师(Odoo)                                </a>

最开始我以为文本前后只有换行符和空格,我就用

jobPosition = job.find("p").find("a").getText()#.replace(" ", "").replace("\n","")

做了一个简单地过滤。最后在导入数据库的时候,发现字符串拼接之后竟然显示不全,导致 Mysql 总是报语句错误,这时我才意识到,该文本前后,不仅仅有换行符和空格,可能还存在制表符和回车符,于是我就用
r’\s+’ 来做文本过滤,

\s 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。


这样下来我们只是得到了简单的职位信息,我们还需要做的是:获取多个页面的职位、得到每个职位的二级链接并分析。
针对第一个任务,再次分析页面。

<li class="bk"><a href="http://search.51job.com/list/020000,000000,0000,00,9,99,Python,2,2.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare=%22">下一页</a></li>

每一个下一页被包裹起来,并且如果能够点击的话会有一个 a ,a 的 href 是对应的下一页。据此我们改造 downPage 函数,让他返回下一页的地址:

def downPage(url):    html = requests.get(url,headers = headers).content    bsObj = BeautifulSoup(html, "html.parser")    searchResult = bsObj.find("div", {"id": "resultList"})    jobList = searchResult.findAll("div", {"class": "el"})    next = bsObj.findAll("li", {"class": "bk"})[1]    nextLink = next.find("a")    if nextLink:        nextUrl = nextLink.get("href")        return nextUrl,jobList        #如果下一页存在,获取并返回下一页的地址和本页职位列表    return None,jobList    #如果不存在,则只返回职位列表

这样,我们还需改造 main :

def main():    nextUrl = searchUrl    count = 0    flagStop = False    with open("jobList.txt","w+",encoding="utf-8") as f:        while nextUrl and not flagStop:            nextUrl, jobList = downPage(nextUrl)            #count += 1            for job in jobList[1:len(jobList)-1]:                jD = parseJob(job)                result = re.search(r'python',jD[0],re.I)                if not result:                    flagStop = True

这里使用 nextUrl 作为是否循环进入下一页获取职位列表的条件。至于另一个条件 flagStop 的添加,原因是这样的:起初我只是以列表数为获取依据,比如搜索结果有350页,我就开始解析前面的35页,然后我发现得到的技能树中竟然包括 c 这样的技能要求,然后我发现,搜索的结果中包括自动化工程师、嵌入式工程师等等,这些并不是 python 对应的正确搜索结果,他们只是相关职位,所以我决定之解析那些职位名称上包含 python 字样的职位,这样的道德结果更准确些。所以我对职位名称进行了过滤,如果不包含 python 字样,就不需要继续循环解析:

result = re.search(r'python',jD[0],re.I)                if not result:                    flagStop = True

在第二个任务中,我们需要解析每个职位的链接页面的职位描述信息。
第一步是得到子页面 url :

def getJobLink(job):    return job.find("p").find("a").get("href")

第二步是解析子页面,得到职位描述中的编程技能,并储存统计每个关键词出现的频率(这里解析职位描述和之前相同):

def getSkillTree(url):    html = requests.get(url, headers=headers).content    bsObj = BeautifulSoup(html, "html.parser")    jobDesc = bsObj.find("div", {"class": "job_msg"})    if jobDesc:        jobDesc = jobDesc.getText()        jobDesc = re.sub(r'\s+', '', jobDesc)        p = re.compile('([a-zA-Z]+)')        #编程技能基本是英文单词,所以只过滤出英文单词        keys = p.findall(jobDesc)        skillTree = list(set([s.lower() for s in keys]))        #技能关键词去重        for skill in skillTree:            if skill not in Skill:                Skill[skill] = 0            Skill[skill] += 1            #这里的 Skill 是一个全局字典,用于记录每一个关键词和期出现的频率,全部页面的都要统计于此

这两个函数我们就添加在 parseJob 函数中:

def parseJob(job):    jobPosition = job.find("p").find("a").getText()#.replace(" ", "").replace("\n","")    jobPosition = re.sub(r'\s+', '', jobPosition)    jobLink = getJobLink(job)    getSkillTree(jobLink)    companyName ……

最后我们在 main 的结尾对 Skill 技能字典进行排序打印:

sortedSkillTree = sorted(Skill.items(),key = itemgetter(1),reverse=True)print(sortedSkillTree)

整个爬虫的运行结果(我们只取前几个有意义的):

[(‘python’, 277), (‘linux’, 160), (‘mysql’, 124), (‘django’, 102),
(‘web’, 87), (‘redis’, 72), (‘flask’, 64), (‘java’, 64), (‘mongodb’,
58), (‘c’, 56), (‘css’, 53), (‘html’, 52), (‘tornado’, 50),
(‘javascript’, 49), (‘git’, 45), (‘shell’, 40), (‘sql’, 37)]

对这个结果,我们就可以有针对的进行学习了!


其实这个爬虫太过简陋,也没有涉及到反爬虫的知识,ip地址切换等等,也没有用到面向对象,不过结果还是得出来了,对这个爬虫的持续优化,后续再接着进行!

原创粉丝点击