引入
使用Scrapy框架爬取某些网站的数据时,往往会页面动态加载数据的情况。如果是直接使用Scrapy对其url发起请求,是绝对获取不到动态加载的数据的。但是通过观察我们会发现,通过浏览器对其url发起请求则会加载出对应的动态数据。那么,如果我们想要在Scrapy中获取动态加载的数据,就必须使用 selenium 来操作浏览器,然后通过浏览器对其url发起请求来获取动态加载的数据。
案例分析
需求:爬取网易新闻的国内,国际,军事,航空板块下的新闻数据
需求分析:当点击板块的超链接进入板块页面后,发现当前页面展示的新闻数据是动态加载出来的。因此,我们需要使用 selenium 操作浏览器来获取其动态加载的数据。
在Scrapy中使用selenium的流程:
- 重写爬虫类的构造方法,在该方法中使用 selenium 实例化一个浏览器对象(因为浏览器对象只需要被实例化一次).
- 重写爬虫文件的 closed 方法,在其内部关闭浏览器对象。该方法在爬虫结束时被调用.
- 重写下载(或爬虫)中间件的 process_response 方法,让该方法对响应对象进行拦截,并篡改 response 中存储的页面数据.
- 在配置文件中开启下载中间件.
代码展示
爬虫文件:
# -*- coding: utf-8 -*- import scrapy from selenium import webdriver from aip import AipNlp # pip install baidu-aip from Test.items import TestItem class TestSpider(scrapy.Spider): name = 'test' start_urls = ['https://news.163.com/'] # 网易首页 # 用于存放四大板块详情页的超链接,在下载中间件中会用到 plate_page = [] # 百度AI,用于提取文章关键字和类型,详见文档:https://ai.baidu.com/docs#/NLP-Python-SDK/cf2f8fbe __APP_ID = '15225447' __API_KEY = 's5m43BMMEGGPaFGxeX3SsY7m' __SECRET_KEY = 'Lca9FEGpWNZW6yd8WWAHAyCyLovmi6rb' client = AipNlp(__APP_ID, __API_KEY, __SECRET_KEY) def __init__(self): # 实例化一个谷歌浏览器对象,在下载中间件中会用到 self.bro = webdriver.Chrome(executable_path=r'V:\Folder\software\谷歌浏览器V69-71版本的驱动\chromedriver.exe') # executable_path:指定你的谷歌浏览器驱动 # 重写父类方法,用于关闭浏览器 def closed(self, spider): """此方法在爬虫程序结束时执行,注意:只会执行一次""" self.bro.quit() print('爬取结束') def parse(self, response): # 获取所有板块的链接 all_li_list = response.xpath('//div[@class="ns_area list"]/ul/li') # 提取指定板块的链接(3-国内,4-国际,6-军事,7-航空) sign_li_list = [all_li_list[i] for i in [3, 4, 6, 7]] # 下面将对提取的四大板块进行解析,并访问其页面 for li in sign_li_list: url = li.xpath('./a/@href').extract_first() self.plate_page.append(url) # 访问每个版块页面 yield scrapy.Request(url, callback=self.parse_plate_page) # callback:指定回调函数,即解析的方法 # 用于解析四大板块页面 def parse_plate_page(self, response): # 注意,此页面中的数据是动态加载的,在下载中间件中来获取动态数据 div_list = response.xpath('//div[@class="ndi_main"]/div') # 获取所有文章对应的<div> # 提取文章基本信息 for div in div_list: if not div.xpath('./a/img/@alt'): continue # 过滤其它标签格式的文章 item = TestItem() item['title'] = div.xpath('./a/img/@alt').extract_first() # 文章标题 item['img_url'] = div.xpath('./a/img/@src').extract_first() # 文章标题图片对应的链接 detail_url = div.xpath('./a/@href').extract_first() # 文章详情页的url # 访问每篇文章的详情页面 yield scrapy.Request(detail_url, callback=self.parse_detail_page, meta={'item': item}) # meta={'item': item}:将当前的item对象传入解析方法 # 用于解析所有文章的详情页面 def parse_detail_page(self, response): # 我们先提取出传过来的参数 item = response.meta['item'] # 获取文章的所有内容并全部解析后保存 content = response.xpath('//div[@id="endText"]//text()').extract() item['content'] = ''.join(content).strip(' \n\t') # 下面将调用百度AI接口,提取文章的关键字和类型: # 实测中有编码问题,这里我们将其替换为空来解决 args = {'title': item['title'].replace(u'\xa0',u''), 'content': item['content'].replace(u'\xa0',u'')} # 开始提取文章关键字 keys = self.client.keyword(**args) item['keys'] = ' '.join([dct.get('tag') for dct in keys.get('items')]) # 保存文章关键字 # 开始提取文章类型 kinds = self.client.topic(**args) item['kind'] = kinds.get('item')['lv1_tag_list'][0]['tag'] # 保存文章类型 # 将准备好的item对象提交给管道,剩下的就是保存数据了 yield item
数据结构模板文件:
class TestItem(scrapy.Item): # define the fields for your item here like: title = scrapy.Field() # 文章标题 img_url = scrapy.Field() # 文章标题图片对应的链接 content = scrapy.Field() # 文章内容 keys = scrapy.Field() # 文章关键字 kind = scrapy.Field() # 文章类型
中间件文件:
from time import sleep from from scrapy.http import HtmlResponse # 用于生成响应对象 # 下载中间件 class TestDownloaderMiddleware(object): def process_response(self, request, response, spider): """ 此方法用于拦截响应 :param request: 当前响应对应的请求 :param response: 响应 :param spider: 爬虫类对象 :return: """ # 我要要在这里拦截四大板块页面的响应,来获取动态加载的内容 # 对于不是访问四大板块页面的,直接放行: if request.url not in spider.plate_page: return response # 能走到这里的,必然是四大板块的响应,下面将篡改响应对象 # 获取在爬虫类中创建好的浏览器对象 bro = spider.bro # 向板块页面发起GET请求 bro.get(url=request.url) sleep(1) # 鼠标滚轮下滚,连滚两次(用于获取更多动态加载的数据) js = 'window.scrollTo(0, document.body.scrollHeight);' bro.execute_script(js) sleep(0.5) bro.execute_script(js) sleep(0.5) # 获取页面源码,这里有我们需要的动态加载的数据 page_text = bro.page_source # 创建一个新的响应对象,并将动态加载到的数据存入该对象中,然后返回该对象 return HtmlResponse(url=bro.current_url, body=page_text, encoding='utf-8', request=request) # bro.current_url:请求的url
管道文件:
"""去吧,创建你的数据表: create table test01( id int primary key auto_increment, -- 自增id title varchar(128), -- 标题 img_url varchar(128), -- 标题图片对应的链接 keyword varchar(64), -- 关键字 kind varchar(32), -- 文章类型 content text -- 文章内容 ); """ import pymysql class TestPipeline(object): # 重写父类方法,用于建立MySQL链接,并创建一个游标 def open_spider(self, spider): """此方法在运行应用时被执行,注意:只会被执行一次""" self.conn = pymysql.Connect( host='localhost', port=3306, user='zyk', password='user@zyk', db='test', # 指定你要使用的数据库 charset='utf8' # 指定数据的编码格式 ) # 建立MySQL链接 # 创建游标 self.cursor = self.conn.cursor() def process_item(self, item, spider): # 我们先准备好sql语句 sql = 'insert into test01(title, img_url, keyword, kind, content) values(%s, %s, %s, %s, %s)' # 开始执行事务 try: self.cursor.execute(sql, (item['title'], item['img_url'], item['keys'], item['kind'], item['content'])) # 写入数据 self.conn.commit() # 提交 print(item['title'], '已保存') except Exception as e: self.conn.rollback() # 回滚 print(e) return item # 重写父类方法,用于关闭MySQL链接 def close_spider(self, spider): """此方法在结束应用时被执行,注意:只会被执行一次""" self.cursor.close() # 关闭游标 self.conn.close() # 关闭连接
配置文件:
# 伪装请求身份载体(User-Agent) USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36' # 是否遵守robots协议 ROBOTSTXT_OBEY = False # 开启的线程数 CONCURRENT_REQUESTS = 100 # 禁用cookie来提升爬取效率 COOKIES_ENABLED = False # 提高日志级别来降低CPU的占用率,以提升爬取效率 LOG_LEVEL = 'ERROR' # 禁用重新请求(对失败的rul)来提升爬取效率 RETRY_ENABLED = False # 开启管道 ITEM_PIPELINES = { 'Test.pipelines.TestPipeline': 300, } # 启用下载中间件 DOWNLOADER_MIDDLEWARES = { 'Test.middlewares.TestDownloaderMiddleware': 543, }
原文地址:https://blog.csdn.net/qq_41964425/article/details/86497474
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。