900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > scrapy由浅入深(三) selenium模拟爬取ajax动态页面(智联招聘)

scrapy由浅入深(三) selenium模拟爬取ajax动态页面(智联招聘)

时间:2022-11-04 21:05:07

相关推荐

scrapy由浅入深(三) selenium模拟爬取ajax动态页面(智联招聘)

爬取智联招聘的网址:/?pageSize=60&jl=489&kw=python&kt=3

上一篇博客爬取了前程无忧的职位招聘信息,总体来说前程无忧的网站信息并不难爬取,前程无忧的网站并没有ajax,直接请求网站就能获得职位信息,但是智联招聘的页面涉及到ajax,直接get网站的url获取不到任何有用的信息,这也是反爬虫的一种手段,因此我们需要借助selenium模拟浏览器访问智联招聘网站。在爬取的过程中有一些非常有意思的问题,下面我会把这些问题以及解决的办法一一列举出来。

1.首先我在分析职位详情(注意不是职位列表页面)网页的结构的时候遇到的一个问题,在分析网页的源码构造xpath的时候,发现无论怎么修改xpath及css选择器,获得的数据都是空([ ])。原来使用爬虫获取到的网页源码与我们在网页上看到的源码不一样,使用scrapy请求网站的时候,网页会将class属性替换掉,所以直接通过网页上的源码来构造xpath和css选择器是不可行的。正确的做法是通过scrapy shell +""(请求的网址),打开浏览器查看正确的class属性,然后再构造xpath及css选择器。2.然后就是涉及到ajax的职位列表页面,细心一点的同学会发现当输入网址之后,下方的职位列表会加载一段时间才会展示出来,如果我们直接get网页的源码,不会得到任何有用的信息,使用scrapy shell + ""(职位列表页面) 可以看到在浏览器中不会显示查询之后的结果,因此我们需要使用selenium模拟获取职位列表页面的所有信息。3.编写使用selenium模拟点击下一页的中间件,职位的详细信息通过scrapy系统的中间件下载,这就会产生数据丢失的问题,因为点击下一页这个动作运行的非常快,那么在点击下一页之后,scrapy会接受该页面的所有职位链接,一个页面有60个职位链接,我试验的时候基本上当selenium中间件点击到将近30页的时候,第一页的所有职位链接才会爬取完,那么就有一个问题,现在scrapy已经接受了几百个职位的url,在请求这些url的时候很有可能会丢掉大部分的数据,造成很多页面没有爬取的漏洞,解决的办法也很简单,设置网页跳转的限制,当一个网页的数据爬取的差不多的时候,比如爬取了50多条数据的时候就能跳转到下一页。

代码思路:1.定义一个中间件处理两种不同的请求,点击下一页或者下载详情页。2.抽取职位列表的所有url,通过scrapy系统的中间件请求职位的详细信息页,防止覆盖掉senium的职位列表的url。3.判断该职位列表页的数据爬取了多少条,如果超过50页,那么点击到下一页。4.将数据保存到数据库

一.创建项目

(1)

scrapy startproject zhipinSpider

创建一个名称为zhipinSpider的项目

(2)手动创建job_detail.py文件,实现爬虫的主要逻辑

二.项目配置

(1)编写items文件

import scrapyclass ZhipinspiderItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()title = scrapy.Field()salary = scrapy.Field()detail = scrapy.Field()

定义三个字段,分别用来保存职位的标题,薪资,职位的要求

(2)编写pipelines文件

import sqlite3db = sqlite3.connect("./../../zhi.db")cursor = db.cursor()class ZhipinspiderPipeline(object):def process_item(self, item, spider):cursor.execute("insert into jobs(title, salary, detail) values (?,?,?)",[item["title"],item["salary"],item["detail"]])mit()

将数据保存到sqlite数据库中,也可以保存到MySQL数据库中,写法类似

(3)编写selenium模拟中间件

class SeleniumMiddleware(object):def __init__(self):self.options = Options()# self.options.add_argument('-headless')self.browser = webdriver.Firefox(executable_path="D:\geckodriver\geckodriver.exe",firefox_options=self.options)# self.option = Options()# self.option.add_argument('-headless')# self.browser1 = webdriver.Firefox(executable_path="D:\geckodriver\geckodriver.exe",firefox_options=self.options)def process_request(self, request, spider):"""通过meta携带的数据判断应该跳转到下一页还是下载详情页这里没有处理详情页的函数,所以当下载详情页的时候会调用scrapy系统的中间件,也就不会覆盖跳转页面的url:param request: :param spider: :return: """if int(request.meta["page"]) == 0:self.browser.get(request.url)self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")time.sleep(1)pages = self.browser.find_element_by_css_selector('button.btn:nth-child(8)')pages.click()time.sleep(5)return HtmlResponse(url=self.browser.current_url,body=self.browser.page_source,encoding="utf-8",request=request)if int(request.meta["page"]) == 2:# 不需要get网页的url否则浏览器会在第一第二页之间来回跳转# self.browser.get(self.browser.current_url)self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")time.sleep(1)pages = self.browser.find_element_by_css_selector('button.btn:nth-child(8)')pages.click()time.sleep(5)return HtmlResponse(url=self.browser.current_url,body=self.browser.page_source,encoding="utf-8",request=request)

(4)配置setting文件

启用selenium中间件

DOWNLOADER_MIDDLEWARES = {'zhipinSpider.middlewares.SeleniumMiddleware': 543,}

启用管道文件

ITEM_PIPELINES = {'zhipinSpider.pipelines.ZhipinspiderPipeline': 300,}

下载延迟

DOWNLOAD_DELAY = 3RANDOMIZE_DOWNLOAD_DELAY = True

(5)编写spider文件

爬虫的主要逻辑

import scrapyfrom scrapy import Requestimport lxml.htmlfrom zhipinSpider.items import ZhipinspiderItemimport sqlite3db = sqlite3.connect("./../../zhi.db")cursor = db.cursor()i = 0def select_from_sql():""":return: 当前数据库中数据的总数"""count = cursor.execute("select * from jobs")return len(count.fetchall())class JobDetailSpider(scrapy.Spider):name = "jobSpider"def start_requests(self):url_str = "/?pageSize=60&jl=489&kw=python&kt=3"yield Request(url=url_str,callback=self.parse,meta={"page":"0"})def parse(self, response):"""抽取出包含职位url的html,并通过函数分离url:param response: :return: """html_str = response.xpath('//div[@class="listItemBox clearfix"]').extract()for html in html_str:job_url = self.parse_one_job(html)yield Request(url=job_url,callback=self.parse_job_text,meta={"page":"1"})def parse_one_job(self,html):"""分理处html中的职位url:param html: :return: """xtree = lxml.html.fromstring(html)job_url = xtree.xpath('//div[@class="jobName"]/a/@href')[0]return job_urldef parse_job_text(self,response):global icount = select_from_sql()# 使用selenium模拟的结果# title = response.xpath('//ul/li/h1/text()').extract()# detail = response.xpath('//div[@class="pos-ul"]/p/text()').extract()# salary = response.xpath('//div[@class="l info-money"]/strong/text()').extract()# 使用view(response)获得的结果title = response.xpath('//div[@class="inner-left fl"]/h1/text()').extract_first()salary = response.xpath('//ul[@class="terminal-ul clearfix"]/li/strong/text()').extract_first()detail = response.xpath('//div[@class="tab-inner-cont"]/*/text()').extract()detail_span = response.xpath('//div[@class="tab-inner-cont"]/p/span/text()').extract()if detail_span is not None:detail = detail_span + detailcontents = ""for content in detail:contents += contentcontents = ' '.join(contents.split())item = ZhipinspiderItem()item["title"] = titleitem["salary"] = salaryitem["detail"] = contents# 判断有没有爬取完当前页面的职位信息(是否达到分页的条件)if count - i > 58:i = countyield Request(url="", callback=self.parse, meta={"page": "2"}, dont_filter=True)yield item

这里分析职位要求的xpath之所以有两个是因为智联招聘所有的职位要求信息,它的html标签会有个别不一样的情况,这里我直接定义了两个xpath处理两种情况,将两种情况得到的信息合并,这样就会减少爬取不到职位要求信息的情况。另外最后这个请求url之所以使用是因为这里我们不需要使用其他的url,只需要点击下一页就行了,这里的url只作为占位使用。

总结:爬取智联招聘难点在于如何爬取ajax数据,如何使用selenium模拟区分职位列表页跟职位详情页,以及为了防止数据丢失而查询数据库数据的条数,判断是否达到了分页的条件。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。