目录
一、目标
二、51job网页分析:
1.网页构成观察
2.网页分析
三、代码实现
1. 踩过的坑-----实现城市选择
2.代码实现
3.代码优化
1)存放格式优化
2)在爬虫中去掉\xa0\xa0
3)用normalize-space(节点)去掉\r\n\t
4.pipelines.py 定义存储
实现 通过传参(职位和地区)控制抓取51job职位信息。
抓取内容包括:职位名称、薪资、招聘公司、福利待遇、城市区域、经验要求、学历要求、招工人数、招聘信息发布时间、公司地址、岗位职责、任职要求、公司性质、公司规模和招聘公司所在行业。
搜索入口1:
搜索入口2:
抓取页面:
搜索入口:
观察比较两个搜索入口,都可以输入职位名称和选取地区,而 https://search.51job.com/ 显然比首页更靠近抓取的信息,所以起始页面选择https://search.51job.com/
抓取内容:
获取每一个岗位链接,进入详情页抓取目标字段内容。
观察过详情页的内容后,将抓取字段确定为:
尝试1:
原本想用selenium+click的方法实现.
通过观察源代码发现点击左边的大写城市首字母,右边的城市会对应更新,试着用selenium将左边首字母全部点击一边,将右边含有城市text的tr标签全部刷新出来。试图以text匹配传入的城市参数,如果匹配上就click该标签来实现城市的选择,(不得不说这是个很烂的想法,)失败了
尝试2:
试图通过setattribute()的方法更改表示城市的value值,也失败了
这次尝试的结果是value值可以更改,但是对于搜索来说不起作用
尝试3:
不气馁,继续找!!!
最终发现实际上起到决定作用的是这串阿拉伯数字的value值。虽然我本次只抓取北上广深四个城市的职位信息,找到这四个城市对应的数字代码自己建个dict就可以爬取了。但是还是想找找一劳永逸的方法~
最终找到一个这样的网址 https://m.51job.com/ ,看起来好像手机端的页面(忘记怎么找到的了,写文章的时候又回页面上去找,就是找不到……只能直接贴结果了。。。)
这个页面可以获取到所有城市对应的数字代码。
这个问题解决了,接下来的就水到渠成了!
爬取所有城市对应数字代码,保存到本地csv中。只需要爬取一次就可以了,需要的时候可以用读取本地csv的方式使用。
def saveID():
if os.path.exists('areaID.csv'): # 如果存在areaID.csv就停止执行此函数
return
else: # 如果不存在就爬取ID 并保存csv
browser = webdriver.Chrome()
url = 'https://m.51job.com/'
browser.get(url)
browser.refresh()
browser.find_element_by_tag_name('header').find_element_by_class_name('location').click()
parent = browser.find_elements_by_class_name('elist')
IDList=[]
for each in parent:
divs = each.find_elements_by_class_name('e')
for every in divs:
id = every.get_attribute('value')
city = every.find_element_by_class_name('tit').text
IDList.append(id)
IDList.append(city)
# 将IDlist存入csv
with open('areaID.csv','w', encoding='utf-8', newline='') as file:
csv.writer(file).writerow(IDList)
编写函数,实现传参控制爬取 不同职位和不同地区及页数
def urls(jobname, count,*area):
# 读取csv,获得IDlist
with open("areaID.csv", "r", encoding="utf-8", newline="") as file:
csvfile = csv.reader(file)
IDList = list(csvfile)[0]
# 遍历IDList,根据传参(*area)获得area对应的ID
idlist = []
for x in range(len(IDList)):
for y in (area):
if IDList[x] == y:
if IDList[x - 1] not in idlist:
idlist.append(IDList[x - 1])
str1 = ','.join(idlist) # 将list转化为字符串
city = request.quote(request.quote(str1)) # 将地区代码加密转化为51job的地区代码,主要是为逗号加密
position = request.quote(request.quote(jobname)) # 将职位加密转为51job的职位代码
url = 'https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,1.html'
# 以下操作用于获得该职位 该地区 共有多少页
browser = webdriver.Chrome()
browser.get(url)
# 过滤文字得到只有数字的字符串
page = ''.join(filter(str.isdigit,browser.find_element_by_class_name('p_in').find_element_by_class_name('td').text))
#拼接参数,得到start_urls
start_urls = ['https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,'+str(i)+'.html' for i in range(count,int(page))]
return start_urls
数据爬取,通过遍历start_urls 链接下的职位列表,获得每个职位的详情链接
class jobinfo(CrawlSpider):
#设置name
name = 'jobinfo'
#设置域名
allowed_domains = ['search.51job.com','jobs.51job.com']
# 设置起始页
# !!!!!!! 在这里改入参获得不同职位和地区对应的网址
start_urls = urls('数据分析',1,'上海','广州','北京','深圳','苏州')
# 定义函数爬取所有详情页的连接
def parse(self, response):
item = JobinfoItem()
# 通过Selector 将response 转换成对应对象
selector = Selector(response)
# 以下为信息提取,用xpath的方式提取信息
# 先获取父元素
divs=selector.xpath('//div[@id="resultList"]//div[@class="el"]')
# 用循环找出详情页的链接
for each in divs:
href = each.xpath('./p/span/a/@href').extract()[0]
# 将提取出来的链接传给parse_detail()函数处理
yield Request(href,meta = {'front_item':copy.deepcopy(item)},callback=self.parse_detail)
# 抓取详情页内容
def parse_detail(self,response):
item = response.meta['front_item']
selector = Selector(response)
# 职位名称
title = selector.xpath('//div[@class="cn"]/h1/@title').extract()
try:
# 薪资
salary = selector.xpath('//div[@class="cn"]/strong/text()').extract()
except:
salary = '面议'
# 公司名称
company = selector.xpath('//p[@class="cname"]/a/@title').extract()
# 招聘简介
req = selector.xpath('//p[@class="msg ltype"]/@title').extract()
try:
# 福利
welfare = selector.xpath("//div[@class='jtag']")[0].xpath('.//span/text()').extract()
except:
welfare = '福利信息缺失'
try:
# 联系方式
contact_info = selector.xpath('//div[@class="bmsg inbox"]//p//text()').extract()
except:
contact_info = '无公司联系方式'
div1 = selector.xpath('//div[@class="com_tag"]')
#公司性质
nature = div1.xpath('p[1]/@title').extract()
#公司规模
size = div1.xpath('p[2]/@title').extract()
#公司行业
industry = div1.xpath('p[3]/@title').extract()
item['title'] = title
item['salary'] = salary
item['company'] = company
item['req'] = req
item['welfare'] = welfare
item['job_info'] = job_info
item['contact_info'] = contact_info
item['nature'] = nature
item['size'] = size
item['industry'] = industry
yield item
运行后的结果如下图,就这样保存丢给清洗的team member后,被大大滴吐槽了........
这些都为后期清洗增加了不少工作量,所以想办法优化下~
在获取内容的时候我用的是extract(),这样获得的是一个list,通过观察网页代码,除“福利”的提取外都可以用extract_first(),这样就可以得到一个string。
也可以用get()
附文档对于get() getall() 和 extract_first() extract() 的用法说明
我们通常所用的空格是\X20,是在标准ASCII可见字符0x20~0x7e范围内
而\xa0属于latin1(ISO/IEC_8859-1)中的扩展字符集字符,代表空白符 (non-breakingspace)
这里用str.replace(u'\xa0',u'')的方式去除
req = selector.xpath('//p[@class="msg ltype"]/@title').extract_first().replace(u'\xa0', u'')
normalize,字面意思就是正规化,加入space大概意思就是空格的处理了。
官方解释是这样的:
通过去掉前导和尾随空白并使用单个空格替换一系列空白字符,使空白标准化。如果省略了该参数,上下文节点的字符串值将标准化并返回。
这里要去除text中的\r\n\t,需要注意的是不可以normalize-space(text()) 这样使用。
.
是当前节点。如果在需要字符串的地方使用它(例如作为参数normalize-space()
),引擎会自动将节点转换为节点的字符串值,对于元素来说,该元素是元素内连接的所有文本节点。
text()
另一方面仅选择作为当前节点的直接子节点的文本节点。
例如,给定XML:
Foo
Bar
lish
假设是你当前的节点,
normalize-space(.)
将返回Foo Bar lish
,但normalize-space(text())
会失败,因为text()
返回两个文本节点(Foo
和lish
)的节点集,它normalize-space()
不接受。
(敲重点!!!)如果你想标准化一个元素中的所有文本,请使用.
。如果要选择特定的文本节点,请使用text()。
尽管名称不同,但text()
返回一个节点集,如果节点集只有一个元素,它将自动转换为字符串。
(normalize-space()的用法引自:https://www.cnblogs.com/songzhenhua/p/10121504.html)
将job_info 和contact_info两个字段做如下修改:
job_info = selector.xpath('normalize-space(//div[@class="bmsg job_msg inbox"]//.)').extract_first().replace(u'\xa0', u'')
contact_info = selector.xpath('normalize-space(//div[@class="bmsg inbox"]//p//.)').extract_first()
抓到的数据干干净净整整齐齐,妈妈再也不用担心我被同事打,欧耶~~~
import csv
class savetocsv(object):
def __init__(self):
self.file= csv.writer(open('job51.csv','a',newline='',encoding='utf-8'))
self.file.writerow('职位名称,薪资,公司名称,招聘简介,福利,职位信息,联系方式,公司性质,公司规模,公司行业'.split(','))
def process_item(self, item, spider):
data=[]
for each in dict(item).values():
if each:
data.append(each)
else:
data.append([''])
self.file.writerow(data)
return item
def close_spider(self,spider):
print('存储CSV文件结束')
-------------------------------------------------------------------我是分割线--------------------------------------------------------------------------------
附上spiders完整代码:
# -*- coding: gbk -*-
from scrapy.spiders import CrawlSpider
from scrapy.selector import Selector
from scrapy.http import Request
from jobinfo.items import JobinfoItem
from urllib import request
from selenium import webdriver
import csv
import os.path
import copy
#获取所有地域对应的ID,保存到csv文件
def saveID():
if os.path.exists('areaID.csv'): # 如果存在areaID.csv就停止执行此函数
return
else: # 如果不存在就爬取ID 并保存csv
browser = webdriver.Chrome()
url = 'https://m.51job.com/'
browser.get(url)
browser.refresh()
browser.find_element_by_tag_name('header').find_element_by_class_name('location').click()
parent = browser.find_elements_by_class_name('elist')
IDList=[]
for each in parent:
divs = each.find_elements_by_class_name('e')
for every in divs:
id = every.get_attribute('value')
city = every.find_element_by_class_name('tit').text
IDList.append(id)
IDList.append(city)
# 将IDlist存入csv
with open('areaID.csv','w', encoding='utf-8', newline='') as file:
csv.writer(file).writerow(IDList)
saveID()
def urls(jobname, count,*area):
# 读取csv,获得IDlist
with open("areaID.csv", "r", encoding="utf-8", newline="") as file:
csvfile = csv.reader(file)
IDList = list(csvfile)[0]
# 遍历IDList,根据传参获得area对应的ID
idlist = []
for x in range(len(IDList)):
for y in (area):
if IDList[x] == y:
if IDList[x - 1] not in idlist:
idlist.append(IDList[x - 1])
str1 = ','.join(idlist) # 将list转化为字符串
city = request.quote(request.quote(str1)) # 将地区代码加密转化为51job的地区代码,主要是为逗号加密
position = request.quote(request.quote(jobname)) # 将职位加密转为51job的职位代码
url = 'https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,1.html'
browser = webdriver.Chrome()
browser.get(url)
# 过滤文字得到只有数字的字符串
page = ''.join(filter(str.isdigit,browser.find_element_by_class_name('p_in').find_element_by_class_name('td').text))
#拼接参数,得到start_urls
start_urls = ['https://search.51job.com/list/'+city+',000000,0000,00,9,99,'+position+',2,'+str(i)+'.html' for i in range(count,int(page))]
return start_urls
class jobinfo(CrawlSpider):
#设置name
name = 'jobinfo'
#设置域名
allowed_domains = ['search.51job.com','jobs.51job.com']
# 设置起始页
# !!!!!!! 在这里改入参获得不同职位和地区对应的网址
start_urls = urls('数据分析',1,'上海','广州','北京','深圳','苏州')
# 定义函数爬取所有详情页的连接
def parse(self, response):
item = JobinfoItem()
# 通过Selector 将response 转换成对应对象
selector = Selector(response)
# 以下为信息提取,用xpath的方式提取信息
# 先获取父元素
divs=selector.xpath('//div[@id="resultList"]//div[@class="el"]')
# 用循环找出详情页的链接
for each in divs:
href = each.xpath('./p/span/a/@href').extract()[0]
# 将提取出来的链接传给parse_detail()函数处理
yield Request(href,meta = {'front_item':copy.deepcopy(item)},callback=self.parse_detail)
# 抓取详情页内容
def parse_detail(self,response):
item = response.meta['front_item']
selector = Selector(response)
# 职位名称
title = selector.xpath('//div[@class="cn"]/h1/@title').extract_first()
try:
# 薪资
salary = selector.xpath('//div[@class="cn"]/strong/text()').extract_first()
except:
salary = '面议'
# 公司名称
company = selector.xpath('//p[@class="cname"]/a/@title').extract_first()
# 招聘简介
req = selector.xpath('//p[@class="msg ltype"]/@title').extract_first().replace(u'\xa0', u'')
try:
# 福利
welfare = selector.xpath("//div[@class='jtag']")[0].xpath('.//span/text()').extract()
except:
welfare = '福利信息缺失'
# 职位信息,抓取div[@class="bmsg job_msg inbox"] 标签下所有的文字
job_info = selector.xpath('normalize-space(//div[@class="bmsg job_msg inbox"]//.)').extract_first().replace(u'\xa0', u'')
try:
# 联系方式
contact_info = selector.xpath('normalize-space(//div[@class="bmsg inbox"]//p//.)').extract_first()
except:
contact_info = '无公司联系方式'
div1 = selector.xpath('//div[@class="com_tag"]')
#公司性质
nature = div1.xpath('p[1]/@title').extract_first()
#公司规模
size = div1.xpath('p[2]/@title').extract_first()
#公司行业
industry = div1.xpath('p[3]/@title').extract_first()
item['title'] = title
item['salary'] = salary
item['company'] = company
item['req'] = req
item['welfare'] = welfare
item['job_info'] = job_info
item['contact_info'] = contact_info
item['nature'] = nature
item['size'] = size
item['industry'] = industry
yield item