Scrapy笔记06- Item Pipeline

当一个item被蜘蛛爬取到以后会被发送给Item Pipeline,然后多个组件依照次序处置这个item。每一个Item Pipeline组件其实就是一个实现了一个简略办法的Python类。他们接收一个item并在上面履行逻辑,还能决议这个item究竟是不是还要持续往下传输,如果不要了就直接抛弃。

应用Item Pipeline的常常使用处景:

  • 清算HTML数据
  • 验证被抓取的数据(检讨item是不是包括某些字段)
  • 反复性检讨(然后抛弃)
  • 将抓取的数据存储到数据库

编写自己的Pipeline

定义一个Python类,然后实现办法process_item(self, item, spider)便可,返回一个字典或Item,或抛出DropItem异常抛弃这个Item。

或还可以实现下面几个办法:

  • open_spider(self, spider) 蜘蛛打开的时履行
  • close_spider(self, spider) 蜘蛛关闭时履行
  • from_crawler(cls, crawler) 可拜访核心组件比如配置和信号,并注册钩子函数到Scrapy中

Item Pipeline示例

价钱验证

我们通过一个价钱验证例子来看看怎样应用

1
2
3
4
5
6
7
8
9
10
11
12
13
from scrapy.exceptions import DropItem

class PricePipeline(object):

vat_factor = 1.15

def process_item(self, item, spider):
if item["price"]:
if item["price_excludes_vat"]:
item["price"] = item["price"] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)

将item写入json文件

下面的这个Pipeline将所有的item写入到一个单独的json文件,一行一个item

1
2
3
4
5
6
7
8
9
10
11
import json

class JsonWriterPipeline(object):

def __init__(self):
self.file = open("items.jl", "wb")

def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item

将item存储到MongoDB中

这个例子应用pymongo来演示怎样讲item保留到MongoDB中。MongoDB的地址和数据库名在配置中指定,这个例子重要是向你展现怎样应用from_crawler()办法,和如何清算资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pymongo

class MongoPipeline(object):

collection_name = "scrapy_items"

def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db

@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get("MONGO_URI"),
mongo_db=crawler.settings.get("MONGO_DATABASE", "items")
)

def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]

def close_spider(self, spider):
self.client.close()

def process_item(self, item, spider):
self.db[self.collection_name].insert(dict(item))
return item

反复过滤器

假定我们的item里面的id字典是唯一的,但是我们的蜘蛛返回了多个雷同id的item

1
2
3
4
5
6
7
8
9
10
11
12
13
from scrapy.exceptions import DropItem

class DuplicatesPipeline(object):

def __init__(self):
self.ids_seen = set()

def process_item(self, item, spider):
if item["id"] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item["id"])
return item

激活一个Item Pipeline组件

你必需在配置文件中将你须要激活的Pipline组件添加到ITEM_PIPELINES

1
2
3
4
ITEM_PIPELINES = {
"myproject.pipelines.PricePipeline": 300,
"myproject.pipelines.JsonWriterPipeline": 800,
}

后面的数字表现它的履行次序,从低到高履行,规模0-1000

Feed exports

这里顺便提下Feed exports,一般有的爬虫直接将爬取成果序列化到文件中,并保留到某个存储介质中。只须要在settings里面设置几个便可:

1
2
3
* FEED_FORMAT= json # json|jsonlines|csv|xml|pickle|marshal
* FEED_URI= file:///tmp/export.csv|ftp://user:[email protected]/path/to/export.csv|s3://aws_key:[email protected]/path/to/export.csv|stdout:
* FEED_EXPORT_FIELDS = ["foo", "bar", "baz"] # 这个在导出csv的时候有用

要求和响应

Scrapy应用RequestResponse对象来爬取网站。Request对象被蜘蛛生成,然后被传递给下载器,以后下载器处置这个Request后返回Response对象,然后返回给生成Request的这个蜘蛛。

给回调函数传递额外的参数

Request对象生成的时候会通过症结字参数callback指定回调函数,Response对象被当作第一个参数传入,有时候我们想传递额外的参数,比如我们构建某个Item的时候,须要两步,第一步是连接属性,第二步是详情属性,可以指定Request.meta

1
2
3
4
5
6
7
8
9
10
11
12
def parse_page1(self, response):
item = MyItem()
item["main_url"] = response.url
request = scrapy.Request("http://www.example.com/some_page.html",
callback=self.parse_page2)
request.meta["item"] = item
return request

def parse_page2(self, response):
item = response.meta["item"]
item["other_url"] = response.url
return item

Request子类

Scrapy为各种不同的场景内置了很多Request子类,你还可以继承它自定义自己的要求类。

FormRequest这个专门为form表单设计,摹拟表单提交的示例

1
2
3
return [FormRequest(url="http://www.example.com/post/action",
formdata={"name": "John Doe", "age": "27"},
callback=self.after_post)]

我们再来一个例子摹拟用户登录,应用了FormRequest.from_response()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scrapy

class LoginSpider(scrapy.Spider):
name = "example.com"
start_urls = ["http://www.example.com/users/login.php"]

def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={"username": "john", "password": "secret"},
callback=self.after_login
)

def after_login(self, response):
# check login succeed before going on
if "authentication failed" in response.body:
self.logger.error("Login failed")
return

# continue scraping with authenticated session...

Response子类

一个scrapy.http.Response对象代表了一个HTTP相应,通常是被下载器下载后得到,并交给Spider做进一步的处置。Response也有很多默许的子类,用于表现各种不同的响应类型。

  • TextResponse 在基本Response类基本之上增长了编码功效,专门用于二进制数据比如图片、声音或其他媒体文件
  • HtmlResponse 此类是TextResponse的子类,通过查询HTML的meta http-equiv属性实现了编码主动发明
  • XmlResponse 此类是TextResponse的子类,通过查询XML声明实现编码主动发明