51Job招聘信息抓取实战分析

鳄鱼君Ba

发表文章数:519

热门标签

,

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python教程 » 51Job招聘信息抓取实战分析

爬取前程无忧的招聘信息,通过设定的关键词与地点来搜索站内的招聘信息并实现数据爬取,这是项目的爬虫功能需求。具体说明如下:
● 在浏览器访问51Job官方网站(https://www.51job.com/),并在搜索框输入关键词“Python”,地点选为“南阳”,单击“搜索”按钮进入搜索页
● 在搜索页中,所有符合条件的职位信息以列表的形式排序并设有分页显示。每条职位信息是一个URL地址,通过URL地址可以进入该职位的详情页
● 职位详情页也是数据爬取的页面,爬取的数据信息有:职位名称、企业名称、待遇、福利及职位要求等

项目的开发工具选择Requests模块Xpath模块实现爬虫开发与数据清洗,数据存储选择Sqlalchemy框架,数据库选择MySQL。

获取城市编号

从招聘主页的搜索框开始,观察搜索页的URL地址变化:
南阳:https://search.51job.com/list/170600,000000,0000,00,9,99,Python,2,1.html
哈尔滨:https://search.51job.com/list/220200,000000,0000,00,9,99,Python,2,1.html
成都:https://search.51job.com/list/090200,000000,0000,00,9,99,Python,2,1.html

不同的地点只需要更换list后面的值即可,值从哪里来呢?大部分都是通过网站的API接口获取的,所以说肯定包含在网站的请求中,如果不知道如何寻找借口,可参考:使用Chrome浏览器开发工具分析网站。这里我直接给出接口来源:https://js.51jobcdn.com/in/js/2016/layer/area_array_c.js,所有的城市编号都包含在内。

只要对上述URL发送HTTP请求即可获取城市的数字编号,并将该请求的响应内容转换为字典格式,再将字典的键值进行互换。由于爬虫是根据使用者输入城市名来获取相应的数字编号,再通过城市编号构建相应的URL地址,所以将字典的键值进行互换可方便数字编号的获取。将城市的数字编号获取功能定义为函数get_city_code,函数的具体代码如下:

import requests
# 获取城市的城市编号
def get_city_code():
    url='https://js.51jobcdn.com/in/js/2016/layer/area_array_c.js?20200429'
    response=requests.get(url).text
    # 字符串转换为字典
    city_dict=eval(response.split('=')[1].rstrip(';')) # 替换干扰字符
    # 字典的键值互换
    city_dict={v:k for k,v in city_dict.items()}
    return city_dict
get_city_code()

获取招聘职位总页数

获取城市编号后,就可以动态构建搜索页的URL地址,实现不同地点的不同关键词的职位搜索。在爬取职位信息之前,还需要确定当前职位的总页数,因为同一职位可能会有成千上万条招聘信息,而这些招聘信息都会进行分页处理。

观察搜索页的网页结构可以发现,总页数的获取方式有两种:在分页栏直接获取总页数或者通过总职位数除以每页的职位数。这里可以随意选择,鳄鱼君Ba使用第二种方式实现,因为考虑到极端情况,当搜索的关键词没有相关的职位,分页栏的总页数显示为1;而总职位数显示为0,通过除法计算得出总页数为0。

总页数的获取若是选择总职位数除以每页的职位数,需要在搜索页上获取总职位数及计算每页的职位数,从而计算得出总页数。每页的职位数在搜索页上通过数数即可获得,每页的职位数量上限为50;总职位数可以在源码中找到.

综合上述分析,我们将职位总页数的获取定义为函数get_pageNumber,参数为city_code和keyword,分别代表城市编号关键词,函数代码如下:

from lxml import etree
import math
# 定义请求头
headers={
    "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
}
# 获取职位的总页数,参数city_code是城市编号,keyword是职位关键字
def get_pageNumber(city_code,keyword):
    # 获取搜索页的网页信息
    url='https://search.51job.com/list/{0},000000,0000,00,9,99,{1},2,1.html'.format(city_code,keyword)
    response=requests.get(url,headers=headers)
    # 使用xpath模块进行数据清洗
    tree=etree.HTML(response.content)
    # 查找总职位数
    find_page=tree.xpath('//div[@class="dw_tlc"]/div[4]/text()')[0].strip()
    # 通过正则提取shuzhi
    temp=re.findall('共(.*?)条职位',find_page,re.S)[0]
    if temp:
        pageNumber=math.ceil(int(temp)/50)
        print(pageNumber)
    else:
        return 0
get_pageNumber('090200','Python')

函数get_pageNumber的实现逻辑大致如下:
(1)首先向网站发送HTTP请求并获取相应的响应内容,发送请求的URL地址为搜索页的URL地址,而搜索页的URL地址是通过参数city_code和keyword构建而成。
(2)然后从响应内容中提取总职位数,提取方式由BeautifulSoup4模块和re模块实现,前者用于对总职位数进行精准定位,后者用来去除总职位数的中文内容。
(3)最后将总职位数和每页的职位数进行除法计算得出总页数,计算过程由math.ceil方法实现,ceil方法是将计算结果的小数点去除并对整数的个位进一。

获取每个职位信息

我们已经构造出总的页码数,翻页的URL地址也很容易一眼就看出来,接下来就是循环获取每页的职位列表

# 遍历每一页的职位信息
def get_page(keyword,pageNumber):
    for i in range(int(pageNumber)):
        url='https://search.51job.com/list/{0},000000,0000,00,9,99,{1},2,{2}.html'.format('090200',keyword,i+1)
        response=requests.get(url,headers=headers)
        tree=etree.HTML(response.content)
        job_href=tree.xpath('//div[@id="resultList"]//div[@class="el"]/p/span/a/@href')
        # 进入职位详情页获取数据
        for a in job_href:
            try:
                info_dict=None
                info_dict=get_info(a)
                # 入库处理
                if info_dict:
                    insert_db(info_dict)
            except Exception as e:
                print(e)
get_page('Python',31)

变量a为职位详情页的URL,传递给函数get_info,函数get_info是实现职位详情页的信息抓取,爬取结果以字典的形式表示并赋值给变量info_dict,变量info_dict作为函数insert_db的参数,函数insert_db是实现数据入库处理。爬取职位详情页数据如下:

def get_info(url):
    temp_dict={}
    if 'https://jobs.51job.com' in url:
        response=requests.get(url,headers=headers)
        tree=etree.HTML(response.content)
        # 职位ID
        temp_dict['job_id']=url.split('.html')[0].split('/')[-1]
        # 职位名称
        temp_dict['job_name']=tree.xpath('/html/body/div[3]/div[2]/div[2]/div/div[1]/h1/text()')[0]
        # 企业名
        temp_dict['company_name']=tree.xpath('/html/body/div[3]/div[2]/div[2]/div/div[1]/p[1]/a[1]/text()')[0]
        # 职位要求
        temp_dict['msy_type']=''.join([s.strip() for s in tree.xpath('/html/body/div[3]/div[2]/div[2]/div/div[1]/p[2]//text()')])
        # 职位标签
        temp_dict['company_tags']='-'.join(tree.xpath('/html/body/div[3]/div[2]/div[2]/div/div[1]/div/div//span/text()'))
        # 职位薪资
        temp_dict['job_money']=tree.xpath('/html/body/div[3]/div[2]/div[2]/div/div[1]/strong/text()')[0]
        # 职位信息
        temp_dict['job_msg']=''.join(tree.xpath('/html/body/div[3]/div[2]/div[3]/div[1]/div//p/text()'))
        # 上班地点
        temp_dict['job_location']=tree.xpath('/html/body/div[3]/div[2]/div[3]/div[2]/div/p/text()')[0]
        return temp_dict

这里没有处理空值,可以自己修改一下。很多数据存在一些换行符,需要替换掉!

数据存储

为了区分数据爬取与数据入库,我们将数据入库的代码编写在insql.py文件中。数据入库使用SQLAlchemy框架+MySQL数据库实现,在MySQL里创建数据库spiderdb,将数据库编码设为utf8。然后根据函数get_info的返回值来定义数据模型,再通过数据模型在数据库中创建数据表。数据模型的定义如下所示:

import time
from sqlalchemy import *
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.mysql import *
engine=create_engine(
    'mysql+pymysql://root:123@localhost:3306/spiderdb?charset=utf-8'
)
DBSession=sessionmaker(bind=engine)
SQLsession=DBSession()
Base=declarative_base()
# 定义数据模型,映射数据表
class table_info(Base):
    __tablename__='job_infos'
    id=Column(Integer(),primary_key=True)
    job_id=Column(String(100),comment='职位 ID')
    job_name=Column(String(1000),comment='职位名称')
    company_name=Column(String(1000),comment='企业名')
    msy_type=Column(String(3000),comment='职位要求')
    company_tags=Column(String(3000),comment='职位标签')
    job_money=Column(String(1000),comment='职位薪资')
    job_msg=Column(String(10000),comment='职位信息')
    job_location=Column(String(2000),comment='上班地点')
    log_date=Column(String(100),comment='记录日期')
# 创建数据表
Base.metadata.create_all(engine)

完成数据模型的定义后,接下来实现函数insert_db的功能代码。函数insert_db会对职位ID进行判断,如果数据表已有这条数据,则对数据表的数据进行更新,否则在数据表中新增数据,函数的代码如下:

def insert_db(info_dict):
    temp_id=info_dict['job_id']
    # 判断是否已经存在记录
    info=SQLsession.query(table_info).filter_by(job_id=temp_id).first()
    if info:
        info.job_id=info_dict.get('job_id',''),
        info.job_name=info_dict.get('job_name',''),
        info.company_name=info_dict.get('company_name',''),
        info.msy_type=info_dict.get('msy_type',''),
        info.company_tags=info_dict.get('company_tags',''),
        info.job_money=info_dict.get('job_money',''),
        info.job_msg=info_dict.get('job_msg',''),
        info.job_location=info_dict.get('job_location',''),
        info.log_date=time.strftime('%Y-%m-%d',time.localtime(time.time())),
    else:
        insert_data=table_info(
            job_id=info_dict.get('job_id',''),
            job_name=info_dict.get('job_name',''),
            company_name=info_dict.get('company_name',''),
            msy_type=info_dict.get('msy_type',''),
            company_tags=info_dict.get('company_tags',''),
            job_money=info_dict.get('job_money',''),
            job_msg=info_dict.get('job_msg',''),
            job_location=info_dict.get('job_location',''),
            log_date=time.strftime('%Y-%m-%d',time.localtime(time.time())),
        )
        SQLsession.add(insert_data)
    SQLsession.commit()

爬虫配置文件

上面共定义了4个函数,分别是get_city_code()、get_pageNumber()、get_info()和get_page()。每个函数实现的功能说明如下:
● get_city_code():获取城市编号,可通过城市名转换城市编号来构建搜索页的URL地址。
● get_pageNumber():在搜索页获取总职位数,并通过总职位数除以每页的职位数来获取总页数。函数参数为city_code和keyword,分别代表城市编号和职位关键词。
● get_info():爬取职位详情页的数据,并以字典的形式返回。函数参数url是职位详情页的URL地址。
● get_page():遍历总页数,每次遍历是为爬取当前搜索页所有招聘职位的详情页地址,将爬取的详情页地址作为函数get_info()的函数参数,通过调用函数get_info()实现招聘信息的爬取,最后再调用函数insert_db进行入库处理。


整个项目的核心功能是由这4个爬虫函数及入库函数insert_db共同实现,4个函数的代码编写在爬虫文件51job.py中。接下来对核心功能进行包装处理。

因此可以为程序添加配置文件,使用者只需对配置文件的配置信息进行修改即可,无需改动源码,这样可以防止源码修改而引发的异常问题。配置文件的文件扩展名为conf,它可支持Windows、Linux和MacOS系统。在爬虫文件51job.py的同一目录下新增配置文件51job.conf,并以记事本的方式打开配置文件,在文件里编写以下配置信息:

[51job] # 配置信息的标题
keyword=python,java
city=广州,北京,郑州

在配置信息中,“[51job]”是配置信息的标题,一个配置文件可以设有多个这样的标题,它的作用是方便程序对配置信息的定位与查找。本项目设计的爬虫程序是根据上述两个条件进行数据爬取,所以在标题下分别设有关键词和地点。每条配置信息(即关键词和地点)可以设置多个配置内容,每个配置内容之间使用英文逗号隔开。

完成配置文件51job.conf的配置后,还需要对爬虫文件51job.py进行修改,还需要实现配置文件的读取和程序的运行方式,具体的代码如下:

if __name__ == '__main__':
    # 获取城市编号
    conf=configparser.ConfigParser()
    conf.read("51job.conf",encoding='utf-8')
    keyword=str(conf.get('51job','keyword')).split(',')
    city=str(conf.get('51job','city')).split(',')
    # 运行主程序
    for c in city:
        # 获取城市编号
        city_code=get_city_code()[c]
        for k in keyword:
            # 获取总页数
            pageName=get_pageNumber(city_code,k)
            #遍历总页数
            get_page(k,pageName)

配置文件的读取是由Python的标准库configparser实现,使用configparser模块读取配置信息并将配置信息进行分段截取,使每个配置内容(即关键词和地点)以列表的元素表示。通过遍历列表keyword和city即可实现不同条件组合的数据爬取。

如果不喜欢MySQL数据存储可以使用MongoDB,自己修改代码

def save_mongo(data):
    data['log_time']=time.strftime('%Y-%m-%d',time.localtime(time.time()))
    client = pymongo.MongoClient()
    db = client['51JOB']
    if db['51job'].insert_one(data):
        print('存储到MongoDB成功')
    else:
        print('存储失败')

未经允许不得转载:作者:鳄鱼君Ba, 转载或复制请以 超链接形式 并注明出处 鳄鱼君Ba
原文地址:《51Job招聘信息抓取实战分析》 发布于2020-06-07

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

6 + 8 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册