如何编写一个Spider

本章以抓取 http://quotes.toscrape.com/ 为例,讲一下如何编写一个简单的spider

首先,我们要在项目目录下用命令创建一个spider,命令scrapy genspider quotes quotes.toscrape.com,该命令会在spiders目录下创建一个名为quotes.py的文件,其内容如下:

# -*- coding: utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        pass

scrapy帮我们定义了一个类,其继承了scrapy.Spider,在类中,帮我们定义了一些参数和函数。

  1. name:用于区别spider,一个项目下不能有相同名字的spider;
  2. allowed_domain:可选的域名列表,其定义了我只能爬取的域名,如果没有这个参数,scrapy将不会限制爬取的域名;
  3. start_urls:包含了scrapy爬取的起始地址列表,后续的爬取URL将会从scrapy中获取,后面会看到如果定义了start_requests函数,将会覆盖这个行为;
  4. parse方法:这个是scrapy的默认回调函数,scrapy在下载完所爬取的页面后,会生成一个Response对象,然后回调parse函数,将Response作为parse函数的参数;该函数包含解析和处理页面的逻辑,并返回获取的数据(以item或dict形式返回)。

设置初始爬取点

除了上面所看到的 start_urls参数来设置起始点,还可以通过定义start_requests函数类设置爬取的起始点,如下:

# -*- coding: utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
#    start_urls = ['http://quotes.toscrape.com/']

    def start_requests(self):
        url = "http://quotes.toscrape.com/"
        yield scrapy.Request(url, callback = self.parse)

    def parse(self, response):
        print(response.body)

通过执行scrapy crawl quotes --nolog > quotes.html,可以看到在本地生成了一个html文件,里面包含了http://quotes.toscrape.com/中的内容。
其实,通过查看scrapy的源码,在父类scrapy.Scrapy实现了下面这一段代码:

for url in self.start_urls:
    yield Request(url, dont_filter=True)

因此,如果你想重写起始点爬取行为的话,可以实现自己的start_requests方法,否则,可以直接在start_urls要爬取的起始地址即可。
Request和Response分别代表了HTTP的请求包和相应包,下面简单描述下:

Request

scrapy.http.Request(url[, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback, flags])

url:请求地址,必填;
callback:页面解析回调函数,默认调用Spider的parse方法;
method:请求的方式,默认为GET
headers:请求头部字典,如果某项的值为None,表示不发送该头部信息;
body:请求正文;
cookies:Cookies,通常为字典,也可以是字典的列表;
meta这个比较重要,这个参数通常用于对象之间传递信息,比如在Response中会保存相对应的Request对象的meta参数;
priority:请求的优先默认值;
dont_filter:用来表示如果该地址之前请求过,本次是否过滤该请求;
errback:请求异常或出现HTTP错误时的回调函数。

Scrapy基于Response提供了一些子类,如FormRequest用来处理HTML FORM

Response

scrapy.http.Response(url[, status=200, headers=None, body=b'', flags=None, request=None])

url:请求的地址;
status:HTTP相应状态码;
headers:相应头部;
body:相应正文,bytes类型;
request:对应的Request对象。

除了构造方法的参数,Response还需要关注以下参数/方法:
meta:来自Request.meta参数;
css(query):使用CSS选择器从Response.body中提取信息,实际是TextResponse.selector.css(query)方法的快捷方式;
xpath(query):使用XPath选择器从Response中提取信息,实际是TextResponse.selector.xpath(query)方法的快捷方式;
urljoin(url):用于将相对路径转化为绝对路径。

使用Selector提取数据

知道页面是如何获取到之后,接下来就是如何从页面中获取所需要的信息。Scrapy提供了两种发生:CSS选择器和Xpath选择器(详细用法可以参考网上相关资料,这边只列举常用方法)。
先来看看Selector:

scrapy.selector.Selector(response=None, text=None, type=None)

response:可基于Response对象生成Selector对象;
text:可基于文本生成Selector,优先级 response > text;
type:解析类型,html/xml,通常不用关心。

Selector还提供了一下方法,下面简要介绍一些常用的方法:
xpath(query):基于Xpath选择器提取数据,返回一个SelectorList元素
css(query):基于CSS选择器提取数据,返回一个SelectorList元素
extract():返回选中元素的Unicode字符串列表;
re(regex):返回选中元素符合regex正则表达式的Unicode字符串列表;

再来看看SelectorList提供的方法:
xpath(query):基于Xpath选择器对列表中的每个元素提取数据,所有结果会组成一个SelectList,并返回;
css(query):基于CSS选择器对列表中的元素提取数据,所有结果会组成一个SelectList,并返回;
extract():对列表中所有元素的调用extract(),所有结果会组成一个SelectList,并返回;
re(regex):对列表中所有元素的调用re(regex),所有结果会组成一个SelectList,并返回;
extract_first():返回第一个元素的Unicode字符串列表;
re_first(regex):返回符合regex正则的第一个元素的Unicode字符串列表;

下面简单看看CSS选择器和XPATH选择器

CSS选择器

CSS的用法可以看W3C
这边增加一条额外用法:

选择器 描述 例子
element::text 选择element元素的文本 p::text
element::attr(attr_name) 选择element元素属性为attr_name的值 a::attr(href)

示例:

>>> html_body = """
... 
... “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.” ... by ... (about) ... ...
... Tags: ... ... ... change ... ... deep-thoughts ... ... thinking ... ... world ... ...
...
... """ >>> selector = scrapy.Selector(text = html_body) >>> type(selector) selector_list = selector.css('div.quote div.tags a.tag::text') #这边我们要提取的是文本直接a.tag::text也可以 >>> type(selector_list) >>> selector_list.extract_first() #提取第一个tag 'change' >>> selector_list.extract() #提取所有tag ['change', 'deep-thoughts', 'thinking', 'world'] >>> selector_list.re_first(r'(^w\w+)') #利用正则提取 'world'
XPath选择器

XPath的用法可以看W3C
这边增加一条额外用法:

选择器 描述
text() 选择文本
//还是用上面的html_body
>>> selector_list = selector.xpath('//div[@class="quote"]/div[@class="tags"]/a[@class="tag"]/text()')

>>> selector_list.extract()
['change', 'deep-thoughts', 'thinking', 'world']
>>> selector_list.re_first(r'(^w\w+)')
'world'
>>> selector_list.extract_first()
'change'

爬取网站

通过结合Response、Request和Selector,就可以写出简单的爬虫。下面是以 http://quotes.toscrape.com/为例,爬取quote/author/tags并返回。

#-*- coding: utf-8 -*-
#quotes.py
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

#    def start_requests(self):
 #       url = "http://quotes.toscrape.com/"
  #      yield scrapy.Request(url, callback = self.parse)

    def parse(self, response):
        quote_selector_list = response.css('body > div > div:nth-child(2) > div.col-md-8 div.quote')

        for quote_selector in quote_selector_list:
            quote = quote_selector.css('span.text::text').extract_first()
            author = quote_selector.css('span small.author::text').extract_first()
            tags = quote_selector.css('div.tags a.tag::text').extract()

            yield {'quote':quote, 'author':author, 'tags':tags}
        #爬取下一页的链接
        next_page_url = response.css('ul.pager li.next a::attr(href)').extract_first()
        if next_page_url:
            next_page_url = response.urljoin(next_page_url)
            yield scrapy.Request(next_page_url, callback = self.parse)

http://quotes.toscrape.com/开启了Robots,所以我们要在爬虫的配置文件中settings.pyROBOTSTXT_OBEY = True改为ROBOTSTXT_OBEY = False
运行命令:scrapy crawl quotes --nolog -o result.json,最终可以在目录下看到生成的文件result.json

爬取结果.png

从上面的代码看到在第一步yield我们所需的数据之后,去爬取了下一页的链接,如果获取到下一页的链接,会再yield一个Request对象,Request对象的callback还是parse()方法,因此会一直爬取跌倒知道该网站没有下一页为止。

总结

这一篇博客描述了一个spider文件是如何运行以及如何爬取数据。下一篇博客将会讲如何使用Scrapy.item来封装爬取的数据。

你可能感兴趣的:(如何编写一个Spider)