前面几个小节已经讲解的爬虫都是抓取一个或几个页面,然后分析页面中的内容,这种爬虫可以称为专用爬虫,通常是用来抓取特定页面中感兴趣的内容,例如,某个城市的天气预报信息,或特定商品的信息等。除了专用爬虫外,还有一类爬虫应用非常广泛,这就是通用爬虫。这种爬虫需要抓取的页面数据量通常非常大。例如,像 Google、百度这样的搜索引擎就是使用这种通用爬虫抓取了整个互联网的数据,然后经过复杂的处理,最终将处理过的数据保存到分布式数据库中,通过搜索引擎查到的最终结果其实是经过整理后的数据,而数据的最初来源是利用通用爬虫抓取的整个互联网的数据。但对于大多数人来说,是没必要抓取整个互联网的数据的,即使抓取了,这么大量的数据也没有那么多硬盘来存放。不过为了研究通用爬虫,可以选择抓取某个网站的满足一定规则的数据,本文就会利用通用爬虫抓取网站中的新闻数据。但在讲解如何实现抓取新闻的通用爬虫前,先要介绍两个重要的工具:CrawlSpider 和 Item Loader。
CrawlSpider 是 Scrapy 提供的一个通用爬虫。CrawlSpider 是一个类,编写的爬虫类可以直接从 CrawlSpider 派生。CrawlSpider 类可以通过指定一些规则让爬虫抓取页面中特定的内容,这些规则需要通过专门的 Rule 指定,爬虫会根据 Rule 来确定当前页面中哪些 URL 需要提取,以及是否抓取这些 URL 对应的 Web 页面。
CrawlSpider 类从 Spider 类继承,除了拥有 Spider 类的所有方法和属性外,还提供了一个非常重要的属性和方法:
(1) rules 属性:用于指定抓取规则,该属性是一个列表,可以包含了一个或多个 Rule 对象。每一个 Rule 对抓取页面的动作都做了定义。CrawlSpider 会读取 rules 中的每一个 Rule 并进行解析。
(2) parse_start_url 方法:这是一个可重写的方法。当 start_urls 里对应的 Request 得到 Response 时,该方法被调用,通常在 parse_start_url 方法中会分析 Response。该方法必须返回 Item 对象 或 Request 对象。
在讲解 rules 属性时涉及一个 Rule,这是一个类,该类的构造方法的定义如下:
class Rule:
def __init__(self, link_extractor=None, callback=None, cb_kwargs=None, follow=None,
process_links=None, process_request=None, errback=None):
self.link_extractor = link_extractor or _default_link_extractor
self.callback = callback
self.errback = errback
self.cb_kwargs = cb_kwargs or {
}
self.process_links = process_links or _identity
self.process_request = process_request or _identity_process_request
self.process_request_argcount = None
self.follow = follow if follow is not None else not callback
.......
(1) link_extractor:是 LinkExtractor 对象。通过该对象,爬虫可以知道需要抓取页面中的哪些 URL,以及在哪个区域提取这些 URL。提取的 URL 会自动生成 Requests 对象。它同时也是一个数据结构,通常用 LxmlLinkExtractor 对象作为参数,LxmlLinkExtractor 类构造方法的定义如下:
class LxmlLinkExtractor(FilteringLinkExtractor):
def __init__(
self, 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, restrict_text=None,
):
.....
LxmlLinkExtractor 类通过构造方法传入一些参数,常用参数的详细说明如下:
除了这些参数外,还有一些其他参数,但这些参数的使用频率不高,对这些参数感兴趣的读者可以参考官方文档的说明。
https://doc.scrapy.org/en/latest/topics/link-extractors.html
(2) callback:回调函数,每次按照 link_extractor 属性指定的规则获取 URL,向服务端发送 Request,获取 Response 后,就会调用 callback 属性指定的回调函数,该函数接收一个 Response 对象,表示 Request 返回的响应结果。callback 函数必须返回一个包含 Item 对象或 Request 对象的列表。注意,不要使用 parse 作为回调函数。由于 CrawlSpider 使用 parse 方法来实现其逻辑,如果 parse 方法被覆盖了,那么 CrawlSpider 将会运行失败。
(3) cb_kwargs:字典类型的参数,包含传递给回调函数的参数值。
(4) follow:布尔类型的参数,其中只能是 True 或 False,该参数指定根据规则从 Response 提取出来的 URL 是否需要跟进。如果 callback 参数值为 None,follow 的默认值是 True,否则默认值是 False。
(5) process_links:指定处理函数,根据规则提取 URL 时该函数会被调用,通常在该函数中过滤提取的 URL。
(6) process_request:同样是处理函数,在根据规则提取 URL 后会自动创建 Request,然后会调用该处理函数,通常用于对 Request 进行处理。该函数必须返回 Request 或 None。
Item Loader 提供了一种便捷的机制来帮助用户方便地创建 Item。它提供了一系列 API 可以分析原始数据,并对 Item 的相应属性进行赋值。Item 对象时用于保存抓取数据的容器,而 ItemLoader 提供的是填充容器的机制。有了 ItemLoader,提取数据会变得更容易。下面是 ItemLoader 类构造方法的定义。
def __init__(self, item=None, selector=None,
response=None, parent=None, **context):
.....
ItemLoader 类的构造方法包含了若干个参数,下面是对常用参数的说明。
下面是一个比较典型的使用 ItemLoader 填充 Item 对象的案例:
def parse_item(self, response):
loader = NewsLoader(item=BlogsspiderItem(), response=response)
loader.add_xpath("name", "//div[@id='name']/text()")
loader.add_css('price', 'p#price')
loader.add_value('country', 'China')
return loader.load_item()
在这个案例中,首先创建了 BlogsspiderItem 类的实例,该类的父类是 Item。BlogsspiderItem 类中包含了name、price、country 属性,分别通过 add_xpath、add_css 和 add_value 方法使用Xpath、CSS 选择器和值的方式为这 3 个属性赋值,最后通过 load_item 方法返回填充后的 Item 对象。
另外,ItemLoader 的每个字段中都包含了一个输入处理器(Input Processor)和一个输出处理器(Output Processor)。输入处理器收到数据时会立刻提取数据,处理结果会被收集起来,并保存在 ItemLoader 中,但不分配给 Item。收集到所有的数据后,load_item 方法被调用来用这些数据填充 Item 对象。在调用时会先调用输出处理器来处理之 前收集到的数据,然后再保存到 Item 中,这时的 Item 就是最终生成的 Item 对象。下面介绍一些内置的处理器(Processor)。
TakeFirst:返回列表的第一个非空值,也就是返回第一个不为 None 或空串的列表元素值,通常会作为 Output Processor。实现代码如下:
Join:用于将列表中每个元素首尾相接合成一个字符串,每个列表元素之间默认的分隔符是空格,其实 Join 处理器内部调用了 Python 内建函数 join 来连接列表中的每一个元素。Join 类的源代码如下:
class Join:
"""
Returns the values joined with the separator given in the ``__init__`` method, which
defaults to ``' '``. It doesn't accept Loader contexts.
When using the default separator, this processor is equivalent to the
function: ``' '.join``
Examples:
>>> from itemloaders.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
'one two three'
>>> proc = Join('
')
>>> proc(['one', 'two', 'three'])
'one
two
three'
"""
def __init__(self, separator=' '):
self.separator = separator
def __call__(self, values):
return self.separator.join(values)
Compose:允许将多个处理器或函数组合在一起使用,Compose 类构造方法的参数允许传入多个处理器或函数,当前一个处理器或函数处理完,会将结果传递给下一个处理器或函数,以此类推,直到执行完最后一个处理器或函数为止。功能有点像 Linux 的管道命令,通过竖线(|)分隔多个命令,前一个命令会将执行结果传递给下一个要执行的命令。
>>> from itemloaders.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'
MapCompose:与 Compose 类似,MapCompose 可以迭代处理一个列表输入值,代码如下:
>>> def filter_world(x):
... return None if x == 'world' else x
...
>>> from itemloaders.processors import MapCompose
>>> proc = MapCompose(filter_world, str.upper)
>>> proc(['hello', 'world', 'this', 'is', 'something'])
['HELLO', 'THIS', 'IS', 'SOMETHING']
SelectJmes:可以通过 key 获得 JSON 对象的 value,不过需要先安装 jmespath 库才可以使用 SelectJmes。pip install jmespath。示例代码如下:
>>> from itemloaders.processors import SelectJmes, Compose, MapCompose
>>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
>>> proc({
'foo': 'bar'})
'bar'
>>> proc({
'foo': {
'bar': 'baz'}})
{
'bar': 'baz'}
Working with Json:
>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
['bar']
1、创建一个工程
scrapy startproject CollegeBeautySpider
2、cd 工程
cd CollegeBeautySpider
3、创建一个基于 CrawlSpider 的爬虫文件
scrapy genspider -t crawl SpiderName www.xxx.com
scrapy genspider -t crawl BeautySpider www.xxx.com
校花网 的网页结构比较简单,这里就不进行具体的分析,我们只需要抓取校花的名字以及校花照片的链接地址。
4、在工程根目录创建一个 main.py
文件,用于启动爬虫文件。示例代码如下:
import os
import sys
from scrapy.cmdline import execute
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute('scrapy crawl BeautySpider -o beauty.csv'.split())
5、在 items.py 中编写 CollegebeautyspiderItem 类,爬虫需要将抓取的新闻数据放到 Item 对象中,所以要先编写一个 CollegebeautyspiderItem 类,该类从 Item 继承,代码如下:
import scrapy
class CollegebeautyspiderItem(scrapy.Item):
beauty_name = scrapy.Field() # 美女的名字
beauty_src = scrapy.Field() # 美女图片的src
6、紧接着在 spiders 文件夹中的爬虫文件 BeautySpider.py
中编写抓取逻辑,如下:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.loader import ItemLoader
from CollegeBeautySpider.items import CollegebeautyspiderItem
class BeautyspiderSpider(CrawlSpider):
name = 'BeautySpider'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.521609.com/daxuemeinv/']
rules = (
Rule(LinkExtractor(allow=r'list\d+\.html'), callback='parse_item', follow=True),
)
# 提取每一个美女的相关数据:名字及图片的src
def parse_item(self, response):
item_loader = ItemLoader(item=CollegebeautyspiderItem(), response=response)
item_loader.add_xpath("beauty_name", '//div[@class="index_img list_center"]/ul/li/a/img/@alt')
item_loader.add_xpath("beauty_src", '//div[@class="index_img list_center"]/ul/li/a/img/@src')
return item_loader.load_item()