Python笔记:爬虫框架Scrapy之Spider的原理

Spider概述

  • 在Scrapy中,要抓取网站的链接配置、抓取逻辑、解析逻辑里其实都是在Spider中配置的。
  • Spider要做的事就是有两件:定义抓取网站的动作分析爬取下来的网页

Spider运行流程

  • 以初始的URL初始化Request,并设置回调函数。请求成功时Response生成并作为参数传给该回调函数。
  • 在回调函数内分析返回的网页内容。返回结果两种形式,一种为字典或Item数据对象;另一种是解析到下一个链接。
  • 如果返回的是字典或Item对象,我们可以将结果存入文件,也可以使用Pipeline处理并保存。
  • 如果返回Request,Response会被传递给Request中定义的回调函数参数,即再次使用选择器来分析生成数据Item。

Spider类分析

  • Spider类源代码:打开文件Python36/Lib/site-packages/scrapy/spiders/__init__.py (windows目录)
import logging
import warnings

from scrapy import signals
from scrapy.http import Request
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider
from scrapy.utils.deprecate import create_deprecated_class
from scrapy.exceptions import ScrapyDeprecationWarning
from scrapy.utils.deprecate import method_is_overridden

#所有爬虫的基类,自定义的爬虫必须从继承此类
class Spider(object_ref):

    #定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。
    #name是spider最重要的属性,而且是必须的。
    #一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 douban.com ,该spider通常会被命名为 douban
    name = None
    custom_settings = None

    #初始化,提取爬虫名字,start_ruls
    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        # 如果爬虫没有名字,中断后续操作则报错
        elif not getattr(self, 'name', None):
            raise ValueError("%s must have a name" % type(self).__name__)

        # python 对象或类型通过内置成员__dict__来存储成员信息
        self.__dict__.update(kwargs)

        #URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
        if not hasattr(self, 'start_urls'):
            self.start_urls = []

    @property
    def logger(self):
        logger = logging.getLogger(self.name)
        return logging.LoggerAdapter(logger, {'spider': self})

    # 打印Scrapy执行后的log信息
    def log(self, message, level=log.DEBUG, **kw):
        log.msg(message, spider=self, level=level, **kw)

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = cls(*args, **kwargs)
        spider._set_crawler(crawler)
        return spider

    #判断对象object的属性是否存在,不存在做断言处理
    def set_crawler(self, crawler):
        assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler
        self._set_crawler(crawler)

    def _set_crawler(self, crawler):
        self.crawler = crawler
        self.settings = crawler.settings
        crawler.signals.connect(self.close, signals.spider_closed)

    #@property
    #def crawler(self):
    #    assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
    #    return self._crawler

    #@property
    #def settings(self):
    #    return self.crawler.settings

    #该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
    #该方法仅调用一次
    def start_requests(self):
        for url in self.start_urls:
            yield self.make_requests_from_url(url)

    #start_requests()中调用,实际生成Request的函数。
    #Request对象默认的回调函数为parse(),提交的方式为get
    def make_requests_from_url(self, url):
        return Request(url, dont_filter=True)

    #默认的Request对象回调函数,处理返回的response。
    #生成Item或者Request对象。用户必须实现这个类
    def parse(self, response):
        raise NotImplementedError

    @classmethod
    def handles_request(cls, request):
        return url_is_from_spider(request.url, cls)

    @staticmethod
    def close(spider, reason):
        closed = getattr(spider, 'closed', None)
        if callable(closed):
            return closed(reason)

    def __str__(self):
        return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))

    __repr__ = __str__
  • Spider类继承自scrapy.spiders.Spider.
  • Spider类这个提供了start_requests()方法的默认实现,读取并请求start_urls属性,并调用parse()方法解析结果。
  • Spider类的属性和方法:
    • name:爬虫名称,必须唯一的,可以生成多个相同的Spider实例,数量没有限制。
    • allowed_domains: 允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。
    • start_urls: 它是起始URL列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。
    • custom_settings: 它是一个字典,专属于Spider的配置,此设置会覆盖项目全局的设置,必须定义成类变量。
    • crawler:它是由from_crawler()方法设置的,Crawler对象包含了很多项目组件,可以获取settings等配置信息。
    • settings: 利用它我们可以直接获取项目的全局设置变量。
    • start_requests(): 使用start_urls里面的URL来构造Request,而且Request是GET请求方法。
    • parse(): 当Response没有指定回调函数时,该方法会默认被调用。
    • closed(): 当Spider关闭时,该方法会调用。

百度文库搜索信息案例

  1. 分析
  • 每页有10条信息
  • https://wenku.baidu.com/search?word=python&pn=0 第一页
  • https://wenku.baidu.com/search?word=python&pn=1 第二页
  1. 爬取实践

2.1 使用scrapy命令创建爬虫项目: wenku_search

  • $ scrapy startproject wenku_search

2.2 创建spider爬虫文件: wenku

  • $ cd wenku_search
  • $ scrapy genspider wenku wenku.baidu.com

2.3 编辑wenku.py爬虫文件,代码如下:

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

class WenkuSpider(scrapy.Spider):
    name = 'wenku'
    allowed_domains = ['wenku.baidu.com']
    start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']

    def parse(self, response):
        print('=' * 50)
        print(response.url)

2.4 运行:

  • $ scrapy crawl wenku

2.5 爬取数据出现错误: DEBUG: Forbidden by robots.txt: 请求被拒绝了

百度文库的robots.txt设置了禁止外部爬取信息,解决方案:
settings.py配置文件中,将ROBOTSTXT_OBEY的值设为False,忽略robot协议,继续爬取 (注:我们只作研究学习使用)。

2.6 Spider爬虫文件:wenku.py 的几种写法:

单请求的信息爬取

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

class WenkuSpider(scrapy.Spider):
    name = 'wenku'
    allowed_domains = ['wenku.baidu.com']
    start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']

    def parse(self, response):
        dllist = response.selector.xpath("//dl")
        #print(len(dllist))
        for dd in dllist:
            print(dd.xpath("./dt/p/a/@title").extract_first())

多个请求的信息爬取

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

class WenkuSpider(scrapy.Spider):
    name = 'wenku'
    allowed_domains = ['wenku.baidu.com']
    start_urls = ['https://wenku.baidu.com/search?word=python&pn=0','https://wenku.baidu.com/search?word=python&pn=10','https://wenku.baidu.com/search?word=python&pn=20']

    def parse(self, response):
        dllist = response.selector.xpath("//dl")
        #print(len(dllist))
        for dd in dllist:
            print(dd.xpath("./dt/p/a/@title").extract_first())

        print("="*70) #输出一条每个请求后分割线

实现一个爬取循环,获取10页信息

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

class WenkuSpider(scrapy.Spider):
    name = 'wenku'
    allowed_domains = ['wenku.baidu.com']
    start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
    p = 0

    def parse(self, response):
        dllist = response.selector.xpath("//dl")
        #print(len(dllist))
        for dd in dllist:
            print(dd.xpath("./dt/p/a/@title").extract_first())

        print("="*70)

        self.p += 1
        if self.p < 10:
            next_url = 'https://wenku.baidu.com/search?word=python&pn=' + str(self.p * 10)
            # url = response.urljoin(next_url) # 构建绝对url地址(这里可省略)
            yield scrapy.Request(url=url,callback=self.parse)

点击这里查看代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值