最近要找长沙的工作,于是通过湖南人才市场搜索了一下职位。结果得到的数据让我很难比较,作为一个 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,好歹还可以空一个核来响应我的桌面操作。

人才市场的IT职位分析的更多相关文章

  1. IT职位分析

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

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

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

  3. 汽车后市场SWOT分析

    客户接待系统SWOT分析 版本  V0.1 所有人: 王超 S  客户接待系统符合市场的目前阶段需求.填补了市场的空白部分. W 市场推广的力度差异,市场由蓝海编成红海,多种厂商参与,创业团队不断进入 ...

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

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

  5. Java和.net对比分析

    .Net和Java是国内市场占有率最高的两门技术,对于准备学习编程语言的初学者来说,.Net和Java是初学者首先考虑的两门技术,因此很多人一遍遍的问“学.Net还是学Java”,社区中也每天都有“. ...

  6. 如何做好FAE工作及FAE职位发展

    此文较长,是作者对于半导体FAE职业的一些总结,码字不容易,耐心的阅读,欢迎点赞. 曾经认识一位做电源研发的工程师,转行在一家代理商做FAE,做了一年半以后,就提出了离职请求,他老板问他是什么原因,他 ...

  7. 职位-CFO:CFO

    ylbtech-职位-CFO:CFO 首席财务官——CFO(Chief Finance Officer)是企业治理结构发展到一个新阶段的必然产物.没有首席财务官的治理结构不是现代意义上完善的治理结构. ...

  8. 如何做好FAE工作及FAE职位发展————资深FAE总结推荐

    http://bbs.elecfans.com/jishu_932585_1_1.html 曾经认识一位做电源研发的工程师,转行在一家代理商做FAE,做了一年半以后,就提出了离职请求,他老板问他是什么 ...

  9. 人才需求之Java程序员与AI程序员

    据100offer报告显示:2018年Java人才市场「高开低走」的动荡局势.整体求职难度变大,且全年波动更剧烈,淡旺季区别明显.企业发出的Java面邀总数几个季度连续下跌,Q4 甚至比去年同期下降了 ...

随机推荐

  1. 用WinForm写的员工考勤项目!!!!!!

    先说几句,作为一个还在学习的程序员,掌握的知识有限:但我利用自身所学,给一些像我一样还在学习的码农提供我的绵薄之力! 写的不好,但是尽力了,希望大牛指点.多多吐槽!!! 好了开始说项目需求: 实现新增 ...

  2. `这个符号在mysql中的作用

    ` 是 MySQL 的转义符,避免和 mysql 的本身的关键字冲突,只要你不在列名.表名中使用 mysql 的保留字或中文,就不需要转义. 所有的数据库都有类似的设置,不过mysql用的是`而已.通 ...

  3. Puppet自动化部署-前期环境准备(2)

    在安装Puppet环境之前需要配置好机器的基本配置,如规范网络地址IP.hostname,certname认证名称,ntp时间同步等配置完毕,完善的搭建自动化环境. 1.环境介绍 此处实现部署的环境是 ...

  4. VirusAnti_didiwei使用说明

    前言 前段时间说要写的一个专杀框架敌敌畏,后改为强撸敌敌畏,以彰显样本查杀时的气势,现在第一版已经完成了,如下图所示,使用的时候强烈建议控制台使用放大模式,这样就可以看见我花了半天才画好了logo , ...

  5. asp.net 字符串替换、截取和从字符串中最后某个字符 开始截取

    有时候要在一段字符串里面把某些字符替换成其他字符,怎么办? 例如: string image=@"csks/news/user_top/qqqq/qqqq.jpg"; image ...

  6. Android进阶系列之源码分析Activity的启动流程

    美女镇楼,辟邪! 源码,是一个程序猿前进路上一个大的而又不得不去翻越障碍,我讨厌源码,看着一大堆.5000多行,要看完得啥时候去了啊.不过做安卓的总有这一天,自从踏上这条不归路,我就认命了.好吧,我慢 ...

  7. jQuery参数学习与整理

    bind---可同时为元素嵌套多个事件. blur---当输入框焦点失去时发生的事件(获得焦点参数focus与之同理) change---当元素值改变时发生的事件 click---单击事件 dbcli ...

  8. mssql与mysql 数据迁移

    概要: mssql向mysql迁移的实例,所要用到的工具bcp和load data local infile. 由于订单记录的数据是存放在mssql服务器上的,而项目需求把数据迁移到mysql ser ...

  9. phoneGap蓝牙设备链接打印操作插件

    前台 bluetooth.js /*Copyright 2013  101.key Licensed under the Apache License, Version 2.0 (the " ...

  10. android studio 报错-----R全部显示红色 ---- .9图片报错

    导入android项目后,R全部变红,控制台有下面的提示 意思是缺少一些资源,比如说图片之类的,然后我发现确实少了一张图片资源,导入图片资源后,依旧报错,如下  Error:Execution fai ...