人才市场的IT职位分析

 

最近要找长沙的工作,于是通过湖南人才市场搜索了一下职位。结果得到的数据让我很难比较,作为一个 IT 业滚爬了多年的程序员,对这样的搜索结果很不满意。于是,我不得不自己来整理数据了。本文内容包括:网页数据抓取、网页数据分析、数据挖掘,python 的多线程,多进程应用等话题。

先上结论

先给出以上概略图,由图可以得出以下结论:

  • 长沙的的IT行业大多数工资开在 2500-3500 之间
  • 有 40% 左右的企业需要面谈工资或者对工资有自己的规定(不使用网站上设置的工资级别)
  • Java 语言的需求量远高于其它语言的需求量

当然,还可以挖掘出更多的有效信息。如按工资排名的职位:

这样,我就可以先快找到当前长沙的高工资职位及要求了。这就是做程序员的好处 ^_^。以下将介绍数据抓取及数据分析。

数据抓取

我写了一个 crawler.py 程序。内容如下:

# -*- coding: utf-8 -*-

import sys, os, re
import http.client
import threading
import time ids = []
# 生成要抓取的职位列表ID
def generate_list_ids():
global ids
for i in range(1, 77):
ids.append(i) # 生成要抓取的职位详情ID
def generate_detail_ids():
global ids
for i in range(1, 77):
f = open(str(i)+'.lst', 'r', encoding='gbk')
s = f.read(1024000)
inputs = re.findall(r"<input type=checkbox name=chk value='.*?'>", s)
for inp in inputs:
m = re.match(r".*value='(.*)'", inp)
ids.append(m.group(1))
f.close() # 用多线程的方式抓取,单线程太慢了
class Crawler(threading.Thread):
def __init__(self, islist=True):
self.h = http.client.HTTPConnection('www.hnrcsc.com')
self.islist = islist
threading.Thread.__init__(self) # 抓取到的网页,将它存入文件
def write_file(self, filename):
o = open(filename, 'wb')
o.write(self.h.getresponse().read(1024000))
o.close() # 抓取职位列表
def get_list(self, cid):
self.h.request('POST', '/Search/searchResult.asp?pagenum='+str(cid), 'flag=0&wkregion=430100&keywordtype=&postypesub=&postypemain=0100&keyword=%C7%EB%CA%E4%C8%EB%B9%D8%BC%FC%D7%D6&during=90&pagenum='+str(cid), {'Content-Type': 'application/x-www-form-urlencoded'})
self.write_file(str(cid)+'.lst') def get_detail(self, cid):
try:
self.h.request('GET', '/jobs/posFiles/showPosDetail.asp?posid='+str(cid))
self.write_file(str(cid)+'.det')
except:
print(cid)
self.h.close()
time.sleep(3)
self.h = http.client.HTTPConnection('www.hnrcsc.com') def run(self):
global ids
cid = ids.pop() if len(ids)>0 else None
while cid:
if self.islist:
self.get_list(cid)
else:
self.get_detail(cid)
cid = ids.pop() if len(ids)>0 else None
print(self.name + ' Finished!')
self.h.close() if len(sys.argv) != 2:
print('''Usage: crawler.py command
list: get list
detail: get detail
clean: clean all webpage
''')
exit() if sys.argv[1] == 'detail': # 抓取职位详情
generate_detail_ids()
for i in range(50):
Crawler(False).start()
elif sys.argv[1] == 'list': # 抓取职位列表
generate_list_ids()
for i in range(10):
Crawler().start()
elif sys.argv[1] == 'clean': # 删除所有抓取到的文件
os.system('del *.lst')
os.system('del *.det')
else:
print('''Usage: crawler.py command
list: get list
detail: get detail
clean: clean all webpage
''')

以上是最终的程序,《黑客与画家》中提到,写程序是一个类似于绘画的过程,这里是可用的半成品了。我大概描述一下完成这个程序的过程:

  1. 用 http.client 取一个网页的数据,很快就可以了解 http.client 的用法
  2. 用一个 for 循环取 2 个页面的数据,测试一下 http.client 多次取数据的情况
  3. 已经完成的内容放到一个方法(get_list)中去,待用
  4. 写一个读文件,并且用正则表达式取出所有职位详情ID的代码,测试通过后,将这部分代码注释掉
  5. 用 http.client 取一个职位详情的数据
  6. 汇总三个内容,即可得到单线程抓取网页的程序

我运行这个程序,然后就跑出去吃饭去了,回来一看,还没有运行完,于是狠下心来看了一下 python 多线程的内容,把它改成多线程的代码,加上一些容错的处理,就完工了。

数据分析

数据分析实际上就是从职位列表文件与职位详情文件当中取到有效信息,将其放入关系数据库中,然后就可以利用关系数据库的强大查询语句来得到重要信息了。数据分析的关键,实际上是用正则表达式匹配关键的数据部分。以下是我写的 passer.py 代码

# -*- encoding: utf-8 -*-

import re, sqlite3, multiprocessing

class Passer(multiprocessing.Process):
def __init__(self, ids, datas):
self.ids = ids
self.datas = datas
multiprocessing.Process.__init__(self) # 获取职位详情数据
def get_detail(self, fid):
f = open(str(fid)+'.det', 'r', encoding='gbk')
s = f.read(1024000)
out = re.match(r'''.*招聘人数</td>.*<td width="217" align="left" class="shangxian">.*人&nbsp;</td>.*<td width="133" align="left" bgcolor="#FAFAFA" class="shangxian" ><img src="/firstpage/ima/diand.gif" width="3" height="3" />&nbsp;&nbsp;&nbsp;&nbsp;发布日期</td>.*<td width="213" align="left" class="shangxian" >(.*)&nbsp;</td>.*</tr>.*招聘部门</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*截止日期</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*发布单位</td>.*target="_blank">(.*)</a> &nbsp;</td>.*工作方式</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*最低学历要求</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*工作地区</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*薪酬待遇</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*.*详细待遇.*class="zhongxia2" colspan="3" align="left" >(.*)&nbsp;</td>.*联系电话</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*电子邮件</td>.*<td align="left" class="zhongxia2" ><a href=".*">(.*)</a> &nbsp;</td>.*联 系 人</td>.*class="zhongxia2" >(.*)&nbsp;</td>.*通讯地址</td>.*<td colspan="3" align="left" class="zhongxia2" >(.*?)&nbsp;.*岗位描述</td>.*<td colspan="3" align="left" class="zhongxia2" style="line-height:25px; padding-top:8px; padding-bottom:8px;">(.*)</td>.*岗位要求</td>.*<td colspan="3" align="left" class="xiaxin" style="line-height:25px; padding-top:8px; padding-bottom:8px;">(.*)</td>.*<td colspan="6" > <!-- Baidu Button BEGIN -->''', s, re.S)
if not out:
out = re.match(r'''.*招聘人数</td>.*<td width="217" align="left" class="shangxian">.*人&nbsp;</td>.*<td width="133" align="left" bgcolor="#FAFAFA" class="shangxian" ><img src="/firstpage/ima/diand.gif" width="3" height="3" />&nbsp;&nbsp;&nbsp;&nbsp;发布日期</td>.*<td width="213" align="left" class="shangxian" >(.*)&nbsp;</td>.*</tr>.*招聘部门</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*截止日期</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*发布单位</td>.*target="_blank">(.*)</a> &nbsp;</td>.*工作方式</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*最低学历要求</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*工作地区</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*详细待遇.*class="zhongxia2" colspan="3" align="left" >(.*)&nbsp;</td>.*联系电话</td>.*<td align="left" class="zhongxia2" >(.*)&nbsp;</td>.*电子邮件</td>.*<td align="left" class="zhongxia2" ><a href=".*">(.*)</a> &nbsp;</td>.*联 系 人</td>.*class="zhongxia2" >(.*)&nbsp;</td>.*通讯地址</td>.*<td colspan="3" align="left" class="zhongxia2" >(.*?)&nbsp;.*岗位描述</td>.*<td colspan="3" align="left" class="zhongxia2" style="line-height:25px; padding-top:8px; padding-bottom:8px;">(.*)</td>.*岗位要求</td>.*<td colspan="3" align="left" class="xiaxin" style="line-height:25px; padding-top:8px; padding-bottom:8px;">(.*)</td>.*<td colspan="6" > <!-- Baidu Button BEGIN -->''', s, re.S)
f.close()
return out.groups() # 处理职位列表
def handle_file(self, fileid):
global datas
f = open(str(fileid)+'.lst', 'r', encoding='gbk')
a = re.findall('<tr class="tdgray".*?</tr>', f.read(1024000), flags=re.S)
for i in a:
out = re.match(r'''<tr class=.*>.*<td align=center valign=middle height=20><input type=checkbox name=chk value='.*'></td>.*<td valign=middle><a target="_blank" href='/jobs/posFiles/showDwDetailnew.asp\?dwid=(.*)' class="blue_9">(.*)</a></td>.*<td valign=middle><a target="_blank" href='/jobs/posFiles/showPosDetail.asp\?posid=(.*)' class="blue_9">(.*)</a></td>.*<td align=center valign=middle>(.*)</td>.*<td align=center valign=middle>(.*)</td>.*<td align=center valign=middle>(.*)</td>.*<td align=center valign=middle>(.*)</td>.*</tr>''', i, re.S)
detail = self.get_detail(out.group(3))
if (len(detail) == 15):
start,dep,end,comp,ptype,degree,area,money,salary,telephone,email,contact,address,desc,require = detail
else:
start,dep,end,comp,ptype,degree,area,salary,telephone,email,contact,address,desc,require = detail
money = None
cid,cname,pid,pname,reqnum,ldegree,larea,pubtime = out.groups()
start = start.replace('.', '-')
end = end.replace('.', '-')
if money:
submoney = re.match('(.*)-(.*)元', money)
if submoney:
lowmoney,highmoney = submoney.groups()
else:
lowmoney,highmoney = 0,0
else:
lowmoney,highmoney = 0,0
telephone = telephone.strip()
desc = desc.replace('&nbsp', '').replace('<br>', '')
require = require.replace('&nbsp', '').replace('<br>', '')
salary = salary.replace('&nbsp', '').replace('<br>', '')
pubtime = pubtime.split('-')
month = pubtime[1] if len(pubtime) == 2 else '0'+pubtime[1]
day = pubtime[2] if len(pubtime[2]) == 2 else '0'+pubtime[2]
pubtime = pubtime[0] + '-' + month + '-' + day
reqnum = reqnum if reqnum != '若干' else 0
self.datas.append((pid, pname, reqnum, pubtime, start, end, cid, comp, dep, ptype, degree, area, lowmoney, highmoney, salary, telephone, email, contact, address, desc, require, fileid))
f.close() def run(self):
cid = self.ids.pop() if len(self.ids)>0 else None
while cid:
print('LEN: ', len(self.ids))
self.handle_file(cid)
cid = self.ids.pop() if len(self.ids)>0 else None
print(self.name + ' Finished!') # 使用多进程方法,只有主进程才执行以下部分
if __name__ == '__main__':
# 用 Manager 生成数据,供各个线程共享
manager = multiprocessing.Manager()
ids = manager.list()
datas = manager.list()
passers = [] for i in range(1, 77):
ids.append(i) # 开出 7 个进程来处理
for i in range(7):
p = Passer(ids, datas)
p.start()
passers.append(p) # 等待所有的进程都完成之后,再将数据插入到 sqlite 中去
for p in passers:
p.join() # 创建 sqlite 数据库
conn = sqlite3.connect('out.db')
c = conn.cursor()
# 初始化职位表
c.execute('drop table if exists position;')
c.execute('''CREATE TABLE position (
pid integer,
pname text,
reqnum integer,
pubtime datetime,
start datetime,
end datetime,
cid integer,
comp text,
dep text,
ptype text,
degree text,
area text,
lowmoney integer,
highmoney integer,
salary text,
telephone text,
email text,
contact text,
address text,
desc text,
require text,
pagenum integer
);
''')
conn.commit() for d in datas:
try:
c.execute('insert into position values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', d)
except:
print(d)
conn.commit()
c.close()
conn.close()

在分析抓取数据的时候,要用多进程,这是为了充分利用CPU。抓取数据用多线程,是因为延迟在网络IO上,所以,哪怕是多进程,也不会有多高的效率提升。

在多进程数据分析运行起来之后,我在电脑再做其它的事情,就响应缓慢了。双核CPU都是 100% 的使用率。这时才想起多核的好处^_^,要是我有八核CPU,好歹还可以空一个核来响应我的桌面操作。

 
 
分类: 形而下
标签: python求职

IT职位分析的更多相关文章

  1. 人才市场的IT职位分析

    最近要找长沙的工作,于是通过湖南人才市场搜索了一下职位.结果得到的数据让我很难比较,作为一个 IT 业滚爬了多年的程序员,对这样的搜索结果很不满意.于是,我不得不自己来整理数据了.本文内容包括:网页数 ...

  2. 想学Python?这里有一个最全面的职位分析

    Python从2015年开始,一直处于火爆的趋势,目前Python工程师超越Java.Web前端等岗位,起薪在15K左右,目前不管是小公司还是知名大公司都在热招中. 当然,每个城市对岗位的需求也不尽相 ...

  3. 小白数据分析——Python职位全链路分析

    最近在做Python职位分析的项目,做这件事的背景是因为接触Python这么久,还没有对Python职位有一个全貌的了解.所以想通过本次分析了解Python相关的职位有哪些.在不同城市的需求量有何差异 ...

  4. Atitit.人力资源管理原理与概论

    Atitit.人力资源管理原理与概论 1. 人力资源管理 第一章 人力资源管理概述 第二章 人力资源理论基础与发展演变 第三章 人力资源规划 第四章工作分析与工作设计 第五章 员工招聘与录用 第六章 ...

  5. 洞见世界(1): 拉勾网大数据告诉你, 会计最高月薪达33k!

    前言: 小李是个很有上进心的女孩, 老板让她了解下会计这个行业的薪资情况, 她不清楚怎么去做, 所以找了笔者, 当当当, 然后这一份薪资报告就出来了^_^.(小李为虚拟人物) by  璀璨者张健, 专 ...

  6. Atitit s2018.2 s2 doc list on home ntpc.docx  \Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别 讯飞科大 语音云.docx \Atitit 代码托管与虚拟主机.docx \Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx \Atitit 几大研发体系对比 Stage-Gat

    Atitit s2018.2 s2 doc list on home ntpc.docx \Atiitt uke制度体系  法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别   ...

  7. python服务端工程师就业面试指导☝☝☝

    python服务端工程师就业面试指导 由Python专业面试官打造的课,少之又少,专业代表着经验,代表着对考察点的通透理解,更代表着对你负责 第1章 Python工程师offer直通车(视频+教辅文档 ...

  8. t5_sumdoc.txt

    C:\Users\Administrator\Documents\sumdoc 2019\sumdoc t5 final\sumdoc t511C:\Users\Administrator\Docum ...

  9. 通俗易懂的分析如何用Python实现一只小爬虫,爬取拉勾网的职位信息

    源代码:https://github.com/nnngu/LagouSpider 效果预览 思路 1.首先我们打开拉勾网,并搜索"java",显示出来的职位信息就是我们的目标. 2 ...

随机推荐

  1. 为代码减负之&lt;一&gt;触发器(SQL)

    对触发器一词早有耳闻(最早是在耿大妈的数据库视频中),当初看完视频后,对理解不深刻的东西如:触发器,存储过程,事务,日志等等没有具体的去查阅,也没有具体的去尝试,应用.所以才导致了今天的博客(把曾经丢 ...

  2. C#快递跟踪(基于快递100深度定制)

    本文主要介绍快递跟踪的相关信息.如根据快递单号预测所属快递公司,判断快递是否已被签收,以及改良官方model后可在不用申请授权的情况下实现json,html,xml及text等多种格式以及单行多行,降 ...

  3. 如何使用Linq或EF来对数据去重——Distinct方法详解

    刚开始接触LINQ时使用distinct去重时和大家一样遇到了一些麻烦,很感谢 http://www.cnblogs.com/A_ming/archive/2013/05/24/3097062.htm ...

  4. 【高德地图API】从零开始学高德JS API(八)——地址解析与逆地址解析

    原文:[高德地图API]从零开始学高德JS API(八)——地址解析与逆地址解析 摘要:无论是百度LBS开放平台,还是高德LBS开放平台,其调用量最高的接口,必然是定位,其次就是地址解析了,又称为地理 ...

  5. Swift中文手册 -- The Basics

    原文:Swift中文手册 -- The Basics 基础部分 Swift 是 iOS 和 OS X 应用开发的一门新语言.然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 S ...

  6. Oracle 11g sys,system 密码忘记设置解决办法

    原文:Oracle 11g sys,system 密码忘记设置解决办法 1.启动sqlplus 2.请输入用户名:  sqlplus/as sysdba 3.输入口令: 直接回车 4.连接到: Ora ...

  7. MVC5 + EF6 + Bootstrap3 (8) HtmlHelper

    MVC5 + EF6 + Bootstrap3 (8) HtmlHelper用法大全(上) 上一节:MVC5 + EF6 + Bootstrap3 (7) Bootstrap的栅格系统 源码下载:点我 ...

  8. Hybrid

    “榕树下·那年”移动app ( hybrid ) 开发总结   榕树下网站本身的技术人员并不多,所以app开发的任务就到了母公司盛大文学这边.       盛大文学无线业务中心负责这次具体开发任务. ...

  9. 基于4.5Framework web程序、SQLSERVER数据库打包

    原文:基于4.5Framework web程序.SQLSERVER数据库打包 估计很多朋友和我一样,对于C/S程序打包很熟悉,但对于B/S程序打包一头雾水... 最近公司要求我们把项目和数据库(SQL ...

  10. ORACLE经常使用的命令

    一个.ORACLE启动和关机 1.在独立环境中 要启用或禁用ORACLE该系统必须切换到ORACLE用户,例如以下 su-oracle a.启动ORACLE系统 oracle>svrmgrl S ...