智联招聘上用Scrapy+selenium进行自动简历投递

  • 所需要的python包
  • 智联网页分析
    • 登陆分析:
    • 各个重要链接的分析
    • 页面处理
  • scrapy 代码部分讲解
    • 创建scrapy 项目
    • 入口代码分析:
    • 下载中间件代码
    • Pipelines代码
    • parseTitle() 代码
    • Item.py
    • settings.py
    • 结束语

所需要的python包

  1. 安装scrapy
  2. 安装selenium
  3. 安装 chrome driver

安装过程请参考其他资料, 这里就不在重复了. 主要一点是 chromedriver的版本同chrome的版本需要对应. 这个请参考 chromedriver官网

智联网页分析

登陆分析:

新版的智联招聘https://www.zhaopin.com/ 登陆采用手机号码发送验证码的方式进行登陆, 本文不对智联招聘进行模拟登陆, 我们仅采用手动登陆的方式进行登陆,并获取登陆后的 cookies. 模拟登陆设置机器学习等知识,太过复杂不适合我们初学者.

各个重要链接的分析

智联可以根据关键字搜索来获取你想要的招聘信息, 搜索网页一般翼 sou.zhaopin.com为主体 后面跟上各种参数.
这里我列举一个url : https://sou.zhaopin.com/?p=1&jl=538&sf=0&st=0&kw=IT&kt=3

p: 页数
sf: 起始薪水
st: 最高薪水
kw: keyword 搜索关键字
jl跟kt 应该跟用户有关,这里暂时不用理会

这个链接会获取各个招聘岗位的主体, 每个主体里又会有详细的工作岗位链接 这个工作安慰链接以 jobs.zhaopin.com 开头 比如 https://jobs.zhaopin.com/CC538140986J00242862803.htm

这里是具体的单个岗位的招聘详情, 有职位, 薪水, 公司, 招聘要求等所有详细的信息

以上两个链接是我们scrapy跟selenium重点需要处理的页面.

页面处理

单独页面的爬去 如果是一些简单的页面, 我们可以用request, bs4 等轻量型的爬虫工具来做, 但是你可以会发现一个问题 比如用request来操作:

import requests
res = requests.get(url)
print(res.text)

这里你会发现并没有任何招聘的信息, 原因是智联网页加载的时候需要执行js脚本 从而来获取招聘list的信息, 所以这里我们需要用selenium来控制网页的加载,driver.get(url)同时要注意 加载是有时间的, 所以需要用WebDriverWait函数来等待你需要获取的哪个元素加载完成,才能够返回. 这个是重点,或者你scrapy parse的时候 很可能获取不到信息

scrapy 代码部分讲解

创建scrapy 项目

这里我们架设你了解scrapy的常用语法以及流程

scrapy genspider zhaopin 'www.zhaopin.com'

入口代码分析:

#!/usr/bin/env python
#-*- coding: UTF-8 -*-"""
@version: Python3.6.1
@author:  Justinli"""
import scrapy
from .tool import parseTitle
from ..items import ZhaopinItem
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import re
import logging
import timelogger = logging.getLogger(__name__)class MaitianSpider(scrapy.Spider):name = "zhaopin"def __init__(self):self.options = Options()self.options.add_argument("--headless")self.options.add_argument('--no-sandbox')self.options.add_argument('User-Agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36"')self.options.add_argument('Accept-Encoding="gzip, deflate, br"')self.options.add_argument('Accept-Language="zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7"')self.options.add_argument('Accept="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"')self.options.add_argument('Host="i.zhaopin.com"')self.options.add_argument('Referer="https://www.zhaopin.com/"')self.options.add_argument('Upgrade-Insecure-Requests="1"')self.options.add_argument('--no-sandbox')self.options.add_argument("--disable-gpu")self.options.add_argument("--window-size=1920x1080")self.driver = webdriver.Chrome(chrome_options=self.options)self.cookiestr = {‘这里填入你的cookies str 直接从浏览器流复制过来就可以了不用这里格式,因为下面的代码会做操作’}self.cookie_dict = {i.split("=")[0].strip(): i.split("=")[1].strip() for i in self.cookiestr.split(";")}self.driver.get("https://jobs.zhaopin.com")for k, v in self.cookie_dict.items():self.driver.add_cookie({'domain': '.zhaopin.com', 'path': '/', 'expires': None, 'name': k, 'value': v})self.zhiwei_urls = []# self.url = "https://sou.zhaopin.com/?jl=538&sf=0&st=0&kw=IT&kt=3"self.start_urls = ["https://sou.zhaopin.com/?p=1&jl=538&sf=0&st=0&kw=IT&kt=3","https://sou.zhaopin.com/?p=1&jl=538&sf=0&st=0&kw=运维&kt=3",]# self.start_urls = ["https://jobs.zhaopin.com/CC538140986J00242862803.htm"]self.allowed_domains = ["zhaopin.com"]def closed(self, spider):print("spider closed")self.driver.close()def start_requests(self):for url in self.start_urls:yield scrapy.Request(url, callback=self.parse_title)def parse_title(self, response):try:next_page = response.xpath('//*[@id="pagination_content"]/div/button[2]/text()')if next_page is not None:if len(next_page) > 0:pnum = re.findall(r'.*\?p=(.+?)&.*', response.url)[0]restr = "?p=" + pnum + "&"newstr = "?p=" + str(int(pnum) + 1) + "&"new_url = response.url.replace(restr, newstr)self.zhiwei_urls.append(new_url)# yield scrapy.Request(new_url, callback=self.parse)zp_list = response.xpath('//*[@id="listContent"]/div')for zp in zp_list:zp_title = zp.xpath('./div/a/div[1]/div[1]/span[1]/@title').extract()if len(zp_title) > 0:zpt = zp_title[0]zp_company = zp.xpath('./div/a/div[1]/div[2]/a/text()').extract()if len(zp_company) > 0:zpc = zp_company[0]zp_link = zp.xpath('./div/a/@href').extract()if len(zp_link):zpl = zp_link[0]zp_salary = zp.xpath('./div/a/div[2]/div[1]/p/text()').extract()if len(zp_salary):zps = zp_salary[0]if zpt is not None and zpc is not None:if parseTitle(zpt, zpc, zps):yield scrapy.Request(zpl, callback=self.parse_job)else:logger.info("@@@@@@@@@")yield scrapy.Request(new_url, callback=self.parse_title)except:print("have error in this place!!!")returndef parse_job(self, response):logger.info("jobparse")jobzhaopin = ZhaopinItem()job_title = response.xpath('//*[@id="root"]/div[3]/div/div/h3/text()').extract()if len(job_title) > 0:jobzhaopin['title'] = job_title[0]else:jobzhaopin['title'] = "nothing"job_company = response.xpath('//*[@id="root"]/div[4]/div[2]/div[1]/div/a[1]/text()').extract()if len(job_company) > 0:jobzhaopin['company'] = job_company[0]else:jobzhaopin['company'] = "nothing"job_salary = response.xpath('//*[@id="root"]/div[3]/div/div/div[2]/div[1]/span/text()').extract()if len(job_salary) > 0:jobzhaopin['salary'] = job_salary[0]else:jobzhaopin['salary'] = "nothing"jobzhaopin['link'] = response.urljob_content = response.xpath('//*[@id="root"]/div[4]/div[1]/div[1]/div[2]/div/p[3]/text()').extract()if len(job_content) > 0:jobzhaopin['content'] = job_content[0]else:jobzhaopin['content'] = "nothing"jobzhaopin['date'] = time.strftime('%Y-%m-%d')return jobzhaopin
  1. init 里保存了 selenium的初始化信息 构建一个chromedriver
  2. start_requests是入口函数, 用来爬取 start_urls里的初始url 并转给parse_title来处理
  3. parse_title 是用来解析 每个 sou.zhaopin.com页面里的所有招聘岗位list的,同时根据 next_page 的情况来构建下一页的url, 并继续转给parse_title来处理, 同时根据parseTitle()函数来判断是该职位是否符合你的实际情况,如果符合继续转向parse_job
  4. parse_job 是用来解析每个jobs.zhaopin.com页面里的单个招聘岗位的详细信息, 并将数据return给 Pipelines来处理, 并将处理过的数据保存到数据库里, 以便parseTitle()判断下次是否要处理,避免你重复投递简历
  5. 注意重点 cookies 要有效, 这个cookies一次登陆可以用好久, 所以你尽量不要在web上点退出登陆或者重复登陆, 以免cookies失效

下载中间件代码

# -*- coding: utf-8 -*-from scrapy import signals
from selenium import webdriver
from scrapy.http import HtmlResponse
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
import logging
logger = logging.getLogger(__name__)class ChromeDownloaderMiddleware(object):def process_request(self, request, spider):try:spider.driver.implicitly_wait(20)spider.driver.get(request.url)if request.url.startswith("https://sou.zhaopin.com"):locator = (By.XPATH, "//*[@id='listContent']/div[1]/div/a/div[1]/div[1]/span[1]")WebDriverWait(spider.driver, 20, 0.5).until(EC.presence_of_all_elements_located(locator))netlocator = (By.XPATH, '//*[@id="pagination_content"]/div/button[2]')WebDriverWait(spider.driver, 20, 0.5).until(EC.presence_of_all_elements_located(netlocator))elif request.url.startswith("https://jobs.zhaopin.com"):#content的xpathlocator = (By.XPATH, '//*[@id="root"]/div[4]/div[1]/div[1]/div[2]/div')#申请该职位的xpathsclick = (By.XPATH, '//*[@id="root"]/div[3]/div/div/div[2]/div[2]/div/button')WebDriverWait(spider.driver, 20, 0.5).until(EC.presence_of_all_elements_located(locator))submit_click = WebDriverWait(spider.driver, 20, 0.5).until(EC.element_to_be_clickable(sclick))detail_content = spider.driver.find_element_by_xpath('//*[@id="root"]/div[4]/div[1]/div[1]/div[2]/div').textif "java" not in detail_content.lower() or "struts" not in detail_content.lower():submit_click.click()except TimeoutException:return HtmlResponse(url=request.url, request=request, encoding='utf-8', status=500)finally:return HtmlResponse(url=request.url, body=spider.driver.page_source, request=request, encoding="utf-8",status=200)

中间件的原理我就不多说了, 这里将chromedriver的处理放在下载中间件里进行,并判断url 是sou.zhaopin.com 还是jobs.zhaopin.com 并做不同的处理, 关键点还是 WebDriverWait() 用来确保你需要的节点已经完全加载了. 同时如果符合条件 可以进行click操做来投递简历. 这里无论是否投递 都将数据返回给Item 并进行数据库操作, 而不是只对click的部分进行保存. 这样下次这些不符合条件的岗位就不会在浪费资源的再来一次了

Pipelines代码

# -*- coding: utf-8 -*-
import pymysql
from twisted.enterprise import adbapi
from pymysql import cursors
from .items import ZhaopinItem
from scrapy.exporters import JsonItemExporter
import timeclass MySQLTwistedPipeline(object):@classmethoddef from_settings(cls, settings):db_params = dict(host=settings['MYSQL_HOST'],user=settings['MYSQL_USER'],passwd=settings['MYSQL_PASSWD'],port=settings['MYSQL_PORT'],db=settings['MYSQL_DBNAME'],charset=settings['MYSQL_CHARSET'],)db_pool = adbapi.ConnectionPool('pymysql', **db_params)return cls(db_pool)def __init__(self, db_pool):self.db_pool = db_pooldef process_item(self, item, spider):query = self.db_pool.runInteraction(self.insert_item, item)query.addErrback(self.handle_error, item, spider)return itemdef handle_error(self, failure, item, spider):print(failure)def insert_item(self, curosr, item):#jobs 是表的名称 根据实际的表名来更改 item里的字段也是 根据实际的情况更改sql = "INSERT INTO zhaopin(company, title, salary, link, submission_date)VALUES (%s, %s, %s, %s, %s)"curosr.execute(sql,(item['company'], item['title'], item['salary'], item['link'], item['date']))

这里的关键点在于 数据库的异步操作, 为什么要用异步? 原因很简单 我们scrapy爬去的时候就是以异步的方式取爬去的, 这里如果数据库用同步的方式,则会非常影响效率
代码详细的就不介绍了,这个是数据库异步代码 相对较简单

parseTitle() 代码

#!/usr/bin/env python
#-*- coding: UTF-8 -*-"""
@version: Python3.6.4
@author:  Justinli"""import sys
import io
from functools import wraps
import pymysql
from scrapy.conf import settings
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')def singleton(cls):instances = {}@wraps(cls)def get_instance(*args, **kw):if cls not in instances:instances[cls] = cls(*args, **kw)return instances[cls]return get_instance@singleton
class MySQLSingle(object):def __init__(self, conn='', server=''):self.conn = connself.host = settings['MYSQL_HOST'],self.user = settings['MYSQL_USER'],self.passwd = settings['MYSQL_PASSWD'],self.port = settings['MYSQL_PORT'],self.db = settings['MYSQL_DBNAME'],self.charset = settings['MYSQL_CHARSET'],# def get_conn(self, host, port, user_name, password, database):#     try:#         self.conn = pymysql.connect(host=host, port=port, user=user_name,#                                        password=password, database=database)#     except Exception as e:#         print('Fail to connect database: %s' % e)#     return self.conndef get_conn(self):try:self.conn = pymysql.connect(host=self.host, port=self.port, user=self.user, password=self.passwd, database=self.db)except Exception as e:print('Fail to connect database: %s' % e)return self.conndef parseCompany(company):db = MySQLSingle()conn = db.get_conn()cursor = conn.cursor()sql = "select * from zhaopin where company = '%s'" % (company)cursor.execute(sql)data = cursor.fetchone()if data:return Falseelse:return Truedef parseSalary(salary):if "-" in salary:salary = int(salary.split("-")[1][:-1])if salary < 30:return Falseelse:return Trueelse:return Falsedef parseTitle(title, company, salary):if "运维" in title and ("总监" in title or "经理" in title):# return "1"if parseSalary(salary):return parseCompany(company)else:return Falseelif "IT" in title and ("总监" in title or "经理" in title):# return "2"if parseSalary(salary):return parseCompany(company)else:return Falseelse:# return "3"return False

这里主要就是你过滤职位信息的代码了, 可以根据公司, 职位, 薪水等关键点来过滤sou.zhaopin.com里获取到的岗位信息, 用来决定是否将url专向下一步 parse_job的操作. 这里的代码也是一般简单的python代码 大家可以根据自己实际的需求来更改, 注意一点: 这里的数据库是同步操作. 因为用于判断.所以不能异步. 后续投递过的公司数据也会在这里进行自动过滤, 从而不在处理.

Item.py

这个就是最简单的item部分了

class ZhaopinItem(scrapy.Item):company = scrapy.Field()title = scrapy.Field()salary = scrapy.Field()date = scrapy.Field()link = scrapy.Field()content = scrapy.Field()

settings.py

根据实际情况将下载中间件 item等管道 都给打开 同时在这里设置mysql的数据信息, mysql信息也写在settings里

结束语

以上代码就是完整的智联招聘自动投递简历的代码了, 有问题的话, 欢迎大家留言来讨论, 放出这份代码主要是自己正好在找工作,同时也懒得每天上智联去看,因此就随意自己写了个代码,加了简单的过滤条件来自动投简历.
本代码已经在我自己的liunx机器上做了crontab来定时跑.目前效果不错! 如果你跑不起来或者运行有错误可以给我留言或者email给我 li_xiaoding@msn.com

在智联招聘上用scrapy+selenium 进行简历自动投递相关推荐

  1. 爬取智联招聘上的求职信息

    爬虫爬取智联招聘上的求职信息,并将爬取的内容保存到文件中 链接:https://pan.baidu.com/s/1p4gn2enm_WnyqK_3kjnoaQ 提取码:prdb 复制这段内容后打开百度 ...

  2. 爬取智联招聘上24座热门城市中Java招聘信息

    一.确定URL及其传递的参数 获取北京中Java的招聘信息url: 获取上海中Java的招聘信息url: 通过对比得知,url中传递了三个参数,jl代表城市的编号,kw代表职业,p代表当前在招聘页面的 ...

  3. 我在智联招聘上的行为测试

    您的行为类型评定为:中间偏B型人格特点 几十年以前,一些内科医生对鉴别他们的病人中哪些人易患心血管疾病感到无能为力.尽管他们指导高血压.吸烟.肥胖和缺少锻炼都会使人易患心脏病,但是只综合考虑这些困难是 ...

  4. python3 scrapy爬取智联招聘存mongodb

    写在前面,这次写智联招聘的爬虫是其次,主要的是通过智联招聘上的数据信息弄一个数据挖掘的小项目,这一篇主要是如何一气呵成的将智联招聘上的招聘信息给爬下来 (一)scrapy框架的使用 scrapy框架是 ...

  5. scrapy项目2:爬取智联招聘的金融类高端岗位(spider类)

    ---恢复内容开始--- 今天我们来爬取一下智联招聘上金融行业薪酬在50-100万的职位. 第一步:解析解析网页 当我们依次点击下边的索引页面是,发现url的规律如下: 第1页:http://www. ...

  6. python爬虫开发之“智联招聘”网页爬取

    先贴上需求: 1. 输入起始页 和结束页 爬取智联招聘上 与python相关职业2. 爬取的信息包括 就业岗位名称 薪资 地区 公司名称 需求{包括学历和经验}3. 爬取的信息以字典形式保存到mong ...

  7. 智联招聘简历如何导出html,如何将拉勾网(智联招聘)的预览简历导出来

    最近在整理简历的时候发现拉勾网/智联招聘上面的预览简历是那么简洁漂亮,可是当我想把他导到本地,发pdf文件给企业时却只能是word,那么的难看,摸索了一会儿找到了一种比较好的方法可以得到预览的简历. ...

  8. 如何将拉勾网(智联招聘)的预览简历导出来

    最近在整理简历的时候发现拉勾网/智联招聘上面的预览简历是那么简洁漂亮,可是当我想把他导到本地,发pdf文件给企业时却只能是word,那么的难看,摸索了一会儿找到了一种比较好的方法可以得到预览的简历. ...

  9. python编程实践(一):统计智联招聘数据

    统计智联招聘上,各个编程语言的在招岗位数量. 思路:写爬虫 的第一步永远是先分析网页 1.用谷歌浏览器打开智联招聘官网. 2.右键"检查"->"Network&qu ...

最新文章

  1. Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​-[Android取经之路]
  2. Ubuntu16.04安装NVIDA显卡驱动
  3. Spring-常见问题25问
  4. ML之sklearn:sklearn库中的ShuffleSplit()函数和StratifiedShuffleSplit()函数的讲解
  5. 思科网络CCNA的学习笔记-关于IP和子网的计算
  6. war包发布找不见路径 weblogic getResource 与getRealPath
  7. java web mvc_构建Java Web应用程序时遵循MVC的三个步骤
  8. 计算所有1-100之间数字之和(偶数之和)代码
  9. 结型场效应管的结构、特性、参数
  10. python中random库中shuffle_[宜配屋]听图阁 - 详解Python中打乱列表顺序random.shuffle()的使用方法...
  11. Linux下服务器搭建(7)——Oracle Linux ISO Images 高速镜像源下载地址(各版本齐全 建议收藏 最后更新2020.07.22)
  12. 计算机不能上网查找原因的步骤,电脑能连接上wifi但不能上网的原因_电脑能连接上wifi但不能上网的解决方法...
  13. CorelDraw绘图技巧十九招
  14. online learning
  15. 如果早晚都要死去,为什么还要活着?
  16. tomcat服务器报503
  17. 在next js中添加google analytics功能
  18. 飞协博携手顺丰速运推出综合物流解决方案
  19. 制作DOS的U盘启动
  20. 安卓钉子户的倔强:只有18.3%的用户会考虑换用iPhone 13

热门文章

  1. pdflush代码分析
  2. 要写易删除,而不易扩展的代码
  3. IBM Expands Watson Platform for Next Generation of Builders
  4. 事件对象e的e.target
  5. 《现代电力电子学与交流传动》读书笔记(九)
  6. 测试开发工程师实习—第一次面试总结
  7. 【服务器数据恢复】华为某型号服务器raid6数据恢复案例
  8. 任务管理器已被管理员禁用
  9. Win10家庭版任务管理器被禁用,解除方法
  10. 基于springboot和mybatis的多数据源配置