Scrapy 深入了解2 [管道类,中间件,scrapy中使用selenium, scrapy框架爬取效率提升]
Scrapy 深入了解2
利用ImagesPipeline管道类爬取图片
### 编码流程
- 从spider爬虫文件夹下的爬虫文件 解析出图片地址
- 将图片的地址封装到 item中,并提交到管道
- 管道文件中自定义一个 ImagesPipeline为父类的管道类
- 重写 3 个方法
- def get_media_requests(self,item,info) # 对 图片地址发送请求
- def file_path(self,request,response=None,info=None) # 保存图片
- def item_completed(self,result,item,info) # 提交到下一个 管道类
- 在settings配置文件中 `开启管道` 且 加上 IMAGES_STORE='./imgLib' 图片存储路径
### scrapy 如何手动 发送请求
- GET 请求
- yield scrapy.Request(url,callback)
- POST 请求
- yield scrapy.FormRequest(url,callback,formdata)
#### 爬虫文件中 ,start_urls 默认是发送 GET 请求, 如何对起始URL发送post请求呢?
- 重写父类方法 start_requests(self)
def start_resquests(self):
for url in self.start_urls:
# callback 是 回调解析函数
# formdata 是 data 参数
yield scrapy.formRequest(url,callback=self.parse,formdata={})
pipelines.py 代码示例
# 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
from itemadapter import ItemAdapter
# class ImgproPipeline:
# def process_item(self, item, spider):
# print('item',item)
# return item
####### 此处有一个大坑 ,需要安装pillow模块, 必须的
from scrapy.pipelines.images import ImagesPipeline
import scrapy
class ImgproPipeline(ImagesPipeline):
# 1. 此方法 是 对 item中的image_src 进行网络请求
def get_media_requests(self, item, info):
image_src = item['image_src']
yield scrapy.Request(url=image_src)
# 2. 保存图片
def file_path(self, request, response=None, info=None, *, item=None):
file_name = request.url.split('/')[-1]
print(file_name)
return file_name
# 3. 提交到下一个管道类
def item_completed(self, result,item, info):
return item
imgspider.py
import scrapy
from ..items import ImgproItem
class ImgspiderSpider(scrapy.Spider):
name = 'imgspider'
# allowed_domains = ['www.baidu.com']
start_urls = ['http://www.521609.com/tuku/']
# 定义一个 通用的URL
url = 'http://www.521609.com/tuku/index_%d.html'
pageNum = 1
def parse(self, response):
li_list = response.xpath('//ul[@class="pbl "]/li')
for li in li_list:
image_src = "http://www.521609.com" + li.xpath('./a/img/@src').extract_first()
item = ImgproItem()
item['image_src'] = image_src
yield item
if self.pageNum < 3:
self.pageNum += 1
url = format(self.url%self.pageNum)
print('url',url)
yield scrapy.Request(url=url, callback=self.parse)
如何提高框架爬虫的效率
增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’
禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 1 超时时间为10s
scrapy 的核心组件
# 引擎 处理所有的任务流
# 调度器 请求的过滤和压入队列
# 下载器 , 基于异步 twisted网络模型
# 爬虫 爬取URL地址, 和 parse 解析结果
# 管道 存储数据
Scrapy 中间件
### 中间有哪些?
- 下载中间件(推荐)
- 爬虫中间件
### 下载中间件的作用
- 拦截所有的请求和响应
### 为什么拦截请求
- 1. 对请求的 `头信息` , 进行修改(UA,conntion)
- 2. 在 process_request 下载中间件的这个方法 设置 请求头
request.headers['User-Agent'] = random.choices(user_agent_list)
- 3. 使用代理,对IP进行修改.同第二步
request.meta['proxy'] = random.choices(http_proxy_lst)
### 为什么拦截响应
- 1. 篡改响应数据
- 2. 篡改响应对象(推荐)
网易新闻爬取实战
# 分析
1. 对 国内,国际,军事数据爬取
2. 对 新闻标日和详情页数据爬取
3. 利用百度提取关键词 , 提取关键词
4. 存储数据到 MySQL 和 Redis中(直接存储字典需要2.10.6版本的Redis工具包)
# 可能出现的问题
1. 校验数据是否爬取到
2. 对响应对象(动态加载的数据,采用 selenium 可见即可得方式获取)
3. redis的版本
news.py
import scrapy
from selenium import webdriver
from ..items import WangyiproItem
from aip import AipNlp
""" 你的 APPID AK SK """
APP_ID = 'XXXX'
API_KEY = 'xxxx'
SECRET_KEY = 'xxxx'
class NewsSpider(scrapy.Spider):
name = 'news'
# allowed_domains = ['www.wangyi.com']
start_urls = ['https://news.163.com/']
client = AipNlp(APP_ID, API_KEY, SECRET_KEY)
plate_urls = []
# 实例化 浏览器自动化对象
options = webdriver.ChromeOptions()
options.binary_location = r"E:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
# bro = webdriver.Chrome('chromedriver.exe', options=options)
bro = webdriver.Chrome(executable_path=r'E:\安装包\谷歌下载文件\软件包\chromedriver.exe', options=options)
# 解析出每一个网易新闻的板块URL
def parse(self, response):
li_lst = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div//li')
indexs = [3, 4, 6, 7, 8]
for index in indexs:
li_plate = li_lst[index]
# 解析到每一个板块对应到的URL
plate_url = li_plate.xpath('./a/@href').extract_first()
# 存放 5个板块对应的URL
self.plate_urls.append(plate_url)
# 对 具体的板块 URL 手动发送请求
yield scrapy.Request(url=plate_url, callback=self.parse_plate_page)
def parse_plate_page(self, response):
'''
# 此方法 用来解析每一个板块对应的新闻数据
# 此处的 response 是不满足需求的,因为获取到的Response不包含动态加载的新闻数据
# 因此 需要修改response 板块响应数据. 需要利用到中间件
:param response:
:return:
'''
div_lst = response.xpath('//div[@class="ndi_main"]/div')
for div in div_lst:
news_title = div.xpath('.//div[@class="news_title"]//a/text()').extract_first()
news_detail_url = div.xpath('.//div[@class="news_title"]//a/@href').extract_first()
item = WangyiproItem()
item['title'] = news_title
if not news_title and not news_detail_url:
continue
yield scrapy.Request(url=news_detail_url, callback=self.parse_deatile_method, meta={'item': item})
def parse_deatile_method(self, response):
# 接收item
item = response.meta['item']
news_content = response.xpath('//*[@id="content"]//div[@class="post_body"]//text()').extract()
news_content = ''.join(news_content)
item['content'] = news_content
yield item
# 该方法是 在最后被执行一次
def closed(self, spider):
# 关闭浏览器
self.bro.quit()
middlewares.py
import time
import scrapy
from scrapy import signals
from itemadapter import is_item, ItemAdapter
from scrapy.http import HtmlResponse
class WangyiproDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
@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
def process_request(self, request, spider):
return None
# 拦截到所有的响应对象
def process_response(self, request, response, spider):
'''
# 此处 修改 不满足条件的5个响应对象
# 1. 根据 5个 板块请求对象 , 定位到对应的响应对象
# 2. spider 是爬虫文件的内部的数据,打点调用
:param request:
:param response:
:param spider:
:return:
'''
plate_urls = spider.plate_urls
bro = spider.bro
if request.url in plate_urls:
# 重新获得 5个板块的响应数据
# 需要 重新实例化一个响应对象,需要利用 HtmlResponse
bro.get(request.url)
time.sleep(1)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(1)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(1)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(1)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# page_text 页面 数据
page_text = bro.page_source
new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
# 方法 2 , 待测试
# new_response = scrapy.Request(url=response.url)
return new_response
else:
return response
def process_exception(self, request, exception, spider):
pass
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
pipelines.py
from itemadapter import ItemAdapter
import pymysql
from redis import Redis
def NplDealwith(client, title, content):
result = client.topic(title, content)
try:
score = result['item']['lv2_tag_list'][0]['score']
tag = result['item']['lv2_tag_list'][0]['tag']
return score, tag
except Exception as e:
print("NPL:",e)
return "Score Error","KeyWord Error"
class WangyiMySQL(object):
conn = None
cursor = None
def open_spider(self, spider):
self.conn = pymysql.Connection(
host='127.0.0.1',
port=3306,
user='root',
password='xxx',
db='xxx',
charset='utf8'
)
print(self.conn)
def process_item(self, item, spider):
score, tag = NplDealwith(spider.client, item['title'], item['content'])
item['keyword'] = tag
item['score'] = score
sql = 'INSERT INTO wangyi_news values ("%s","%s","%s","%s")' % (
item['title'], item['content'], tag, score)
self.cursor = self.conn.cursor()
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback()
# 交给下一个管道执行
return item
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
class WangyiRedis(object):
def open_spider(self, spider):
self.conn = Redis(
host='127.0.0.1',
port=6379
)
print(self.conn)
def process_item(self, item, spider):
# 把 item 以字典形式存储
self.conn.lpush('news', item)
def close_spider(self, spider):
pass