zoukankan      html  css  js  c++  java
  • Python 爬虫从入门到进阶之路(十七)

    在之前的文章中我们介绍了 scrapy 框架并给予 scrapy 框架写了一个爬虫来爬取《糗事百科》的糗事,本章我们继续说一下 scrapy 框架并对之前的糗百爬虫做一下优化和丰富。

    在上一篇文章中,我们在项目中创建了一个 qiushiSpider.py 的文件,代码如下:

     1 import scrapy
     2 from ..items import QiushiItem
     3 
     4 
     5 class QiushiSpider(scrapy.Spider):
     6     # 爬虫名
     7     name = "qiubai"
     8     # 允许爬虫作用的范围,不能越界
     9     allowd_domains = ["https://www.qiushibaike.com/"]
    10     # 爬虫起始url
    11     start_urls = ["https://www.qiushibaike.com/text/page/1/"]
    12 
    13     # 我们无需再像之前利用 urllib 库那样去请求地址返回数据,在 scrapy 框架中直接利用下面的 parse 方法进行数据处理即可。
    14     def parse(self, response):
    15         # 通过 scrayy 自带的 xpath 匹配想要的信息
    16         qiushi_list = response.xpath('//div[contains(@id,"qiushi_tag")]')
    17         for site in qiushi_list:
    18             # 实例化从 items.py 导入的 QiushiItem 类
    19             item = QiushiItem()
    20             # 根据查询发现匿名用户和非匿名用户的标签不一样
    21             try:
    22                 # 非匿名用户
    23                 username = site.xpath('./div/a/img/@alt')[0].extract()  # 作者
    24                 imgUrl = site.xpath('./div/a/img/@src')[0].extract()  # 头像
    25             except Exception:
    26                 # 匿名用户
    27                 username = site.xpath('./div/span/img/@alt')[0].extract()  # 作者
    28                 imgUrl = site.xpath('./div/span/img/@src')[0].extract()  # 头像
    29             content = site.xpath('.//div[@class="content"]/span[1]/text()').extract()
    30             item['username'] = username
    31             item['imgUrl'] = "https:" + imgUrl
    32             item['content'] = content
    33             yield item

    在上面的代码中我们定义了一个 QiubaiSpider 的类,并且集成了 scrapy.Spider,我们就对这个类做一下详细分析。

    Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。

     class scrapy.Spider 是最基本的类,所有编写的爬虫必须继承这个类。

    主要用到的函数及调用顺序为:

     __init__()  初始化爬虫名字和start_urls列表

     start_requests() 调用 make_requests_from url() 生成Requests对象交给Scrapy下载并返回response

     parse() 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse() ),一直进行循环,直到处理完所有的数据为止。

     1 #所有爬虫的基类,用户定义的爬虫必须从这个类继承
     2 class Spider(object_ref):
     3 
     4     #定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。
     5     #name是spider最重要的属性,而且是必须的。
     6     #一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite
     7     name = None
     8 
     9     #初始化,提取爬虫名字,start_ruls
    10     def __init__(self, name=None, **kwargs):
    11         if name is not None:
    12             self.name = name
    13         # 如果爬虫没有名字,中断后续操作则报错
    14         elif not getattr(self, 'name', None):
    15             raise ValueError("%s must have a name" % type(self).__name__)
    16 
    17         # python 对象或类型通过内置成员__dict__来存储成员信息
    18         self.__dict__.update(kwargs)
    19 
    20         #URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
    21         if not hasattr(self, 'start_urls'):
    22             self.start_urls = []
    23 
    24     # 打印Scrapy执行后的log信息
    25     def log(self, message, level=log.DEBUG, **kw):
    26         log.msg(message, spider=self, level=level, **kw)
    27 
    28     # 判断对象object的属性是否存在,不存在做断言处理
    29     def set_crawler(self, crawler):
    30         assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler
    31         self._crawler = crawler
    32 
    33     @property
    34     def crawler(self):
    35         assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
    36         return self._crawler
    37 
    38     @property
    39     def settings(self):
    40         return self.crawler.settings
    41 
    42     #该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
    43     #该方法仅调用一次
    44     def start_requests(self):
    45         for url in self.start_urls:
    46             yield self.make_requests_from_url(url)
    47 
    48     #start_requests()中调用,实际生成Request的函数。
    49     #Request对象默认的回调函数为parse(),提交的方式为get
    50     def make_requests_from_url(self, url):
    51         return Request(url, dont_filter=True)
    52 
    53     #默认的Request对象回调函数,处理返回的response。
    54     #生成Item或者Request对象。用户必须实现这个类
    55     def parse(self, response):
    56         raise NotImplementedError
    57 
    58     @classmethod
    59     def handles_request(cls, request):
    60         return url_is_from_spider(request.url, cls)
    61 
    62     def __str__(self):
    63         return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
    64 
    65     __repr__ = __str__

    主要属性和方法

    • name

      定义spider名字的字符串。

      例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite

    • allowed_domains

      包含了spider允许爬取的域名(domain)的列表,可选。

    • start_urls

      初始URL元祖/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。

    • start_requests(self)

      该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取(默认实现是使用 start_urls 的url)的第一个Request。

      当spider启动爬取并且未指定start_urls时,该方法被调用。

    • parse(self, response)

      当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的response,以及生成Item或者Request对象。

    • log(self, message[, level, component])

      使用 scrapy.log.msg() 方法记录(log)message。

    接下来我们再将之前的的糗百爬虫进行一下丰富。之前的爬虫,我们只是爬取了第一页内糗事,即  start_urls = ["https://www.qiushibaike.com/text/page/1/"] ,其中最末尾的 1 即代表了当前页码,所以当该页数据爬取完毕后让其自动 +1 即为下一页内容,然后再接着爬取我们想要的数据。

     1 import scrapy
     2 from ..items import QiushiItem
     3 import re
     4 
     5 
     6 class QiushiSpider(scrapy.Spider):
     7     # 爬虫名
     8     name = "qiubai"
     9     # 允许爬虫作用的范围,不能越界
    10     allowd_domains = ["https://www.qiushibaike.com/"]
    11     # 爬虫起始url
    12     start_urls = ["https://www.qiushibaike.com/text/page/1/"]
    13 
    14     # 我们无需再像之前利用 urllib 库那样去请求地址返回数据,在 scrapy 框架中直接利用下面的 parse 方法进行数据处理即可。
    15     def parse(self, response):
    16         # 通过 scrayy 自带的 xpath 匹配想要的信息
    17         qiushi_list = response.xpath('//div[contains(@id,"qiushi_tag")]')
    18         for site in qiushi_list:
    19             # 实例化从 items.py 导入的 QiushiItem 类
    20             item = QiushiItem()
    21             # 根据查询发现匿名用户和非匿名用户的标签不一样
    22             try:
    23                 # 非匿名用户
    24                 username = site.xpath('./div/a/img/@alt')[0].extract()  # 作者
    25                 imgUrl = site.xpath('./div/a/img/@src')[0].extract()  # 头像
    26             except Exception:
    27                 # 匿名用户
    28                 username = site.xpath('./div/span/img/@alt')[0].extract()  # 作者
    29                 imgUrl = site.xpath('./div/span/img/@src')[0].extract()  # 头像
    30             content = site.xpath('.//div[@class="content"]/span[1]/text()').extract()
    31             item['username'] = username
    32             item['imgUrl'] = "https:" + imgUrl
    33             item['content'] = content
    34 
    35             # 通过 re 模块匹配到请求的 url 里的数字
    36             currentPage = re.search('(d+)', response.url).group(1)
    37             page = int(currentPage) + 1
    38             url = re.sub('d+', str(page), response.url)
    39 
    40             # 将获取的数据交给 pipeline
    41             yield item
    42 
    43             # 发送新的 url 请求加入待爬队列,并调用回调函数 self.parse
    44             yield scrapy.Request(url, callback=self.parse)

    在上面的代码中,我们在将获取到的 item 数据交给管道文件 pipeline 之后,获取到此时的请求 url 链接,根据 re 模块获取到链接里的数字,即页码数,然后将页码数 +1,再次发送新的 url 链接请求即可获取其余页码的数据了。

  • 相关阅读:
    11. Container With Most Water
    9. Palindrome Number
    375. 猜数字大小 II leetcode java
    leetcode 72 编辑距离 JAVA
    73. 矩阵置零 leetcode JAVA
    快速排序 JAVA实现
    63. 不同路径 II leetcode JAVA
    重写(override)与重载(overload)
    62 不同路径 leetcode JAVA
    leetcode 56 合并区间 JAVA
  • 原文地址:https://www.cnblogs.com/weijiutao/p/10900634.html
Copyright © 2011-2022 走看看