(Python版) Scrapy+Django+Selenium 爬取Boss直聘 职位信息

絮叨一下(本言论参考其他作者)

boos直聘,想必对于找工作的同志都非常熟悉,为了快速获取boss上的发布职位信息

今天就用scrapy框架进行岗位,薪资,待遇,公司,招聘要求 等信息进行爬取

之前尝试单独使用scrapy进行爬取,直接在web上登录boss,从浏览器截取cookie,遇到了boss的各种反扒机制

为了解决这些头疼的反扒,在scrapy爬虫框架的基础上融入了django与selenium


你有可能会问,为什么要用django,scrapy框架就是一款极其简单的爬虫而已,没有封装ORM,对于大多数学习过Python的人来说,web框架中接触的最多的就是Django了,而Django自带的ORM非常好用,有ORM的帮助,爬下来的数据插入数据库非常的方便

不要认为scrapy提供的导入json文件或者把爬到的数据写入xsl表格就可以,因为在爬虫调试阶段存在各种试错与添加if判断,加上boss的反扒监测非常严格,既然启动了爬虫获得了数据,就不要浪费了,记录到数据库中,下次调试获取到新数据的时候只需要update新字段就行,如果觉得自己的SQL原生语句能力很强,那么请忽略我这段话继续往下看

分析

boss直聘网站: 「北京招聘信息」北京招聘网 - BOSS直聘

他的反爬还是很讨厌的,信息都是用cookies渲染生成的,cookies时效很短,很快就失效了,快速访问还会封掉你的ip ,使用代理会提示ip异常,最终一样逃不过人机交互接入码校验平台了,所以在这里我尝试过各种办法后,在爬虫中加入selenium来提高爬虫的稳定性

当然了首页是没有反爬的,那就慢一点,虽然这一点都不像爬虫的正确姿势,为了能稳定获取数据,也要慢慢来

思路简介

爬虫的核心还是要靠scrapy,因此django只是提供一个保存数据库的方法(django中叫做model),所以我们需要做的就是

在django上搭建一个model,让所有需要保存的数据通过这个model,依靠django框架完成保存

在scrapy上写一个爬虫,得到数据源,然后传递到这个model中

而selenium帮助你解决boss直聘更新cookie出现302跳转的问题,用scrapy中的middlewares中间件来截获selenium产生的response对象

大致的流程如下:

scrapy启动-->打开selenium-->创建浏览器对象-->打开浏览器载入scrapy中的第一个爬虫url-->selenium通过浏览器获取到boss直聘响应的response信息-->scrapy中的middlewares中间件截获response对象进行数据处理-->通过代码xpath拿到response对象中的数据-->数据关联django中的model模型完成数据的入库操作

开撸(环境是Python3.7.3 win64位 ,开发工具Pycharm2020.2版)


需要准备如下插件:

1、scrapy
2、django
3、selenium
4、pymysql
5、mysqlclient
6、scrapy-djangoitem

由于本人使用Pycharm2020.2(2020以上版本的IDE都支持中文)开发,所以对于环境安装非常便捷,这里不过于啰嗦环境的安装,所有的环境都在windows上安装,因为有使用到selenium,不推荐服务器版linux,有图形化界面的linux也行,因为boss一旦给你跳出人机校验的话,这里还是需要人工去解决以下的,目前暂时没有找到代码的处理方案

在Pycharm中安装插件包的方式如下:

点击“文件按钮”,在列表中找到“设置”按钮,并点击
在打开的设置窗口中,在左侧栏目找到“项目:Scrapy_Boss”,展开栏目后,点击第一个“Python解释器”,右侧窗口打开后,点击“+”号
新打开窗口“Available Packages”,在安装窗口的搜索栏中搜索需要的框架或第三方库名称
附一个本人安装的第三方库的列表,如果使用本文章的代码发现各种报错,详细检查下我使用的三方库的版本

以上插件都准备妥当之后

开始在Django上进行操作(先操作Django还是先操作Scrapy都无所谓,新手建议跟着我的步骤来,熟手可以无视步骤)

这里建议新建一个空文件夹,方便一步步熟悉整个流程,同时其他框架的文件也都在这个目录下。这里我新建了一个空文件夹:Scrapy_Boss

文件夹建立好了之后,在IDE的最下方点击“终端”,在终端的界面中输入以下命令来完成Django项目的创建操作(我创建的django项目名称为djangoboss):

1.在命令行中cd到这个空文件下,然后:

django-admin startproject djangoboss

截图都是建立好的,目的是为了让读者有直观的感受

如果是pycharm的编辑器,把Scrapy_Boss下的djangoboss文件夹标记为资源文件:(右键,下面有一个“标记目录为” 然后选“源 根”),文件夹为蓝色的就对了

image

这样就新建了一个django的项目,名字是djangoboss。总的来说在django上只需要完成两部操作:修改django中的settings文件来链接mysql数据库,同时建立一个app用来创建models模型

2、配置mysql

在这个位置找到settings.py文件,打开:

image

然后找到这段话,将这段话给注释掉(django默认配置sqlite数据库、sqlite是一种轻量级的微型数据库,多数在手机中使用,浏览器默认支持sqlite3):

DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
     }
 }

替换掉,具体的配置根据自己的情况来:

DATABASES= {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  #django的数据库插件地址
        'NAME': 'zhpdb_test', #数据库的名称
        'HOST': '127.0.0.1', #数据库的地址
        'PORT': 3306, #数据库端口
        'USER': 'root', #数据库登录用户名
        'PASSWORD': '123456', #数据库密码
    }
}

此时完成Scrapy项目整体的30%配置,完成度django部分 50%

3、新建model

想要新建model,需要先建立一个app,然后依靠app来建model:

  • 新建 app
    1.首先进入这个项目中,命令需要使用manage.py,所以进入到manage.py同级的目录:

cd djangoboss/

2.然后建立app:为了便于区分,我叫它 savebossdate,用以表示是做存储的app:

python manage.py startapp savebossdate

3.在刚才的setting文件中找到这一栏,末尾添加‘savebossdate’:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'savebossdate', #将你新创建的项目载入到INSTALLED_APPS中,使得savebossdate项目在django中生效
]

4.到这里新建app的操作已经完成

  • 新建model
    1.打开savebossdate的文件夹,找到models.py文件并打开
    添加如下代码:
# models.py
from django.db import models

# Create your models here.

class HrRecruitmentCatch(models.Model):
    id = models.CharField(primary_key=True, max_length=40)
    pk_id = models.CharField(max_length=50, blank=True, null=True)
    region = models.CharField(max_length=100, blank=True, null=True)
    experience = models.CharField(max_length=50, blank=True, null=True)
    education = models.CharField(max_length=50, blank=True, null=True)
    person_sum = models.CharField(max_length=10, blank=True, null=True)
    hiring_time = models.CharField(max_length=50, blank=True, null=True)
    position_info = models.CharField(max_length=100, blank=True, null=True)
    address = models.CharField(max_length=500, blank=True, null=True)
    salary = models.CharField(max_length=100, blank=True, null=True)
    company_name = models.CharField(max_length=200, blank=True, null=True)
    company_desc = models.TextField(blank=True, null=True)
    job_description = models.TextField(blank=True, null=True)
    company_address = models.CharField(max_length=200, blank=True, null=True)
    source = models.CharField(max_length=20, blank=True, null=True)
    source_keyword = models.CharField(max_length=20, blank=True, null=True)
    url = models.CharField(max_length=500, blank=True, null=True)
    job_url = models.CharField(max_length=500, blank=True, null=True)
    create_time = models.DateTimeField(blank=True, null=True)
    create_by = models.CharField(max_length=20, blank=True, null=True)
    update_time = models.DateTimeField(blank=True, null=True)
    update_by = models.CharField(max_length=20, blank=True, null=True)
    company_logo = models.CharField(max_length=300, blank=True, null=True)
    company_tags = models.CharField(max_length=300, blank=True, null=True)
    job_vline = models.CharField(max_length=100, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'hr_recruitment_catch'

此models中的字段信息是与数据库表一一对应的,如果对django很熟悉的童鞋可以用manage.py里的makemigrations命令来完成数据库迁移【这里的迁移是生成对应的表】,不熟悉的童鞋可以用建表语句去数据库创建,建表语句下面给出来

2.在当前目录(目录文件中包含有manage.py)的命令行中,输入如下命令,完成数据库新建超出:

python manage.py makemigrations
python manage.py migrate

与上面models对应的建表语句(数据库是mysql)

/*
 Navicat Premium Data Transfer

 Source Server         : 123.57.55.99
 Source Server Type    : MySQL
 Source Server Version : 50730
 Source Host           : 123.57.55.99:3306
 Source Schema         : zhpdb_test

 Target Server Type    : MySQL
 Target Server Version : 50730
 File Encoding         : 65001

 Date: 06/11/2020 17:30:32
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for hr_recruitment_catch
-- ----------------------------
DROP TABLE IF EXISTS `hr_recruitment_catch`;
CREATE TABLE `hr_recruitment_catch`  (
  `id` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `pk_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '抓取数据唯一标识',
  `region` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '招聘地区',
  `experience` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经验',
  `education` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '学历',
  `person_sum` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '招聘人数',
  `hiring_time` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发布时间',
  `position_info` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职位',
  `address` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  `salary` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '薪资范围',
  `company_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司名称',
  `company_desc` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '公司介绍',
  `job_description` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '职位要求',
  `company_address` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司地址',
  `source` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '来源',
  `source_keyword` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '搜索关键字',
  `url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司网站地址',
  `job_url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '招聘说明网站地址',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `update_by` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
  `company_logo` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司Logo',
  `company_tags` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '技能要求',
  `job_vline` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '招聘年限',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `recruitment_catch_index1`(`id`) USING BTREE,
  INDEX `recruitment_catch_index2`(`pk_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

完成以上操作后,django这部分就结束了,之后就是scrapy的操作了。

在scrapy上的操作

  • scrapy上总的来说有五步:

1、item.py编写,把django中的models延伸到scrapy中来
2、pipeline.py编写,告诉django保存item中的数据
3、settings.py配置,使整个scrapy爬虫框架生效
4、爬虫类编写,获取数据源
5、使我们可以在编辑器中调试这个整合过的项目

首先创建scrapy项目

首先我们需要先建立一个爬虫项目,通过pycharm底部的终端窗口,将命令行所在的位置移动到Scrapy_Boss这个根目录下,执行下面的命令就可以了:

cd .. # 进入到Scrapy_Boss的目录下,使得创建的scrapy项目与django是平级关系
scrapy startproject boss # 创建爬虫项目,名字是:boss

然后把最外层的boss标记为“源 根”(如果是pycharm)
连续点开两个boss文件后,我们可以看到这样子的目录:


根节点的boss文件夹为蓝色,同时可以看到scrapy中的目录层次,因为本图为后截,所以多了一些文件,无视就行
  • 1、items的编写
    点开这个items.py文件,可以看到有一个BossItem(类文件)的模板
    我们修改这个模板,为(修改后,可以通过from的导入路径发现HrRecruitmentCatch来自于Django中的models,同时让BossItem类继承DjangoItem,这样就强制的将scrapy与Django的ORM模型进行了关联,具体用法往后看):
import scrapy
from scrapy_djangoitem import DjangoItem #类文件的继承
from savebossdate.models import HrRecruitmentCatch #关联django中的models

class BossItem(DjangoItem):
    django_model = HrRecruitmentCatch  #HrRecruitmentCatch  是django中的models

这里,我们更换了继承的类,同时通过这个scrapy的items.py导入了django中的app的model,完成了两个框架间的关联

  • 2、pipeline.py编写,告诉django保存item中的数据(使用django来保存数据,没有django如何保存数据,看2.1)
    只需要添加一句话就行,在原有的BossPipeline的基础上改为(只需要一个item.save()就可以了):
class BossPipeline(object):
  def process_item(self, item, spider):
    item.save()
    return item
  • 2.1、在没有django的情况下scrapy保存数据用以下方式修改(没有django就需要新增一个“init”函数来建立SQL的链接,同时在process_item函数中手写SQL语句来完成数据的写入,因为process_item函数中的item参数是一个字典,里面有获取到的页面数据,保存方式可以有很多种,用IO写入到硬盘,或者写成cvs,写成xsl表格都是能记录数据的方式,本人喜欢SQL记录):
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
import logging

import pymysql
from itemadapter import ItemAdapter

from savebossdate.models import HrRecruitmentCatch


class BossPipeline(object):

    # 创建init,使得方法运行时初始化mysql的链接
    def __init__(self):
        # 连接数据库
        self.connect = pymysql.connect(
            host='127.0.0.1',  # 数据库地址
            port=3306,  # 数据库端口
            db='zhpdb_test',  # 数据库名
            user='root',  # 数据库用户名
            passwd='123456',  # 数据库密码
            charset='utf8',  # 编码方式
            use_unicode=True)
        # 通过cursor执行增删查改
        self.cursor = self.connect.cursor()


    def process_item(self, item, spider):
        # pk_id = item['pk_id']
        # source_keyword = item['source_keyword']
        # pk_id_obj = HrRecruitmentCatch.objects.filter(pk_id=pk_id, source_keyword=source_keyword)
        # if not pk_id_obj:
        #     item.save()
        # else:
        #     pk_id_obj.update(**item)


        company_uuid = str(uuid.uuid1().hex)
        pk_id = item['company_pkid']
        company_name = item['company_name']
        region = item['job_area']
        position_info = item['job_name']
        company_logo = item['company_icon']
        company_tags = item['company_tags']
        hiring_time = item['job_pub_time']
        salary = item['job_limit']
        job_vline = item['job_vline']
        url = item['url']

        sql_inset = 'INSERT INTO hr_recruitment_catch(id,pk_id,company_name,region,position_info,company_logo' \
                    ',company_tags,hiring_time,salary,job_vline,url,source) ' \
                    'VALUES ("{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}"' \
                    ')'.format(company_uuid, pk_id, company_name, region, position_info, company_logo
                               , company_tags, hiring_time, salary, job_vline, url, 'BOSS直聘')
        print(sql_inset)

        try:
            # 执行sql语句
            self.cursor.execute(sql_inset)
            # 提交到数据库执行
            self.connect.commit()
            print('提交了数据')
        except:
            print('发生了错误')
            # 如果发生错误则回滚
            self.connect.rollback()

        return item
  • 3、settings.py配置,使整scrapy完整,基本可以运行
  • 第一个:

ROBOTSTXT_OBEY = False # 这个是一个机器人标识,请关掉它

  • 第二个:默认会被注释掉,只需要取消注释即可
ITEM_PIPELINES = {
   'boss.pipelines.BossPipeline': 300,
}
  • 第三个,在settings.py文件的最上方添加以下代码,目的是为了让scrap运行的同时去找到django,并把django给启动,django的models是需要django运行才能正常使用的
import datetime
import os
import sys

import django

BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
PRO_ROOT = os.path.dirname(BASE_DIR)  # 两个项目共同的根目录

sys.path.append(os.path.join(PRO_ROOT, 'boss'))
sys.path.append(os.path.join(BASE_DIR, 'boss'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangoboss.settings'
django.setup()
  • 第四个,爬虫类编写,获取数据源
    首先建立一个爬虫,进入到终端,用以下命令:

scrapy genspider startboss 'www.zhipin.com'

此时你就可以在boss>boss的目录下找到一个刚刚生成的spiders文件夹,此文件夹下有一个startboss.py的文件,此文件用来编写爬虫的具体实现方式,具体代码已全部给出:

(代码具体说明,新手注意看)因为文章写到这整体爬虫项目是可以完整运行的,唯独缺少了Selenium,如果没有Selenium的情况下StartbossSpider类文件中必须配置以下1、2,如果配置了Selenium,那么cookie_list就变得无所谓了:
1、cookie_list
2、custom_settings
因为boss的反扒机制,所有的页面的数据会有类似防伪标志一样,防伪数据是写入到cookie里的,如果你问我cookie_list里的参数去哪找,看截图:


请求页面后,通过浏览器的开发者工具,查看当前请求页面,从中间找到cookie,把值拷贝下来即可

在文章的开头我就说过,boss直聘的反扒机制很恶心,我分析过之后boss的反扒机制是大致情况如下:

  • 在固定频率的请求下boss直聘的服务器会检测你的请求,如果都是固定的请求时间,比如都是1秒请求一次,服务器会将你列为可疑对象,大概过一段时间IP直接拉黑
  • 登陆自己的帐号之后可以防止服务器拉黑你的IP,但是服务器还是会把你列为可疑对象,此时boss直聘的服务器就利用cookie来测试你是否为爬虫,原来请求的一个A链接地址会突然出现一个302跳转,此时的一个跳转页面会将你当前的请求重定向去cookie更新的服务器获取一个叫zp_stoken参数,然后这个参数会每隔几秒或者几分钟失效,失效后又出现一个302跳转来更新zp_stoken参数的值,如果你是机器爬虫,肯定不好解决这个cookie写入的问题,之前有看一个反扒取大神找到了zp_stoken的生成js,通过反解析js提取到了生成方法ABC函数,利用Node.js来运行,自己手动注入新的cookie来完善爬虫
  • 如果你真的以为找到ABC函数自己注入cookie就结束了吗?因为你的请求已经被boss直聘服务器监控到了,最终他们会在你的请求中302强制跳转一个人机交互验证码出来,是下面这个样子的,boss直聘用的空间推理验证码,出自GEETEST厂家非常恶心,代码是完全无法跳过这一层的,所以为了稳定爬取数据,我选择加入selenium来完成这个恶心的验证码,别以为机器能绕过,这个恶心的验证码一定是人工无法绕过的。
    已经找到了boss直聘用的空间推理验证码了
import random
import time
import uuid

import scrapy

from boss.items import BossItem
from boss.replacetool import replace_tool


class StartbossSpider(scrapy.Spider):
    name = 'startboss'
    allowed_domains = ['www.zhipin.com']
    start_urls = ['https://www.zhipin.com/c101010100/?query=web&page=4&ka=page-4']
  
    cookie_list = 'JSESSIONID=""; _bl_uid=4ykqjfX6hdkyydmCyw1Lp9t5pIn8; t=qh4neJeO0hJ6VRos; wt=qh4neJeO0hJ6VRos; lastCity=101010100; __zp_seo_uuid__=206a837f-b53b-4e88-9e8a-53313a08b64c; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1603241871,1604389355,1604568321,1604569983; __g=-; __c=1601020932; __l=l=%2Fwww.zhipin.com%2Fbeijing%2F&r=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DY0MdiRccX-4tmkpeDiIC0ijU-gd1YeFv87bTN880xDar_lR5pQIs1LjhXwPev-UA%26wd%3D%26eqid%3D876d94d600040234000000035fa3cb79&g=&friend_source=0&friend_source=0; __a=86720333.1601020932..1601020932.189.1.189.189; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1604570405; __zp_stoken__=c1a6baTQnfQ13ED8OPSBbMF01XmESdmlnPkdSbikDdjhAfz1jAzlkZXhjIQJnOU9GD3tWBEgoFgwid2Y1MmcpEEloaVIwW0k%2FSB54J2ogC0oiQSZJQ3MISlFiOSwOTEQcCSpcFwdWDAZdfnhWOQ%3D%3D; __zp_sseed__=HXp3GPvGQtLqMBAdoUyHHD7OOY8XbWzi5fpcYurmhA8=; __zp_sname__=c61cd8a7; __zp_sts__=1604570407347'

    custom_settings = {
        'DEFAULT_REQUEST_HEADERS': {
            'Cookie': cookie_list,
            'Referer': 'https://www.zhipin.com',
        }
    }

    def parse(self, response):
        print('*' * 40)
        print(response.url)
        # if not response.headers.getlist('Set-Cookie'):
        #     return


        # 获取当前页面的列表数据
        job_list = response.xpath('//div[@class="job-list"]/ul/li')
        # print(job_list)
        for i in job_list:
            print('开始获取数据')
            bossitem = BossItem()
            bossitem['id'] = str(uuid.uuid1().hex)
            company_url = i.xpath('./div/div/div[@class="info-company"]/div/h3/a/@href').get()
            bossitem['url'] = 'https://www.zhipin.com{}'.format(company_url)
            bossitem['pk_id'] = company_url.split('/')[2].split('.')[0]
            pk_id = bossitem['pk_id']
            url = bossitem['url']
            bossitem['job_url'] = 'https://www.zhipin.com{}'.format(
                i.xpath('./div/div[1]/div[1]/div/div[1]/span[1]/a/@href').get())
            job_url = bossitem['job_url']
            bossitem['company_name'] = i.xpath('./div/div/div[@class="info-company"]/div/h3/a/text()').get()
            bossitem['company_logo'] = i.xpath('./div/div[1]/div[2]/a/img/@src').get()
            tags = i.xpath('./div/div[2]/div[1]/span/text()').extract()
            bossitem['company_tags'] = ','.join(tags)
            bossitem['position_info'] = i.xpath(
                './div/div[1]/div[@class="primary-wrapper"]/div/div[@class="job-title"]/span[1]/a/text()').get()
            bossitem['region'] = i.xpath('./div/div[1]/div[1]/div/div[1]/span[2]/span/text()').get()
            bossitem['hiring_time'] = i.xpath('./div/div[1]/div[1]/div/div[1]/span[3]/text()').get()
            bossitem['salary'] = i.xpath('./div/div[1]/div[1]/div/div[2]/span/text()').get()
            job_limit = i.xpath('./div/div[1]/div[1]/div/div[2]/p/text()').extract()
            bossitem['job_vline'] = job_limit[0]
            bossitem['education'] = job_limit[1]
            bossitem['source'] = 'BOSS直聘'
            bossitem['source_keyword'] = 'web'
            yield bossitem
            time.sleep(2)
            # yield scrapy.Request(url, callback=lambda response, pk_id=pk_id: self.boss_company_desc(response, pk_id))
            yield scrapy.Request(job_url, callback=lambda response, pk_id=pk_id: self.boss_company_job(response, pk_id))


        # company_url = response.xpath('//div[@class="job-list"]/ul/li/div/div/div[@class="info-company"]/div/h3')
        # for i in company_url:
        #     url = 'https://www.zhipin.com' + i.xpath('./a/@href').get()
        #     print(url)
        #     pk_id = i.xpath('./a/@href').get().split('/')[2].split('.')[0]
        #     time.sleep(random.randint(1,5))
        #     yield scrapy.Request(url, cookies=self.cookie_list, callback=lambda response,pk_id=pk_id:self.boss_company_desc(response,pk_id))

        # 定义下页标签的元素位置
        next_page = response.xpath('//div[@class="page"]/a/@href').extract()[-1]
        print('next_page--->', next_page)
        # 判断什么时候下页没有任何数据
        if next_page != 'javascript:;':
            base_url = "https://www.zhipin.com"
            url = base_url + next_page
            time.sleep(random.randint(5,10))
            yield scrapy.Request(url=url, cookies=self.cookie_list, callback=self.parse)
 
    #采集公司信息
    def boss_company_desc(self, response, pk_id):
        bossitem = BossItem()
        bossitem['pk_id'] = pk_id
        bossitem['company_desc'] = response.xpath('//*[@id="main"]/div[3]/div[1]/div[1]/div/div[1]/div/text()').get()
        yield bossitem
    
     #采集公司发布的职位信息,发现这个页面也包含公司信息,所以注释掉了boss_company_desc函数的运行实体,上面的函数不会运行
    def boss_company_job(self, response, pk_id):
        bossitem = BossItem()
        bossitem['pk_id'] = pk_id
        bossitem['job_description'] = replace_tool(response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div').get())
        bossitem['company_desc'] = replace_tool(response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[2]/div/text()').get())
        bossitem['source_keyword'] = 'web'
        yield bossitem
  • 第五个、建立一个main文件,方便爬虫直接运行,无需命令运行,在boss>boss目录下新建main.py,在此文件中加入以下代码:
# from scrapy import cmdline
#
# cmdline.execute("scrapy crawl startboss".split())


import sys
import os
from scrapy.cmdline import execute

sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy", "crawl", "startboss"])  # 这句代码会执行爬虫类中 name = "startboss"的类,也就是运行StartbossSpider类,使得爬虫正式运行

加入Selenium完善爬虫的稳定性

因为没有ABC的函数体,也不清楚boss是否有更新ABC函数,在无法解决cookie加密的问题与302重定向的问题,既然浏览器自己本身可以完成cookie的更新获取,那我只需要通过载入selenium加载浏览器帮我完成这一步就可以自动更新cookie了,废话不多说,开始继续写代码,文章的开头有让大家安装Selenium三方库

安装命令如下

pip install selenium

Selenium的运行是会在你的电脑上打开浏览器的,所以需要对应浏览器的驱动,去下面根据自己的浏览器版本下载驱动,推荐Chrome与Firefox,驱动下载:

对测试人员来说selenium非常的强大,selenium的具体使用方式参考全网超级详细的selenium介绍! 本文不对selenium做过多的介绍

根据你使用的浏览器下载好驱动之后,把驱动exe文件放入到boss>boss目录下,别放错目录:


与middlewares同级的目录,因为浏览器的调用是在middlewares中实现的

打开middlewares调整中间件(拦截器)的代码,用于截取Selenium中的response

不要动middlewares.py中原来的代码,因为在scrapy的settings.py文件中我们还没有打开中间件的设置,所以我们只需要加入代码即可,打开middlewares加入以下代码:

class CookiesMiddlewares(object):
    # 需要随机的请求头
    USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
        'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36',
        'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; pl-PL; rv:1.0.1) Gecko/20021111 Chimera/0.6',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko, Safari) Cheshire/1.0.UNOFFICIAL',
        'Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1b2) Gecko/20060821 BonEcho/2.0b2 (Debian-1.99+2.0b2+dfsg-1)'
    ]

    def __init__(self):
        print("初始化浏览器")
        self.driver = webdriver.Chrome()

    def process_request(self,request,spider):
        # 随机生成一个请求头
        user_agent = random.choice(self.USER_AGENTS)
        request.headers['User-Agent'] = user_agent

        self.driver.get(request.url)
        time.sleep(5)
        # 我们等待5秒钟,让其加载
        source = self.driver.page_source
        #获取页面的源码
        response = HtmlResponse(url=self.driver.current_url,body=source,request=request,encoding='utf-8')
        # Response 对象用来描述一个HTTP响应
        return response
        # 这样我们就获取到了所有的信息,并返回response

然后打开scrapy中的settings.py文件,修改配置使得middlewares中的类文件生效:

#修改下载延迟时间,DOWNLOAD_DELAY设置越大请求越慢
DOWNLOAD_DELAY = 3
# 修改并发请求数,修改为1,或者2,越小爬取速度越慢,太快容易被识别到
CONCURRENT_REQUESTS = 1

"""cookie的设置"""
COOKIES_ENABLED = False

"""开启中间建"""
DOWNLOADER_MIDDLEWARES = {
    'boss.middlewares.CookiesMiddlewares': 543,
    # 'boss.middlewares.UserAgentDownloadMiddleWare': 543,
}

做完以上的所有操作后整个的(Python版) Scrapy+Django+Selenium 爬取Boss直聘 职位信息到这里就算告一段落了

整体代码大部分都带有注释,新手使用注意看注释自己调整

写给看到最后的你

朋友,感谢你看到了最后,技术不成熟的地方可以给予评论,一同完善,原创不宜,请勿乱喷,作者深知高手在明间

同时写给看到最后的“你”

一路走来,风雨无阻,感谢你陪我一起深入python的世界进行探索,共同成长,希望这篇文章能够帮助你了解scrapy

所有的代码仅供大家练习Scrapy使用,如果有侵权现象,请联系作者第一时间删除本文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343