Pyhton爬虫实战
Pyhton爬虫实战
零、致谢
感谢BOSS直聘相对权威的招聘信息,使本人有了这次比较有意思的研究之旅。
由于爬虫持续爬取 www.zhipin.com 网站,以致产生的服务器压力,本人深感歉意,并没有 DDoS 和危害贵网站的意思。
[2017-12-14更新]在跑了一夜之后,服务器 IP 还是被封了,搞得本人现在家里、公司、云服务器三线作战啊
[2017-12-19更新]后续把拉勾网的数据也爬到,加了进来
一、抓取详细的职位描述信息
1.1 前提数据
这里需要知道页面的 id 才能生成详细的链接,在 Python爬虫框架Scrapy实战 - 抓取BOSS直聘招聘信息 中,我们已经拿到招聘信息的大部分信息,里面有个 pid 字段就是用来唯一区分某条招聘,并用来拼凑详细链接的。
是吧,明眼人一眼就看出来了。
1.2 详情页分析
详情页如下图所示

在详情页中,比较重要的就是职位描述和工作地址这两个
由于在页面代码中岗位职责和任职要求是在一个 div 中的,所以在抓的时候就不太好分,后续需要把这个连体婴儿,分开分析。
1.3 爬虫用到的库
使用的库有
- requests
- BeautifulSoup4
- pymongo
对应的安装文档依次如下,就不细说了
1.4 Python 代码
"""
@author: jtahstu
@contact: root@jtahstu.com
@site: http://www.jtahstu.com
@time: 2017/12/10 00:25
"""
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import time
from pymongo import MongoClient
headers = {
'x-devtools-emulate-network-conditions-client-id': "5f2fc4da-c727-43c0-aad4-37fce8e3ff39",
'upgrade-insecure-requests': "1",
'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
'accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
'dnt': "1",
'accept-encoding': "gzip, deflate",
'accept-language': "zh-CN,zh;q=0.8,en;q=0.6",
'cookie': "__c=1501326829; lastCity=101020100; __g=-; __l=r=https%3A%2F%2Fwww.google.com.hk%2F&l=%2F; __a=38940428.1501326829..1501326829.20.1.20.20; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1501326839; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1502948718; __c=1501326829; lastCity=101020100; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1501326839; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1502954829; __l=r=https%3A%2F%2Fwww.google.com.hk%2F&l=%2F; __a=38940428.1501326829..1501326829.21.1.21.21",
'cache-control': "no-cache",
'postman-token': "76554687-c4df-0c17-7cc0-5bf3845c9831"
}
conn = MongoClient('127.0.0.1', 27017)
db = conn.iApp # 连接mydb数据库,没有则自动创建
def init():
items = db.jobs_php.find().sort('pid')
for item in items:
if 'detail' in item.keys(): # 在爬虫挂掉再此爬取时,跳过已爬取的行
continue
detail_url = "https://www.zhipin.com/job_detail/%s.html?ka=search_list_1" % item['pid']
print(detail_url)
html = requests.get(detail_url, headers=headers)
if html.status_code != 200: # 爬的太快网站返回403,这时等待解封吧
print('status_code is %d' % html.status_code)
break
soup = BeautifulSoup(html.text, "html.parser")
job = soup.select(".job-sec .text")
if len(job) < 1:
continue
item['detail'] = job[0].text.strip() # 职位描述
location = soup.select(".job-sec .job-location")
item['location'] = location[0].text.strip() # 工作地点
item['updated_at'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) # 实时爬取时间
res = save(item) # 保存数据
print(res)
time.sleep(40) # 停停停
# 保存数据到 MongoDB 中
def save(item):
return db.jobs_php.update_one({"_id": item['_id']}, {"$set": item})
if __name__ == "__main__":
init()
代码 easy,初学者都能看懂。
1.5 再啰嗦几句
在 上一篇文章 中只是爬了 上海-PHP 近300条数据,后续改了代码,把12个城市的 PHP 相关岗位的数据都抓下来了,有3500+条数据,慢慢爬吧,急不来。
像这样


二、数据清洗
2.1 校正发布日期
"time" : "发布于03月31日",
"time" : "发布于昨天",
"time" : "发布于11:31",
这里拿到的都是这种格式的,所以简单处理下
import datetime
from pymongo import MongoClient
db = MongoClient('127.0.0.1', 27017).iApp
def update(data):
return db.jobs_php.update_one({"_id": data['_id']}, {"$set": data})
# 把时间校正过来
def clear_time():
items = db.jobs_php.find({})
for item in items:
if not item['time'].find('布于'):
continue
item['time'] = item['time'].replace("发布于", "2017-")
item['time'] = item['time'].replace("月", "-")
item['time'] = item['time'].replace("日", "")
if item['time'].find("昨天") > 0:
item['time'] = str(datetime.date.today() - datetime.timedelta(days=1))
elif item['time'].find(":") > 0:
item['time'] = str(datetime.date.today())
update(item)
print('ok')
2.2 校正薪水以数字保存
"salary" : "5K-12K",
#处理成下面的格式
"salary" : {
"low" : 5000,
"high" : 12000,
"avg" : 8500.0
},
# 薪水处理成数字,符合 xk-yk 的数据处理,不符合的跳过
def clear_salary():
items = db.jobs_lagou_php.find({})
for item in items:
if type(item['salary']) == type({}):
continue
salary_list = item['salary'].lower().replace("k", "000").split("-")
if len(salary_list) != 2:
print(salary_list)
continue
try:
salary_list = [int(x) for x in salary_list]
except:
print(salary_list)
continue
item['salary'] = {
'low': salary_list[0],
'high': salary_list[1],
'avg': (salary_list[0] + salary_list[1]) / 2
}
update(item)
print('ok')
[2017-12-19更新]这里在处理 Boss 直聘的数据时,比较简单正常,但是后续抓到拉勾网的数据,拉勾网的数据有些不太规范。比如有‘20k以上’这种描述
2.3 根据 工作经验年限 划分招聘等级
# 校正拉勾网工作年限描述,以 Boss直聘描述为准
def update_lagou_workyear():
items = db.jobs_lagou_php.find({})
for item in items:
if item['workYear'] == '应届毕业生':
item['workYear'] = '应届生'
elif item['workYear'] == '1年以下':
item['workYear'] = '1年以内'
elif item['workYear'] == '不限':
item['workYear'] = '经验不限'
update_lagou(item)
print('ok')
# 设置招聘的水平,分两次执行
def set_level():
items = db.jobs_zhipin_php.find({})
# items = db.jobs_lagou_php.find({})
for item in items:
if item['workYear'] == '应届生':
item['level'] = 1
elif item['workYear'] == '1年以内':
item['level'] = 2
elif item['workYear'] == '1-3年':
item['level'] = 3
elif item['workYear'] == '3-5年':
item['level'] = 4
elif item['workYear'] == '5-10年':
item['level'] = 5
elif item['workYear'] == '10年以上':
item['level'] = 6
elif item['workYear'] == '经验不限':
item['level'] = 10
update(item)
print('ok')
这里有点坑的就是,一般要求经验不限的岗位,需求基本都写在任职要求里了,所以为了统计的准确性,这个等级的数据,后面会被舍弃掉。
[2017-12-14更新]从后续的平均数据来看,这里的经验不限,一般要求的是1-3年左右,但是还是建议舍弃掉。
[2017-12-19更新]拉勾网的职位描述和 Boss直聘稍有不同,需要先校正,然后再设置等级
2.4 区分开<岗位职责>和<任职要求>
对于作者这个初学者来说,这里还没有什么好的方法,知道的同学,请务必联系作者,联系方式在个人博客里
so , i'm sorry.
为什么这两个不好划分出来呢?
因为这里填的并不统一,可以说各种花样,有的要求在前,职责在后,有的又换个名字区分。目前看到的关于要求的有['任职条件', '技术要求', '任职要求', '任职资格', '岗位要求']这么多说法。然后顺序还不一样,有的要求在前,职责在后,有的又反之。
举个栗子
会基本的php编程!能够修改简单的软件!对云服务器和数据库能够运用!懂得微信公众账号对接和开放平台对接!我们不是软件公司,是运营公司!想找好的公司学习的陕西基本没有,要到沿海城市去!但是我们是实用型公司,主要是软件应用和更适合大众!
啥也不说的,这里可以认为这是一条脏数据了。
再举个栗子
PHP中级研发工程师(ERP/MES方向)
1、计算机或相关学科本科或本科以上学历;
2、php和Java script的开发经验。
3、Linux和MySQL数据库的开发经验;
5、有ERP、MES相关开发经验优先;
6、英语的读写能力;
7、文化的开放性;
我们提供
1、有趣的工作任务;
2、多元的工作领域;
3、与能力相关的收入;
4、年轻、开放并具有创造力的团队和工作氛围;
5、不断接触最新科技(尤其是工业4.0相关);
6、可适应短期出差(提供差补);
这个只有要求,没职责,还多了个提供,我乐个趣 ╮(╯▽╰)╭
所以,气的想骂人。
2.5 缺失值分析 [2017-12-19]更新
Boss直聘这里有部分招聘没有industryField、financeStage和companySize值,这个可以看前一篇的爬虫代码,拉勾网的数据基本没问题。
2.6 异常值分析 [2017-12-19] 更新
- 岗位要求工作年限和职位描述里的要求不一致,比如岗位列表里要求的是
1年以内,但是职位描述里却是2年以上工作经验,这是由于 HR 填写不规范引起的误差。 - 由第1点引起的另一个问题,就是与工作年限要求不对应的薪水,使计算的平均薪水偏高。比如一条记录,年限要求是
一年以内,所以等级为2,但是薪水却是20k-30k,实际上这是等级为3的薪水,这里就得校正 level 字段,目前只是手动的把几个较高的记录手动改了,都校正过来很困难,得文本分析招聘要求。
2.7 失效值排除 [2017-12-19] 更新
首先这里需要一个判断某条招聘是否还挂在网站上的方法,这个暂时想到了还没弄
然后对于发布时间在两个月之前的数据,就不进行统计计算
ok ,现在我们的数据基本成这样了
{
"_id" : ObjectId("5a30ad2068504386f47d9a4b"),
"city" : "苏州",
"companyShortName" : "蓝海彤翔",
"companySize" : "100-499人",
"education" : "本科",
"financeStage" : "B轮",
"industryField" : "互联网",
"level" : 3,
"pid" : "11889834",
"positionLables" : [
"PHP",
"ThinkPHP"
],
"positionName" : "php研发工程师",
"salary" : {
"avg" : 7500.0,
"low" : 7000,
"high" : 8000
},
"time" : "2017-06-06",
"updated_at" : "2017-12-13 18:31:15",
"workYear" : "1-3年",
"detail" : "1、处理landcloud云计算相关系统的各类开发和调研工作;2、处理coms高性能计算的各类开发和调研工作岗位要求:1、本科学历,两年以上工作经验,熟悉PHP开发,了解常用的php开发技巧和框架;2、了解C++,python及Java开发;3、有一定的研发能力和钻研精神;4、有主动沟通能力和吃苦耐劳的精神。",
"location" : "苏州市高新区科技城锦峰路158号101park8幢"
}
由于还没到数据展示的时候,所以现在能想到的就是先这样处理了
项目开源地址:http://git.jtahstu.com/jtahstu/Scrapy_zhipin
三、展望和设想
首先这个小玩意数据量并不够多,因为爬取时间短,站点唯一,再者广度局限在 PHP 这一个岗位上,以致存在一定的误差。
所以为了数据的丰富和多样性,这个爬虫是一定要持续跑着的,至少要抓几个月的数据才算可靠吧。
然后准备再去抓下拉勾网的招聘数据,这也是个相对优秀的专业 IT 招聘网站了,数据也相当多,想当初找实习找正式工作,都是在这两个 APP 上找的,其他的网站几乎都没看。
最后,对于科班出身的学弟学妹们,过来人说一句,编程相关的职业就不要去志连、钱尘乌有、five eight桐城了,好吗?那里面都发的啥呀,看那些介绍心里没点数吗?
四、help
这里完全就是作者本人依据个人微薄的见识,主观臆断做的一些事情,所以大家有什么点子和建议,都可以评论一下,多交流交流嘛。
后续会公开所有数据,大家自己可以自己分析分析。
我们太年轻,以致都不知道以后的时光,竟然那么长,长得足够让我们把一门技术研究到顶峰,乱花渐欲迷人眼,请不要忘了根本好吗。
Pyhton爬虫实战的更多相关文章
- Pyhton爬虫实战 - 抓取BOSS直聘职位描述 和 数据清洗
Pyhton爬虫实战 - 抓取BOSS直聘职位描述 和 数据清洗 零.致谢 感谢BOSS直聘相对权威的招聘信息,使本人有了这次比较有意思的研究之旅. 由于爬虫持续爬取 www.zhipin.com 网 ...
- 【图文详解】python爬虫实战——5分钟做个图片自动下载器
python爬虫实战——图片自动下载器 之前介绍了那么多基本知识[Python爬虫]入门知识,(没看的先去看!!)大家也估计手痒了.想要实际做个小东西来看看,毕竟: talk is cheap sho ...
- Python爬虫实战(4):豆瓣小组话题数据采集—动态网页
1, 引言 注释:上一篇<Python爬虫实战(3):安居客房产经纪人信息采集>,访问的网页是静态网页,有朋友模仿那个实战来采集动态加载豆瓣小组的网页,结果不成功.本篇是针对动态网页的数据 ...
- Python爬虫实战(2):爬取京东商品列表
1,引言 在上一篇<Python爬虫实战:爬取Drupal论坛帖子列表>,爬取了一个用Drupal做的论坛,是静态页面,抓取比较容易,即使直接解析html源文件都可以抓取到需要的内容.相反 ...
- 关于Python网络爬虫实战笔记③
Python网络爬虫实战笔记③如何下载韩寒博客文章 Python网络爬虫实战笔记③如何下载韩寒博客文章 target:下载全部的文章 1. 博客列表页面规则 也就是, http://blog.sina ...
- 爬虫实战:爬虫之 web 自动化终极杀手 ( 上)
欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:陈象 导语: 最近写了好几个简单的爬虫,踩了好几个深坑,在这里总结一下,给大家在编写爬虫时候能给点思路.本次爬虫内容有:静态页面的爬 ...
- 自己动手,丰衣足食!Python3网络爬虫实战案例
本教程是崔大大的爬虫实战教程的笔记:网易云课堂 Python3+Pip环境配置 Windows下安装Python: http://www.cnblogs.com/0bug/p/8228378.html ...
- Python爬虫实战四之抓取淘宝MM照片
原文:Python爬虫实战四之抓取淘宝MM照片其实还有好多,大家可以看 Python爬虫学习系列教程 福利啊福利,本次为大家带来的项目是抓取淘宝MM照片并保存起来,大家有没有很激动呢? 本篇目标 1. ...
- Java基础-爬虫实战之爬去校花网网站内容
Java基础-爬虫实战之爬去校花网网站内容 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 爬虫这个实现点我压根就没有把它当做重点,也没打算做网络爬虫工程师,说起爬虫我更喜欢用Pyt ...
随机推荐
- JS new date在IOS出现的问题
实例代码: input = input.replace(/\-/g, "/");//横杠的时间不能被识别,所以要替换程斜杠 let time = new Date(input); ...
- 搭建spring项目,无法创建RequestMappingHandlerMapping异常
异常详情: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMa ...
- HAWQ技术总结
HAWQ技术总结: 1. 官网: http://hawq.incubator.apache.org/ 2. 特性 2.1 sql支持完善 ANSI SQL标准,OLAP扩展,标准JDBC/ODBC支持 ...
- PHP 实现并发-进程控制 PCNTL
参考 基于PCNTL的PHP并发编程 PCNTL 是 PHP 中的一组进程控制函数,可以用来 fork(创建)进程,传输控制信号等. 在PHP中,进程控制支持默认关闭.编译时通过 --enable-p ...
- spring map获取同类型的bean
今天看博客怎么减少if else 方法, 才发现spring 还有很多功能我没有用到,以后真的得花时间学学spring,今天学到的东西如下: 1.定义一个接口 store public interfa ...
- mysql5.7插入数据报错 Incorrect integer value
mysql5.7插入字符串为空的时候取出来的值设置为null
- Python基础-1 python由来 Python安装入门 注释 pyc文件 python变量 获取用户输入 流程控制if while
1.Python由来 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚 ...
- Symmetric Tree(对称二叉树)
来源:https://leetcode.com/problems/symmetric-tree Given a binary tree, check whether it is a mirror of ...
- Leveldb--Slice
http://www.kuqin.com/database/20110919/265041.html Slice非常简单的数据结构,它包括length和一个指向外部字节数组的指针.为什么使用Slice ...
- RocketMQ安装部署及整合Springboot
消息中间件的功能: 通过学习ActiveMq,kafka,rabbitMq这些消息中间件,我们大致能为消息中间件的功能做一下以下定义:可以先从基本的需求开始思考 最基本的是要能支持消息的发送和接收,需 ...