欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

scrapy由浅入深(二) 爬取51job职位薪资信息

程序员文章站 2022-05-09 22:15:21
...

        上次的爬虫只是爬取了CSDN论坛的问题数据,相对来说比较简单,本篇文章来介绍一下爬取51job网站,获取它的职位,薪资,职位要求等信息。

        代码思路:1.首先获取到种子网页的所有职位的url,以及下一页的url。2.通过抽取到的职位的url来依次请求相应职位的详细信息,包括薪资,职位要求等。3.定义解析数据的函数,通过xpath或者css选择器获取到职位薪资信息。4.请求第一步中获取到的下一页的网址,对下一页的网址进行分析,抽取出url,再依次分析。5.将数据保存到数据库。

1.创建项目

scrapy startproject wuyou

创建一个名称为wuyou的项目,爬取前程无忧网站的职位信息。

手动在spiders文件夹下创建一个job_spider.py文件,用来实现爬虫主要逻辑。

二.配置项目

(1)编写items文件

import scrapy


class WuyouItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    salary = scrapy.Field()
    profile = scrapy.Field()

我在这里定义了三个字段,分别对应职位名称,薪资,职位要求。

(2)编写pipelines文件

import sqlite3
db = sqlite3.connect("./../jobs.db")
cursor = db.cursor()

class WuyouPipeline(object):
    def process_item(self, item, spider):
        cursor.execute("insert into job(title, salary, profile) values (?,?,?)",(item["title"],item["salary"],item["profile"]))
        db.commit()

将数据保存在sqlite数据库中,也可以保存在MySQL数据库中,写法类似。

(3)配置settings文件

启用管道文件

ITEM_PIPELINES = {
   'wuyou.pipelines.WuyouPipeline': 300,
}

启用下载延迟

DOWNLOAD_DELAY = 3
RANDOMIZE_DOWNLOAD_DELAY = True

(4)编写spider文件

爬虫主要逻辑

# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from wuyou.items import WuyouItem


class JobSpiderSpider(scrapy.Spider):
    name = 'job_spider'
    # allowed_domains = ['jobs.51job.com']

    def start_requests(self):
        url_str = 'https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,1.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='
        yield Request(url=url_str,callback=self.parse,meta={"page":"0"})

    def parse(self, response):
        """
        解析出职位的url以及下一页的url
        :param response:
        :return:
        """
        all_href = response.xpath('//div[@class="el"]/p/span[not(@class)]/a/@href').extract()
        next_page = response.xpath('//a[text()="下一页"]/@href').extract_first()

        for href in all_href:
            yield Request(url=href,callback=self.parse_one_job,meta={"page":"0"})
        if next_page:
            yield Request(url=next_page,callback=self.parse,meta={"page":"0"},dont_filter=True)


    def parse_one_job(self,response):
        """
        通过xpath获取职位,薪资,职位要求信息
        :param response:
        :return:
        """
        title = response.xpath('//div[@class="cn"]/h1/@title').extract()[0]
        salary = response.xpath('//div[@class="cn"]/strong/text()').extract()[0]
        job_profile = response.xpath('//div[@class="bmsg job_msg inbox"]/p/text()').extract()
        # 因为51job页面有的职位信息xpath不相同,所以使用两个xpath来获取职位要求信息
        job_profile_div = response.xpath('//div[@class="bmsg job_msg inbox"]/div/text()').extract()
        if job_profile_div is not None:
            job_profile = job_profile_div + job_profile
        content = ""
        for profile in job_profile:
            content += profile
        content = ' '.join(content.split())
        item = WuyouItem()
        item["title"] = title
        item["salary"] = salary
        item["profile"] = content
        yield item

首先请求种子网址,从种子网址中抽取每个职位对应的url与下一页的url,通过函数parse_one_job解析每个工作的详细信息,通过对下一页的url解析,判断是否有下一页,如果有则请求下一页,并对下一页进行分析。

注意:因为51job网站工作详情的html标签并不相同,这里即使用到了两个xpath来获取职位的详细信息,但是依然有个别职位的详细信息无法爬取到。下次准备爬取智联招聘这个网站,虽然同样是招聘网站,但是它跟51job最大的区别在于,智联招聘是ajax动态页面,无法直接获取,下次介绍一下使用selenium模拟浏览器来爬取ajax动态页面。

优化(防止数据丢失)

        我在后续的爬去过程中发现这个程序会造成数据丢失的问题,原因在于,当我们判断如果有下一页就请求下一页的时候,这个请求与下载一个页面所有的职位信息速度不匹配,也就相当于当你请求到第十几页的时候,第一页的所有数据才会下载完成。那么这个时候scrapy已经接受了几百个爬取职位详细信息的请求,因为请求过多,所以会造成数据丢失的后果。

        解决办法:查询数据库的数据记录,因为51job一个网页有50条职位信息,当我们爬取了大约45条数据的时候就能执行请求下一页的操作了,这样就相当于只有在下载完当前页面的数据才会请求下一页的数据,也就大大减少了数据丢失的可能性。

        优化代码:

# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from wuyou.items import WuyouItem
import sqlite3
import time

db = sqlite3.connect("./../jobs.db")
cursor = db.cursor()

i = 0

def select_from_sql():
    """
    :return: 当前数据库中数据的总数
    """
    count = cursor.execute("select * from job")
    return len(count.fetchall())

class JobSpiderSpider(scrapy.Spider):
    name = 'job_spider'
    # allowed_domains = ['jobs.51job.com']

    def start_requests(self):
        url_str = 'https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,1.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='
        yield Request(url=url_str,callback=self.parse)

    def parse(self, response):
        """
        解析出职位的url以及下一页的url
        :param response:
        :return:
        """
        all_href = response.xpath('//div[@class="el"]/p/span[not(@class)]/a/@href').extract()
        next_page = response.xpath('//a[text()="下一页"]/@href').extract_first()

        for href in all_href:
            yield Request(url=href,callback=self.parse_one_job,meta={"next_page":next_page})


    def parse_one_job(self,response):
        """
        通过xpath获取职位,薪资,职位要求信息
        :param response:
        :return:
        """
        global i
        next_page = response.meta["next_page"]
        count = select_from_sql()

        title = response.xpath('//div[@class="cn"]/h1/@title').extract()[0]
        salary = response.xpath('//div[@class="cn"]/strong/text()').extract()[0]
        job_profile = response.xpath('//div[@class="bmsg job_msg inbox"]/p/text()').extract()
        # 因为51job页面有的职位信息xpath不相同,所以使用两个xpath来获取职位要求信息
        job_profile_div = response.xpath('//div[@class="bmsg job_msg inbox"]/div/text()').extract()
        if job_profile_div is not None:
            job_profile = job_profile_div + job_profile
        content = ""
        for profile in job_profile:
            content += profile
        content = ' '.join(content.split())


        item = WuyouItem()
        item["title"] = title
        item["salary"] = salary
        item["profile"] = content

        if count - i > 45:
            if next_page:
                i = count
                yield Request(url=next_page, callback=self.parse, meta={"page": "2"}, dont_filter=True)
        yield item