环境安装:pip install Pillow
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
需求:将图片的名称和详情页中图片的数据进行爬取,持久化存储。
分析:
爬虫文件:
import scrapy
from ..items import DeepimgproItem
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://pic.netbian.com/4kmeinv/']
#通用的url模板
url_model = 'https://pic.netbian.com/4kmeinv/index_%d.html'
page_num = 2
def parse(self, response):
#解析出了图片的名称和详情页的url
li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
for li in li_list:
title = li.xpath('./a/b/text()').extract_first() + '.jpg'
detail_url = 'https://pic.netbian.com'+li.xpath('./a/@href').extract_first()
item = DeepimgproItem()
item['title'] = title
#需要对详情页的url发起请求,在详情页中获取图片的下载链接
yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
if self.page_num <= 2:
new_url = format(self.url_model%self.page_num)
self.page_num += 1
yield scrapy.Request(url=new_url,callback=self.parse)
#解析详情页的数据
def detail_parse(self,response):
meta = response.meta
item = meta['item']
img_src = 'https://pic.netbian.com'+response.xpath('//*[@id="img"]/img/@src').extract_first()
item['img_src'] = img_src
yield item
管道:
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
import scrapy
from itemadapter import ItemAdapter
from scrapy.pipelines.images import ImagesPipeline
class DeepimgproPipeline(ImagesPipeline):
# def process_item(self, item, spider):
# return item
def get_media_requests(self, item, info):
img_src = item['img_src']
#请求传参,将item中的图片名称传递给file_path方法
#meta会将自身传递给file_path
print(item['title'],'保存下载成功!')
yield scrapy.Request(url=img_src,meta={'title':item['title']})
def file_path(self, request, response=None, info=None, *, item=None):
#返回图片的名称
#接收请求传参过来的数据
title = request.meta['title']
return title
def item_completed(self, results, item, info):
return item
增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为WORNING或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’
禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
问题:在之前代码中,我们从来没有手动的对start_urls列表中存储的起始url进行过请求的发送,但是起始url的确是进行了请求的发送,那这是如何实现的呢?
解答:其实是因为爬虫文件中的爬虫类继承到了Spider父类中的start_requests(self)这个方法,该方法就可以对start_urls列表中的url发起请求:
def start_requests(self):
for u in self.start_urls:
yield scrapy.Request(url=u,callback=self.parse)
【注意】该方法默认的实现,是对起始的url发起get请求,如果想发起post请求,则需要子类重写该方法。
import scrapy
class FanyiSpider(scrapy.Spider):
name = 'fanyi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://fanyi.baidu.com/sug']
#父类中的方法:该方法是用来给起始的url列表中的每一个url发请求
def start_requests(self):
data = {
'kw':'dog'
}
for url in self.start_urls:
#formdata是用来指定请求参数
yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)
def parse(self, response):
result = response.json()
print(result)
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
- 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
scrapy的中间件有两个:
中间件重要方法:
# 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
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
class MiddleproDownloaderMiddleware:
#类方法:作用是返回一个下载器对象(忽略)
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
#拦截处理所有的请求对象
#参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
#spider参数的作用可以实现爬虫类和中间类的数据交互
def process_request(self, request, spider):
return None
#拦截处理所有的响应对象
#参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
def process_response(self, request, response, spider):
return response
#拦截和处理发生异常的请求对象
#参数:reqeust就是拦截到的发生异常的请求对象
def process_exception(self, request, exception, spider):
pass
#控制日志数据的(忽略)
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
request.meta[‘proxy’] = proxy
# 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
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
from scrapy import Request
class MiddleproDownloaderMiddleware:
#类方法:作用是返回一个下载器对象(忽略)
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
#拦截处理所有的请求对象
#参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
#spider参数的作用可以实现爬虫类和中间类的数据交互
def process_request(self, request, spider):
#是的所有的请求都是用代理,则代理操作可以写在该方法中
request.meta['proxy'] = 'http://ip:port'
#弊端:会使得整体的请求效率变低
print(request.url+':请求对象拦截成功!')
return None
#拦截处理所有的响应对象
#参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
def process_response(self, request, response, spider):
print(request.url+':响应对象拦截成功!')
return response
#拦截和处理发生异常的请求对象
#参数:reqeust就是拦截到的发生异常的请求对象
#方法存在的意义:将发生异常的请求拦截到,然后对其进行修正
def process_exception(self, request, exception, spider):
print(request.url+':发生异常的请求对象被拦截到!')
#修正操作
#只有发生了异常的请求才使用代理机制,则可以写在该方法中
request.meta['proxy'] = 'https://ip:port'
return request #对请求对象进行重新发送
#控制日志数据的(忽略)
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
request.headers[‘User-Agent’] = ua
def process_request(self, request, spider):
request.headers['User-Agent'] = '从列表中随机选择的一个UA值'
print(request.url+':请求对象拦截成功!')
return None
request.cookies = cookies
def process_request(self, request, spider):
request.headers['cookie'] = 'xxx'
#request.cookies = 'xxx'
print(request.url+':请求对象拦截成功!')
return None
需求:将网易新闻中的国内,国际,军事,航空四个板块下的新闻标题和内容进行数据爬取
爬虫文件
class WangyiSpider(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')
#必须在整个爬虫结束后,关闭浏览器
def closed(self,spider):
print('爬虫结束')
self.bro.quit()
中间件文件:
from scrapy.http import HtmlResponse
#参数介绍:
#拦截到响应对象(下载器传递给Spider的响应对象)
#request:响应对象对应的请求对象
#response:拦截到的响应对象
#spider:爬虫文件中对应的爬虫类的实例
def process_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
配置文件:
DOWNLOADER_MIDDLEWARES = {
'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
最新方式的全栈数据爬取:CrawlSpider的一个爬虫类
分布式
增量式