实战案例 | Scrapy 集成Selenium爬取智联招聘数据

2021-08-09 14:52:04 浏览数 (1)

人生苦短,快学Python!

初学scrapy之后,发现就是效率对比于selenium和requests快了很多,那么问题来了,如果网站设置了反爬,比如User-Agent反爬,cookie反爬,IP封禁等等,所以我们需要通过集成selenium到scrapy中,绕过网站反爬,达到目的。

这里选择智联招聘网站作为案例,就是虽然不是动态网页,但是它需要模拟登录,所以我们通过scrapy集成selenium进行数据抓取。

一、需求分析

打开目标网站,搜索web前端开发工程师

这是首页,由于我的当前位置在武汉,所以系统自动定位到武汉,点击搜索后:

这个就是需要通过selenium出路的一个点。

手动登录后得到以下界面:

我们的目标是每一条招聘信息的8条数据:

  1. name 职位名称
  2. salary 薪资
  3. adress 地区
  4. experience 经验
  5. eduBack 教育背景
  6. company 公司名称
  7. companyType 公司类型
  8. scale 公司规模
  9. info 简介

二、scrapy项目文件配置

定义items

代码语言:javascript复制
import scrapy

class ZlzpItem(scrapy.Item):
    name = scrapy.Field()
 ***薪资  公司  规模...***
    info = scrapy.Field()

定义scrapy爬虫:zl.py(智联)

代码语言:javascript复制
#这里先说明下url:
firstPageUrl : 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p=1'
    #作为第一页的url,下面的myspider.py中就不在展示,避免代码冗余。
    base_url = 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p={}'

然后下面是zl.py的源码:(分为几个部分)

1、初始化设置:

代码语言:javascript复制
# -*- coding: utf-8 -*-
import scrapy
from zlzp.items import ZlzpItem

count = 1   # 定义一个全局变量,与base_url构建 下一页的url 
class ZlSpider(scrapy.Spider):
    name = 'zl'
    allowed_domains = ['zhaopin.com']
    start_urls = [firstPageUrl]

2、parse函数:

代码语言:javascript复制
    def parse(self, response):

        global count
        count  = 1  # 每解析一次页面,让count 1,和baseurl构造下一页的url

        jobList = response.xpath('//div[@class="positionlist"]/div/a')

        for job in jobList:
            name =  job.xpath("./div[1]/div[1]/span[1]/text()").extract_first() 
    ...salary***,company***,....
            info = job.xpath("./div[3]/div[1]//text()").extract_first()


            item = ZlzpItem(name=name,salary=salary,company=company,adress=adress,experience=experience,eduBack=eduBack,companyType=companyType,scale=scale,info=info)
            yield item

3、分页:

代码语言:javascript复制
        next_url = 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p={}'.format(count)
        if count == 34:
            return None # 设置程序停止的条件
        if next_url:
            yield scrapy.Request(next_url,callback=self.parse)

定义下载器中间件(DownloadMiddleware):myDownloadMiddleware.py

代码语言:javascript复制
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
...
class ZlzpDownloaderMiddleware:
    def __init__(self):
        self.driver = webdriver.Chrome()
    def process_request(self, request, spider):
        self.driver.get(request.url)
        time.sleep(3) # 休息3s
  # 设置显示等待,由于需要登录,我们手机扫码登录,知道页面出现(即url显示为firstpageurl)
        WebDriverWait(self.driver, 1000).until(
            EC.url_contains(request.url)
        )
        time.sleep(6) # 登录成功之后页面需要时间加载出来,再休息几秒
        return HtmlResponse(url=self.driver.current_url, body=self.driver.page_source, encoding="utf-8",
                            request=request)  # 然后把这个response对象返回给爬虫(zl.py)

说明:

  1. selenium集成到scrapy中的核心就是在爬虫中间件中拦截请求,把处理后的响应对象返回,对应于爬虫文件(这里的zl.py)parse函数中的response,如果不集成selenium,那么response对象不能很好应对网站的反爬.
  2. 此处的parse_request方法中只有少量的selenium代码,因为动态操作其实不多.
  3. 重点:return后面的response对象:

在这里我们不能return None,如果return None,那么请求会被发送到下载中间件去下载这个页面,在将这个页面的response返回给spider(hr.py)。但是我们上面browser.get的时候就已经下载了这个页面的内容,所以没有必要在下载一次,我们只要制定一个response对象,直接返回这个response给spider即可

定义管道(Pipeline):pipelines.py

代码语言:javascript复制
from itemadapter import ItemAdapter
import csv

class ZlzpPipeline:
    def __init__(self):
        self.f = open('zlJob.csv', 'w', encoding='utf-8', newline='')
        # self.file_name = ['name','upTime','salary','needs','welfare','company','scale','types']
        self.file_name = ['name','salary','company','adress','experience','eduBack','companyType','scale','info'] 
        self.writer = csv.DictWriter(self.f, fieldnames=self.file_name)
        self.writer.writeheader()

    def process_item(self, item, spider):
        self.writer.writerow(dict(item))# 写入spider传过来的具体数值
        return item # 写入完返回

    def close_spider(self, spider):
        self.f.close()

settings.py配置

代码语言:javascript复制
BOT_NAME = 'zlzp'

SPIDER_MODULES = ['zlzp.spiders']
NEWSPIDER_MODULE = 'zlzp.spiders'
LOG_LEVEL = 'WARNING'
......
ROBOTSTXT_OBEY = False

......
DEFAULT_REQUEST_HEADERS = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36',
  'Accept': 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
}
......
DOWNLOADER_MIDDLEWARES = {
   'zlzp.middlewares.ZlzpDownloaderMiddleware': 543,
}
......
ITEM_PIPELINES = {
   'zlzp.pipelines.ZlzpPipeline': 300,
}
......

......表示注释代码,这里省略。

三、程序运行

命令行键入:

代码语言:javascript复制
scrapy crawl hr

pic1:运行程序结束到第34页,对应count = 34

pic02:(csv文件)

四、数据简单分析

查看数据

代码语言:javascript复制
import pandas as pd
df = pd.read_csv('./zlJob.csv')
df.head()

薪资饼图展示

代码语言:javascript复制
c = (
    Pie(init_opts=opts.InitOpts(bg_color="white"))
    .add("", [list(z) for z in zip(typesX,number)])   # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
    .set_global_opts(title_opts=opts.TitleOpts(title="类型:"))  # 标题
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))  # 数据标签设置
)

c.render_notebook()  

经验要求柱图展示

代码语言:javascript复制
from pyecharts.charts import Bar
bar = Bar()
bar.add_xaxis(['3-5年', '1-3年', '不限', '5-10年', '无经验', '1年以下', '10年以上'])
bar.add_yaxis('经验要求',[462,329,83,78,19,15,4])
bar.render()

学历要求柱图展示

代码语言:javascript复制
c = (
    Pie(init_opts=opts.InitOpts(bg_color="white"))
    .add("", [list(z) for z in zip(educationTypes,number)])   # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
    .set_global_opts(title_opts=opts.TitleOpts(title="类型:"))  # 标题
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))  # 数据标签设置
)

c.render_notebook()  

大多数要求本科学历,或者说大专及以上学历。

公司类型柱图展示

代码语言:javascript复制
c = (
    Pie(init_opts=opts.InitOpts(bg_color="white"))
    .add("", [list(z) for z in zip(companyTypes,number)])   # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
    .set_global_opts(title_opts=opts.TitleOpts(title="类型:"))  # 标题
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))  # 数据标签设置
)

c.render_notebook()  

可以看到大多数公司是民营或者上市公司。

五、总结

页面翻页处理,由于我们只是使用selenium就是打开网页请求数据,所以一般在爬虫文件中进行翻页处理,如果对应的下一页的a标签的href属性不是下一页的页面url,我们需要设置动态全局变量,构建动态的url。

下载中间件中设置的selenium的相关操作,动态点击,页面滚轮操作,显隐式等待等等,重要的是返回的response对象,这个是集成selenimu到scrapy的核心,在下载中间件中拦截请求,把处理后的response对象返回给爬虫。

0 人点赞