[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)

 

目录

 

一、目标

二、51job网页分析:

1.网页构成观察

2.网页分析

三、代码实现

1. 踩过的坑-----实现城市选择

2.代码实现

3.代码优化

1)存放格式优化

 2)在爬虫中去掉\xa0\xa0

3)用normalize-space(节点)去掉\r\n\t

4.pipelines.py 定义存储


一、目标

实现 通过传参(职位和地区)控制抓取51job职位信息。

抓取内容包括:职位名称、薪资、招聘公司、福利待遇、城市区域、经验要求、学历要求、招工人数、招聘信息发布时间、公司地址、岗位职责、任职要求、公司性质、公司规模和招聘公司所在行业。

二、51job网页分析:

1.网页构成观察

搜索入口1:

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第1张图片

搜索入口2: 

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第2张图片

抓取页面: 

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第3张图片

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第4张图片

2.网页分析

搜索入口:

观察比较两个搜索入口,都可以输入职位名称和选取地区,而 https://search.51job.com/ 显然比首页更靠近抓取的信息,所以起始页面选择https://search.51job.com/

抓取内容:

获取每一个岗位链接,进入详情页抓取目标字段内容。

观察过详情页的内容后,将抓取字段确定为:

  • title:职位名称
  • salary:薪资
  • company: 公司名称
  • req(request): 招聘简介(包括城市区域、经验要求、招工人数和发布时间)
  • welfare: 福利
  • job_info: 职位信息(包括岗位职责和岗位要求(即任职要求))
  • contact_info: 公司地址
  • nature:公司性质
  • size:公司规模
  • industry: 公司行业

三、代码实现

1. 踩过的坑-----实现城市选择

尝试1:

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第5张图片

原本想用selenium+click的方法实现.

通过观察源代码发现点击左边的大写城市首字母,右边的城市会对应更新,试着用selenium将左边首字母全部点击一边,将右边含有城市text的tr标签全部刷新出来。试图以text匹配传入的城市参数,如果匹配上就click该标签来实现城市的选择,(不得不说这是个很烂的想法,)失败了

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第6张图片

 尝试2:

试图通过setattribute()的方法更改表示城市的value值,也失败了

这次尝试的结果是value值可以更改,但是对于搜索来说不起作用

尝试3:

不气馁,继续找!!!

最终发现实际上起到决定作用的是这串阿拉伯数字的value值。虽然我本次只抓取北上广深四个城市的职位信息,找到这四个城市对应的数字代码自己建个dict就可以爬取了。但是还是想找找一劳永逸的方法~

最终找到一个这样的网址  https://m.51job.com/ ,看起来好像手机端的页面(忘记怎么找到的了,写文章的时候又回页面上去找,就是找不到……只能直接贴结果了。。。)

这个页面可以获取到所有城市对应的数字代码。

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第7张图片

这个问题解决了,接下来的就水到渠成了!


2.代码实现

爬取所有城市对应数字代码,保存到本地csv中。只需要爬取一次就可以了,需要的时候可以用读取本地csv的方式使用。

def saveID():
    if os.path.exists('areaID.csv'): # 如果存在areaID.csv就停止执行此函数
        return
    else: # 如果不存在就爬取ID 并保存csv
        browser = webdriver.Chrome()
        url = 'https://m.51job.com/'
        browser.get(url)
        browser.refresh()
        browser.find_element_by_tag_name('header').find_element_by_class_name('location').click()
        parent = browser.find_elements_by_class_name('elist')
        IDList=[]
        for each in parent:
            divs = each.find_elements_by_class_name('e')
            for every in divs:
                id = every.get_attribute('value')
                city = every.find_element_by_class_name('tit').text
                IDList.append(id)
                IDList.append(city)
        # 将IDlist存入csv
        with open('areaID.csv','w', encoding='utf-8', newline='') as file:
            csv.writer(file).writerow(IDList)

编写函数,实现传参控制爬取 不同职位和不同地区及页数

def urls(jobname, count,*area):
    # 读取csv,获得IDlist
    with open("areaID.csv", "r", encoding="utf-8", newline="") as file:
        csvfile = csv.reader(file)
        IDList = list(csvfile)[0]
    # 遍历IDList,根据传参(*area)获得area对应的ID
    idlist = []
    for x in range(len(IDList)):
        for y in (area):
            if IDList[x] == y:
                if IDList[x - 1] not in idlist:
                    idlist.append(IDList[x - 1])
    str1 = ','.join(idlist)  # 将list转化为字符串
    city = request.quote(request.quote(str1))  # 将地区代码加密转化为51job的地区代码,主要是为逗号加密
    position = request.quote(request.quote(jobname))  # 将职位加密转为51job的职位代码
    url = 'https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,1.html'
    # 以下操作用于获得该职位 该地区 共有多少页
    browser = webdriver.Chrome()
    browser.get(url)
    # 过滤文字得到只有数字的字符串
    page = ''.join(filter(str.isdigit,browser.find_element_by_class_name('p_in').find_element_by_class_name('td').text))

    #拼接参数,得到start_urls
    start_urls = ['https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,'+str(i)+'.html' for i in range(count,int(page))]
    return start_urls

 数据爬取,通过遍历start_urls 链接下的职位列表,获得每个职位的详情链接

class jobinfo(CrawlSpider):
    #设置name
    name = 'jobinfo'
    #设置域名
    allowed_domains = ['search.51job.com','jobs.51job.com']
    # 设置起始页
    # !!!!!!! 在这里改入参获得不同职位和地区对应的网址
    start_urls = urls('数据分析',1,'上海','广州','北京','深圳','苏州')
    # 定义函数爬取所有详情页的连接
    def parse(self, response):
        item = JobinfoItem()
        # 通过Selector 将response 转换成对应对象
        selector = Selector(response)
        # 以下为信息提取,用xpath的方式提取信息
        # 先获取父元素
        divs=selector.xpath('//div[@id="resultList"]//div[@class="el"]')
        # 用循环找出详情页的链接
        for each in divs:
            href = each.xpath('./p/span/a/@href').extract()[0]
            # 将提取出来的链接传给parse_detail()函数处理

            yield Request(href,meta = {'front_item':copy.deepcopy(item)},callback=self.parse_detail)

    # 抓取详情页内容
    def parse_detail(self,response):
        item = response.meta['front_item']
        selector = Selector(response)
        # 职位名称
        title = selector.xpath('//div[@class="cn"]/h1/@title').extract()
        try:
            # 薪资
            salary = selector.xpath('//div[@class="cn"]/strong/text()').extract()
        except:
            salary = '面议'
        # 公司名称
        company = selector.xpath('//p[@class="cname"]/a/@title').extract()
        # 招聘简介
        req = selector.xpath('//p[@class="msg ltype"]/@title').extract()
        try:
            # 福利
            welfare = selector.xpath("//div[@class='jtag']")[0].xpath('.//span/text()').extract()
        except:
            welfare = '福利信息缺失'
        try:
            # 联系方式
            contact_info = selector.xpath('//div[@class="bmsg inbox"]//p//text()').extract()
        except:
            contact_info = '无公司联系方式'

        div1 = selector.xpath('//div[@class="com_tag"]')
        #公司性质
        nature = div1.xpath('p[1]/@title').extract()
        #公司规模
        size = div1.xpath('p[2]/@title').extract()
        #公司行业
        industry = div1.xpath('p[3]/@title').extract()

        item['title'] = title
        item['salary'] = salary
        item['company'] = company
        item['req'] = req
        item['welfare'] = welfare
        item['job_info'] = job_info
        item['contact_info'] = contact_info
        item['nature'] = nature
        item['size'] = size
        item['industry'] = industry

        yield item

3.代码优化

运行后的结果如下图,就这样保存丢给清洗的team member后,被大大滴吐槽了........

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第8张图片

  1.  首先所有字段都以列表的形式存放,后期读取需要做多一步的转换。
  2. 字段中有很多\xa0\xa0
  3. 字段中有大量\r\n\t

这些都为后期清洗增加了不少工作量,所以想办法优化下~

1)存放格式优化

在获取内容的时候我用的是extract(),这样获得的是一个list,通过观察网页代码,除“福利”的提取外都可以用extract_first(),这样就可以得到一个string。

也可以用get()

附文档对于get()   getall()  和  extract_first()   extract() 的用法说明

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第9张图片

 2)在爬虫中去掉\xa0\xa0

我们通常所用的空格是\X20,是在标准ASCII可见字符0x20~0x7e范围内

而\xa0属于latin1(ISO/IEC_8859-1)中的扩展字符集字符,代表空白符 (non-breakingspace)

这里用str.replace(u'\xa0',u'')的方式去除

req = selector.xpath('//p[@class="msg ltype"]/@title').extract_first().replace(u'\xa0', u'')

3)用normalize-space(节点)去掉\r\n\t

normalize,字面意思就是正规化,加入space大概意思就是空格的处理了。

官方解释是这样的:

通过去掉前导和尾随空白并使用单个空格替换一系列空白字符,使空白标准化。如果省略了该参数,上下文节点的字符串值将标准化并返回。

这里要去除text中的\r\n\t,需要注意的是不可以normalize-space(text()) 这样使用。

.是当前节点。如果在需要字符串的地方使用它(例如作为参数normalize-space()),引擎会自动将节点转换为节点的字符串值,对于元素来说,该元素是元素内连接的所有文本节点。

text() 另一方面仅选择作为当前节点的直接子节点的文本节点。

例如,给定XML:

Foo
    Bar
  lish

假设是你当前的节点,normalize-space(.)将返回Foo Bar lish,但normalize-space(text())会失败,因为text()返回两个文本节点(Foolish)的节点集,它normalize-space()不接受。

(敲重点!!!)如果你想标准化一个元素中的所有文本,请使用.。如果要选择特定的文本节点,请使用text()。

尽管名称不同,但text()返回一个节点集,如果节点集只有一个元素,它将自动转换为字符串。

(normalize-space()的用法引自:https://www.cnblogs.com/songzhenhua/p/10121504.html)

将job_info 和contact_info两个字段做如下修改:

job_info = selector.xpath('normalize-space(//div[@class="bmsg job_msg inbox"]//.)').extract_first().replace(u'\xa0', u'')
contact_info = selector.xpath('normalize-space(//div[@class="bmsg inbox"]//p//.)').extract_first()

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第10张图片

[Python] scrapy + selenium 抓取51job 职位信息(实现 传参 控制抓取 页数+职位名称+城市)_第11张图片

抓到的数据干干净净整整齐齐,妈妈再也不用担心我被同事打,欧耶~~~

4.pipelines.py 定义存储

import csv
class savetocsv(object):
    def __init__(self):
        self.file= csv.writer(open('job51.csv','a',newline='',encoding='utf-8'))
        self.file.writerow('职位名称,薪资,公司名称,招聘简介,福利,职位信息,联系方式,公司性质,公司规模,公司行业'.split(','))
    def process_item(self, item, spider):
        data=[]
        for each in dict(item).values():
            if each:
                data.append(each)
            else:
                data.append([''])
        self.file.writerow(data)
        return item

    def close_spider(self,spider):
        print('存储CSV文件结束')

-------------------------------------------------------------------我是分割线--------------------------------------------------------------------------------

附上spiders完整代码:

# -*- coding: gbk -*-
from scrapy.spiders import CrawlSpider
from scrapy.selector import Selector
from scrapy.http import Request
from jobinfo.items import JobinfoItem
from urllib import request
from selenium import webdriver
import csv
import os.path
import copy


#获取所有地域对应的ID,保存到csv文件
def saveID():
    if os.path.exists('areaID.csv'): # 如果存在areaID.csv就停止执行此函数
        return
    else: # 如果不存在就爬取ID 并保存csv
        browser = webdriver.Chrome()
        url = 'https://m.51job.com/'
        browser.get(url)
        browser.refresh()
        browser.find_element_by_tag_name('header').find_element_by_class_name('location').click()
        parent = browser.find_elements_by_class_name('elist')
        IDList=[]
        for each in parent:
            divs = each.find_elements_by_class_name('e')
            for every in divs:
                id = every.get_attribute('value')
                city = every.find_element_by_class_name('tit').text
                IDList.append(id)
                IDList.append(city)
        # 将IDlist存入csv
        with open('areaID.csv','w', encoding='utf-8', newline='') as file:
            csv.writer(file).writerow(IDList)
saveID()
def urls(jobname, count,*area):
    # 读取csv,获得IDlist
    with open("areaID.csv", "r", encoding="utf-8", newline="") as file:
        csvfile = csv.reader(file)
        IDList = list(csvfile)[0]
    # 遍历IDList,根据传参获得area对应的ID
    idlist = []
    for x in range(len(IDList)):
        for y in (area):
            if IDList[x] == y:
                if IDList[x - 1] not in idlist:
                    idlist.append(IDList[x - 1])
    str1 = ','.join(idlist)  # 将list转化为字符串
    city = request.quote(request.quote(str1))  # 将地区代码加密转化为51job的地区代码,主要是为逗号加密
    position = request.quote(request.quote(jobname))  # 将职位加密转为51job的职位代码
    url = 'https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,1.html'
    browser = webdriver.Chrome()
    browser.get(url)
    # 过滤文字得到只有数字的字符串
    page = ''.join(filter(str.isdigit,browser.find_element_by_class_name('p_in').find_element_by_class_name('td').text))

    #拼接参数,得到start_urls
    start_urls = ['https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,'+str(i)+'.html' for i in range(count,int(page))]
    return start_urls

class jobinfo(CrawlSpider):
    #设置name
    name = 'jobinfo'
    #设置域名
    allowed_domains = ['search.51job.com','jobs.51job.com']
    # 设置起始页
    # !!!!!!! 在这里改入参获得不同职位和地区对应的网址
    start_urls = urls('数据分析',1,'上海','广州','北京','深圳','苏州')
    # 定义函数爬取所有详情页的连接
    def parse(self, response):
        item = JobinfoItem()
        # 通过Selector 将response 转换成对应对象
        selector = Selector(response)
        # 以下为信息提取,用xpath的方式提取信息
        # 先获取父元素
        divs=selector.xpath('//div[@id="resultList"]//div[@class="el"]')
        # 用循环找出详情页的链接
        for each in divs:
            href = each.xpath('./p/span/a/@href').extract()[0]
            # 将提取出来的链接传给parse_detail()函数处理

            yield Request(href,meta = {'front_item':copy.deepcopy(item)},callback=self.parse_detail)

    # 抓取详情页内容
    def parse_detail(self,response):
        item = response.meta['front_item']
        selector = Selector(response)
        # 职位名称
        title = selector.xpath('//div[@class="cn"]/h1/@title').extract_first()
        try:
            # 薪资
            salary = selector.xpath('//div[@class="cn"]/strong/text()').extract_first()
        except:
            salary = '面议'
        # 公司名称
        company = selector.xpath('//p[@class="cname"]/a/@title').extract_first()
        # 招聘简介
        req = selector.xpath('//p[@class="msg ltype"]/@title').extract_first().replace(u'\xa0', u'') 
        try:
            # 福利
            welfare = selector.xpath("//div[@class='jtag']")[0].xpath('.//span/text()').extract()
        except:
            welfare = '福利信息缺失'
        # 职位信息,抓取div[@class="bmsg job_msg inbox"] 标签下所有的文字
        job_info = selector.xpath('normalize-space(//div[@class="bmsg job_msg inbox"]//.)').extract_first().replace(u'\xa0', u'')
        try:
            # 联系方式
            contact_info = selector.xpath('normalize-space(//div[@class="bmsg inbox"]//p//.)').extract_first()
        except:
            contact_info = '无公司联系方式'

        div1 = selector.xpath('//div[@class="com_tag"]')
        #公司性质
        nature = div1.xpath('p[1]/@title').extract_first()
        #公司规模
        size = div1.xpath('p[2]/@title').extract_first()
        #公司行业
        industry = div1.xpath('p[3]/@title').extract_first()

        item['title'] = title
        item['salary'] = salary
        item['company'] = company
        item['req'] = req
        item['welfare'] = welfare
        item['job_info'] = job_info
        item['contact_info'] = contact_info
        item['nature'] = nature
        item['size'] = size
        item['industry'] = industry

        yield item

 

你可能感兴趣的:(数据挖掘,数据分析,爬虫,Python)