scrapy由浅入深(三) selenium模拟爬取ajax动态页面(智联招聘)
爬取智联招聘的网址:https://sou.zhaopin.com/?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)
[code]scrapy startproject zhipinSpider
创建一个名称为zhipinSpider的项目
(2)手动创建job_detail.py文件,实现爬虫的主要逻辑
二.项目配置
(1)编写items文件
[code]import scrapy class 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文件
[code]import sqlite3 db = 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"]]) db.commit()
将数据保存到sqlite数据库中,也可以保存到MySQL数据库中,写法类似
(3)编写selenium模拟中间件
[code]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中间件
[code]DOWNLOADER_MIDDLEWARES = { 'zhipinSpider.middlewares.SeleniumMiddleware': 543, }
启用管道文件
[code]ITEM_PIPELINES = { 'zhipinSpider.pipelines.ZhipinspiderPipeline': 300, }
下载延迟
[code]DOWNLOAD_DELAY = 3 RANDOMIZE_DOWNLOAD_DELAY = True
(5)编写spider文件
爬虫的主要逻辑
[code]import scrapy from scrapy import Request import lxml.html from zhipinSpider.items import ZhipinspiderItem import sqlite3 db = sqlite3.connect("./../../zhi.db") cursor = db.cursor() i = 0 def 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 = "https://sou.zhaopin.com/?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_url def parse_job_text(self,response): global i count = 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 + detail contents = "" for content in detail: contents += content contents = ' '.join(contents.split()) item = ZhipinspiderItem() item["title"] = title item["salary"] = salary item["detail"] = contents # 判断有没有爬取完当前页面的职位信息(是否达到分页的条件) if count - i > 58: i = count yield Request(url="http://www.baidu.com", callback=self.parse, meta={"page": "2"}, dont_filter=True) yield item
这里分析职位要求的xpath之所以有两个是因为智联招聘所有的职位要求信息,它的html标签会有个别不一样的情况,这里我直接定义了两个xpath处理两种情况,将两种情况得到的信息合并,这样就会减少爬取不到职位要求信息的情况。另外最后这个请求url之所以使用www.baidu.com是因为这里我们不需要使用其他的url,只需要点击下一页就行了,这里的url只作为占位使用。
总结:爬取智联招聘难点在于如何爬取ajax数据,如何使用selenium模拟区分职位列表页跟职位详情页,以及为了防止数据丢失而查询数据库数据的条数,判断是否达到了分页的条件。
阅读更多
- Python爬虫(二十三)_selenium案例:动态模拟页面点击
- Scrapy抓取Ajax动态页面
- Selenium 模拟浏览器动态加载页面的实现方法
- 利用ajax实现页面动态加载select下拉框
- python下利用Selenium获取动态页面数据
- Scrapy利用Splash抓取动态页面
- ajax动态加载页面(分页)
- Selenium来抓取动态加载的页面
- Struts中利用ajax/jquery页面动态无刷新添加信息
- ajax发送请求,以及动态添加到页面
- selenium得到一个动态页面
- js加载Json数组实现ajax加载动态页面数据
- 原生js,jquery通过ajax获得后台json数据动态新增页面元素
- Scrapy爬虫系列笔记之八:Selenium进行动态网站爬取_by_书訢
- ajax学习笔记之五 模拟google动态提示效果
- 通过Ajax动态生成的页面,解决复选框,选择一次不能再选择的问题
- selenium动态抓取页面元素
- ThinkPHP定时ajax获取后台数据,使用javascript动态修改前端页面的表格来显示数据
- 使用Selenium模拟浏览器登录,并获取页面信息