爬虫 Scrapy 学习系列之四:Spiders

前言

这是 Scrapy 系列学习文章之一,本章主要介绍 Spiders 相关的内容;

本文为作者的原创作品,转载需注明出处;

Spiders

Spiders 是一系列用来定义如何爬取特定网站的 classes,包括如何爬取,如何从它们的页面中提取结构化的数据;换句话说,Spiders 就是开发者用来对特定的网址自定义爬取动作以及如何解析的地方;

从总体而言,爬取的步骤如下,

  1. 通过生成 initial Requests 去爬取最初的 URLS 开始,然后通过指定 callback 方法处理 requests 的 response 的返回;
    最开始通过start_requests()方法获取最开始的 requests,start_requests()内部通过start_urls返回,start_urls包含了请求的 URLS;parse()方法作为 Request 请求的回调方法;
  2. 在该回调方法(一般指的就是parse())中,通过解析 response (既网页的内容)然后返回 dicts,Item 对象,Request 对象或者是一个 iterable 的对象;返回的 Requests 同样可以包含 callback 回调函数,这些 Requests 同样将会通过 Scrapy 去下载网页内容,并通过该 callback 回调函数进行解析;(这里其实讲的就是爬取的深度了)
  3. 在回调函数中,通过解析页面的内容,典型的解析方式是通过使用 Selectors (还可以使用 BeautifulSoup 或者是 lxml ) 生成解析后的 items (数据);
  4. 最后,从爬虫中返回的数据(items)将会被持续化到数据库中(通常是在 Item Pipeline 中执行)或者是通过 Feed exports 写入文件中;

虽然上述的步骤或多或少的应用到了所有类型的 spiders 中,但是仍然有为了不同目的不同类型的默认爬虫;

scrapy.Spider

1
class scrapy.spiders.Spider

这是最简单的爬虫,而且是其它所有的爬虫必须要继承的;它没有提供任何特殊的方法,它只提供了一个默认的start_requests()实现,用来从start_urls解析得到请求 requests 并发送请求 requests,然后通过调用 spider 的parse方法来解析每一次请求所响应的 response;下面,我们就依次来看一看对应的属性,

name

用来定义 Spider 的名字,用来定位一个 spider,所以它必须是全局唯一的;但是,没有任何限制机制放置你这么做,你可以定义两个同名的 spiders,所以在编程的时候一定要小心了;

allowed_domains

一个可选的 domains 队列限制了 spiders 的爬取行为,约定只能爬取该 domains 队列中的域名;

start_urls

指定了一组 URLs 告诉 spider 从什么地方开始去进行爬取;

custom_settings

当在执行某个 spider 的时候通过它可以覆盖该 spider 所涉及的 project 范围内的 settings;它必须被定义为类属性,因为它们必须在初始化之前定义;更多的 built-in 配置参考 Built-in settings reference

crawler

该属性是通过from_crawler()类方法在 class 初始化以后进行设置的,然后为当前的 Spider 实例链接到该Crawler对象;

Crawlers 在项目中封装了大量的模块(包括,extensions, middlewares, signals managers 等等),查看 Crawler API 去更了解它的功能;

settings

当前 spider 执行相关的配置;这是一个 Settings 实例,查看 settings 相关内容以获取更多的信息;

logger

通过 Spider 的名字来创建 Python logger;通过 Logging from Spiders 以查看更多的相关内容;

from_crawler(crawler, *args, **kwargs)

该类方法是一个类方法,是 Scrapy 用来创建 spiders 所用的;

你通常不需要覆盖此方法,因为它是通过构造函数 __init__() 进行初始化的;尽管如此,该方法为新的实例设置了 crawler 和 settings 属性,所以它们可以在随后的 spider 代码中进行访问;

参数解释

  • crawler (Crawler instance) – crawler to which the spider will be bound
  • args (list) – arguments passed to the __init__() method
  • kwargs (dict) – keyword arguments passed to the __init__() method

start_requests()

该方法为 spider 返回首批需要进行爬取的 Requests;它在 Spider 初始化以后并且在开始爬取数据的时候才会被调用;Scrapy 只调用它一次,所以将start_requests实现为一个 generator 是非常安全的;

该方法默认将会为 start_urls 中所包含的每一个 url 生成Request(url, dont_filter=True)对象;

那么该方法能够起到什么作用呢?下面通过一个例子来进行描述,如果你需要在爬取之前通过 POST 请求进行登录操作,你可以执行如下的代码,

1
2
3
4
5
6
7
8
9
10
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
return [scrapy.FormRequest("http://www.example.com/login",
formdata={'user': 'john', 'pass': 'secret'},
callback=self.logged_in)]
def logged_in(self, response):
# here you would extract links to follow and return Requests for
# each of them, with another callback
pass

parse(response)

当没有为它们的 requests 指定任何特殊的回调方法的时候,这是一个默认的回调方法用来处理所有的 downloaded responses;

同其他 Request callback 一样,该方法也必须返回一个 Request、dicts 或者 Item 对象的 iterable;

log(message[, level, component])

输出日志信息,更多有关 Scrapy 日志方面的内容,请查看 Logging from Spiders

closed(reason)

当 spider 关闭的时候调用,该方法为 signals.connect() 发送 spider_closed 消息;

一些用例

1
2
3
4
5
6
7
8
9
10
11
12
13
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.logger.info('A response from %s just arrived!', response.url)

注意,该用例没有为单独的,特定的 Request 设置 callback 方法,所以,任何 Request 的响应都会回调该默认的parse()方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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):
for h3 in response.xpath('//h3').extract():
yield {"title": h3}

for url in response.xpath('//a/@href').extract():
yield scrapy.Request(url, callback=self.parse)

对上面的例子稍微做了一些改动,在parse()方法中通过解析得到不同的元素,生成新的 Request 对象;

除了直接使用 start_urls,你可以通过使用 start_request() 来初始化相关的 Requests,同时,为了生成结构化的数据,你可以使用 Items

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']

def start_requests(self):
yield scrapy.Request('http://www.example.com/1.html', self.parse)
yield scrapy.Request('http://www.example.com/2.html', self.parse)
yield scrapy.Request('http://www.example.com/3.html', self.parse)

def parse(self, 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)

start_requests()的一个非常重要的方法,就是可以扩展出自定义的回调方法 parse;

Spider 参数

Spider 可以通过接收参数的方式来修改他们的行为;可以限制 spider 爬取的范围也可以配置 spider 的功能;

如何传参和获取参数

Spider 的参数是通过crawl命令通过-a选项传入的,比如,

1
scrapy crawl myspider -a category=electronics

Spiders 可以通过 __init__ 方法来访问它们的参数,比如下面的 MySpider 实例通过 __init__ 方法可以获取从命令行输入的参数 category

1
2
3
4
5
6
7
8
9
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]
# ...

还可以将参数写在schedule.json文件中,参考 Scrapyd document

其它用例

可以为使用了 HttpAuthMiddleware 和 UserAgentMiddleware 这些中间件的 Spiders 传递参数;

1
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot

通用的 Spiders

Scrapy 提供了一些有用的且通用的 spiders 可以作为父类来定义你自己的 Spiders;它的目的是为一些通用的爬取用例提供一些便利的方法,就像一个网站有自己通用的爬取规则,从 Sitemaps 中爬取,或者是从 XML/CSV feed 中进行爬取;

为了进行如下的用例的演示,我们首先假定你已经由有一个在myproject.items中申明的TestItem模块;如下,

1
2
3
4
5
6
import scrapy

class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()

CrawlSpider

1
class scrapy.spiders.CrawlSpider

这是被用得最普遍的用来爬取常用的 websites 的 Spider;除了从 [Spider class] 中所继承的属性意外,该类提供了一些新的属性:

新的属性

rules

Rule 对象的一个列表,每一个 Rule 定义了一些特定的爬取行为;如果多个 Rule 匹配了同一个 link,第一个将会被使用,这个第一个指的就是 rules 中排在一个的元素 Rule;

parse_start_url(response)

这时 CrawlSpider 暴露出来可以被重载的方法;通过它来解析初始化的 responses,而且必须返回 Item 对象或者一个 Request 对象,或者是一个 iterable 对象其中的元素包含 Item 或者是 Request;

Crawling rules

1
class scrapy.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
  • link_extractor
    是一个 Link Extractor 对象专门用来定义这些链接将会如何从被爬取页面中提取;
  • callback
    回调函数;回调每一个通过特定 link_extractor 所提取的 link;该回调方法接收一个 response 作为其第一个参数,然后必须返回一个包含 Item 或者是 Request 的 list;
    注意,当使用 Crawl spider rules 的时候,避免使用 parse 作为回调方法,因为CrawlSpider通过封装了parse方法来实现它的内部逻辑;所以,一旦你自己重载了parse方法,crawl spider 将不会再工作了;
  • cb_kwargs
    这是一个 dict 包含了传递给回调方法的关键字参数;
  • follow

    is a boolean which specifies if links should be followed from each response extracted with this rule. If callback is None follow defaults to True, otherwise it defaults to False.

  • process_links
    一个回调方法,将在 links 从 response 中提出出来以后被调用;主要是起到过滤的作用;

XMLFeedSpider

TODO

CSVFeedSpider

TODO

SitemapSpider

TODO

References

https://doc.scrapy.org/en/latest/topics/spiders.html