本笔记介绍几种spider,分别是basic(默认Spider)、CrawlSpider、XMLFeedSpder、CSVFeedSpider四种,以及另外一种SitemapSpider
Spiders这个类定义如何爬取网页,包括如何执行爬虫,比如说追踪链接(follow links),和如何提取网页结构数据(比如爬取items),换句话说,Spiders就是定义爬虫行为和解析特定网页(一堆网页)的类。对于爬虫来说,爬取步骤基本如下:
start_requests()
方法通过特定的start_urls
属性发出请求并且利用parse()
方法回调;这种过程适用于所有爬虫,之后会介绍四种默认的爬虫basic、crawl、csvfeed、xmlfeed和一种基于sitemap的爬虫。
这是最简单的爬虫basic,所有的爬虫类都必须继承(inherit)这个类。它不提供任何特殊的方法,它只提供默认的start_requests()从start_urls这个属性发出请求并且调用parse方法获得响应(response)
name
定义爬虫名称的字符串。name决定爬虫如何被scrapy部署(实例化),所以它必须是独一无二的,但是没有什么可以阻止你实例化多个相同的爬虫实例。name是最重要的爬虫属性
如果一个爬虫爬取单独的domain(域名),这个爬虫的name最好和域名保持一致,比如说要爬取“baidu.com”这个域名,它的name应该被命名为baidu。
allowed_domains
一个可选的字符串列表包含一些允许爬虫爬取的域名。不属于这些域名的url链接不会被爬虫爬取,除非OffsiteMiddleware被使用。
比如说你打算爬取https://www.example.com/1.html
,那么你就可以加上example.com
到allowed_domain中,你的爬虫就只会爬取域名为example.com的链接。
start_urls
当没有特定的url链接被指定时,一个爬虫爬取的初始URL列表就是start_urls。随后的url陆续地从这个列表中生成。
custom_settings
自定义的设置属性,字典形式,执行该爬虫时它将会覆盖settings.py中的设置。在实例化之前它将被定义为类属性。具体定义等讲settings之后就明白了。
crawler
这个属性从实例化该类后的from_crawler()类方法中设置,这个属性将会连接到Crawler对象然后绑定这个爬虫。
Crawler在项目中封装了许多组件,用于它们的单个条目访问(例如extensions、middlewares、signals managers等等)。具体等讲到Crawler API。
settings
用于设置爬虫
logger
使用爬虫的name创建的Python日志记录器。你可以使用它来发送日志消息。
from_crawler(crawler, *args, **kwargs)
用来创建爬虫的类方法。
你可能不需要直接重写这个方法,因为默认的实现__init__()
方法的代理,用给定的参数args和命名参数kwargs来调用它。尽管如此,这个方法在新实例中设置了crawler和settings属性,以便稍后在爬虫代码中访问它们。
参数:
__init__()
方法的参数__init__()
方法的关键字参数start_requests()
这个方法必须返回一个可迭代的Requests。当爬虫执行时被Scrapy调用。Scrapy只调用一次,所以将这个方法作为生成器实现是很安全的。
默认的实现对start_urls中的每个url链接生成Request(url, dont_filter=True)
如果你想要更改用于开始抓取某个域名的请求,重写该方法即可。
例子:
import scrapy
class MaterialSpider(scrapy.Spider):
name = 'material' # 一个项目中独一无二的爬虫名字
def start_requests(self):
urls = [
'http://588ku.com/pt/chengshi.html',
'http://588ku.com/pt/lvxing.html'
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page = response.url.split('/')[-1][:-5]
filename = 'material-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)
parse(response)
这是默认的回调方法,用来处理响应(response)。
parse方法用来管理处理响应并且返回爬取到的数据或者更多的URL链接来follow,其中,数据必须以字典的形式返回,或者返回Item对象,返回的Request必须是可迭代的,可以在for循环中使用yield来返回。其他的Requests回调方法有同样的要求。
参数:
log(message[, component])
通过爬虫的logger发送日志消息的包装器(Wrapper),保持向后兼容性。
closed(reason)
当关闭爬虫时调用。这个方法提供一个捷径来作为spider_closed signal调用signals.connect()方法。
例子1:
import scrapy
class BilibiliSpider(scrapy.Spider):
name = 'bilibili.com'
allowed_domains = ['bilibili.com']
start_urls = [
'https://www.bilibili.com/video/music.html',
'https://www.bilibili.com/video/douga.html',
'https://www.bilibili.com/video/game.html'
]
def parse(self, response):
self.logger.info('网页 %s 的内容提取成功!', response.url)
例子2,从一个callback返回多个Request和item:
import scrapy
class BilibiliSpider(scrapy.Spider):
name = 'bilibili.com'
allowed_domains = ['bilibili.com']
start_urls = [
'https://www.bilibili.com/video/music.html',
'https://www.bilibili.com/video/douga.html',
'https://www.bilibili.com/video/game.html'
]
def parse(self, response):
for title in response.xpath('//p[@class="title"]/text()').extract():
yield {
'title': title
}
for url in response.xpath('//*[@id="primary_menu"]/ul/li/a/@href').extract():
url = 'https://' + url
yield scrapy.Request(url, callback=self.parse)
爬虫可以接收参数来规范它们的行为。对于爬虫参数的一些常见用途是定义初始的url链接或将爬虫限制到站点的某些部分,但是它们可以用于配置爬虫的任何功能。
爬虫参数通过crawl -a
命令来传达,用法:
scrapy crawl
爬虫可以在他们的__init__()
中访问参数,例如:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def __init__(self, category=None, * args, ** kwargs):
super(MySpider, self).__init__( * args, ** kwargs)
self.start_urls = ['http://www.example.com/categories/%s' % category]
# ...
就可以在执行crawl命令的时候利用-a给category传参数:
scrapy crawl myspider -a category=electronics
默认的__init__()
方法将获得任何的参数并且复制他们给爬虫作为参数,比如你可以这样写:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
yield scrapy.Request('http://www.example.com/categories/%s' % self.category)
不过你要记住,传给爬虫的参数都是字符串,爬虫不会给你解析参数的类型,所以如果你传给它的是start_url,你将不得不亲自解析它,把它变成列表,利用ast.literal_eval或者json.loads然后将它变成参数。否则,你将对一个字符串进行迭代导致一个字符被视为一个url链接,肯定爬不出来什么东西,所以要注意。
一个重要的情况是设置HttpAuthMiddleware使用的http身份验证凭证(http auth credentials)或 UserAgentMiddleware使用的user agent:
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot
爬虫参数也可以通过爬取的 schedule.json API 传递。
Scrapy提供了一些通用的爬虫你可以通过继承他们来使用。主要目的就是为了方便,为那些常见、通用的情况提供便利的方法,比如基于一些特定的rules爬取、从Sitemaps爬取 或 解析XML/CSV来爬取网站的所有链接并且follow他们。
接下来的例子要先在items.py中定义如下:
import scrapy
class HelloprojectItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
class scrapy.spiders.CrawlSpider
这是最常见的用来爬普通网页的爬虫,因为它通过定义一组规则为接下来的链接提供了一种方便的机制。它可能不是最适合爬取你指定网页或项目的爬虫,但在几种情况下,它是通用的,所以你可以从它开始,根据你的需要重写它的方法来更加自定义化。
除了从Spider继承的属性以外,这个类支持一个新的属性,rules:
这个类有一个可以重写的方法:
类:class scrapy.spiders.Rule(link_extractor, callback=None, follow=None, process_link=None, process_request=None)
Warning:当你要写rules的时候,请避免使用parse作为回调函数,因为CrawlSpider用parse方法本身来实现这种逻辑。
cb_kwargs
cb_kwargs是一个字典,它包含了要传给callback函数的关键字参数(keyword arguments)
follow
follow是一个布尔型变量,指定出用此rule顺着爬出来的链接是否要继续爬下去如果callback是None,则follow默认是True,否则它默认是False
process_links
process_links是可调用的,或者是字符串(在这种情况下,使用该名称的、爬虫对象的方法将被使用),它将使用指定的link_extractor从每个响应中提取出的每个链接列表。这主要用于过滤(filtering)目的。
process_request
process_request是一个可调用的或一个字符串(在这种情况下,使用该名称的、爬虫对象的方法将被使用),它将调用通过rule所提取的每个请求,并且必须返回一个request或None(以过滤request)。
让我们看一个有rules的CrawlSpider的例子:
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class MyspiderSpider(CrawlSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://example.com/']
rules = (
# Extract links matching 'category.php' (but not matching 'subsection.php')
# and follow links from the, (since no callback means follow=True by default).
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# Extract links matching 'item.php' and parse them with the spider's method parse_item
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
)
def parse_item(self, response):
self.logger.info('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
这个爬虫将开始爬取example.com的主页,收集category
的链接和item链接,通过parse_item
解析。对于每一个item response,一些数据将会从HTML中用XPath提取,并且Item将被它填充。
Link extractors对象专门用来解析网页链接,用于CrawlSpider爬虫,当然你也可以单独使用在你的爬虫里。
其中的extract_links方法接收一个Response对象并且返回由scrapy.link.Link对象组成的列表。Link Extractors实例化一次要调用几次extract_links()根据不同的Response来解析不同的链接。
scrapy默认的link extractor是LinkExtractor,它和LxmlLinkextractors一样。
class scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(),
restrict_xpaths=(), tags=('a', 'area'), attrs=('href',), canonicalize=False,
unique=True, process_value=None, deny_extensions=None, restrict_css=(), strip=True)
LxmlLinkExtractor是推荐使用的链接提取器,它是用lxml的 HTMLParser实现的。
allow (单独的正则表达式或者列表的正则表达式)
它用来匹配链接,没有匹配到的链接就扔掉。如果为空,或者没有这个参数,它将匹配所有的链接
deny (一个或一列表正则表达式)
如果匹配到链接,它将排除(exclude)该链接,否则不排除,deny参数优先于allow参数。
allow_domains (字符串或列表)
包含的域名将被解析来提取链接,不包含的不会提取。
deny_domains (字符串或列表)
所包含的域名不会被解析。
deny_extensions(列表)
单个的值或者一列表的字符串,在解析链接时会把包含这些字符串的链接无视掉。
restrict_xpaths(字符串或列表)
用XPath定义的区域,链接将在这个区域被解析出来。
restrict_css(字符串或列联表)
用CSS 选择器定义的区域,链接将在这个区域被解析出来
tags(字符串或列表)
一个或一列表的标签(tag)在解析链接时需要考虑。默认的是tag=(‘a’, ‘area’)
attrs(列表)
一个或由属性组成的列表(只会作用于指定的tag中的属性)在解析链接的时候会被考虑。默认的是attrs=(‘href’,)
canonicalize(boolean变量)
使用w3lib.url.canonicalize_url来规范每个提取的链接,默认False。它是用来重复检查的,它可以更改服务器端可见的URL,因此对于具有规范化和原始URL的请求,其响应可能不同。如果你使用LinkExtractor来提取链接,那最好将它设置为默认的False。
unique(boolean变量)
是否应该对爬取的链接进行重复过滤,正如它的意思一样——独一无二!
process_value(callable可调用的函数)
接收每个从标签和属性中提取出的值并且修改这个值然后返回一个新的值,或者返回None来完全无视这个链接。如果没有设置,它默认是lambda x: x(意思就是返回本身,可以百度lambda)。举个例子:
比如这个链接:
"javascript:goToPage('../other/page.html'); return false">Link text
你可以定义如下方法:
def process_value(value):
m = re.search("javascript:goToPage\('(.*?)'", value)
if m:
return m.group(1)
来匹配单引号中的网址。
、
标签的href属性,
标签的src属性,
元素等等。所以LinkExtractor默认是去除空行的。不过你可以将它设置为False(比如你爬取一些允许有空格的网站,这种情况可以设置为False)class scrrapy.spiders.XMLFeedSpider
XMLFeedSpider被设计用于通过迭代某个确定的节点来解析XML源(XML feed)。迭代器可以从iternodes、xml、html 中选择。从性能角度一般推荐iternodes。因为xml和html迭代器一次生成整个DOM(文档对象模型Document Object Model)来解析。然而,使用html作为迭代器解析XML时对付错的markup会很有用。
为了设置迭代器和标签名,你必须定义下列类属性:
itertag = 'product'
(prefix, uri)元组
组成的列表,定义了在该文档(document)中将被spider处理的、可用的namespaces。prefix
和uri
会自动地被register_namespace()
方法调用。(PS:可以手动百度uri与url的区别)例子:class YourSpider(XMLFeedSpider):
namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
itertag = 'n:url'
# ...
除了这些新的属性,这个爬虫也有下列可重写的方法:
Item对象
、Request对象
或者一个包含两者之一的迭代器。这些爬虫很好用,让我们看一个例子:
# -*- coding: utf-8 -*-
from scrapy.spiders import XMLFeedSpider
from HelloProject.items import HelloprojectItem
class XmlspiderSpider(XMLFeedSpider):
name = 'xmlspider'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.xml']
iterator = 'iternodes' # you can change this; see the docs
itertag = 'item' # change it accordingly
def parse_node(self, response, node):
self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))
item = HelloprojectItem()
item['id'] = node.xpath('@id').extract()
item['name'] = node.xpath('name').extract()
item['description'] = node.xpath('description').extract()
return item
基本上,我们在那里做的就是创建一个爬虫并从给定的链接下载一个源(feed),然后从每个item标签开始迭代,将他们打印,并在Item中储存一些随机的数据。
在介绍该爬虫之前,先了解一下CSV文件比较好Wiki百科-CSV(左下角中文翻译,建议看英文)
class scrapy.spiders.CSVFeedSpider
这个爬虫和XMLFeedSpider很像,但它是按行(row)遍历,而XMLFeedSpider是按结点遍历。每次遍历时调用的方法是parse_row()
。
,
(逗号,comma)。(PS:delimiter的意思是定界符)"
(quotation mark,引号)(PS:quotechar的意思是引用字符;原文中enclosure character,不好翻译。不过了解了CSV文件之后应该好理解)adapt_response()
和process_results()
方法来预处理(pre-process)和后处理(post-processing)。让我们看一个和之前很像的例子,却用的是CSVFeedSpider:
# -*- coding: utf-8 -*-
from scrapy.spiders import CSVFeedSpider
from HelloProject.items import HelloprojectItem
class CsvspiderSpider(CSVFeedSpider):
name = 'csvspider'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.csv']
headers = ['id', 'name', 'description']
delimiter = ','
quotechar = "'"
def parse_row(self, response, row):
item = HelloprojectItem()
item['id'] = row['id']
item['name'] = row['name']
item['description'] = row['description']
return item
class scrapy.spiders.SitemapSpider
SitemapSpider允许你通过Sitemap发现URL链接来爬取一个网站。(解释见Wiki)它支持嵌套的(nested)sitemaps和从robots.txt发现sitemap。你可以找个网站看一看sitemap.xml究竟是什么,比如极客学院的sitemap.xml。下面介绍该爬虫:
http://www.jikexueyuan.com/sitemap.xml
,当然可能有其他格式比如说txt之类。你也可以指向robots.txt,然后它将从里面解析网页(很强势)。sitemap_rules = [('/product/', 'parse_product')]
<url>
<loc>http://example.com/loc>
<xhtml:link rel="alternate" hreflang="de" href="http://example.com/de/">
url>
当sitemap_alternate_link
设置后,两个链接都会被跟进;如果没有设置,只有http://example.com/
会跟进。默认不设置。
最简单的例子:使用parse处理所有通过sitemap找到的链接:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
def parse(self, response):
pass # 在这里定义如何解析
使用确定的回调函数处理一些链接,其他链接用不同的回调函数处理:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
sitemap_rules = [
('/product/', 'parse_product'),
('/category/', 'parse_category'),
]
def parse_product(self, response):
pass # 定义如何爬取带有/product/的链接
def parse_category(self, response):
pass # 定义如何爬取带有/category/的链接
跟进在robots.txt中定义的sitemaps,并且只跟进包含/sitemap_shop
的链接:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
sitemap_follow = ['/sitemap_shops']
def parse_shop(self, response):
pass # 定义如何爬取shop
将SitemapSpider与其他url源结合:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
def start_requests(self):
requests = list(super(MySpider, self).start_requests())
requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
return requests
def parse_shop(self, response):
pass # 定义如何爬取shop
def parse_other(self, response):
pass # 定义如何爬取其他网页
这里都是理论基础,例子并不是很多,也不实用,所以我打算在之后多写一些关于这些类型的爬虫的例子,否则光介绍不举例子很难让人接受,写了这么多知识点,不如举一堆例子来的容易。当然举例子靠的也就是这些基础。本笔记参考的是scrapy官方英文文档,我之后也会不断地翻这个笔记,因为这些知识点可能有错误,并且也不是很细,我会及时更改。