一. 引入
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值。
二. 今日详情
1.案例分析:
(1).需求: 爬取网易新闻的国内板块下的新闻数据
(2).需求分析: 当点击国内超链接进入国内对应的页面时, 会发现当前页面展示的新闻数据是被动态加载出来的, 如果直接通过程序对url进行请求, 是获取不到动态加载出的新闻数据的, 则就需要我们使用selenium实例化一个浏览器对象, 在该对象中进行url请求, 获取动态加载的新闻数据
2. selenium在scrapy中使用的原理分析:
当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作。
3. selenium在scrapy中的使用流程:
- 重写爬虫文件的构造方法,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)
- 重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时被调用
- 重写下载中间件的process_response方法,让该方法对响应对象进行拦截,并篡改response中存储的页面数据
- 在配置文件中开启下载中间件
4. 代码展示:
爬虫文件代码:
# -*- coding: utf-8 -*- import scrapy from Wangyixinwen.items import WangyixinwenItem from selenium import webdriver class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['www.xxx.com'] #自动发送请求的 去设置里将user-agent以及robots协议设置好 start_urls = ['https://news.163.com/'] """" 我们的第一次请求对象就是start_urls开始,我们还需要对UA伪装同时还有IP代理等,可以在下载中间件的process_request中设置UA伪装 process_exception中设置代理ip(等到自己ip出问题了就用代理ip)。等到响应对象缺失就可以在process_response中拦截。 """ bro = webdriver.Chrome("./chromedriver.exe") urls = [] def parse(self, response): #从响应对象中获取到全部目标 target_list = response.xpath('//*[@id="js_festival_wrap"]/div[3]/div[2]/div[2]/div[2]/div/ul/li') #遍历目标得到具体的url for index in [3,6,7,8]: target = target_list[index] #得到目标的url 并且取出url target_url = target.xpath('./a/@href').extract_first() self.urls.append(target_url) #对目标url发起请求 yield scrapy.Request(url=target_url,callback=self.parse_target) def parse_target(self,response): #将第二次请求的响应对象开始解析,分析由于还未编写代码就知道这次是含有动态加载数据,因此这次 #分析可以用到selenium一起解析数据,下面解析出新闻标题以及具体的详情页的url(响应的数据缺失,因此我们需要去下载中间件设置) detail_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for detail in detail_list: title = detail.xpath('./div/div[1]/h3/a/text()').extract_first() detail_url = detail.xpath('./div/div[1]/h3/a/@href').extract_first() #实例化item对象,封装数据 item = WangyixinwenItem() item["title"] = title yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={"item":item}) def parse_detail(self,response): item = response.meta['item'] content = response.xpath('//*[@id="endText"]/p/text()').extract() #将内容转换为字符串对象 content = "".join(content) item["content"] = content #提交数据 yield item def close(self,spider): # 爬虫结束,浏览器也关闭 print("爬虫结束!!!") self.bro.quit()
中间件文件拦截代码展示:
# -*- coding: utf-8 -*- # Define here the models for your spider middleware # # See documentation in: # https://docs.scrapy.org/en/latest/topics/spider-middleware.html from scrapy import signals from scrapy.http import HtmlResponse import random from time import sleep class WangyixinwenDownloaderMiddleware(object): # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. def process_request(self, request, spider): return None def process_response(self, request, response, spider): #刚才响应体缺失,因此从这里我们应该重新返回新的响应体 #这里要用到爬虫程序中的urls,判断url是否在里面,在urls里面的就会出现响应缺失, # 、因此需要返回新的响应体 if request.url in spider.urls: #响应缺失是因为是动态加载数据,因此我们配合selenium使用 #在这里实例化selenium的话会被实例化多次,然而selenium只需要实例化一次, #这个时候我们可以将selenium放在实例化一次的爬虫程序开始的时候,实例化完成引入 sleep(2) bro = spider.bro.get(url=request.url)#浏览器中发送请求 sleep(1) spider.bro.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(1.5) spider.bro.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(0.7) spider.bro.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(1) spider.bro.execute_script("window.scrollTo(0,document.body.scrollHeight)") #发送到请求我们需要获取浏览器当前页面的源码数据 获取数据之前需要翻滚页面 page_text = spider.bro.page_source #改动返回响应对象 scrapy提供了一个库url=spider.bro.current_url, body=page_text, encoding='utf-8', request=request new_response = HtmlResponse(url=request.url,body=page_text,encoding="utf-8",request=request) return new_response else: return response #提交完新的响应体之后,去设置将下载中间件打开 def process_exception(self, request, exception, spider): pass
备注:仔细看代码注释,能够迅速唤醒记忆,更快开发。