概述

本文主要介绍scrapy架构图、组建、工作流程,以及结合selenium boss直聘爬虫案例分析

架构图

组件

Scrapy 引擎(Engine)

引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件.

调度器(Scheduler)

调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎.

下载器(Downloader)

下载器负责获取页面数据并提供给引擎,而后提供给spider.

Spiders

Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类. 每个spider负责处理一个特定(或一些)网站,我们前面几篇文章中,通过Scrapy框架实现的爬虫例子都是在Spiders这个组件中实现. 更多内容请看 Spiders .

下载器中间件(Downloader Middlewares)

下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response. 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能.更多内容请看 Downloader Middleware .

Spider中间件(Spider Middlewares)

Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests). 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能

管道(Item Pipeline)

Item Pipeline负责处理被spider提取出来的item.典型的处理有清理、 验证及持久化(例如存取到数据库中). 更多内容查看 Item Pipeline .

工作流程

Scrapy中的数据流由执行引擎控制,其过程如下:

  • 引擎从Spiders中获取到最初的要爬取的请求(Requests).
  • 引擎安排请求(Requests)到调度器中,并向调度器请求下一个要爬取的请求(Requests).
  • 调度器返回下一个要爬取的请求(Requests)给引擎.
  • 引擎将上步中得到的请求(Requests)通过下载器中间件(Downloader Middlewares)发送给下载器(Downloader ),这个过程中下载器中间件(Downloader Middlewares)中的process_request()函数会被调用到.
  • 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(Downloader Middlewares)发送给引擎,这个过程中下载器中间件(Downloader Middlewares)中的process_response()函数会被调用到.
  • 引擎从下载器中得到上步中的Response并通过Spider中间件(Spider Middlewares)发送给Spider处理,这个过程中Spider中间件(Spider Middlewares)中的process_spider_input()函数会被调用到.
  • Spider处理Response并通过Spider中间件(Spider Middlewares)返回爬取到的Item及(跟进的)新的Request给引擎,这个过程中Spider中间件(Spider Middlewares)的process_spider_output()函数会被调用到.
  • 引擎将上步中Spider处理的其爬取到的Item给Item 管道(Pipeline),将Spider处理的Request发送给调度器,并向调度器请求可能存在的下一个要爬取的请求(Requests).
  • (从第二步)重复直到调度器中没有更多的请求(Requests).

案例分析:BOSS直聘

  • 定义Item
# -*- coding: utf-8 -*-

import scrapy

# 继承Item: items.py
class Boss(scrapy.Item):
"""
定义需要爬取的字段及类型
""" position = scrapy.Field(serializer=str) # 招聘职位
salary = scrapy.Field(serializer=str) # 薪资
addr = scrapy.Field(serializer=str) # 工作地址
years = scrapy.Field(serializer=str) # 工作年限
education = scrapy.Field(serializer=str) # 学历
company = scrapy.Field(serializer=str) # 招聘公司
industry = scrapy.Field(serializer=str) # 行业
nature = scrapy.Field(serializer=str) # 性质:是否上市
scale = scrapy.Field(serializer=str) # 规模:人数
publisher = scrapy.Field(serializer=str) # 招牌者
publisherPosition = scrapy.Field(serializer=str) # 招聘者岗位
publishDateDesc = scrapy.Field(serializer=str) # 发布时间
  • 定义scrapy爬虫: myspider.py
# -*- coding: utf-8 -*-
import scrapy from spider.items import Boss class BossSpider(scrapy.Spider):
name = "boss" # 设定域名
allowed_domains = ["www.zhipin.com"] def start_requests(self):
"""
设置第一个爬取的URL,即boss直聘第一页
"""
urls = [
'https://www.zhipin.com/c101210100/h_101210100/?page=1&ka=page-1',
] # 每次yield都会调用下载器中间件,即 mySpiderMiddleware.SeleniumMiddleware
# 这里由selenium进行动态抓取招聘信息
for url in urls:
yield scrapy.Request(url=url, callback=self.parse) def parse(self, response):
"""
初始化Item:Boss
:param response:
:return:
"""
boss = Boss() # 利用xpath筛选想要爬取的数据
for box in response.xpath('//div[@class="job-primary"]'):
boss['position'] = box.xpath('.//div[@class="job-title"]/text()').extract()[0]
boss['salary'] = box.xpath('.//span[@class="red"]/text()').extract()[0]
boss['addr'] = box.xpath('.//p[1]/text()').extract()[0]
boss['years'] = box.xpath('.//p[1]/text()').extract()[1]
boss['education'] = box.xpath('.//p[1]/text()').extract()[2]
boss['company'] = box.xpath('.//div[@class="info-company"]//a/text()').extract()[0]
boss['industry'] = box.xpath('.//p[1]//text()').extract()[3]
boss['nature'] = box.xpath('.//p[1]//text()').extract()[4]
boss['scale'] = box.xpath('.//p[1]//text()').extract()[5]
boss['publisher'] = box.xpath('.//div[@class="info-publis"]//h3/text()').extract()[0]
boss['publisherPosition'] = box.xpath('.//div[@class="info-publis"]//h3/text()').extract()[1]
boss['publishDateDesc'] = box.xpath('.//div[@class="info-publis"]//p/text()').extract()[0] # 将Item:Boss传递给Spider中间件,由它进行数据清洗(去空,去重)等操作
# 每次yield都将调用SpiderMiddleware, 这里是 mySpiderMiddleware.MyFirstSpiderMiddleware
yield boss # 分页
url = response.xpath('//div[@class="page"]//a[@class="next"]/@href').extract()
if url:
page = 'https://www.zhipin.com' + url[0]
yield scrapy.Request(page, callback=self.parse)
  • 定义下载器中间件(DownloadMiddleware): myDownloadMiddleware.py
# -*- coding: utf-8 -*-
from scrapy.http import HtmlResponse
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options class SeleniumMiddleware(object):
"""
下载器中间件
""" @classmethod
def process_request(cls, request, spider):
if spider.name == 'boss':
if request.url == 'https://www.zhipin.com/c101210100/h_101210100/?page=1&ka=page-1':
options = Options()
options.add_argument('-headless') # geckodriver需要手动下载
driver = Firefox(executable_path='/ddhome/bin/geckodriver', firefox_options=options)
driver.get(request.url) searchText = driver.find_element_by_xpath('//div[@class="search-form-con"]//input[1]')
searchText.send_keys(unicode("大数据研发工程师"))
searchBtn = driver.find_element_by_xpath('//div[@class="search-form "]//button[@class="btn btn-search"]')
searchBtn.click() html = driver.page_source
driver.quit() # 构建response, 将它发送给spider引擎
return HtmlResponse(url=request.url, body=html, request=request, encoding='utf-8')
  • 定义Spider中间件(SpiderMiddleware): mySpiderMiddleware.py
# -*- coding: utf-8 -*-

import logging

logger = logging.getLogger(__name__)

class MyFirstSpiderMiddleware(object):

    @staticmethod
def process_start_requests(start_requests, spider):
"""
第一次发送请求前调用,之后不再调用
:param start_requests:
:param spider:
:return:
"""
logging.debug("#### 2222222 start_requests %s , spider %s ####" % (start_requests, spider))
last_request = []
for one_request in start_requests:
logging.debug("#### one_request %s , spider %s ####" % (one_request, spider))
last_request.append(one_request)
logging.debug("#### last_request %s ####" % last_request) return last_request @staticmethod
def process_spider_input(response, spider):
logging.debug("#### 33333 response %s , spider %s ####" % (response, spider))
return @staticmethod
def process_spider_output(response, result, spider):
logging.debug("#### 44444 response %s , result %s , spider %s ####" % (response, result, spider))
return result
  • 定义管道(Pipeline): pipelines.py
# -*- coding: utf-8 -*-

import json
import codecs
from scrapy.contrib.exporter import CsvItemExporter
from scrapy import signals
import os class CSVPipeline(object):
"""
导出CSV格式
"""
def __init__(self):
self.file = {}
self.csvpath = os.path.dirname(__file__) + '/spiders/output'
self.exporter = None @classmethod
def from_crawler(cls, crawler):
pipeline = cls()
crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
return pipeline def spider_opened(self, spider):
"""
当蜘蛛启动时自动执行
:param spider:
:return:
"""
f = open('%s/%s_items.csv' % (self.csvpath, spider.name), 'a') # r只读, w可写, a追加
self.file[spider] = f
self.exporter = CsvItemExporter(f)
self.exporter.fields_to_export = spider.settings['FIELDS_TO_EXPORT']
self.exporter.start_exporting() def process_item(self, item, spider):
"""
蜘蛛每yield一个item,这个方法执行一次
:param item:
:param spider:
:return:
"""
self.exporter.export_item(item)
return item def spider_closed(self, spider):
self.exporter.finish_exporting()
f = self.file.pop(spider)
f.close() class JSONPipeline(object):
"""
导出JSON格式
"""
def __init__(self):
self.file = None
self.csvpath = os.path.dirname(__file__) + '/spiders/output' def process_item(self, item, spider):
self.file = codecs.open('%s/%s_items.json' % (self.csvpath, spider.name), 'a', encoding='utf-8')
line = json.dumps(dict(item), ensure_ascii=False) + '\n'
self.file.write(line)
# return item def spider_closed(self, spider):
self.file.close()
  • settings.py配置

英文文档

BOT_NAME = 'spider'

SPIDER_MODULES = ['spider.spiders']
NEWSPIDER_MODULE = 'spider.spiders' FEED_EXPORT_ENCODING = 'utf-8' # Obey robots.txt rules
ROBOTSTXT_OBEY = False # Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
'spider.middlewares.mySpiderMiddleware.MyFirstSpiderMiddleware': 543,
} # Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'spider.middlewares.myDownloadMiddleware.SeleniumMiddleware': 542,
'spider.middlewares.myDownloadMiddleware.PhantomJSMiddleware': 543, # 键为中间件类的路径,值为中间件的顺序
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # 禁止内置的中间件
} # Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'spider.pipelines.CSVPipeline': 300,
'spider.pipelines.JSONPipeline': 301
} FEED_EXPORTERS = {
'csv': 'spider.spiders.csv_item_exporter.MyProjectCsvItemExporter',
} CSV_DELIMITER = ',' FIELDS_TO_EXPORT = [
'position',
'salary',
'addr',
'years',
'education',
'company',
'industry',
'nature',
'scale',
'publisher',
'publisherPosition',
'publishDateDesc'
]

文档

Python爬虫——Scrapy整合Selenium案例分析(BOSS直聘)的更多相关文章

  1. python爬虫Scrapy(一)-我爬了boss数据

    一.概述 学习python有一段时间了,最近了解了下Python的入门爬虫框架Scrapy,参考了文章Python爬虫框架Scrapy入门.本篇文章属于初学经验记录,比较简单,适合刚学习爬虫的小伙伴. ...

  2. scrapy——7 scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

    scrapy——7 什么是scrapy-redis 怎么安装scrapy-redis scrapy-redis常用配置文件 scrapy-redis键名介绍 实战-利用scrapy-redis分布式爬 ...

  3. python分析BOSS直聘的某个招聘岗位数据

    前言 毕业找工作,在职人员换工作,离职人员找工作……不管什么人群,应聘求职,都需要先分析对应的招聘岗位,岗位需求是否和自己匹配,常见的招聘平台有:BOSS直聘.拉钩招聘.智联招聘等,我们通常的方法都是 ...

  4. Scrapy 爬取BOSS直聘关于Python招聘岗位

    年前的时候想看下招聘Python的岗位有多少,当时考虑目前比较流行的招聘网站就属于boss直聘,所以使用Scrapy来爬取下boss直聘的Python岗位. 1.首先我们创建一个Scrapy 工程 s ...

  5. python爬虫scrapy项目详解(关注、持续更新)

    python爬虫scrapy项目(一) 爬取目标:腾讯招聘网站(起始url:https://hr.tencent.com/position.php?keywords=&tid=0&st ...

  6. Python的scrapy之爬取boss直聘网站

    在我们的项目中,单单分析一个51job网站的工作职位可能爬取结果不太理想,所以我又爬取了boss直聘网的工作,不过boss直聘的网站一次只能展示300个职位,所以我们一次也只能爬取300个职位. jo ...

  7. python爬虫scrapy框架——人工识别登录知乎倒立文字验证码和数字英文验证码(2)

    操作环境:python3 在上一文中python爬虫scrapy框架--人工识别知乎登录知乎倒立文字验证码和数字英文验证码(1)我们已经介绍了用Requests库来登录知乎,本文如果看不懂可以先看之前 ...

  8. 爬虫系列---scrapy post请求、框架组件和下载中间件+boss直聘爬取

    一 Post 请求 在爬虫文件中重写父类的start_requests(self)方法 父类方法源码(Request): def start_requests(self): for url in se ...

  9. python爬虫动态html selenium.webdriver

    python爬虫:利用selenium.webdriver获取渲染之后的页面代码! 1 首先要下载浏览器驱动: 常用的是chromedriver 和phantomjs chromedirver下载地址 ...

随机推荐

  1. netstat 检测及监测网络连接

    网站没有很多流量,可查带宽却跑了几十M? 这是什么情况呢?      如果是出现这种情况,不排除是被CC了的可能.那么如何确定是否真的被CC了,被CC又有什么对应政策呢? netstat -na ,用 ...

  2. 规划将 IaaS 资源从经典部署模型迁移到 Azure Resource Manager

    尽管 Azure 资源管理器提供了许多精彩功能,但请务必计划迁移,以确保一切顺利进行. 花时间进行规划可确保执行迁移活动时不会遇到问题. Note 以下指导的主要参与者为 Azure 客户顾问团队,以 ...

  3. Layer的shadow属性

    Layer的shadow属性 Layer中的阴影都是可以做动画处理的. - (void)viewDidLoad { [super viewDidLoad]; CALayer *layer = [CAL ...

  4. Win10 Docker修改镜像存储位置

    发生现象: 在windows10下安装docker for windows,随着用docker pull image文件后,C盘的容量越来越小了,你可能也有一种跟我一样的想法,想改变默认的安装路径,本 ...

  5. 沉淀再出发:Bean,JavaBean,POJO,VO,PO,EJB等名词的异同

    沉淀再出发:Bean,JavaBean,POJO,VO,PO,EJB等名词的异同 一.前言 想必大家都有这样的困惑,接触的东西越多却越来越混乱了,这个时候就要进行对比和深入的探讨了,抓住每一个概念背后 ...

  6. C#网络编程(一)基础篇

    简介: C#网络编程API包含在System.Net和System.Net.Sockets命名空间下,大部分网络操作都可以在其中找到相应的类来实现:包括Socket的创建和连接,网络流收发方法的封装, ...

  7. docker 17.09.0-ce 启动更换网络地址

    一.环境准备 环境1 台虚拟机,系统为centos7 二.17.09.0-ce 安装 卸载安装的所有Docker组件 在 Docker17.03.0-ce 版本中,与在 Docker 1.12 中引入 ...

  8. unittest:2 执行多条用例,仅执行一次setUp和tearDown

    对象方法setUp()和tearDown() 每个用例执行前后都会被调用.但是有另外一种场景:setUp之后执行完所有用例,最后调用一次tearDown.比如打开网页,多条用例分别验证网页上的元素正确 ...

  9. Linux系统之路——如何在服务器用U盘安装CentOS7.2(一)

    终于将CentOS7装上服务器(thinkserver250,不得不说联想的太烂了)了,过程无比艰辛,因为我发现网上大家提到的所有U盘安装CentOS7时碰到的问题几乎都被我碰到了,像什么: 1.刻录 ...

  10. 简单说说Spring Security 使用(附加验证码登录,自定义认证)

    先看官方文档:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/ spring security4已 ...