第一步:
找到可以爬取的三个招聘网站(估计只能爬取一个网站了,因为才一个小网站,就耗费了大量时间):
1 job.cn
a.广东广州:www.job.cn/job/list/0-…
c. 北京:www.job.cn/job/list/0-…
第二步:
1 settings设置日志级别,和日志存储文件等。
# 设置日志
# 以日期来命名日志名称,这样达到每天建立一个日志文件的要求。
today = datetime.datetime.today().now()
log_file_path = "./log_{}_{}_{}_{}_{}.log".format
(today.year,today.month,today.day,today.hour,today.minute)
# 1.设置日志等级 (这样在控制台输出的信息中就只会有爬取的数据,除非出现warning以上的日志信息。)
LOG_LEVEL = "WARNING"
# 2.保存日志文件到本地
LOG_FILE = log_file_path
# 这里一定要注释掉或者改为False# scrapy的ROBOTSTXT_OBEY 字面意思是:遵循Robots协议
#(也称为爬虫协议、机器人协议等,全称是“网络爬虫排除标准”(Robots Exclusion Protocol))
# 如果爬取目标目录在目标网站的robots.txt文本中列为disallow,则scrapy会放弃爬取动作。
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# 开启管道:
ITEM_PIPELINES = {
'lyfScrapy.pipelines.LyfscrapyPipeline': 300,
}
2 创建scrapy爬虫项目,创建爬虫文件。测试网站是否可以爬取。
发现问题1:
# 在爬取时出现403错误
# Crawled (403) 和 HTTP status code is not handled or not allowed
# 则在setting下添加如下代理
USER_AGENT = 'Mozilla/5.0(Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)Chrome/70.0.3538.110 Safari/537.36'
spider代码:
import scrapy
# 配置日志,通过日志来监控爬虫程序
import logging
logger = logging.getLogger(__name__)
# 设置时间
import datetime
# 导入items,items是保存数据的容器,它使用的方法和字典很相似,但是相比字典item多了额外的保护机制,可以避免拼写错误
from lyfScrapy.items import LyfscrapyItem,LyfscrapyItem_jd
class JobSpider(scrapy.Spider):
# 爬虫的名字:必须
name = 'job'
# 爬取的域:可选
allowed_domains = ['job.cn']
# 设定需要爬取的岗位页数
count = 1
jd_count = 0
# 记录爬取的页码
i = 0
# 爬取的网页起始页码
offset = 1
baseURL = 'http://www.job.cn/job/list/' # 这是网页的头部
# 爬取的网站列表,爬虫会从这里加载爬取的网页。
# 3409:北京 3411:上海 6_76:广东广州
start_urls = [baseURL + '0-3409-0-0_0_0_0_0_0_0_0-0-0-0-' + str(offset) + '.html']
def parse(self, response):
# *********************** 测试************************************
# 检测是否可以爬取这个网站,不可以则在settings中添加代理。
# print(response.body)
# 数据长度:为了判断是否爬取到了网页
lenth = len(response.body)
# print(lenth)
if lenth != 0:
logger.warning("这里是parse,访问状态码:200,该网站数据已经爬取到,可以进行解析")
else:
logger.error("网站爬取的数据为空,不能进行解析!!!")
# ************************* 测试end****************************************
self.i += 1
logger.warning("正在爬取第%d页的数据" % self.i)
# ********************* 解析数据**********************
# 在谷歌浏览器使用xpath helper 插件 可以快速定位数据
# 注意:这个class的值后面有个空格
node_list = response.xpath("//div[@class='search_job_list ']")
if len(node_list) != 0:
logger.warning("爬取成功,爬取的数据数量为:"+str(len(node_list)))
else:
logger.warning("爬取失败!!!请检查xpath!!")
logger.warning("正在解析数据第%d页的数据...."%self.i)
for node in node_list:
item = LyfscrapyItem()
# 岗位名称
item['positionName'] = node.xpath(".//a[@class='yunjoblist_newname_a']/text()").extract_first()
# 岗位链接
item['positionLink'] = node.xpath(".//a[@class='yunjoblist_newname_a']/@href").extract_first()
# 将岗位描述网址,提取出来,发送给调度器,交给下载器去下载,然后数据再返回给pare1中处理
yield scrapy.Request(item['positionLink'], callback=self.parse1)
# 岗位类别
item['positionType'] = node.xpath(".//div[@class='company_det_hy']/div/text()[1]").extract_first()
# 工作地点 node.xpath("//span[1]/em[@class='com_search_job_em']/text()").extract_first()
item['workPlace'] = '北京'
# 公司名称
item['companyName'] = node.xpath(".//a[@class='search_job_com_name']/text()").extract_first()
# 公司主页
item['companyLink'] = node.xpath(".//a[@class='search_job_com_name']/@href").extract_first()
# 薪资
item['monthlyPay'] = node.xpath(".//div[@class='yunjoblist_newxz']/text()").extract_first()
# 发布时间
pubTime = node.xpath(".//span[@class='yunjoblist_new_time']/text() | .//span[@class='yunjoblist_new_time']/span/text()").extract_first()
# 反爬机制的作用,不得不使用这样的方法.因为爬取到的时间有可能是‘14:00’ 或者 ”10:00“等,所以直接赋值当前的日期
if pubTime== " " or pubTime ==' ' or pubTime ==' ':
item['publishTime'] = datetime.datetime.today().date()
else:
item['publishTime'] = pubTime
# 拿到数据就发送给管道,交给管道处理,拿一些就交给管道处理一些,这样就不用一直存在内存里面,占系统内存,特别是大量数据的情况下,不会导致内存瞬间增大。
logger.warning("正在将parse的数据传送给管道>>>>>>>>")
yield item
# 方法一:爬取多页,爬取指定的页
if self.offset < self.count:
self.offset += 1
# print("当前爬取的网页为 :" + self.url)
self.url = self.baseURL+'0-3409-0-0_0_0_0_0_0_0_0-0-0-0-'+str(self.offset)+'.html'
# 这个函数可以不断将提取的url提交给调度器,发给当前函数进行处理
yield scrapy.Request(self.url, callback=self.parse)
if len(item) != 0:
logger.warning("解析数据成功!")
else:
logger.warning("解析数据失败")
# ********************* 解析数据end**********************
# 岗位名称 //div[@class='search_job_list']//a[@class='yunjoblist_newname_a']/text()
# 公司名称 //div[@class='search_job_list']//a[@class='search_job_com_name']/text()
# 工资://div[@class='search_job_list']//div[@class='yunjoblist_newxz']/text()
# 工作地点: 北京
# 岗位类别: //div[@class='search_job_list']//div[@class='company_det_hy']/div/text()
# 发布时间: //div[@class='search_job_list']//span[@class='yunjoblist_new_time']/text()
# 工作描述链接://div[@class='search_job_list']//a[@class='yunjoblist_newname_a']/@href
# 职位描述 jobDescription
def parse1(self, response):
# print("我是parse1")
# print(response.body)
# 数据长度
lenth = len(response.body)
if lenth != 0:
self.jd_count += 1
logger.warning("这里是parse1,GET:200,可以解析,正在解析第%d个子网页数据...!"%self.jd_count)
else:
logger.error("子网页爬取数据为空!!!")
# 通过items存值
item_jd = LyfscrapyItem_jd()
# normalize-space(): 去掉空格和换行符,但是不能和text()一起使用。
item_jd['positionName_1'] = response.xpath("normalize-space(//div[@class='job_details_topleft']/h1)").extract()
item_jd['jobDescription'] = response.xpath("normalize-space(//div[@class='job_details_describe'])").extract()
str1 = ''
for item in item_jd['positionName_1']:
str1 = str1 + item + ''
if len(str1) > 0:
item_jd['positionName_1'] = str1
else:
item_jd['positionName_1'] = 'NopositionName'
str = ''
for item in item_jd['jobDescription']:
str = str+item + ''
# self.jd_count = self.jd_count+1
if len(str) > 0:
item_jd['jobDescription'] = str
else:
item_jd['jobDescription'] = '暂无公司描述'
# print(self.jd_count)
# print(item_jd)
logger.warning("第%d个子网页解析成功"%self.jd_count)
logger.warning("正在将parse1的数据传送给管道>>>>>>>>")
yield item_jd
# 岗位描述: //div[@class='job_details_describe']/text()
# 限制://div[@class='job_details_describe']/span[1]/text()
pass
3 同一个爬虫爬取主页和不同的子网页
在爬取子网后,需要在items中创建两个类,这样才能在同一个管道中,根据类型的不同,取出不同的数据。
items代码:
import scrapy
# items是保存数据的容器,它使用的方法和字典很相似,但是相比字典item多了额外的保护机制,
#可以避免拼写错误# 为了保证能从同一个管道中取出不同线程拿到的数据,需要在items中创建两个类,在管道中,通过判断类的不同,来存储数据。
class LyfscrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 岗位名称
positionName = scrapy.Field()
# 岗位链接
positionLink = scrapy.Field()
# 岗位类型
positionType = scrapy.Field()
# 工作地点
workPlace = scrapy.Field()
# 公司名称
companyName = scrapy.Field()
# 公司链接
companyLink = scrapy.Field()
# 月薪
monthlyPay = scrapy.Field()
# 发布时间
publishTime = scrapy.Field()
pass
class LyfscrapyItem_jd(scrapy.Item):
# 子网页的岗位名称
positionName_1 = scrapy.Field()
# 子网页中的岗位描述
jobDescription = scrapy.Field()
pass
4 所有的数据通过yield交给管道后,在管道文件pipelines.py里面处理
pipelines.py代码
from itemadapter import ItemAdapter
# 配置日志,通过日志来监控管道文件
import logging
logger = logging.getLogger(__name__)
# # 新建excel时导入这个包
from openpyxl import Workbook
# 导入items,items是保存数据的容器,它使用的方法和字典很相似,但是相比字典item多了额外的保护机制,可以避免拼写错误
# 这里用来判断和提取数据
from lyfScrapy.items import LyfscrapyItem,LyfscrapyItem_jd
class LyfscrapyPipeline:
# *************************存入excel************************
# 此函数只执行一次
def __init__(self):
# 定义初始的行号
self.row_count = 0
# 打开新的Excel表
self.wb = Workbook()
logger.warning("表格创建成功,表格打开成功,准备写入数据>>>>>>")
# 打开默认的sheet工作表
self.ws = self.wb.active
# 添加一行,作为列标题
self.ws.append(['positionName', 'positionLink', 'positionType', 'workPlace', 'companyName', 'companyLink', 'monthlyPay', 'publishTime','positionName_1','jobDescription'])
# 此函数会被频繁调用,执行多次
def process_item(self, item, spider):
if len(item) != 0:
logger.warning("管道已经拿到数据,正在处理>>>>>>>>")
# print(item)
# 为了保证能从同一个管道中取出不同线程拿到的数据,需要在items中创建两个类,在管道中,通过判断类的不同,来存储数据。
# 提取第一个类中的数据
if type(item) == LyfscrapyItem:
# print(item)
# 把数据每一行整理出来
line = [item['positionName'], item['positionLink'], item['positionType'], item['workPlace'],
item['companyName'], item['companyLink'], item['monthlyPay'], item['publishTime']]
# 将数据以行的形式添加到xlsx中
self.ws.append(line)
# 提取第二个类中的数据
if type(item) == LyfscrapyItem_jd:
self.row_count += 1
# print(item)
# print("最大列数:",self.ws.max_column)
# 将数据以列的形式,加入到表中。
self.ws.cell(row=self.row_count+1,column=self.ws.max_column-1).value = item['positionName_1']
self.ws.cell(row=self.row_count+1,column=self.ws.max_column).value = item['jobDescription']
# return:将item再返回给其他管道处理。
logger.warning("处理成功!!!")
return item
# 此函数只执行一次
def close_spider(self, spider):
# 保存xlsx文件
self.wb.save('job.xlsx')
logger.warning("表格关闭成功,数据存入表格成功!")
# *************************存入excel**end************************