这节课我们来爬取网易新闻中的国内,国际,军事,航空这四个板块下所有的新闻数据(标题+内容)
分析:
-
首页没有动态加载的数据
爬取四个板块对应的url
-
每一个板块对应的页面中的新闻标题是动态加载
爬取新闻标题+详情页url
-
每一条新闻详情页面中的数据不是动态加载
爬取新闻内容
创建工程
scrapy startproject wangyiPro
cd wangyiPro
scrapy genspider wangyi www.xxx.com
修改配置文件等
数据解析
- 获取各板块的url
import scrapy
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://news.163.com/']
# 创建存储url的列表,方便后续循环发起请求
model_urls = []
# 数据解析:获取每个板块的url
def parse(self, response):
# xpath很容易获取
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
# 因为我们只取2,3,5,6这四个板块,所以这里我们创建个列表
indexs = [2, 3, 5, 6]
for index in indexs:
# 取当前块的li
model_li = li_list[index]
# 获取板块的url
model_url = model_li.xpath('./a/@href').extract_first()
# 存储到url列表中
self.model_urls.append(model_url)
-
对每一个板块的url发起请求
接着上面的代码,然后对每一个板块的url发起请求
for url in self.model_urls: yield scrapy.Request(url=url,callback=self.parse_model)
-
对新闻标题+新闻详情页的url(动态加载)的说明
def parse_model(self, response): pass
在写对详情页数据解析的函数之前,我们知道这是一个动态加载的。如果直接写的话是获取不到数据的。
- 直接对response解析新闻标题数据是无法直接获取的,是动态加载的
- 说明当前response是不满足当下需求的response,需要将其变成满足需求的response
- 满足需求的response就是包含了动态加载数据的response
- 满足需求的response和不满足的response区别在哪里?
- 区别在于响应数据不同。我们可以使用中间件将不满足需求的响应对象中的响应数据篡改成包含了动态加载数据的响应数据,将其变成满足需求的响应对象
-
使用中间件
middlewares.py
篡改响应数据在中间件课程中(scrapy中间件教学),我们只知道。一般情况下我们只需要用到下载中间件。我们删除不需要的内容,方便些代码
class WangyiproDownloaderMiddleware: # 拦截所有(正常&异常)的请求 # 参数:request就是拦截到的请求,spider就是爬虫类实例化的对象 def process_request(self, request, spider): return None # 拦截所有的响应对象 # 参数:respone拦截到的响应对象,request响应对象对应的请求对象 def process_response(self, request, response, spider): return response # 拦截异常的请求 # 参数:request就是拦截到的发生异常的请求 # 作用:想要将异常的请求进行修正,将其变成正常的请求,然后对其进行重新发送 def process_exception(self, request, exception, spider): pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
我们主要重新的方法就是
process_response
:拦截所有的响应对象:
- 整个工程发起的请求:1+4+n。相应也会有1+4+n个响应对象
- 只有指定的4个响应对象是不满足需求,就是4个板块里的内容是动态加载的
- 只需要将不满足需求的4个指定的响应对象的响应数据进行篡改
这里我们就用到了
selenium
:
selenium教程既然参数
spider
是爬虫类实例化的对象,那么我们直接在爬虫文件中引入。import scrapy from selenium import webdriver class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['www.xxx.com'] start_urls = ['https://news.163.com/'] # 创建存储url的列表,方便后续循环发起请求 model_urls = [] # 实例化了一个全局的浏览器对象 bro = webdriver.Chrome(executable_path='./chromedriver')
process_response
只需要将不满足需求的4个指定的响应对象的响应数据进行篡改:
# 拦截所有的响应对象 # 整个工程发起的请求:1+4+n。相应也会有1+4+n个响应对象 # 只有指定的4个响应对象是不满足需求 # 只将不满足需求的4个指定的响应对象的响应数据进行篡改 def process_response(self, request, response, spider): # 将拦截到的所有的响应对象中指定的4个响应对象找出 if request.url in spider.model_urls: bro = spider.bro
现在调用到了,
selenium
的代码就很容易写def process_response(self, request, response, spider): # 将拦截到的所有的响应对象中指定的4个响应对象找出 if request.url in spider.model_urls: bro = spider.bro # response 表示的就是指定的不满足需求的5个响应对象 # 篡改响应数据:首先先获取满足需求的响应数据,将其篡改到响应对象中即可 # 满足需求的响应数据就可以使用selenium获取 bro.get(request.url) # 对五个板块的url发起请求 sleep(2) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) # 捕获到了板块页面中加载出来的全部数据(包含了动态加载的数据) page_text = bro.page_source
写到了这里,我们获取到了页面源码。但我们要返回的是
response
这里我们可以利用
scrapy的HtmlResponse
from time import sleep from scrapy.http import HtmlResponse # scrapy封装好的响应类 class WangyiproDownloaderMiddleware: def process_request(self, request, spider): return None # 拦截所有的响应对象 # 整个工程发起的请求:1+4+n。相应也会有1+4+n个响应对象 # 只有指定的4个响应对象是不满足需求 # 只将不满足需求的4个指定的响应对象的响应数据进行篡改 def process_response(self, request, response, spider): # 将拦截到的所有的响应对象中指定的4个响应对象找出 if request.url in spider.model_urls: bro = spider.bro # response 表示的就是指定的不满足需求的5个响应对象 # 篡改响应数据:首先先获取满足需求的响应数据,将其篡改到响应对象中即可 # 满足需求的响应数据就可以使用selenium获取 bro.get(request.url) # 对五个板块的url发起请求 sleep(2) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) # 捕获到了板块页面中加载出来的全部数据(包含了动态加载的数据) page_text = bro.page_source # 返回了一个新的响应对象,新的对象替换原来不满足需求的旧的响应对象 return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) else: return response
-
数据解析: 新闻标题+新闻详情页的url
获取到了
response
,那么我们在爬虫文件中可以直接进行解析了
# 数据解析: 新闻标题+新闻详情页的url(动态加载)
def parse_model(self, response):
# 篡改后的response 进行正常解析
div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div')
for div in div_list:
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
if new_detail_url:
item = WangyiproItem()
item['title'] = title
# 对新闻详情页的url发起请求
yield scrapy.Request(url=new_detail_url, callback=self.parse_new_detail, meta={'item': item})
-
新闻详情页数据解析
def parse_new_detail(self, respone): content = respone.xpath('//*[@id="content"]/div[2]/p/text()').extract() content = '.'.join(content) item = respone.meta['item'] item['content'] = content yield item
-
要记得关闭
selenium
# 爬虫类父类的方法,该方法是在爬虫结束的最后一刻执行 def closed(self, spider): self.bro.quit()
验证代码是否正确
解析完成后我们在管道中打印试试,看下是否正确
- 打开配置文件中的管道
- 打开配置文件中的中间件
运行后,我们看到正常打印了数据并关闭了浏览器
完整代码:
wangyi.py
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://news.163.com/']
# 创建存储url的列表,方便后续循环发起请求
model_urls = []
# 实例化了一个全局的浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver')
# 数据解析:获取每个板块的url
def parse(self, response):
# xpath很容易获取
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
# 因为我们只取2,3,5,6这四个板块,所以这里我们创建个列表
indexs = [2, 3, 5, 6]
for index in indexs:
# 取当前块的li
model_li = li_list[index]
# 获取板块的url
model_url = model_li.xpath('./a/@href').extract_first()
self.model_urls.append(model_url)
# 然后对每一个板块的url发起请求
for url in self.model_urls:
yield scrapy.Request(url=url, callback=self.parse_model)
# 数据解析: 新闻标题+新闻详情页的url(动态加载)
def parse_model(self, response):
# 篡改后的response 进行正常解析
div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div')
for div in div_list:
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
if new_detail_url:
item = WangyiproItem()
item['title'] = title
# 对新闻详情页的url发起请求
yield scrapy.Request(url=new_detail_url, callback=self.parse_new_detail, meta={'item': item})
def parse_new_detail(self, respone):
content = respone.xpath('//*[@id="content"]/div[2]/p/text()').extract()
content = '.'.join(content)
item = respone.meta['item']
item['content'] = content
yield item
# 爬虫类父类的方法,该方法是在爬虫结束的最后一刻执行
def closed(self, spider):
self.bro.quit()
pipelines.py
class WangyiproPipeline:
def process_item(self, item, spider):
print(item)
items.py
import scrapy
class WangyiproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
middleware.py
from time import sleep
from scrapy.http import HtmlResponse
class WangyiproDownloaderMiddleware:
def process_request(self, request, spider):
return None
# 此方法拦截所有的响应对象
# 整个工程发起的请求:1+4+n。相应也会有1+4+n个响应对象
# 只有指定的4个响应对象是不满足需求
# 只将不满足需求的4个指定的响应对象的响应数据进行篡改
def process_response(self, request, response, spider):
if request.url in spider.model_urls:
bro = spider.bro
# response 表示的就是指定的不满足需求的5个响应对象
# 篡改响应数据:首先先获取满足需求的响应数据,将其篡改到响应对象中即可
# 满足需求的响应数据就可以使用selenium获取
bro.get(request.url) # 对五个板块的url发起请求
sleep(2)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(1)
# 捕获到了板块页面中加载出来的全部数据(包含了动态加载的数据)
page_text = bro.page_source
# response.text = page_text
# return response
# 返回了一个新的响应对象,新的对象替换原来不满足需求的旧的响应对象
return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
else:
return response
def process_exception(self, request, exception, spider):
pass
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
settings.py
打开设置下述
UA
ROBOTSTXT_OBEY
LOG_LEVEL
DOWNLOADER_MIDDLEWARES
ITEM_PIPELINES
总结:
代码看起来多,实际上很简单,只需了解下面步骤:
-
在爬虫类中实例化一个浏览器对象,将其作为爬虫类的一个属性
-
在中间件中实现浏览器自动化的相关的操作
-
在爬虫类重新closed(self,spider),在其内部关闭浏览器对象
关注Python涛哥!学习更多Python知识!