一、项目背景
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值。
二、网易新闻案例分析
需求:爬取网易新闻的国内板块下的新闻数据
需求分析:当点击国内超链进入国内对应的页面时,会发现当前页面展示的新闻数据是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出的新闻数据的。则就需要我们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的新闻数据。
三、selenium在scrapy中使用的原理分析:
当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作。
四、selenium在scrapy中的使用流程:
重写爬虫文件的构造方法,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)
重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时被调用
重写下载中间件的process_response方法,让该方法对响应对象进行拦截,并篡改response中存储的页面数据
在配置文件中开启下载中间件
五、代码的框架分析(简)
爬虫文件:
classWangyiSpider(RedisSpider):
name= ‘wangyi’
#allowed_domains = [‘www.xxxx.com’]
start_urls = [‘https://news.163.com’]def __init__(self):#实例化一个浏览器对象(实例化一次)
self.bro = webdriver.Chrome(executable_path=’/Users/bobo/Desktop/chromedriver’)#必须在整个爬虫结束后,关闭浏览器
defclosed(self,spider):print(‘爬虫结束’)
self.bro.quit()
中间件文件:
from scrapy.http importHtmlResponse#参数介绍:
#拦截到响应对象(下载器传递给Spider的响应对象)
#request:响应对象对应的请求对象
#response:拦截到的响应对象
#spider:爬虫文件中对应的爬虫类的实例
defprocess_response(self, request, response, spider):#响应对象中存储页面数据的篡改
if request.url in[‘http://news.163.com/domestic/’,’http://news.163.com/world/’,’http://news.163.com/air/’,’http://war.163.com/’]:
spider.bro.get(url=request.url)
js= ‘window.scrollTo(0,document.body.scrollHeight)’spider.bro.execute_script(js)
time.sleep(2) #一定要给与浏览器一定的缓冲加载数据的时间
#页面数据就是包含了动态加载出来的新闻数据对应的页面数据
page_text =spider.bro.page_source#篡改响应对象
return HtmlResponse(url=spider.bro.current_url,body=page_text,encoding=’utf-8′,request=request)else:return response
六、项目的实际分析,详细代码示例(全)
配置文件:settings.py
BOT_NAME = ‘wangyiPro’SPIDER_MODULES= [‘wangyiPro.spiders’]
NEWSPIDER_MODULE= ‘wangyiPro.spiders’
#UA伪装
USER_AGENT = ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36’
#关闭robots
ROBOTSTXT_OBEY =False#日志等级
LOG_LEVEL = “ERROR”
#开启下载中间件
DOWNLOADER_MIDDLEWARES ={‘wangyiPro.middlewares.WangyiproDownloaderMiddleware’: 543,
}#开启管道
ITEM_PIPELINES ={‘wangyiPro.pipelines.WangyiproPipeline’: 300,
}
百度API接口:baiduapi.py
通过文章标题与文章内容,通过自然语言处理(nlp),获取(文章标签)和(文章分类)
#标签类型 这里调用nlp自然语言处理(API文章标签接口)#AppID:16985987#APIKey:dshTH2UtLWArRAKKAtR1RiOS
from aip importAipNlp#初始化API接口
defclient_init():”””你的 APPID AK SK”””APP_ID= ‘169xxx87’API_KEY= ‘dshTH2xxxKKAtR1RiOS’SECRET_KEY= ‘gbkssDmmxxxxhI8utGqa96TS’client=AipNlp(APP_ID, API_KEY, SECRET_KEY)returnclient#文章标签API
defget_keyword(title, content):#初始化api函数,创建一个aip接口对象
client =client_init()”””调用文章标签”””ret=client.keyword(title, content)try:return ret[“items”][1][“tag”]except:pass
#文章分类API
defget_topic(title, content):#初始化api函数,创建一个aip接口对象
client =client_init()”””调用文章分类”””ret=client.topic(title, content)try:return ret[“item”][“lv1_tag_list”][0][“tag”]except:pass
爬虫文件:对新闻数据做数据解析。wangyi.py
#-*- coding: utf-8 -*-
importscrapyfrom ..items importWangyiproItemfrom selenium importwebdriverfrom ..baiduapi importget_keyword, get_topicclassWangyiSpider(scrapy.Spider):def __init__(self):#重写__init__方法,实例化selenium的浏览器对象
self.dri =webdriver.Chrome(
executable_path=r”C:\Users\Administrator\Desktop\json\spider\day10820190809\chromedriver.exe”)
name= ‘wangyi’
#allowed_domains = [‘www.xx.com’]
start_urls = [‘https://news.163.com/’]#创建一个全局url列表,存放标题url地址
title_urls =[]#数据解析
defparse(self, response):
li_list= response.xpath(‘//*[@id=”index2016_wrap”]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li’)#获取国内,国际,军事,航空,无人机
#indexs = [3, 4, 6, 7, 8]
indexs = [3]for index inindexs:#获取标题地址
title_url = li_list[index].xpath(‘./a/@href’).extract_first()#将内容块标题地址url放入全局列表中,在中间件做动态数据请求
self.title_urls.append(title_url)#手动请求标题url
yield scrapy.Request(title_url, callback=self.parse_title)#解析标题数据(国内,国际,军事,航空,无人机), 这里数据是动态加载,需要在中间件通过selenium请求返回的response对象
defparse_title(self, response):
div_list= response.xpath(‘/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div’)for div indiv_list:#文章标题API
title = div.xpath(‘.//div[@class=”news_title”]/h3/a/text()’).extract_first()#文章内容详情页API
detail_url = div.xpath(‘.//div[@class=”news_title”]/h3/a/@href’).extract_first()#实例化item对象,存储title值
item =WangyiproItem()
item[“title”] = title.replace(“\n”, “”).replace(“\r”, “”).replace(” “, “”)#手动请求内容详情页
yield scrapy.Request(detail_url, callback=self.parse_detail, meta={“item”: item})#内容详情页数据解析,这里静态页面
defparse_detail(self, response):#接收请求参数item
item = response.meta[“item”]#获取到内容是一个列表
content = response.xpath(‘//*[@id=”endText”]/p/text()’).extract()
content= “”.join(content).replace(“\n”, “”).replace(“\r”, “”).replace(” “, “”)
item[“content”] =content#文章标签API
item[“keyword”] = get_keyword(item[‘title’], content)#item[“keyword”] = “11”
#文章分类API
item[“topic”] = get_topic(item[‘title’], content)#item[“topic”] = “222”
#提交数据到管道
yielditem#爬虫结束之前,最后执行closed函数
defclosed(self, spider):print(‘结束爬虫!!!’)
self.dri.quit()
配置items文件,创建item对象类,用于存储item对象。items.py
importscrapyclassWangyiproItem(scrapy.Item):#define the fields for your item here like:
title =scrapy.Field()
content=scrapy.Field()
keyword=scrapy.Field()
topic= scrapy.Field()
配置中间件:在下载器中间中,处理动态请求的响应数据。middlewares.py
#-*- coding: utf-8 -*-
#Define here the models for your spider middleware#
#See documentation in:#https://doc.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy importsignalsfrom time importsleepfrom scrapy.http importHtmlResponseclassWangyiproDownloaderMiddleware(object):#拦截所有请求对象
defprocess_request(self, request, spider):returnNone#拦截所有响应对象,由于请新闻版块的动态加载,通过request请求的静态页面不是我们想要的数据
#这里我们通过selenium模拟请求,获取动态加载数据
#这里我们需要实例化一次的浏览器对象,在spider应用中实例化
#request与response(一个请求对象对应一个响应对象)
defprocess_response(self, request, response, spider):#获取爬虫文件中,标题url列表
title_urls =spider.title_urls#判断当前请求的url是否为标题url,只有标题url列表中的请求的动态请求
if request.url intitle_urls:#获取dri浏览器对象
dri =spider.dri#通过selenium请求指定url
dri.get(request.url)#延时,等待浏览器请求响应的延迟
sleep(2)#js语法:向下滑动一页
js = “window.scrollTo(0, document.body.scrollHeight)”
#执行js代码,向下滑动一页
dri.execute_script(js)
sleep(0.5)
dri.execute_script(js)
sleep(0.5)#获取请求的动态加载数据
page_text =dri.page_source#通过实例化构建新的响应对象
new_response = HtmlResponse(url=request.url, body=page_text, encoding=”utf-8″, request=request)returnnew_response#其他静态页面响应的数据
returnresponse#拦截所有异常响应或者请求
defprocess_exception(self, request, exception, spider):pass
持久化存储:这里获取spider爬虫文件数据解析后数据做持久化存储。pipelines.py
importpymysqlclassWangyiproPipeline(object):#初始化mysql连接与游标
conn =None
cursor=None#一般处理句柄或者数据库连接,函数只执行一次
defopen_spider(self, spider):
self.conn= pymysql.connect(host=”127.0.0.1″, port=3306, user=”root”, password=”1234″, db=”spider”)#持久化存储
defprocess_item(self, item, spider):#创建游标
self.cursor =self.conn.cursor()#获取数据属性
title = item[“title”].strip()
content= item[“content”].strip()
keyword= item[“keyword”]
topic= item[“topic”]print(title, content, keyword, topic)#sql语句 插入数据
sql = “insert into wangyi values(‘%s’,’%s’,’%s’,’%s’)” %(title, content, keyword, topic)try:#执行sql语句
self.cursor.execute(sql)#事务提交
self.conn.commit()exceptException as e:#打印错误信息
print(e)#事务回滚
self.conn.rollback()
self.cursor.close()#返回item对象到管道,供下一个管道类接收
returnitem#一般处理句柄或者数据库关闭,函数只执行一次
defclose_spider(self, spider):
self.conn.close()