如何编写一个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 = """
... <div class="quote" itemscope="" itemtype="http://schema.org/CreativeWork">
...         <span class="text" itemprop="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span>
...         <span>by <small class="author" itemprop="author">Albert Einstein</small>
...         <a href="/author/Albert-Einstein">(about)</a>
...         </span>
...         <div class="tags">
...             Tags:
...             <meta class="keywords" itemprop="keywords" content="change,deep-thoughts,thinking,world">
...
...             <a class="tag" href="/tag/change/page/1/">change</a>
...
...             <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
...
...             <a class="tag" href="/tag/thinking/page/1/">thinking</a>
...
...             <a class="tag" href="/tag/world/page/1/">world</a>
...
...         </div>
...     </div>
...     """
>>> selector = scrapy.Selector(text = html_body)
>>> type(selector)
<class 'scrapy.selector.unified.Selector'>
selector_list = selector.css('div.quote div.tags a.tag::text') #这边我们要提取的是文本直接a.tag::text也可以
>>> type(selector_list)
<class 'scrapy.selector.unified.SelectorList'>
>>> 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来封装爬取的数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容