scrapy学习
创建项目
scrapy startproject tutorial
该命令将会创建包含下列内容的 tutorial 目录:
scrapy.cfg: 项目的配置文件
tutorial/: 该项目的python模块。之后您将在此加入代码。
tutorial/items.py: 项目中的item文件.
tutorial/pipelines.py: 项目中的pipelines文件.
tutorial/settings.py: 项目的设置文件.
tutorial/spiders/: 放置spider代码的目录.
Item 是保存爬取到的数据的容器
编辑 tutorial 目录中的 items.py 文件:
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。
必须继承 scrapy.Spider 类, 且定义以下三个属性
name:
start_urls:
parse()
以下为我们的第一个Spider代码,保存在 tutorial/spiders 目录下的 dmoz_spider.py 文件中:
import scrapy
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
filename = response.url.split("/")[-2]
with open(filename, 'wb') as f:
f.write(response.body)
启动爬虫:
scrapy crawl dmoz
parse:
Scrapy使用了一种基于 XPath 和 CSS 表达式机制: Scrapy Selectors
Selector有四个基本的方法(点击相应的方法可以看到详细的API文档):
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
extract(): 序列化该节点为unicode字符串并返回list。
re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
Scrapy Shell需要您预装好IPython(一个扩展的Python终端)。
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
*当您在终端运行Scrapy时,请一定记得给url地址加上引号,否则包含参数的url(例如 & 字符)会导致Scrapy运行失败。
当shell载入后,您将得到一个包含response数据的本地 response 变量。输入 response.body 将输出response的包体, 输出 response.headers 可以看到response的包头。
response.xpath('//title').extract()
在我们的spider中加入这段代码:
import scrapy
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
for sel in response.xpath('//ul/li'):
title = sel.xpath('a/text()').extract()
link = sel.xpath('a/@href').extract()
desc = sel.xpath('text()').extract()
print title, link, desc
现在尝试再次爬取dmoz.org,您将看到爬取到的网站信息被成功输出:
使用Item:
Item 对象是自定义的python字典。
一般来说,Spider将会将爬取到的数据以 Item 对象返回。所以为了将爬取的数据返回
我们最终的代码将是:
import scrapy
from tutorial.items import DmozItem
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
for sel in response.xpath('//ul/li'):
item = DmozItem()
item['title'] = sel.xpath('a/text()').extract()
item['link'] = sel.xpath('a/@href').extract()
item['desc'] = sel.xpath('text()').extract()
yield item
保存爬取到的数据
最简单存储爬取的数据的方式是使用 Feed exports:
scrapy crawl dmoz -o items.json
如果需要对爬取到的item做更多更为复杂的操作,您可以编写 Item Pipeline 。 类似于我们在创建项目时对Item做的,用于您编写自己的 tutorial/pipelines.py 也被创建。 不过如果您仅仅想要保存item,您不需要实现任何的pipeline。
):命令行工具(Command line tools)
scrapy.cfg 存放的目录被认为是 项目的根目录 。该文件中包含python模块名的字段定义了项目的设置。
使用 scrapy 工具
步骤:
scrapy startproject myproject
cd myproject
scrapy genspider mydomain mydomain.com
scrapy <command> -h
scrapy -h
全局命令:
startproject
settings
runspider
shell
fetch
view
version
项目(Project-only)命令:
crawl
check
list
edit
parse
genspider
deploy
bench
startproject
在 project_name 文件夹下创建一个名为 project_name 的Scrapy项目。
genspider
在当前项目中创建spider。
scrapy genspider -l
scrapy genspider -d basic
scrapy genspider -t basic example example.com
crawl
使用spider进行爬取。
scrapy crawl myspider
check
运行contract检查。
scrapy check -l
list
列出当前项目中所有可用的spider。每行输出一个spider。
scrapy list
edit
使用 EDITOR 中设定的编辑器编辑给定的spider
scrapy edit spider1
fetch
使用Scrapy下载器(downloader)下载给定的URL,并将获取到的内容送到标准输出。
该命令以spider下载页面的方式获取页面。
scrapy fetch --nolog http://www.example.com/some/page.html
scrapy fetch --nolog --headers http://www.example.com/
view
在浏览器中打开给定的URL,并以Scrapy spider获取到的形式展现。
scrapy view http://www.example.com/some/page.html
shell
以给定的URL(如果给出)或者空(没有给出URL)启动Scrapy shell。
scrapy shell http://www.example.com/some/page.html
parse
获取给定的URL并使用相应的spider分析处理。如果您提供 --callback 选项,则使用spider的该方法处理,否则使用 parse 。
scrapy parse http://www.example.com/ -c parse_item
settings
获取Scrapy的设定
在项目中运行时,该命令将会输出项目的设定值,否则输出Scrapy默认设定。
$ scrapy settings --get BOT_NAME
runspider
在未创建项目的情况下,运行一个编写在Python文件中的spider。
scrapy runspider myspider.py
version
输出Scrapy版本。配合 -v 运行时,该命令同时输出Python, Twisted以及平台的信息,方便bug提交。
deploy
将项目部署到Scrapyd服务。查看 部署您的项目 。
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
创建item
>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)
获取字段的值
>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC
>>> product['price']
1000
>>> product['last_updated']
Traceback (most recent call last):
...
KeyError: 'last_updated'
>>> product.get('last_updated', 'not set')
not set
>>> product['lala'] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala'
>>> product.get('lala', 'unknown field')
'unknown field'
>>> 'name' in product # is name field populated?
True
>>> 'last_updated' in product # is last_updated populated?
False
>>> 'last_updated' in product.fields # is last_updated a declared field?
True
>>> 'lala' in product.fields # is lala a declared field?
False
设置字段的值
>>> product['last_updated'] = 'today'
>>> product['last_updated']
today
>>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'
>>> product.keys()
['price', 'name']
>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]
复制item:
>>> product2 = Product(product)
>>> print product2
Product(name='Desktop PC', price=1000)
>>> product3 = product2.copy()
>>> print product3
Product(name='Desktop PC', price=1000)
根据item创建字典(dict):
>>> dict(product) # create a dict from all populated values
{'price': 1000, 'name': 'Desktop PC'}
根据字典(dict)创建item:
>>> Product({'name': 'Laptop PC', 'price': 1500})
Product(price=1500, name='Laptop PC')
>>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'
您可以通过继承原始的Item来扩展item(添加更多的字段或者修改某些字段的元数据)。
class DiscountedProduct(Product):
discount_percent = scrapy.Field(serializer=str)
discount_expiration_date = scrapy.Field()
class SpecificProduct(Product):
name = scrapy.Field(Product.fields['name'], serializer=my_serializer)
class scrapy.item.Item([arg])
返回一个根据给定的参数可选初始化的item。
class scrapy.item.Field([arg])
Field 仅仅是内置的 dict 类的一个别名,并没有提供额外的方法或者属性。
Spider
在运行 crawl 时添加 -a 可以传递Spider参数:
scrapy crawl myspider -a category=electronics
内置Spider
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
self.log('A response from %s just arrived!' % response.url)
另一个在单个回调函数中返回多个Request以及Item的例子:
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
sel = scrapy.Selector(response)
for h3 in response.xpath('//h3').extract():
yield MyItem(title=h3)
for url in response.xpath('//a/@href').extract():
yield scrapy.Request(url, callback=self.parse)
CrawlSpider样例
接下来给出配合rule使用CrawlSpider的例子:
import scrapy
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com']
rules = (
# 提取匹配 'category.php' (但不匹配 'subsection.php') 的链接并跟进链接(没有callback意味着follow默认为True)
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# 提取匹配 'item.php' 的链接并使用spider的parse_item方法进行分析
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
)
def parse_item(self, response):
self.log('Hi, this is an item page! %s' % response.url)
item = scrapy.Item()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
return item
XMLFeedSpider例子
该spider十分易用。下边是其中一个例子:
from scrapy import log
from scrapy.contrib.spiders import XMLFeedSpider
from myproject.items import TestItem
class MySpider(XMLFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.xml']
iterator = 'iternodes' # This is actually unnecessary, since it's the default value
itertag = 'item'
def parse_node(self, response, node):
log.msg('Hi, this is a <%s> node!: %s' % (self.itertag, ''.join(node.extract())))
item = TestItem()
item['id'] = node.xpath('@id').extract()
item['name'] = node.xpath('name').extract()
item['description'] = node.xpath('description').extract()
return item
CSVFeedSpider例子
下面的例子和之前的例子很像,但使用了 CSVFeedSpider:
from scrapy import log
from scrapy.contrib.spiders import CSVFeedSpider
from myproject.items import TestItem
class MySpider(CSVFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.csv']
delimiter = ';'
headers = ['id', 'name', 'description']
def parse_row(self, response, row):
log.msg('Hi, this is a row!: %r' % row)
item = TestItem()
item['id'] = row['id']
item['name'] = row['name']
item['description'] = row['description']
return item
SitemapSpider样例
Selectors:
以文字构造:
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').extract()
[u'good']
以response构造:
>>> response = HtmlResponse(url='http://example.com', body=body)
>>> Selector(response=response).xpath('//span/text()').extract()
[u'good']
Item Loaders:
用Item Loaders装载Items
要使用Item Loader, 你必须先将它实例化. 你可以使用类似字典的对象(例如: Item or dict)来进行实例化, 或者不使用对象也可以, 当不用对象进行实例化的时候,Item会自动使用 ItemLoader.default_item_class 属性中指定的Item 类在Item Loader constructor中实例化.
然后,你开始收集数值到Item Loader时,通常使用 Selectors. 你可以在同一个item field 里面添加多个数值;Item Loader将知道如何用合适的处理函数来“添加”这些数值.
from scrapy.contrib.loader import ItemLoader
from myproject.items import Product
def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath('name', '//div[@class="product_name"]')
l.add_xpath('name', '//div[@class="product_title"]')
l.add_xpath('price', '//p[@id="price"]')
l.add_css('stock', 'p#stock]')
l.add_value('last_updated', 'today') # you can also use literal values
return l.load_item()
Item Loader在每个(Item)字段中都包含了一个输入处理器和一个输出处理器。 输入处理器收到数据时立刻提取数据 (通过 add_xpath(), add_css() 或者 add_value() 方法) 之后输入处理器的结果被收集起来并且保存在ItemLoader内.
l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath1) # (1)
l.add_xpath('name', xpath2) # (2)
l.add_css('name', css) # (3)
l.add_value('name', 'test') # (4)
return l.load_item() # (5)
从 xpath1 提取出的数据,传递给 输入处理器 的 name 字段.输入处理器的结果被收集和保存在Item Loader中(但尚未分配给该Item)。
输入和输出处理器的优先顺序如下:
Item Loader特定于字段的属性:field_in和field_out(最优先)
字段元数据(input_processor和output_processor键)
Item Loader默认值:ItemLoader.default_input_processor()和 ItemLoader.default_output_processor()(最少优先级)
There are several ways to modify Item Loader context values:
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
loader = ItemLoader(product, unit='cm')
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
ItemLoader对象
返回一个新的Item Loader来填充给定的Item。如果没有给出项目,则使用该类自动实例化一个项目 default_item_class。
Examples:
>>> from scrapy.contrib.loader.processor import TakeFirst
>>> loader.get_value(u'name: foo', TakeFirst(), unicode.upper, re='name: (.+)')
'FOO`
Examples:
loader.add_value('name', u'Color TV')
loader.add_value('colours', [u'white', u'blue'])
loader.add_value('length', u'100')
loader.add_value('name', u'name: foo', TakeFirst(), re='name: (.+)')
loader.add_value(None, {'name': u'foo', 'sex': u'male'})
# HTML snippet: <p class="product-name">Color TV</p>
loader.get_xpath('//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_xpath('//p[@id="price"]', TakeFirst(), re='the price is (.*)')
# HTML snippet: <p class="product-name">Color TV</p>
loader.add_xpath('name', '//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
# HTML snippet: <p class="product-name">Color TV</p>
loader.get_css('p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_css('p#price', TakeFirst(), re='the price is (.*)')
# HTML snippet: <p class="product-name">Color TV</p>
loader.add_css('name', 'p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_css('price', 'p#price', re='the price is (.*)')
Available built-in processors
>>> from scrapy.contrib.loader.processor import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']
>>> from scrapy.contrib.loader.processor import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
>>> from scrapy.contrib.loader.processor import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
u'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
u'one<br>two<br>three'
>>> from scrapy.contrib.loader.processor import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'
>>> >>> defdef filter_worldfilter_world((xx):):
... ... returnreturn NoneNone ifif xx ==== 'world''world' elseelse xx
......
>>> >>> fromfrom scrapy.contrib.loader.processorscrapy.contrib.loader.processor importimport MapComposeMapCompose
>>> >>> procproc == MapComposeMapCompose((filter_worldfilter_world,, unicodeunicode..upperupper))
>>> >>> procproc([([uu'hello''hello',, uu'world''world',, uu'this''this',, uu'is''is',, uu'scrapy''scrapy'])])
[u'HELLO, u'THIS', u'IS', u'SCRAPY'][u'HELLO, u'THIS', u'IS', u'SCRAPY']
Scrapy终端(Scrapy shell)
该终端是用来测试XPath或CSS表达式,查看他们的工作方式及从爬取的网页中提取的数据。
强烈推荐您安装 IPython
您可以使用 shell 来启动Scrapy终端:
scrapy shell <url>
可用的快捷命令(shortcut)
shelp() - 打印可用对象及快捷命令的帮助列表
fetch(request_or_url) - 根据给定的请求(request)或URL获取一个新的response,并更新相关的对象
view(response) - 在本机的浏览器打开给定的response。
首先,我们启动终端:
scrapy shell 'http://scrapy.org' --nolog
有时您想在spider的某个位置中查看被处理的response, 以确认您期望的response到达特定位置。
这可以通过 scrapy.shell.inspect_response 函数来实现。
以下是如何在spider中调用该函数的例子:
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = [
"http://example.com",
"http://example.org",
"http://example.net",
]
def parse(self, response):
# We want to inspect one specific response.
if ".org" in response.url:
from scrapy.shell import inspect_response
inspect_response(response, self)
# Rest of parsing code.
当运行spider时,您将得到类似下列的输出:
2014-01-23 17:48:31-0400 [myspider] DEBUG: Crawled (200) <GET http://example.com> (referer: None)
2014-01-23 17:48:31-0400 [myspider] DEBUG: Crawled (200) <GET http://example.org> (referer: None)
[s] Available Scrapy objects:
[s] crawler <scrapy.crawler.Crawler object at 0x1e16b50>
...
>>> response.url
'http://example.org'
您可以在浏览器里查看response的结果,判断是否是您期望的结果:
>>> view(response)
True
最后您可以点击Ctrl-D(Windows下Ctrl-Z)来退出终端,恢复爬取:
由于该终端屏蔽了Scrapy引擎,您在这个终端中不能使用 fetch 快捷命令(shortcut)
Item Pipeline
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
以下是item pipeline的一些典型应用:
清理HTML数据
验证爬取的数据(检查item包含某些字段)
查重(并丢弃)
将爬取结果保存到数据库中
验证价格,同时丢弃没有价格的item
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将所有(从所有spider中)爬取到的item,存储到一个独立地 items.jl 文件,每行包含一个序列化为JSON格式的item:
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
JsonWriterPipeline的目的只是为了介绍怎样编写item pipeline,如果你想要将所有爬取的item都保存到同一个JSON文件, 你需要使用 Feed exports 。
去重
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组件
为了启用一个Item Pipeline组件,你必须将它的类添加到 ITEM_PIPELINES 配置
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。
Feed exports
实现爬虫时最经常提到的需求就是能合适的保存爬取到的数据,或者说,生成一个带有爬取数据的”输出文件”(通常叫做”输出feed”),来供其他系统使用。
Scrapy自带了Feed输出,并且支持多种序列化格式(serialization format)及存储方式(storage backends)。
feed输出使用到了 Item exporters 。其自带支持的类型有:
JSON
JSON lines
CSV
XML
您也可以通过 FEED_EXPORTERS 设置扩展支持的属性。
存储(Storages)
使用feed输出时您可以通过使用 URI (通过 FEED_URI 设置) 来定义存储端。 feed输出支持URI方式支持的多种存储后端类型。
自带支持的存储后端有:
本地文件系统
FTP
S3 (需要 boto)
标准输出
存储URI参数
存储URI也包含参数。当feed被创建时这些参数可以被覆盖:
%(time)s - 当feed被创建时被timestamp覆盖
%(name)s - 被spider的名字覆盖
reg:
存储在FTP,每个spider一个目录:
ftp://user:password@ftp.example.com/scraping/feeds/%(name)s/%(time)s.json
存储在S3,每一个spider一个目录:
s3://mybucket/scraping/feeds/%(name)s/%(time)s.json
存储端(Storage backends)
将feed存储在本地系统。
URI scheme: file
URI样例: file:///tmp/export.csv
需要的外部依赖库: none
设定(Settings)
这些是配置feed输出的设定:
FEED_URI (必须)
FEED_FORMAT
FEED_STORAGES
FEED_EXPORTERS
FEED_STORE_EMPTY
Link Extractors
Link Extractors 是那些目的仅仅是从网页(scrapy.http.Response 对象)中抽取最终将会被follow链接的对象。
每个LinkExtractor有唯一的公共方法是 extract_links ,它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。Link Extractors,要实例化一次并且 extract_links 方法会根据不同的response调用多次提取链接。
LxmlLinkExtractor
是推荐的链接提取器,具有方便的过滤选项。它是使用lxml强大的HTMLParser实现的。
SgmlLinkExtractor
其提供了过滤器(filter),以便于提取包括符合正则表达式的链接。
BaseSgmlLinkExtractor
这个Link Extractor的目的只是充当了Sgml Link Extractor的基类。
Logging
Scrapy提供了log功能。您可以通过 scrapy.log 模块使用。
log服务必须通过显示调用 scrapy.log.start() 来开启。
Scrapy提供5层logging级别:
CRITICAL - 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)
如何设置log级别
您可以通过终端选项(command line option) –loglevel/-L 或 LOG_LEVEL 来设置log级别。
如何记录信息(log messages)
下面给出如何使用 WARNING 级别来记录信息的例子:
from scrapy import log
log.msg("This is a warning", level=log.WARNING)
scrapy.log.start(logfile=None, loglevel=None, logstdout=None)
启动log功能。
scrapy.log.msg(message, level=INFO, spider=None)
记录信息(Log a message)
。。。
Logging设置
以下设置可以被用来配置logging:
LOG_ENABLED
LOG_ENCODING
LOG_FILE
LOG_LEVEL
LOG_STDOUT
数据收集(Stats Collection)
Scrapy提供了方便的收集数据的机制。数据以key/value方式存储,值大多是计数值。 该机制叫做数据收集器(Stats Collector),可以通过 Crawler API 的属性 stats 来使用。
无论数据收集(stats collection)开启或者关闭,数据收集器永远都是可用的。
class ExtensionThatAccessStats(object):
def __init__(self, stats):
self.stats = stats
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.stats)
设置数据:
stats.set_value('hostname', socket.gethostname())
增加数据值:
stats.inc_value('pages_crawled')
当新的值比原来的值大时设置数据:
stats.max_value('max_items_scraped', value)
当新的值比原来的值小时设置数据:
stats.min_value('min_free_memory_percent', value)
获取数据:
>>> stats.get_value('pages_crawled')
8
获取所有数据:
>>> stats.get_stats()
{'pages_crawled': 1238, 'start_time': datetime.datetime(2009, 7, 14, 21, 47, 28, 977139)}
可用的数据收集器
除了基本的 StatsCollector ,Scrapy也提供了基于 StatsCollector 的数据收集器。 您可以通过 STATS_CLASS 设置来选择。默认使用的是 MemoryStatsCollector 。
发送email
smtplib 库
有两种方法可以创建邮件发送器(mail sender)。 您可以通过标准构造器(constructor)创建:
from scrapy.mail import MailSender
mailer = MailSender()
或者您可以传递一个Scrapy设置对象,其会参考 settings:
mailer = MailSender.from_settings(settings)
这是如何来发送邮件了(不包括附件):
mailer.send(to=["someone@example.com"], subject="Some subject", body="Some body", cc=["another@example.com"])
在Scrapy中发送email推荐使用MailSender。
Mail设置
这些设置定义了 MailSender 构造器的默认值。其使得在您不编写任何一行代码的情况下,为您的项目配置实现email通知的功能。
MAIL_FROM
默认值: 'scrapy@localhost'
用于发送email的地址(address)(填入 From:) 。
MAIL_HOST
默认值: 'localhost'
发送email的SMTP主机(host)。
MAIL_PORT
默认值: 25
发用邮件的SMTP端口。
MAIL_USER
默认值: None
SMTP用户。如果未给定,则将不会进行SMTP认证(authentication)。
MAIL_PASS
默认值: None
用于SMTP认证,与 MAIL_USER 配套的密码。
MAIL_TLS
默认值: False
强制使用STARTTLS。STARTTLS能使得在已经存在的不安全连接上,通过使用SSL/TLS来实现安全连接。
MAIL_SSL
默认值: False
强制使用SSL加密连接。
Telnet终端(Telnet Console)
Scrapy提供了内置的telnet终端,以供检查,控制Scrapy运行的进程。
telnet终端监听设置中定义的 TELNETCONSOLE_PORT ,默认为 6023 。 访问telnet请输入:
telnet localhost 6023
>>>
crawler Scrapy Crawler (scrapy.crawler.Crawler 对象)
engine Crawler.engine属性
spider 当前激活的爬虫(spider)
slot the engine slot
extensions 扩展管理器(manager) (Crawler.extensions属性)
stats 状态收集器 (Crawler.stats属性)
settings Scrapy设置(setting)对象 (Crawler.settings属性)
est 打印引擎状态的报告
prefs 针对内存调试 (参考 调试内存溢出)
p pprint.pprint 函数的简写
hpy 针对内存调试 (参考 调试内存溢出)
Web Service
Scrapy提供用于监控及控制运行中的爬虫的web服务(service)。
Simple JSON resources - 只读,输出JSON数据
JSON-RPC resources - 通过使用 JSON-RPC 2.0 协议支持对一些Scrapy对象的直接访问
Crawler JSON-RPC资源
默认访问地址: http://localhost:6080/crawler
状态收集器(Stats Collector)JSON-RPC资源
默认访问地址: http://localhost:6080/stats
爬虫管理器(Spider Manager)JSON-RPC资源
http://localhost:6080/crawler/spiders
扩展管理器(Extension Manager)JSON-RPC资源
可用JSON资源
引擎状态JSON资源
常见问题(FAQ)
BeautifulSoup 及 lxml 是HTML和XML的分析库。Scrapy则是 编写爬虫,爬取网页并获取数据的应用框架(application framework)。
Scrapy是以广度优先还是深度优先进行爬取的呢?
默认情况下,Scrapy使用 LIFO 队列来存储等待的请求。简单的说,就是 深度优先顺序 。深度优先对大多数情况下是更方便的。如果您想以 广度优先顺序 进行爬取,你可以设置以下的设定:
DEPTH_PRIORITY = 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'
为什么Scrapy下载了英文的页面,而不是我的本国语言?
尝试通过覆盖 DEFAULT_REQUEST_HEADERS 设置来修改默认的 Accept-Language 请求头。
调试(Debugging)Spiders
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = (
'http://example.com/page1',
'http://example.com/page2',
)
def parse(self, response):
# collect `item_urls`
for item_url in item_urls:
yield scrapy.Request(item_url, self.parse_item)
def parse_item(self, response):
item = MyItem()
# populate `item` fields
# and extract item_details_url
yield scrapy.Request(item_details_url, self.parse_details, meta={'item': item})
def parse_details(self, response):
item = response.meta['item']
# populate more `item` fields
return item
简单地说,该spider分析了两个包含item的页面(start_urls)。Item有详情页面, 所以我们使用 Request 的 meta 功能来传递已经部分获取的item。
检查spier输出的最基本方法是使用 parse 命令。
查看特定url爬取到的item:
$ scrapy parse --spider=myspider -c parse_item -d 2 <item_url>
[ ... scrapy log lines crawling example.com spider ... ]
>>> STATUS DEPTH LEVEL 2 <<<
# Scraped Items ------------------------------------------------------------
[{'url': <item_url>}]
# Requests -----------------------------------------------------------------
[]
使用 --verbose 或 -v 选项,查看各个层次的状态:
$ scrapy parse --spider=myspider -c parse_item -d 2 -v <item_url>
检查从单个start_url爬取到的item也是很简单的:
$ scrapy parse --spider=myspider -d 3 'http://example.com/page1'
Scrapy终端(Shell)
检查回调函数内部的过程
from scrapy.shell import inspect_response
def parse_details(self, response):
item = response.meta.get('item', None)
if item:
# populate more `item` fields
return item
else:
inspect_response(response, self)
有时候您想查看某个response在浏览器中显示的效果,这是可以使用 open_in_browser 功能。
from scrapy.utils.response import open_in_browser
def parse_details(self, response):
if "item name" not in response.body:
open_in_browser(response)
Logging
记录(logging)是另一个获取到spider运行信息的方法。
from scrapy import log
def parse_details(self, response):
item = response.meta.get('item', None)
if item:
# populate more `item` fields
return item
else:
self.log('No item received for %s' % response.url,
level=log.WARNING)
Spiders Contracts
Scrapy通过合同(contract)的方式来提供了测试spider的集成方法。
每个contract包含在文档字符串(docstring)里,以 @ 开头。 查看下面的例子:
def parse(self, response):
""" This function parses a sample response. Some contracts are mingled
with this docstring.
@url http://www.amazon.com/s?field-keywords=selfish+gene
@returns items 1 16
@returns requests 0 0
@scrapes Title Author Year Price
"""
该constract(@url)设置了用于检查spider的其他constract状态的样例url。 该contract是必须的,所有缺失该contract的回调函数在测试时将会被忽略:
@url url
该contract(@returns)设置spider返回的items和requests的上界和下界。 上界是可选的:
@returns item(s)|request(s) [min [max]]
该contract(@scrapes)检查回调函数返回的所有item是否有特定的fields:
@scrapes field_1 field_2 ...
自定义Contracts
如果您想要比内置scrapy contract更为强大的功能,可以在您的项目里创建并设置您自己的 contract,并使用 SPIDER_CONTRACTS 设置来加载:
SPIDER_CONTRACTS = {
'myproject.contracts.ResponseCheck': 10,
'myproject.contracts.ItemValidate': 10,
}
每个contract必须继承 scrapy.contracts.Contract 并覆盖下列三个方法:
adjust_request_args(args)
pre_process(response)
post_process(output)
实践经验(Common Practices)
除了常用的 scrapy crawl 来启动Scrapy,您也可以使用 API 在脚本中启动Scrapy。
需要注意的是,Scrapy是在Twisted异步网络库上构建的, 因此其必须在Twisted reactor里运行。
同一进程运行多个spider
默认情况下,当您执行 scrapy crawl 时,Scrapy每个进程运行一个spider。 当然,Scrapy通过 内部(internal)API 也支持单进程多个spider
from twisted.internet import reactor
from scrapy.crawler import Crawler
from scrapy import log
from testspiders.spiders.followall import FollowAllSpider
from scrapy.utils.project import get_project_settings
def setup_crawler(domain):
spider = FollowAllSpider(domain=domain)
settings = get_project_settings()
crawler = Crawler(settings)
crawler.configure()
crawler.crawl(spider)
crawler.start()
for domain in ['scrapinghub.com', 'insophia.com']:
setup_crawler(domain)
log.start()
reactor.run()
分布式爬虫(Distributed crawls)
如果您有很多spider,那分布负载最简单的办法就是启动多个Scrapyd,并分配到不同机器上。
如果想要在多个机器上运行一个单独的spider,那您可以将要爬取的url进行分块,并发送给spider。
首先,准备要爬取的url列表,并分配到不同的文件url里:
http://somedomain.com/urls-to-crawl/spider1/part1.list
http://somedomain.com/urls-to-crawl/spider1/part2.list
http://somedomain.com/urls-to-crawl/spider1/part3.list
接着在3个不同的Scrapd服务器中启动spider。spider会接收一个(spider)参数 part , 该参数表示要爬取的分块:
curl http://scrapy1.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=1
curl http://scrapy2.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=2
curl http://scrapy3.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=3
避免被禁止(ban)
有些网站实现了特定的机制,以一定规则来避免被爬虫爬取。
下面是些处理这些站点的建议(tips):
使用user agent池,轮流选择之一来作为user agent。池中包含常见的浏览器的user agent(google一下一大堆)
禁止cookies(参考 COOKIES_ENABLED),有些站点会使用cookies来发现爬虫的轨迹。
设置下载延迟(2或更高)。参考 DOWNLOAD_DELAY 设置。
如果可行,使用 Google cache 来爬取数据,而不是直接访问站点。
使用IP池。例如免费的 Tor项目 或付费服务(ProxyMesh)。
使用高度分布式的下载器(downloader)来绕过禁止(ban),您就只需要专注分析处理页面。这样的例子有: Crawlera
如果您仍然无法避免被ban,考虑联系 商业支持.
动态创建Item类
对于有些应用,item的结构由用户输入或者其他变化的情况所控制。您可以动态创建class。
from scrapy.item import DictItem, Field
def create_item_class(class_name, field_list):
fields = {field_name: Field() for field_name in field_list}
return type(class_name, (DictItem,), {'fields': fields})
通用爬虫(Broad Crawls)
其能爬取大量(甚至是无限)的网站, 仅仅受限于时间或其他的限制。一般用于搜索引擎。
Scrapy默认的全局并发限制对同时爬取大量网站的情况并不适用,因此您需要增加这个值。 增加多少取决于您的爬虫能占用多少CPU。 一般开始可以设置为 100 。不过最好的方式是做一些测试,获得Scrapy进程占取CPU与并发数的关系。
为了优化性能,您应该选择一个能使CPU占用率在80%-90%的并发数。
降低log级别
在生产环境中进行通用爬取时您不应该使用 DEBUG log级别。 不过在开发的时候使用 DEBUG 应该还能接受。
设置Log级别:
LOG_LEVEL = 'INFO'
禁止cookies能减少CPU使用率及Scrapy爬虫在内存中记录的踪迹,提高性能。
禁止cookies:
COOKIES_ENABLED = False
禁止重试:
对失败的HTTP请求进行重试会减慢爬取的效率,尤其是当站点响应很慢(甚至失败)时, 访问这样的站点会造成超时并重试多次。这是不必要的,同时也占用了爬虫爬取其他站点的能力。
RETRY_ENABLED = False
减小下载超时
如果您对一个非常慢的连接进行爬取(一般对通用爬虫来说并不重要), 减小下载超时能让卡住的连接能被快速的放弃并解放处理其他站点的能力。
减小下载超时:
DOWNLOAD_TIMEOUT = 15
关闭重定向:
REDIRECT_ENABLED = False
启用 “Ajax Crawlable Pages” 爬取
有些站点(基于2013年的经验数据,之多有1%)声明其为 ajax crawlable 。 这意味着该网站提供了原本只有ajax获取到的数据的纯HTML版本。 网站通过两种方法声明:
在url中使用 #! - 这是默认的方式;
使用特殊的meta标签 - 这在”main”, “index” 页面中使用。
Scrapy自动解决(1);解决(2)您需要启用 AjaxCrawlMiddleware:
AJAXCRAWL_ENABLED = True
通用爬取经常抓取大量的 “index” 页面; AjaxCrawlMiddleware能帮助您正确地爬取。 由于有些性能问题,且对于特定爬虫没有什么意义,该中间默认关闭。
借助Firefox来爬取
对爬取有帮助的实用Firefox插件
Firebug
XPather
XPath Checker
Tamper Data
Firecookie
使用Firebug进行爬取
创建第一个爬取规则:
Rule(LinkExtractor(allow='directory.google.com/[A-Z][a-zA-Z_/]+$', ),
'parse_category',
follow=True,
),
Rule 对象指导基于 CrawlSpider 的spider如何跟进目录链接。 parse_category 是spider的方法,用于从页面中处理也提取数据。
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
class GoogleDirectorySpider(CrawlSpider):
name = 'directory.google.com'
allowed_domains = ['directory.google.com']
start_urls = ['http://directory.google.com/']
rules = (
Rule(LinkExtractor(allow='directory\.google\.com/[A-Z][a-zA-Z_/]+$'),
'parse_category', follow=True,
),
)
def parse_category(self, response):
# write the category page data extraction code here
pass
调试内存溢出
为了帮助调试内存泄露,Scrapy提供了跟踪对象引用的机制,叫做 trackref , 或者您也可以使用第三方提供的更先进内存调试库 Guppy (更多内容请查看下面)。而这都必须在 Telnet终端 中使用。
内存泄露的常见原因
(常见原因)内存泄露经常是由于Scrapy开发者在Requests中(有意或无意)传递对象的引用(例如,使用 meta 属性或request回调函数),使得该对象的生命周期与 Request的生命周期所绑定。
使用 trackref 调试内存泄露
trackref 是Scrapy提供用于调试大部分内存泄露情况的模块。
>>> prefs()
oldest值越高越是其内存泄漏问题的出处
很多spider?
如果您的项目有很多的spider,prefs() 的输出会变得很难阅读。针对于此, 该方法具有 ignore 参数,用于忽略特定的类(及其子类)。
使用Guppy调试内存泄露
如果使用 setuptools , 您可以通过下列命令安装Guppy:
easy_install guppy
telnet终端也提供了快捷方式(hpy)来访问Guppy堆对象(heap objects)。
>>> x = hpy.heap()
>>> x.bytype
如果您想要查看哪些属性引用了这些字典
>>> x.bytype[0].byvia
Leaks without leaks
有时候,您可能会注意到Scrapy进程的内存占用只在增长,从不下降。不幸的是, 有时候这并不是Scrapy或者您的项目在泄露内存。这是由于一个已知(但不有名)的Python问题。 Python在某些情况下可能不会返回已经释放的内存到操作系统。
下载项目图片
Scrapy提供了一个 item pipeline ,来下载属于某个特定项目的图片,比如,当你抓取产品时,也想把它们的图片下载到本地。
这条管道,被称作图片管道,在 ImagesPipeline 类中实现,提供了一个方便并具有额外特性的方法,来下载并本地存储图片:
Pillow 是用来生成缩略图,并将图片归一化为JPEG/RGB格式,因此为了使用图片管道,你需要安装这个库。
使用样例
为了使用图片管道,你仅需要 启动它 并用 image_urls 和 images 定义一个项目:
import scrapy
class MyItem(scrapy.Item):
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
开启你的图片管道
为了开启你的图片管道,你首先需要在项目中添加它 ITEM_PIPELINES setting:
ITEM_PIPELINES = {'scrapy.contrib.pipeline.images.ImagesPipeline': 1}
并将 IMAGES_STORE 设置为一个有效的文件夹,用来存储下载的图片。
比如:
IMAGES_STORE = '/path/to/valid/dir'
图片失效
图像管道避免下载最近已经下载的图片。使用 IMAGES_EXPIRES 设置可以调整失效期限,可以用天数来指定:
# 90天的图片失效期限
IMAGES_EXPIRES = 90
缩略图生成
图片管道可以自动创建下载图片的缩略图。
为了使用这个特性,你需要设置 IMAGES_THUMBS 字典,其关键字为缩略图名字,值为它们的大小尺寸。
比如:
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
<size_name> 是 IMAGES_THUMBS 字典关键字(small, big ,等)
<image_id> 是图像url的 SHA1 hash
滤出小图片
你可以丢掉那些过小的图片,只需在:setting:IMAGES_MIN_HEIGHT 和 IMAGES_MIN_WIDTH 设置中指定最小允许的尺寸。
比如:
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
实现定制图片管道
在工作流程中可以看到,管道会得到图片的URL并从项目中下载。为了这么做,你需要重写 get_media_requests() 方法,并对各个图片URL返回一个Request:
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
这些请求将被管道处理,当它们完成下载后,结果将以2-元素的元组列表形式传送到 item_completed() 方法
当一个单独项目中的所有图片请求完成时(要么完成下载,要么因为某种原因下载失败), ImagesPipeline.item_completed() 方法将被调用。
item_completed() 方法需要返回一个输出,其将被送到随后的项目管道阶段,因此你需要返回(或者丢弃)项目,如你在任意管道里所做的一样。
reg:如果其中没有图片,我们将丢弃项目:
from scrapy.exceptions import DropItem
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item
定制图片管道的例子
下面是一个图片管道的完整例子:
import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item
Ubuntu 软件包
把Scrapy签名的GPG密钥添加到APT的钥匙环中:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 627220E7
执行如下命令,创建 /etc/apt/sources.list.d/scrapy.list 文件:
echo 'deb http://archive.scrapy.org/ubuntu scrapy main' | sudo tee /etc/apt/sources.list.d/scrapy.list
更新包列表并安装 scrapy-0.24:
sudo apt-get update && sudo apt-get install scrapy-0.24
自动限速(AutoThrottle)扩展
该扩展能根据Scrapy服务器及您爬取的网站的负载自动限制爬取速度。
在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
限速算法
算法根据以下规则调整下载延迟及并发数:
spider永远以1并发请求数及 AUTOTHROTTLE_START_DELAY 中指定的下载延迟启动。
当接收到回复时,下载延迟会调整到该回复的延迟与之前下载延迟之间的平均值。
AutoThrottle扩展尊重标准Scrapy设置中的并发数及延迟。这意味着其永远不会设置一个比 DOWNLOAD_DELAY 更低的下载延迟或者比 CONCURRENT_REQUESTS_PER_DOMAIN 更高的并发数 (或 CONCURRENT_REQUESTS_PER_IP ,取决于您使用哪一个)。
Benchmarking(基准测试)
Scrapy提供了一个简单的性能测试工具。其创建了一个本地HTTP服务器,并以最大可能的速度进行爬取。 该测试性能工具目的是测试Scrapy在您的硬件上的效率,来获得一个基本的底线用于对比。 其使用了一个简单的spider,仅跟进链接,不做任何处理。
scrapy bench
Jobs: 暂停,恢复爬虫
Scrapy通过如下工具支持这个功能:
一个把调度请求保存在磁盘的调度器
一个把访问请求保存在磁盘的副本过滤器[duplicates filter]
一个能持续保持爬虫状态(键/值对)的扩展
要启用持久化支持,你只需要通过 JOBDIR 设置 job directory 选项。这个路径将会存储 所有的请求数据来保持一个单独任务的状态(例如:一次spider爬取(a spider run))。
要启用一个爬虫的持久化,运行以下命令:
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
然后,你就能在任何时候安全地停止爬虫(按Ctrl-C或者发送一个信号)。恢复这个爬虫也是同样的命令:
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
保持状态
有的时候,你希望持续保持一些运行长时间的蜘蛛的状态。这时您可以使用 spider.state 属性,
请求序列化
请求是由 pickle 进行序列化的,所以你需要确保你的请求是可被pickle序列化的。 这里最常见的问题是在在request回调函数中使用 lambda 方法,导致无法序列化。
DjangoItem
DjangoItem 是一个item的类,其从Django模型中获取字段(field)定义。
创造一个Django模型:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=255)
age = models.IntegerField()
定义一个基本的 DjangoItem:
from scrapy.contrib.djangoitem import DjangoItem
class PersonItem(DjangoItem):
django_model = Person
DjangoItem 的使用方法和 Item 类似:
>>> p = PersonItem()
>>> p['name'] = 'John'
>>> p['age'] = '22'
要从item中获取Django模型,调用 DjangoItem 中额外的方法 save():
>>> person = p.save()
>>> person.name
'John'
>>> person.age
'22'
>>> person.id
1
当我们调用 save() 时,模型已经保存了。我们可以在调用时带上 commit=False 来避免保存, 并获取到一个未保存的模型:
>>> person = p.save(commit=False)
>>> person.name
'John'
>>> person.age
'22'
>>> person.id
None
配置Django的设置
在Django应用之外使用Django模型(model),您需要设置 DJANGO_SETTINGS_MODULE 环境变量以及 –大多数情况下– 修改 PYTHONPATH 环境变量来导入设置模块。
架构概览
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。
数据流(Data flow)
Scrapy中的数据流由执行引擎控制,其过程如下:
引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
引擎向调度器请求下一个要爬取的URL。
调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
事件驱动网络(Event-driven networking)
Scrapy基于事件驱动网络框架 Twisted 编写。因此,Scrapy基于并发性考虑由非阻塞(即异步)的实现。
下载器中间件(Downloader Middleware)
下载器中间件是介于Scrapy的request/response处理的钩子框架。 是用于全局修改Scrapy request和response的一个轻量、底层的系统。
激活下载器中间件
要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
这里是一个例子:
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
}
DOWNLOADER_MIDDLEWARES 设置会与Scrapy定义的 DOWNLOADER_MIDDLEWARES_BASE 设置合并(但不是覆盖), 而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近下载器的。
编写您自己的下载器中间件
process_request(request, spider)
当每个request通过下载中间件时,该方法被调用。
process_response(request, response, spider)
process_request() 必须返回以下之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
process_exception(request, exception, spider)
当下载处理器(download handler)或 process_request() (下载中间件)抛出异常(包括 IgnoreRequest 异常)时, Scrapy调用 process_exception() 。
内置下载中间件参考手册
CookiesMiddleware
该中间件使得爬取需要cookie(例如使用session)的网站成为了可能。 其追踪了web server发送的cookie,并在之后的request中发送回去, 就如浏览器所做的那样。
HttpCacheMiddleware
该中间件为所有HTTP request及response提供了底层(low-level)缓存支持。 其由cache存储后端及cache策略组成。
Scrapy提供了两种HTTP缓存存储后端:
Filesystem storage backend (默认值)
DBM storage backend
Scrapy提供了两种了缓存策略:
RFC2616策略
Dummy策略(默认值)
Dummy策略(默认值)
该策略不考虑任何HTTP Cache-Control指令。每个request及其对应的response都被缓存。 当相同的request发生时,其不发送任何数据,直接返回response。
RFC2616策略
该策略提供了符合RFC2616的HTTP缓存,例如符合HTTP Cache-Control, 针对生产环境并且应用在持续性运行环境所设置。该策略能避免下载未修改的数据(来节省带宽,提高爬取速度)。
使用这个策略,设置:
HTTPCACHE_POLICY 为 scrapy.contrib.httpcache.RFC2616Policy
。。。
Spider中间件(Middleware)
Spider中间件是介入到Scrapy的spider处理机制的钩子框架,您可以添加代码来处理发送给 Spiders 的response及spider产生的item和request。
激活spider中间件
要启用spider中间件,您可以将其加入到 SPIDER_MIDDLEWARES 设置中。 该设置是一个字典,键位中间件的路径,值为中间件的顺序(order)。
样例:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
}
SPIDER_MIDDLEWARES 设置会与Scrapy定义的 SPIDER_MIDDLEWARES_BASE 设置合并(但不是覆盖), 而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近spider的。
如果您想禁止内置的(在 SPIDER_MIDDLEWARES_BASE 中设置并默认启用的)中间件, 您必须在项目的 SPIDER_MIDDLEWARES 设置中定义该中间件,并将其值赋为 None 。 例如,如果您想要关闭off-site中间件:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None,
}
编写您自己的spider中间件
process_spider_input(response, spider)
当response通过spider中间件时,该方法被调用,处理该response。
process_spider_input() 应该返回 None 或者抛出一个异常。
如果其返回 None ,Scrapy将会继续处理该response,调用所有其他的中间件直到spider处理该response。
如果其跑出一个异常(exception),Scrapy将不会调用任何其他中间件的 process_spider_input() 方法,并调用request的errback。 errback的输出将会以另一个方向被重新输入到中间件链中,使用 process_spider_output() 方法来处理,当其抛出异常时则带调用 process_spider_exception() 。
process_spider_output(response, result, spider)
当Spider处理response返回result时,该方法被调用。
process_spider_output() 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)。
process_spider_exception(response, exception, spider)
当spider或(其他spider中间件的) process_spider_input() 跑出异常时, 该方法被调用。
process_start_requests(start_requests, spider)
该方法以spider 启动的request为参数被调用,执行的过程类似于 process_spider_output() ,只不过其没有相关联的response并且必须返回request(不是item)。
其接受一个可迭代的对象(start_requests 参数)且必须返回另一个包含 Request 对象的可迭代对象。
内置spider中间件参考手册
DepthMiddleware
DepthMiddleware是一个用于追踪每个Request在被爬取的网站的深度的中间件。 其可以用来限制爬取深度的最大深度或类似的事情。
HttpErrorMiddleware
过滤出所有失败(错误)的HTTP response,因此spider不需要处理这些request。 处理这些request意味着消耗更多资源,并且使得spider逻辑更为复杂。
HTTPERROR_ALLOWED_CODES
默认: []
忽略该列表中所有非200状态码的response。
HTTPERROR_ALLOW_ALL
默认: False
忽略所有response,不管其状态值。
。。。
扩展(Extensions)
扩展设置(Extension settings)
扩展使用 Scrapy settings 管理它们的设置
通常扩展需要给它们的设置加上前缀,以避免跟已有(或将来)的扩展冲突。 比如,一个扩展处理 Google Sitemaps, 则可以使用类似 GOOGLESITEMAP_ENABLED、GOOGLESITEMAP_DEPTH 等设置。
加载和激活扩展
扩展在扩展类被实例化时加载和激活。 因此,所有扩展的实例化代码必须在类的构造函数(__init__)中执行。
要使得扩展可用,需要把它添加到Scrapy的 EXTENSIONS 配置中。 在 EXTENSIONS 中,每个扩展都使用一个字符串表示,即扩展类的全Python路径。 比如:
EXTENSIONS = {
'scrapy.contrib.corestats.CoreStats': 500,
'scrapy.webservice.WebService': 500,
'scrapy.telnet.TelnetConsole': 500,
}
扩展之间一般没有关联。 扩展加载的顺序并不重要,因为它们并不相互依赖。
[1] 这也是为什么Scrapy的配置项 EXTENSIONS_BASE (它包括了所有内置且开启的扩展)定义所有扩展的顺序都相同 (500)。
可用的(Available)、开启的(enabled)和禁用的(disabled)的扩展
禁用扩展(Disabling an extension)
EXTENSIONS = {
'scrapy.contrib.corestats.CoreStats': None,
}
实现你的扩展
Scrapy扩展(包括middlewares和pipelines)的主要入口是 from_crawler 类方法, 它接收一个 Crawler 类的实例,该实例是控制Scrapy crawler的主要对象。
通常来说,扩展关联到 signals 并执行它们触发的任务。
最后,如果 from_crawler 方法抛出 NotConfigured 异常, 扩展会被禁用。否则,扩展会被开启。
reg:该扩展会在以下事件时记录一条日志:
spider被打开
spider被关闭
爬取了特定数量的条目(items)
该扩展通过 MYEXT_ENABLED 配置项开启, items的数量通过 MYEXT_ITEMCOUNT 配置项设置。
from scrapy import signals
from scrapy.exceptions import NotConfigured
class SpiderOpenCloseLogging(object):
def __init__(self, item_count):
self.item_count = item_count
self.items_scraped = 0
@classmethod
def from_crawler(cls, crawler):
# first check if the extension should be enabled and raise
# NotConfigured otherwise
if not crawler.settings.getbool('MYEXT_ENABLED'):
raise NotConfigured
# get the number of items from settings
item_count = crawler.settings.getint('MYEXT_ITEMCOUNT', 1000)
# instantiate the extension object
ext = cls(item_count)
# connect the extension object to signals
crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
crawler.signals.connect(ext.item_scraped, signal=signals.item_scraped)
# return the extension object
return ext
def spider_opened(self, spider):
spider.log("opened spider %s" % spider.name)
def spider_closed(self, spider):
spider.log("closed spider %s" % spider.name)
def item_scraped(self, item, spider):
self.items_scraped += 1
if self.items_scraped % self.item_count == 0:
spider.log("scraped %d items" % self.items_scraped)
内置扩展介绍
通用扩展
记录统计扩展(Log Stats extension)
class scrapy.contrib.logstats.LogStats
Web service 扩展
class scrapy.webservice.WebService
内存使用扩展(Memory usage extension)
class scrapy.contrib.memusage.MemoryUsage
监控Scrapy进程内存使用量,并且:
如果使用内存量超过某个指定值,发送提醒邮件
如果超过某个指定值,关闭spider
该扩展通过 MEMUSAGE_ENABLED 配置项开启,可以使用以下选项:
MEMUSAGE_LIMIT_MB
MEMUSAGE_WARNING_MB
MEMUSAGE_NOTIFY_MAIL
MEMUSAGE_REPORT
内存调试扩展(Memory debugger extension)
class scrapy.contrib.memdebug.MemoryDebugger
该扩展用于调试内存使用量,它收集以下信息:
没有被Python垃圾回收器收集的对象
应该被销毁却仍然存活的对象。
CLOSESPIDER_ERRORCOUNT
一个整数值,指定spider可以接受的最大错误数。 如果spider生成多于该数目的错误,它将以 closespider_errorcount 的原因关闭。 如果设置为0(或者未设置),spiders不会因为发生错误过多而关闭。
StatsMailer extension
class scrapy.contrib.statsmailer.StatsMailer
这个简单的扩展可用来在一个域名爬取完毕时发送提醒邮件, 包含Scrapy收集的统计信息。 邮件会发送个通过 STATSMAILER_RCPTS 指定的所有接收人。
Debugging extensions
Stack trace dump extension
class scrapy.contrib.debug.StackTraceDump
当收到 SIGQUIT 或 SIGUSR2 信号,spider进程的信息将会被存储下来。 存储的信息包括:
engine状态(使用 scrapy.utils.engin.get_engine_status())
所有存活的引用(live references)(参考 使用 trackref 调试内存泄露)
所有线程的堆栈信息
当堆栈信息和engine状态存储后,Scrapy进程继续正常运行。
该扩展只在POSIX兼容的平台上可运行(比如不能在Windows运行), 因为 SIGQUIT 和 SIGUSR2 信号在Windows上不可用。
至少有两种方式可以向Scrapy发送 SIGQUIT 信号:
在Scrapy进程运行时通过按Ctrl-(仅Linux可行?)
运行该命令(<pid> 是Scrapy运行的进程):
kill -QUIT <pid>
调试扩展(Debugger extension)
class scrapy.contrib.debug.Debugger
当收到 SIGUSR2 信号,将会在Scrapy进程中调用 Python debugger 。 debugger退出后,Scrapy进程继续正常运行。
核心API
目标用户是开发Scrapy扩展(extensions)和中间件(middlewares)的开发人员。
Scrapy API的主要入口是 Crawler 的实例对象, 通过类方法 from_crawler 将它传递给扩展(extensions)。
requests和response
Scrapy使用Request和Response对象来抓取网站。
一个Request对象表示一个HTTP请求,它通常在Spider中生成并由Downloader执行,从而生成一个Response。
cookies (dict or list) –
the request cookies. These can be sent in two forms.
Using a dict:
request_with_cookies = Request(url="http://www.example.com",
cookies={'currency': 'USD', 'country': 'UY'})
Using a list of dicts:
request_with_cookies = Request(url="http://www.example.com",
cookies=[{'name': 'currency',
'value': 'USD',
'domain': 'example.com',
'path': '/currency'}])
FormRequest对象
FormRequest类扩展了基础Request,具有处理HTML表单的功能。它使用lxml.html表单 来预先填充表单字段,其中包含来自Response对象的表单数据。
Using FormRequest to send data via HTTP POST
If you want to simulate a HTML Form POST in your spider and send a couple of key-value fields, you can return a FormRequest object (from your spider) like this:
return [FormRequest(url="http://www.example.com/post/action",
formdata={'name': 'John Doe', 'age': '27'},
callback=self.after_post)]
使用FormRequest.from_response()方法模拟用户登录
通常网站通过 <input type="hidden"> 实现对某些表单字段(如数据或是登录界面中的认证令牌等)的预填充。 使用Scrapy抓取网页时,如果想要预填充或重写像用户名、用户密码这些表单字段, 可以使用 FormRequest.from_response() 方法实现。下面是使用这种方法的爬虫例子:
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.log("Login failed", level=scrapy.log.ERROR)
return
# continue scraping with authenticated session...
Settings
Scrapy设定(settings)提供了定制Scrapy组件的方法。您可以控制包括核心(core),插件(extension),pipeline及spider组件。
如何访问设定(How to access settings)
设定可以通过Crawler的 scrapy.crawler.Crawler.settings 属性进行访问。其由插件及中间件的 from_crawler 方法所传入:
class MyExtension(object):
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
if settings['LOG_ENABLED']:
print "log is enabled!"
信号(Signals)
Scrapy使用信号来通知事情发生。
内置信号
reg:
scrapy.signals.engine_started()
当Scrapy引擎启动爬取时发送该信号。
scrapy.signals.item_scraped(item, response, spider)
当item被爬取,并通过所有 Item Pipeline 后(没有被丢弃(dropped),发送该信号。
scrapy.signals.response_received(response, request, spider)
当引擎从downloader获取到一个新的 Response 时发送该信号。
异常(Exceptions)
内置异常
reg:
scrapy.exceptions.DropItem
该异常由item pipeline抛出,用于停止处理item。
CloseSpider
该异常由spider的回调函数(callback)抛出,来暂停/停止spider。
Item Exporters
Scrapy提供了Item Exporters 来创建不同的输出格式,如XML,CSV或JSON。
在实例化了 exporter 之后,你必须:
调用方法 start_exporting() 以标识 exporting 过程的开始。
对要导出的每个项目调用 export_item() 方法。
最后调用 finish_exporting() 表示 exporting 过程的结束
序列化
1. 在 field 类中声明一个 serializer
您可以在 field metadata 声明一个 serializer。该 serializer 必须可调用,并返回它的序列化形式。
实例:
import scrapy
def serialize_price(value):
return '$ %s' % str(value)
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field(serializer=serialize_price)
2. 覆盖(overriding) serialize_field() 方法
你可以覆盖 serialize_field() 方法来自定义如何输出你的数据。
在你的自定义代码后确保你调用父类的 serialize_field() 方法。
实例:
from scrapy.contrib.exporter import XmlItemExporter
class ProductXmlExporter(XmlItemExporter):
def serialize_field(self, field, name, value):
if field == 'price':
return '$ %s' % str(value)
return super(Product, self).serialize_field(field, name, value)
Item Exporters
这是一个对所有 Item Exporters 的(抽象)父类。它对所有(具体) Item Exporters 提供基本属性,如定义export什么fields, 是否export空fields, 或是否进行编码。
XmlItemExporter
以XML格式 exports Items 到指定的文件类.
CsvItemExporter
输出 csv 文件格式. 如果添加 fields_to_export 属性, 它会按顺序定义CSV的列名.
JsonItemExporter
输出 JSON 文件格式,
JsonLinesItemExporter
输出 JSON 文件格式, 每行写一个 JSON-encoded 项.这个类能很好的处理大量数据.