Scrapy(五):Response与Request、数据提取、Selector、Pipeline
学习自Requests and Responses — Scrapy 2.5.0 documentation
Request在Spider中生成,被Downloader执行,之后会得到网页的Response
1、Request
1)构造
scrapy.http.Request(*args,**kw)
2)构造时传入参数
参数 | 说明 | 补充 |
url | ||
callback | 对该URL的返回页面进行处理的回调函数;当该项未指定时,则默认用parse()方法 | |
method | HTTP请求方法,默认'GET' | GET、POST、PUT |
meta | 一些元数据 | |
body | 请求体 | |
headers | 请求头 | |
cookies | ||
encoding | 请求的编码方式 | |
priority | 请求优先度 | |
dont_filter | 标识该请求不被Scheduler过滤;当我们要经常用到某个request时用到 | |
errback | 异常处理方法;包括网页404错误 | |
flags | ||
cb_kwargs |
Dict;标识传入参数 return response.follow(url,self.parse_additional_page,cb_kwargs=dict(item=item)
|
补充说明:
1)Request回调函数的作用时机是该request对象对应的response被下载之后,该回调函数将用该Response作为其第一个参数。举例如下:
def parse_page1(self, response): return scrapy.Request("http://www.example.com/some_page.html", callback=self.parse_page2) def parse_page2(self, response): # this would log http://www.example.com/some_page.html self.logger.info("Visited %s", response.url)
2)一些情况下我们想向回调函数传递参数,可以通过Request.cb_kwargs属性实现。举例如下:
def parse(self,response): request = scrapy.Request( url, callback=self.parse_page, cb_kwargs=dict(main_url=response.url) ) # 为回调函数传入额外参数 request.cb_kwars['foo']='bar' yield request #传入参数可直接写在回调函数的参数中, #而不用通过response.xxx访问 def parse_pages(self,response,main_url,foo): yield dict( main_url=main_url, ther_url=response.url, foo=foo )
3)使用errback处理异常情况
errback是构造Request时定义的一个异常处理函数,当有异常发生时,会调用该方法。它接收Failure作为第一个参数,可以用于追踪超时、DNS错误等。
2、Response
1)属性、方法
属性 | 类型 | 说明 |
url | ||
status | int | 响应状态码;默认200 |
headers | Dict | 响应头部 |
body | bytes | 响应正文 |
text |
str |
文本形式的响应正文 response.text = response.body.decode(response.encoding) |
encoding | str | 响应正文的编码 |
request | Request | 产生该响应的Request对象 |
meta | 与该Response对应的Request的meta属性;在构造Request对象时,可以将传递给响应处理函函数的信息通过meta参数传入;响应处理函数处理相应时,通过response.meta将信息提取出来 | |
cb_kwargs | 传入参数;与Request中的cb_kwargs相对应,通过Response.cb_kwargs提取出来 | |
selector | Selector对象用于在Response中提取数据 |
方法 | 说明 |
xpath(query) | 根据XPath路径表达式提取要素 |
css(query) | 根据CSS语法提取要素 |
urljoin(url) | 用于构造绝对url,当传入的url是一个相对地址时,根据response.url计算出相应的绝对url |
follow | 返回一个Request实例,接收与Request.__init__方法相同的参数。 |
follow_all | 返回一个Request Iterable,接收与__init__方法相同的参数。 |
部分方法的详细说明:
follow:
follow( url, callback, mehod='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback, cb_kwargs, flags )
返回url对应的Request实例。参数写法与Request对象构建时相同,只是url可以是相对URL,而非绝对URL。
follow_all:
follow_all( urls, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback, cb_kwargs, flags )
返回Iterable Request,这些Requests与urls相关联。其他方面与follow方法相同。
3、用一个Spider程序来说明Request与Response
import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" def start_requests(self): urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] for url in urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self,response) page=response.url.split("/")[-2] filename=f'quotes-{page}.html' with open(filename,'wb') as f: f.write(response.body) self.log(f'Saved file {filename}')
①属性与方法
name:Spider的名字,每个Spider的名字不能相同;
start_requests:必须返回Request()的迭代器(可以是一个Requests List或yield Request())。这些Requests是针对爬虫起始URL进行的;后续的Requests将从这些初始URL中生成。
parse:对之前所有的Requests的Response进行处理,参数中的response保存了页内容并且有一系列方法来处理它。parse方法中一般将爬取到的数据保存为Dict并会自行寻找下一个新的URL(当然这也需要用yield Request(url)进行说明)。
上文的Parse中提取到的网页内容,保存在response.body中,如果要保存为二进制文件,直接保存该项就行了,如果要保存为文本文件,用response.text。
②补充
如果不想写start_requests方法,只需要设置start_urls属性,就可以不用写之前的方法,而对start_urls中的初始URL调用默认的start_requests来对这些URL进行处理。
2、数据提取
最好的提取数据方法是在scrapy shell中进行。
①以http://quotes.toscrape.com/page/1/为例,运行以下指令打开Shell
scrapy shell https://scrapy.org
②之后就可以用response的各项属性和方法进行数据提取了
response.xpath('//title/text()')
提取结果形式为:[ <Selector xpath='xpath表达式' data='提取到的信息' ]
[<Selector xpath='//title/text()' data='Scrapy | A Fast and Powerful Scraping...'>]
如果要提取其中的数据部分,可以用方法extract或getall(提取全部)、get或extract_first(提取首项),当有多个提取项时,extract、getall方法的提取结果是结果List,需要用切片[]的方式提取;也可以用join方法把所有数据连接成为一个str。
③使用正则表达式进行数据提取:re方法
用法:response.xpath('xpath表达式').re(r'正则表达式')
说明:用正则表达式对提取到的要素进行数据再提取,规则与之前正则表达式那一节所说相同,不过re方法,应该是相当于re模块下的search方法,区别在于不需要用group方法,这里re直接返回的结果就是匹配结果,如果正则表达式中有括号,则匹配结果List中保存有原文中符合匹配规则的项
In [8]: response.xpath('//title/text()').re(r'(S\w+)') Out[8]: ['Scrapy', 'Scraping']
④用yield关键字实现自动爬取
将需要保存的内容,写为Dict形式,用yield标记,即可不停地提取并保存数据:
def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').get(), 'author': quote.css('small.author::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), }
⑤存储爬取数据
scrapy crawl 爬虫名 -o 文件 #追加写 scrapy crawl 爬虫名 -0 文件 #覆盖写 大写字母而非数字
如果要爬取的内容较多,或者更为复杂的存储类型,可以通过在pipelines.py写相应的数据存储语句。
⑥自动翻页功能:response.urlopen(next_page)
该方法只用于静态有规律的URL翻页功能。
next_page = response.urljoin(next_page)
这句话的作用是,将next_page连接到原URL上,构成新的URLnext_page;这样,利用for循环、if语句、yield语句的综合,可以实现不断爬取下一页内容的功能:
#以下代码均写在parse方法中的数据提取语句之后 for i in range(4): #假设提取前3页 next_page=response.urljoin('pw='+str(i))#得到next_page的URL yield scrapy.Request(next_page,callback=self.parse)
⑦使用参数
方法:在运行爬虫的指定中加入-a后缀
scrapy crawl quotes -0 quotes-humor.json -a tag=humor
这些参数将传入爬虫的__init__方法中,并成为爬虫的默认参数,访问时通过self.tag在__init__中使用。
4、Selector
Selector通过XPath和CSS表达式筛选网页要素
Selector对象是response对象调用XPath与CSS方法后得到的,比如:
scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html
In [1]: response.xpath('//title/text()') Out[1]: [<Selector xpath='//title/text()' data='Example website'>]
如果从Selector中将data提取出来,方法有很多,这里不再多说,见本文第二部分
5、Pipeline
①简介
Item被爬取之后,将被送入Pipeline进行进一步的处理。
每一个Pipeline都是一个class,这个class继承了方法process_item,该方法接收item作为参数,在该方法中对该Item进行一系列处理。
典型的处理方法有:对HTML数据的清洗、检查爬取数据的正确性、检查重复项、存储数据到数据库中。
②类方法
process_item:每个pipeline都必须实现的方法,在其中完成对item的处理
该方法必须返回1)一个Item对象;2)抛出一个DropItem异常;当抛出该异常后,对应的Item将不再会被处理。
open_spider:当Spider开始运行时调用该方法
close_spider:当Spider结束运行时调用该方法
③例子
1)保存提取到了Price的Item,修改其Price,放弃那些不含Price的Item:
from itemadapter import ItemAdapter from scrapy.exceptions import DropItem class PricePipeline: def process_item(self,item,spider): adapter = ItemAdapter(item) if adapter.get('price'): adapter['price'] = adapter['price'] *2 return item else: raise DropItem(f'Miss price in {item}')
2)把数据保存到JSON文件中
import json from itemadapter import ItemAdapter class JsonWriterPipeline: def open_spider(self,spider): self.file=open('items.jl','w') def close_spider(self,spider): self.file.close() def process_item(self,item,spider): line=json.dumps(dict(item),ensure_ascii=Flase)+'\n' self.file.write(line) return item
④激活Pipeline
需要在Setting中设置ITEM_PIPELINES项,才能正确激活Pipeline
ITEM_PIPELINES = { 'myproject.pipelines.PricePipeline': 300, 'myproject.pipelines.JsonWriterPipeline': 800, }
⑤补充
在没有较多规则限制的情况下,可以在输出时直接输出为json文件,通过-o或-O实现;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性