Scrapy-redis爬取51Job数据

Scrapy-redis爬取51Job数据

一、工具

  • python3
  • scrapy-redis
  • redis

二、准备工作

(一)安装各个模块

项目中使用到工具主要是Python3和Redis,且需要安装相应的python模块,即scrapy、scrapy_redis、redis等。

具体安装流程可以百度一下。

(二)页面分析

1、关键字页面

51job有一个页面https://jobs.51job.com/,其中有全部招聘职位关键字的搜索结果页链接,因此,只要获取该页面中所有招聘职位的链接,再逐一使用scrapy进行爬取即可。

Scrapy-redis爬取51Job数据

该项具体获取方式比较简单,提取出数据存为列表即可。

2、搜索结果页

在搜索结果页中,有每一项招聘信息的职位名、公司名、地址、薪资、学历要求、经验要求、公司性质、公司规模、职位描述,这些信息在详情页中也有,但在这一页中结构更加清晰。

Scrapy-redis爬取51Job数据

3、职位详情页

除去搜索结果页获取的数据之外,该页可以获取职位标签与行业两项信息。

Scrapy-redis爬取51Job数据

Scrapy配置

  • 新建项目,在控制台中cd到对应的目录下后输入:
scrapy startproject 项目名
  • 新建爬虫,进入爬虫目录后输入
scrapy genspider 爬虫名 "目标网站"
  • setting设置
#默认配置
BOT_NAME = 'JobSpider_51'
SPIDER_MODULES = ['JobSpider_51.spiders']
NEWSPIDER_MODULE = 'JobSpider_51.spiders'

"""
Scrapy_Redis配置信息
""" 

#指定scrapy-redis的调度器
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'

#指定使用scrapy-redis进行去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'

# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True

"""
注意:上面四项配置后,可能导致无法使用scrapy shell,具体原因没有深究, 大致为scrapy-redis和scrapy的版本存在不兼容问题
"""

# Redis数据库配置
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

#配置pipelines,值越小优先级越高
ITEM_PIPELINES = {
   'JobSpider_51.pipelines.Jobspider51Pipeline': 300,
   'scrapy_redis.pipelines.RedisPipeline': 299,
}

三、主程序

(一)spider

import re
import time
import json
import scrapy
import logging
from scrapy_redis.spiders import RedisSpider
from scrapy.linkextractors import LinkExtractor
from JobSpider_51.items import Jobspider51Item

class Job51Spider(RedisSpider):
    name = 'job51'
    redis_key = "job51"
    
    def parse(self, response):
        e_divs = response.xpath("//div[@class='e ']")
        for e_div in e_divs:
            item = Jobspider51Item()
            #获取数据
            info_p = e_div.xpath("./p[@class='info']")
            order_p = e_div.xpath("./p[@class='order']")
            item['p_name'] = info_p.xpath(".//a/@title")[0].extract()
            item['p_company'] = info_p.xpath(".//a/@title")[1].extract()
            item['p_address'] = info_p.xpath(".//span[@class='location name']/text()").extract_first().split('-')
            item['p_salary'] = info_p.xpath(".//span[@class='location']/text()").extract_first()
            order_p_list = order_p.xpath("./text()").extract()
            item['p_education'] = get_content(order_p_list[0])
            item['p_experience'] = get_content(order_p_list[1])
            item['c_info'] = get_content(order_p_list[2])
            item['c_size'] = get_content(order_p_list[3])
			#替换html中无意义的符号
            p_description = re.sub(r"\xa0", ' ', e_div.xpath("./p[@class='text']/@title").extract_first())
            item['p_description'] = p_description
            
            url = info_p.xpath(".//a/@href")[0].extract()
            #获取详情页
            yield scrapy.Request(url=url, callback=self.parseDetail, meta={'item': item}, dont_filter=False)
        #请求剩余结果页面
        pageLinkExtractor = LinkExtractor(allow=('.*?/p\d/'))
        pageLinks = pageLinkExtractor.extract_links(response)
        for link in pageLinks:
            yield scrapy.Request(url=link.url,callback=self.parse, dont_filter=False)
        
    def parseDetail(self, response):
        item = response.meta['item']
        item['p_feature'] = response.xpath("//span[@class='sp4']/text()").extract()
        comTag = response.xpath("//div[@class='com_tag']")
        item['c_industryField'] = comTag.xpath("./p[3]/a/text()").extract()
        
        yield item
        
def get_content(strings):
    content = re.compile(r":(.*)")
    result = content.search(strings).group(1)
    
    if result == '':
        return "无要求"
    
    return result

(二)item

import scrapy

class Jobspider51Item(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # define the fields for your item here like:
    # name = scrapy.Field()
    p_name = scrapy.Field() #positionName
    p_salary = scrapy.Field() #salary
    p_address = scrapy.Field() #city district businessZones[]
    p_experience = scrapy.Field()   #workYear
    p_education = scrapy.Field()    #education
    p_feature = scrapy.Field()  #positionAdvantage
    p_description = scrapy.Field()  #
    p_company = scrapy.Field()  #companyFullName
    c_industryField = scrapy.Field()    #industryField
    c_size = scrapy.Field()     #companySize
    c_info = scrapy.Field()

四、运行爬虫

(一)redis_key存入数据

将从关键字列表中获取到的搜索结果页链接存入redis中的job51(spider中设定的redis_key)中。

import redis
r = redis.Redis("127.0.0.1",6379)

for i in hrefs:
    r.lpush("job51", i)

(二)开启爬虫

控制台项目目录下:

scrapy crawl job51

五、问题与总结

(一)scrapy与scrapy-redis冲突问题

准备获取页面数据时,需要使用scrapy shell进行测试,但如上文中scrapy setting配置中所言,

开启了scrapy-redis相关的一些配置后,可能导致scrapy shell报错无法使用,具体细节没有深究,github上看到的结论是scrapy-redis与scrapy版本间存在不兼容的问题,所以在使用scrapy shell时可以先注释掉相关配置,或在另一测试项目中使用scrapy shell。

(二)redis操作失误

操作时为了监控数据的爬取,定期读取redis中的数据,查看当前的待请求以及已获取数据的数量。写的时候没有注意,每次都使用lrange获取全部的数据,再读取长度。在数据量较小的时候影响没有很大,就没有改,但是数据量大一些之后,长期内进行这样大量的读取,在云服务器的配置较低的情况下,redis会频繁自动关闭,导致大量数据没有顺利存储,查看日志也没有找到原因,查了很久之后才找到原因,改用了llen来获取数据。

(三)注意存储日志

爬虫运行结束后,当时查看的是数据已经获取完毕,数据量也没有问题,但是再去读取redis时却发现已获取的数据大量丢失了,幸好之前开启了日志,而日志中包含了爬取到的数据,最后使用正则表达式从日志中拿回了数据,没有造成损失

(四)总结

51job页面结构清晰,相对智联招聘、boss直聘、拉勾网而言,反爬措施较弱,但请求量太高的话会干扰网站的正常运行,所以最好还是delay一下。因为请求基本都不会出错,因此上面的程序没有做错误处理,但在爬取反爬措施比较严格的网站时还是需要做一下失败页面的处理。

如有错误,欢迎指正。

上一篇:vue 打包路径不对设置方法


下一篇:向量化计算 2