一、前言
洞悉岗位要求最直接有效的方式就是分析各公司发布的招聘需求,本文使用python爬取了拉勾网近期发布的产品经理岗位的相关信息,内容侧重于网页的解析,能够让没有python基础的小伙伴直接套用代码获取想要的数据,也让自己更加明确未来要准备的方向,同时希望能给有兴趣从事产品经理岗位的小伙伴一些参考信息。
严正声明:本次爬取的信息不包含任何个人隐私数据,未对访问网站造成大批量的请求。代码仅供学习使用,不得用于不合规的用途。
代码链接
如果代码页面加载不出来,可以打开
这个网页,再粘贴地址https://nbviewer.jupyter.org/github/zyyssr/spider_pm_from_lagou/blob/main/spider_pm_from_lagou.ipynb
二、爬虫基础知识
当我们在浏览器中输入一个url后回车,后台会发生什么?
这段过程发生了以下四个事件:
(1)查找域名对应的IP地址。
(2)向IP对应的服务器发送请求。
(3)网站服务器响应请求,返回对应的响应。
(4)浏览器解析网页内容。
网络爬虫要做的,简单来说,就是实现浏览器的功能。通过指定url,直接返回用户所需的数据,不需要一步步手动操作浏览器获取。
一句话概括:爬虫就是获取网页并提取和保存信息的自动化程序。
用户获取网络数据的方式:
浏览器提交请求->下载网页代码->解析成页面
爬虫获取网络数据的方式:
模拟浏览器发送请求(获取网页代码)->提取有用的数据->存放于数据库或文件中
HTTP 请求方法
我们平常遇到的绝大部分请求都是 GET 或 POST 请求,另外还有一些请求方法,如 HEAD、PUT、DELETE、OPTIONS、CONNECT、TRACE 等,感兴趣参考:HTTP 请求方法 | 菜鸟教程。
本项目中,职位简介页面是通过POST方式,而职位详情页面是通过GET方式。
Json基础结构
所用工具
Python版本: Python3.8
IDE: Jupyter notebook
浏览器:Chrome
爬虫软件包:为了复习使用,采用了两个不同的包
urllib:内置的http请求库,一般要先构建get或者post请求,然后再发起请求
requests:是对urllib的再次封装,可以直接发起请求
三、职位简介爬取
3.1 网页解析
和很多网站一样,拉勾网也采用的是Ajax(异步加载)的技术,并且使用Json来传输网站数据。
我们通过Chrome打开拉勾网主页,搜索栏搜索”产品经理“,并勾选自己想要的筛选条件。点击下一页,可以发现搜索栏的网址并没有改变。
在搜索结果的页面中,我们按照以下顺序操作:
(1)右键单击检查
(2)默认打开的是Elements页面
(3)切换到Network标签,输入json,Ctrl + R刷新网页,可以找到positionAjax.json
(4)因为该网站是异步请求,所以打开Network中的XHR,针对JSON中的数据进行分析
结果如下图所示:
(1)General
首先是 General 部分,Request URL 为请求的 URL,Request Method 为请求的方法,Status Code 为响应状态码,Remote Address 为远程服务器的地址和端口,Referrer Policy 为 Referrer 判别策略。
(2)Response Headers
Response Headers 就是响应的一部分,例如其中包含了服务器的类型、文档类型、日期等信息,浏览器接收到响应后,会解析响应内容,进而呈现网页内容。
(3)Request Headers
下面是我们需要构造的请求头信息,如果这里没有构造好的话,容易被网站识别为爬虫,被拒绝访问。
红框部分是我们构造请求头时选择的重要信息。
Authority:表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略。
Cookie:也作复数Cookies。它的主要功能是维持当前访问会话。当我们输入用户名和密码成功登录某个网站后,服务器会用会话保存登录状态信息,后面我们每次刷新或请求该站点的其他页面时,会发现都是登录状态,这就是 Cookies 的功劳。
Referer:此内容用来标识这个请求是从哪个页面发过来的,通常在访问链接时,都要带上Referer字段,服务器会进行来源验证,后台通常会用此字段作为防盗链的依据。
User-Agent:后台通常会通过此字段判断用户设备类型、系统以及浏览器的型号版本。如果不加此字段,很可能会被识别出为爬虫。
(4)Form Data
下面是我们发送POST请求时需要包含的表单信息Form Data。将请求的参数构造成一个字典,传给requests.post()的data参数即可。
3.2 爬取策略
- 构建请求头、表单信息,使用request库请求
- 爬取第一页信息,分析数据结构,构建列表存储自己想要的数据
- 爬取所有页面的信息,将数据存储到csv文件中
3.3 代码部分
下图是爬取单个页面信息的函数:
def get_json(url, page_num):
'''
从网页获取JSON,使用POST请求,加上头部信息请求的header
'''
# 搜索栏的网址,调用requests对象的cookies属性获得登录的cookies,并赋值给变量cookies,最后带着cookies去请求
url1 = 'https://www.lagou.com/jobs/list_%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86/p-city_0?px=default&gx= \
%E5%85%A8%E8%81%8CC&gj=%E5%9C%A8%E6%A0%A1/%E5%BA%94%E5%B1%8A,3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B&xl= \
%E6%9C%AC%E7%A7%91,%E7%A1%95%E5%A3%AB#filterBox'
# 构造请求头信息
header = {
'authority': 'www.lagou.com',
'referer': 'https://www.lagou.com/jobs/list_%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86/p-city_0? \
px=default&gx=%E5%85%A8%E8%81%8C&gj=%E5%9C%A8%E6%A0%A1/%E5%BA%94%E5%B1%8A,\
3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B&xl=%E6%9C%AC%E7%A7%91,%E7%A1%95%E5%A3%AB',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/66.0.3359.139 Safari/537.36',
}
# 构造表单信息
form_data = {
'first': 'true',
'pn': page_num,
'kd': '产品经理',
}
session = requests.Session()
cookie = session.get(url = url1, headers = header, timeout = 5).cookies
res = requests.post(url, headers = header, data = form_data, cookies = cookie, timeout = 5)
res.encoding = 'utf-8'
page_data = res.json()
return page_data
在网页解析阶段,点击”preview”, 发现无法预览源代码,也就无从得知数据结构,所以我们先调用get_json函数,分析搜索结果第一页的数据组成。
分析可得,我们想要的数据都存储在[‘content’]里。[‘positionResult’][‘pageSize’]告诉我们每页最多15条职位信息,产品经理岗位的总体数量存储在totalCount,[‘positionResult’][‘result’]里面是每个岗位有哪些信息,可将其都打印出来,以便选择自己想要的内容。
url = 'https://www.lagou.com/jobs/positionAjax.json? \
gj=%E5%9C%A8%E6%A0%A1%2F%E5%BA%94%E5%B1%8A%2C3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B \
&xl=%E6%9C%AC%E7%A7%91%2C%E7%A1%95%E5%A3%AB&px=default&gx=%E5%85%A8%E8%81%8C&needAddtionalResult=false'
first_page = get_json(url,1)
print(first_page)
page_size = first_page['content']['pageSize']
total_count = first_page['content']['positionResult']['totalCount']
print("每页职位数: {}, 总数为: {}".format(page_size, total_count))
position = first_page['content']['positionResult']['result']
df = pd.DataFrame(position)
df.info()
接下来设置列表以存储自己想要的数据。
def get_page_info(jobs_list):
'''
获取每一页的职位信息
'''
page_info_list = []
for i in jobs_list:
job_info = []
job_info.append(i['positionId'])
job_info.append(i['positionName'])
job_info.append(i['companyFullName'])
job_info.append(i['companyShortName'])
job_info.append(i['companySize'])
job_info.append(i['industryField'])
job_info.append(i['financeStage'])
job_info.append(i['companyLabelList'])
job_info.append(i['firstType'])
job_info.append(i['secondType'])
job_info.append(i['skillLables'])
job_info.append(i['positionLables'])
job_info.append(i['createTime'])
job_info.append(i['city'])
job_info.append(i['district'])
job_info.append(i['salary'])
job_info.append(i['salaryMonth'])
job_info.append(i['workYear'])
job_info.append(i['education'])
job_info.append(i['positionAdvantage'])
job_info.append(i['resumeProcessRate'])
page_info_list.append(job_info)
return page_info_list
同时,我们发现拉勾网最多展示30页搜索结果。
def get_page_num(count):
'''
确定要抓取的页数,通过观察,可以发现拉勾网最多显示30页结果
'''
page_num = math.ceil(count / 15)
if page_num > 30:
return 30
else:
return page_num
然后,我们爬取所有页面信息,存储到csv文件中。
def main():
url = 'https://www.lagou.com/jobs/positionAjax.json? \
gj=%E5%9C%A8%E6%A0%A1%2F%E5%BA%94%E5%B1%8A%2C3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B \
&xl=%E6%9C%AC%E7%A7%91%2C%E7%A1%95%E5%A3%AB&px=default&gx=%E5%85%A8%E8%81%8C&needAddtionalResult=false'
first_page = get_json(url,1)
page_size = first_page['content']['pageSize']
total_count = first_page['content']['positionResult']['totalCount']
print("每页职位数: {}, 总数为: {}".format(page_size, total_count))
num = get_page_num(total_count)
total_info = []
for num in range(1, num + 1):
# 获取每一页的职位相关的信息
page_data = get_json(url, num)
jobs_list = page_data['content']['positionResult']['result']
page_info = get_page_info(jobs_list)
print('第{}页搜索结果:{}'.format(num, page_info))
total_info += page_info
print('当前职位总数为:', len(total_info))
time.sleep(25) # 爬取一次休息一段时间,以免被识别
# 将数据转化为data frame,写入到csv文件中
df = pd.DataFrame(data=total_info,
columns=['职位编号', '职位名称', '公司全称', '公司简称', '公司规模', '所属领域', '融资阶段', '公司标签',
'职位类型', '第二职位类型', '技能标签', '职位标签', '发布时间', '城市', '区域', '薪资', '薪酬月数',
'工作经验', '学历要求', '职位福利', '简历处理速度'])
df.to_csv('pm_position_data.csv', index=False)
if __name__ == '__main__':
main()
四、职位详情描述爬取
4.1 网页解析
通过观察可以发现,拉勾网的职位页面详情是由 http://m.lagou.com/jobs/ + ***** (PositionId).html 组成,而PositionId可以在上一阶段获得。同时,要注意到此页面请求方式为Get方法。
4.2 爬取策略
- 根据上一阶段爬取的PositionId构造url,使用urllib库发起请求
- 爬取一个样例,分析数据结构
- 通过正则化方法解析每个职位对应的职位描述信息
4.3 代码实现
def get_content(url):
'''
获取职位描述信息
'''
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/85.0.4183.121 Mobile Safari/537.36',
}
req = request.Request(url, headers=headers)
page = request.urlopen(req).read()
content = page.decode('utf-8')
return content
预览一个样例,可以发现我们想要的内容在<div class="content">部分。
正则化解析内容
def get_jd(content):
'''
正则化解析页面
'''
soup = BS(content, 'lxml')
job_description = soup.select('div[class="content"]')
job_description = re.sub(r'</p><p>', '\n', str(job_description[0]))
result = re.sub(r'<[^>]+>', '', job_description)
result = result.strip()
return result
得到以下结果:
最后,爬取所有职位描述数据,与上一阶段的数据合并,存入csv文件。
data = pd.read_csv("pm_position_data.csv") #导入上一阶段爬取的数据
data = data.drop_duplicates() # 去重
position_ids = data['职位编号'].tolist()
jd_dict = {}
for i in position_ids:
url = 'https://m.lagou.com/jobs/' + str(i) + '.html'
jd = get_jd(get_content(url))
jd_dict[i] = jd
time.sleep(15)
data_jd = pd.DataFrame([jd_dict]).T.reset_index().rename(columns={'index':'职位编号',0:'职位描述'})
final_data = pd.merge(data, data_jd, on = '职位编号', how = 'left') # 将两组数据合并
final_data.to_csv('pm_position.csv', index=False, encoding = 'utf-8-sig') # 导出爬取最终数据
五、数据可视化呈现
由于时间关系,本次爬取的数据量较少,如需获取更多的职位信息,可以分类爬取,例如循环公司规模这个类别,观察url可寻得相应类别的规律,这里不多赘述。拉勾网专注的是互联网垂直领域的招聘,所以还需要从其它全类招聘网站上获取更多的数据才能进行分析。也因个人需求设置了相应的筛选条件,因此此次数据分析的结果并不具代表性,仅简单说明爬取结果。
经过去重处理后,共有410条产品经理岗位招聘数据,岗位信息来自279家规模各异的公司,其中招聘需求最大的是字节跳动公司。根据本次搜索结果,有21座城市提供了产品经理的岗位,其中北深上广杭需求较大。
在岗位数量排名前7的城市里,薪酬中位数似乎也呈现阶梯式排名。北京已超25k/月,深圳、上海和杭州月薪水平相当,在20k/月左右,不管从岗位需求量还是薪酬水平方面看,成都表现得都不错,这也和近几年不少大公司都在成都设立了业务部门有关。
全面了解产品经理这个岗位的要求是本次爬取网页的主要目的,从这个词云看,产品经理必须具备产品设计规划能力、产品运营推广能力和多部门之间的协调沟通能力。产品经理必须掌握的软件和技能分为以下几类:原型设计——Axure、Sketch,思维导图——Xmind、MindManager,流程图制作——Visio,用户需求调研——CRM,项目管理——Project,以及三大文档——PRD、MRD、BRD。还可以看到几个比较醒目的英文单词:Web、PC、AI、SaaS(Software-as-a-Service)、ERP(Enterprise Resource Planning),产品的发展趋势一直在变化,要求产品经理不断深耕和变革,这也是产品经理这个岗位最吸引我的地方。
六、结语
转行路上,切实体会到了“选择比努力更重要”这句话的分量。愿大家都能快乐工作,快乐生活!