引入:
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值
以一个案例引入需求:
1.案例分析:
- 需求:爬取网易新闻的国内,国际,军事,航空板块下的新闻数据
- 需求分析:当点击国内,国际,军事,航空超链进入国内对应的页面时,会发现当前页面展示的新闻数据是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出的新闻数据的。则就需要我们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的新闻数据。
2.selenium在scrapy中使用的原理分析:
当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作。
3.selenium在scrapy中的使用流程:
1.导入selenium需要的模块
2.在爬虫文件中重写爬虫文件的构造方法,使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)
3.重写爬虫类父类一closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时被调用
4.在下载中间件中process_response中:
a:获取爬虫文件中实例化好的浏览器对象
b:执行浏览器自动化的行为动作
c:实例化了一个新的响应对象,并且将浏览器获取的页面源码数据加载到了该对象中,注意,需要导入模块
d:返回这个新的响应对象
5.在配置文件中开启下载中间件
4.代码展示:
- 爬虫文件
import scrapy from wyxwPro.items import WyxwproItem from selenium import webdriver from aip import AipNlp class WyxwSpider(scrapy.Spider): # 导入百度AI """ 你的 APPID AK SK """ APP_ID = '15425006' API_KEY = 'tr4c0uDUOCQl1X3BeGZX2C2n' SECRET_KEY = 'AkihR0ilwsyOcgrvOX558qSy1Kpytuj7 ' client = AipNlp(APP_ID, API_KEY, SECRET_KEY) name = 'wyxw' # allowed_domains = ['www.xxx.com'] start_urls = ['https://news.163.com/'] # 存储四大新闻板块的url new_urls = [] def __init__(self): # 实例化一个浏览器对象(实例化一次),这里我们选择可视化页面,主要是方便看到展示效果 self.bro = webdriver.Chrome(executable_path=r'D:\pycharm\exercise\13.爬虫\day7\chromedriver.exe') # 必须在整个爬虫结束后,关闭浏览器 def closed(self,spider): self.bro.quit() def parse(self, response): # 1.获取指定四大板块的链接(国内,国际,军事,航空) li_list = response.xpath('//div[@class="bd"]/div/ul/li') # 2.从网页中进行查看指定四大板块对应li标签,获取其对应的索引 indexs = [3,4,6,7] # 3.定义一个空列表,将获取到的四大板块的li标签,添加到列表中 new_li_list = [] for index in indexs: new_li_list.append(li_list[index]) # 4.将四大板块对应的li标签进行解析(拿到的是详情页面的超链接) for li in new_li_list: # 四大板块对应的url detail_url = li.xpath('./a/@href').extract_first() # 将四大板块对应的url添加到new_urls列表中 self.new_urls.append(detail_url) yield scrapy.Request(url=detail_url,callback=self.detailNews) def detailNews(self,response): # 获取新闻类型的页面 div_list = response.xpath('//div[@class="ndi_main"]/div') for div in div_list: # 进行数据解析 title = div.xpath('./a/img/@alt').extract_first() img_url = div.xpath('./a/img/@src').extract_first() # 拿到当前某一板块类型新闻下,对应的一条详细新闻的url new_detail_url = div.xpath('./a/@href').extract_first() if new_detail_url: print("详情url:", new_detail_url) # 实例化一个item类型的对象,并将解析到的数据封装到该对象当中 item = WyxwproItem() item["title"] = title item["img_url"] = img_url # 使用yield获取详情页面,通过meta参数进行Request的数据传递(请求传参) yield scrapy.Request(url=new_detail_url,callback=self.detailNewsInfo,meta={"item":item}) def detailNewsInfo(self,response): # 通过response获取item对象 item = response.meta["item"] content = response.xpath('//div[@id="endText"]//text()').extract() content = "".join(content).strip("\n\t") # 将解析到的数据,封装到item对象中 item["content"] = content # 调用百度AI接口,提取文章的类型和关键字 # 调用百度AI的文章分类 key_list =[] keyword = self.client.keyword(item["title"].replace(u'\xa0',u''),item["content"].replace(u'\xa0',u'')) # 返回结果是一个字典 for dic in keyword["items"]: key_list.append(dic["tag"]) # 将拿到的列表转化字符串 item["keyword"] = "".join(key_list) # 调用百度AI文章分类接口 kind_type= self.client.topic(item["title"].replace(u'\xa0',u''), item["content"].replace(u'\xa0',u'')) item["type"] = kind_type["item"]["lv1_tag_list"][0]["tag"] yield item
middlewares.py (中间件文件)
# 导入一个新的响应对象类型HtmlResponse from scrapy.http import HtmlResponse import time class WyxwproDownloaderMiddleware(object): # 参数介绍: # 拦截到响应对象(下载器传递给Spider的响应对象) # request:响应对象对应的请求对象 # response:拦截到的响应对象 # spider:爬虫文件中对应的爬虫类的实例 def process_response(self, request, response, spider): # 指定拦截的响应 if request.url in spider.new_urls: # 处理响应对象: url = request.url # 1.获取在爬虫文件中创建好的浏览器对象 bro = spider.bro # 2.执行浏览器自动化的行为动作 #通过get发送请求 bro.get(url=url) time.sleep(2) # 获取当前页面显示的高度 js = "window.scrollTo(0,document.body.scrollHeight)" # 执行js代码,execute_script该函数可以执行一组字符串形式的js代码 bro.execute_script(js) time.sleep(1) # 给与浏览器一定的缓冲加载数据的时间 bro.execute_script(js) time.sleep(1) bro.execute_script(js) time.sleep(1) # # 定位到点击加载更多标签 a = bro.find_elements_by_xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/a') if len(a) !=0: a[0].click() time.sleep(2) bro.execute_script(js) # 获取需求中需要的数据源,(动态加载出来的新闻数据的页面源数据) page_text = bro.page_source # 3.创建一个新的响应对象并且将上述获取的数据源加载到该响应对象中, # 4.然后返回该响应对象 # bro.current_url表示当前浏览器所打开的页面,对应的url 即是request.url # request 表示当前响应对象所对应的请求对象 return HtmlResponse(url=bro.current_url,body=page_text,encoding="utf-8",request=request) return response
items.py (属性设置)
import scrapy class WyxwproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() # 新闻的标题 img_url = scrapy.Field() # 新闻图片的url content = scrapy.Field() # 新闻的内容 type = scrapy.Field() # 拿到文章的类型 keyword = scrapy.Field() # 拿到内容中的关键字
pipelines.py (管道文件)
# 将管道文件获取的数据,储存到mysql数据库中 import pymysql class WyxwproPipeline(object): conn =None cursor = None def open_spider(self,spider): self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='',db='scrapy') print(self.conn,"爬虫开始了") def process_item(self, item, spider): self.cursor = self.conn.cursor() sql = 'insert into news values("%s","%s","%s","%s","%s")'%(item["title"],item["content"],item["img_url"],item["type"],item["keyword"]) try: self.cursor.execute(sql) self.conn.commit() except Exception as e: print(e) self.conn.rollback() print(item) return item def close_spider(self,spider): self.cursor.close() self.conn.close()
setting.py文件(配置文件)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' ROBOTSTXT_OBEY = False # 开启下载中间件服务 DOWNLOADER_MIDDLEWARES = { 'wyxwPro.middlewares.WyxwproDownloaderMiddleware': 543, } # 开启管道服务 ITEM_PIPELINES = { 'wyxwPro.pipelines.WyxwproPipeline': 300, } # 进行加速处理 CONCURRENT_REQUESTS = 5 COOKIES_ENABLED = False LOG_LEVEL = "ERROR" RETRY_ENDBLED = False