课程笔记7:Scrapy框架——规则化爬虫

搭建一个基础爬虫

1.新建项目:

scrapy startproject scrapyuniversaldemo

2.查看可用模版并指定crawl模版创建爬虫

scrapy genspider -l
# 查看模版非必要
scrapy genspider -t crawl movie ssr1.scrape.center

3.在爬虫的rules中使用Rule来定义index页中的爬取逻辑和解析逻辑

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import MovieItem


class MoiveSpider(CrawlSpider):
    name = 'movie'
    allowed_domains = ['ssr1.scrape.center']
    start_urls = ['http://ssr1.scrape.center/']

    rules = (
        Rule(LinkExtractor(restrict_css='.item .name'), callback='parse_detail', follow=True),
        Rule(LinkExtractor(restrict_css='.next'), follow=True)
    )

这里用到了Rule的三个参数:link_extractor、callback、follow

  • link_extractor:一个LinkExtractor对象,指向需要提取的链接,提取出的链接会自动生成Request;
  • callback:指向负责处理response的方法,该方法会返回包含Item或Request对象的列表(不要使用parse方法);
  • follow: 一个布尔值,指定从response提取的链接是否需要跟进爬取(即进一步生成Request),如果不跟进,一般可以定义回调方法解析内容,生成Item。

在LinkExtractor中用到了一个参数:restrict_css

  • restrict_css:表示从当前页面中与CSS选择器匹配的区域提取链接
  • restrict_xpath:表示从当前页面中与XPath匹配的区域提取链接
  • tags:指定从某个节点中提取链接,默认是('a','area')
  • attrs:指定从节点的某个属性中提取链接,默认是('href',),搭配tags使用
  • unique:是否需要对提取到的链接进行去重,默认是True
  • strip:是否需要对提取到的结果进行去掉首尾空格的处理,默认是True
  • allow:一个正则表达式(或列表),规定提取链接的白名单
  • deny:一个正则表达式(或列表),规定提取链接的黑名单
  • allow_domains:定义域名白名单
  • deny_domains:定义域名黑名单
  • deny_extensions:定义链接后缀黑名单(默认值包括7z、7zip、apk、dmg、ico、iso、tar等)

4.在爬虫中创建parse_detail方法,定义detail页中的爬取逻辑

-省略-


class MoiveSpider(CrawlSpider):
    -省略-

    def parse_detail(self, response):
        item = MovieItem()
        item['name'] = response.css('.item h2::text').get()
        item['categories'] = response.css('.categories .category span::text').getall()
        item['cover'] = response.css('.cover::attr(src)').get()
        item['published_at'] = response.css('.info span::text').re_first('(\d{4}-\d{2}-\d{2})\s?上映')
        item['score'] = response.css('.score::text').get().strip()
        item['drama'] = response.css('.drama p::text').get().strip()
        yield item

5.在items中定义MovieItem并指定好需要的字段

import scrapy


class MovieItem(scrapy.Item):
    name = scrapy.Field()
    cover = scrapy.Field()
    categories = scrapy.Field()
    published_at = scrapy.Field()
    drama = scrapy.Field()
    score = scrapy.Field()

至此,可以实现到以下效果: 

改进成半规则化爬虫

但现在这种实现方式并不能实现可配置化,需要进行修改:

6.将parse_detail方法改写为Item Loaders来实现

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import MovieItem
from ..loaders import MovieItemLoader


class MoiveSpider(CrawlSpider):
    name = 'movie'
    allowed_domains = ['ssr1.scrape.center']
    start_urls = ['http://ssr1.scrape.center/']

    rules = (
        Rule(LinkExtractor(restrict_css='.item .name'), callback='parse_detail', follow=True),
        Rule(LinkExtractor(restrict_css='.next'), follow=True)
    )

    def parse_detail(self, response):
        loader = MovieItemLoader(item=MovieItem(), response=response)
        # 声明一个MovieItem,用该Item和Response对象实例化MovieItemLoader
        loader.add_css('name', '.item h2::text')
        # 调用add_css方法将数据提取出来,分配给name属性
        loader.add_css('categories', '.categories .category span::text')
        loader.add_css('cover', '.cover::attr(src)')
        loader.add_css('published_at', '.info span::text', re='(\d{4}-\d{2}-\d{2})\s?上映')
        loader.add_css('score', '.score::text')
        loader.add_css('drama', '.drama p::text')
        yield loader.load_item()
        # 调用load_item方法实现对Item的解析

这里用到了Item Loader的两个参数:item和response

  • item:Item对象,可以调用add_xpath、add_css、add_value等方法来填充Item对象
  • response:Response对象,用于使用构造选择器的Response
  • selector:Selector对象,用来提取填充数据的选择器

7.创建loaders.py(和items.py同级), 在内定义ItemLoader的子类 

  • 默认的ItemLoader在这里不够好使,通过继承、创建一个它的子类,来加上项目需要的功能;
  • Item Loader的每个字段中都包含了一个Input Processor和一个Out Processor;
  • Input Processor(输入处理器):收到数据时立刻提取数据,结果收集起来并保存在ItemLoader内,不会分配给Item;
  • Output Processor(输出处理器):收集到所有数据后,load_item方法会被调用来填充再生成Item对象——在此之前,会先调用Output Processor对数据进行处理。
from scrapy.loader import ItemLoader
from itemloaders.processors import TakeFirst, Identity, Compose


class MovieItemLoader(ItemLoader):
    default_output_processor = TakeFirst()
    # 设置默认的输出处理器
    # 代替get()操作,以每个字段的第一个提取结果作为最终结果
    categories_out = Identity()
    # 保持原来的结果不变(列表),覆盖前面的默认值
    score_out = Compose(TakeFirst(), str.strip)
    drama_out = Compose(TakeFirst(), str.strip)
    # 取出第一个结果并作去除前后空格处理,覆盖前面的默认值

这里用到了两种Scrapy提供的Processor:TakeFirst、Identity

  • TakeFirst:返回列表的第一个非空值,类似get()的功能
  • Identity:不进行任何处理,直接返回原来的数据
  • Join:把列表拼接成字符串(字符串默认用空格分隔)
  • Compose:可以传入多个函数对一个输入值进行处理
  • MapCompose:可以传入多个函数对一个列表输入值进行处理
  • SelectJmes:可以查询JSON(传入Key,返回查询所得的Value;需要先安装jmespath库)

输出结果和前面的一致。至此,实现了爬虫的半规则化。

改进成完全规则化爬虫

8.创建一个通用的Spider(universal.py)

scrapy genspider -t crawl universal universal

9.创建configs目录(和spiders目录同级),在configs目录下创建movie.json,将Spider内的属性抽离并配置到这个JSON文件内

{
  "spider": "universal",
  "type": "电影",
  "home": "https://ssr1.scrape.center/",
  "settings": {
    "USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43"
  },
  "start_urls": [
    "https://ssr1.scrape.center/"
  ],
  "allowed_domains": [
    "ssr1.scrape.center"
  ],
  "rules": [
    {
      "link_extractor": {
        "restrict_css": ".item .name"
      },
      "follow": true,
      "callback": "parse_detail"
    },
    {
      "link_extractor": {
        "restrict_css": ".next"
      },
      "follow": true
    }
  ]
}

10.创建utils.py(和items.py同级),用于读取前面定义的JSON,然后动态加载到Spider中

from os.path import realpath, dirname, join
import json


def get_config(name):
    path = join(dirname(realpath(__file__)), 'configs', f'{name}.json')
    with open(path, 'r', encoding='utf-8') as f:
        return json.loads(f.read())

11.在项目根目录创建入口run.py(和scrapy.cfg同级)

from scrapy.utils.project import get_project_settings
from scrapyuniversaldemo.utils import get_config
from scrapy.crawler import CrawlerProcess
import argparse

parser = argparse.ArgumentParser(description='Universal Spider')
parser.add_argument('name', help='name of spider to run')
args = parser.parse_args()
name = args.name
# 使用argparse要求运行时指定name参数(即对应的JSON配置文件的名称)


def run():
    config = get_config(name)
    # 利用config传入JSON配置文件
    spider = config.get('spider', 'universal')
    # 获取爬取使用的Spider名称
    project_settings = get_project_settings()
    settings = dict(project_settings.copy())
    # 获取配置文件中的settings配置
    settings.update(config.get('settings'))
    # 将获取到的settings配置和项目全局的settings配置进行合并
    process = CrawlerProcess(settings)
    # 新建一个CrawlerProcess,通过代码更加灵活自定义需要的Spider和启动配置
    process.crawl(spider, **{'name': name})
    process.start()


if __name__ == '__main__':
    run()

12.在universal.py中新建__init__方法,进行初始化配置

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..utils import get_config

class UniversalSpider(CrawlSpider):
    name = 'universal'

    def __init__(self, name, *args, **kwargs):
        config = get_config(name)
        # 接收name参数并通过get_config方法读取配置文件的内容
        self.config = config
        self.start_urls = config.get('start_urls')
        self.allowed_domains = config.get('allowed_domains')
        rules = []
        # 分别将start_urls、allowed_domains、rules进行初始化
        for rule_kwargs in config.get('rules'):
            # 遍历rules配置
            link_extractor = LinkExtractor(**rule_kwargs.get('link_extractor'))
            # 每个rule的配置赋值为rule_kwargs字典,
            # 然后读取rule_kwargs的link_extractor属性,将其构造为LinkExtractor对象
            rule_kwargs['link_extractor'] = link_extractor
            # 将link_extractor属性赋值到rule_kwargs字典中
            rule = Rule(**rule_kwargs)
            # 使用rule_kwargs初始化一个Rule对象
            rules.append(rule)
            # 将多个Rule对象构造成一个rules列表
        self.rules = rules
        super(UniversalSpider, self).__init__(*args, **kwargs)
        # 将rules列表赋值给CrawlSpider

13.将原Spider文件中的解析部分抽离并配置到JSON文件中

{
  "spider": "universal",
  "type": "电影",
  "home": "https://ssr1.scrape.center/",
  "settings": {
    "USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43"
  },
  "start_urls": [
    "https://ssr1.scrape.center/"
  ],
  "allowed_domains": [
    "ssr1.scrape.center"
  ],
  "rules": [
    {
      "link_extractor": {
        "restrict_css": ".item .name"
      },
      "follow": true,
      "callback": "parse_detail"
    },
    {
      "link_extractor": {
        "restrict_css": ".next"
      },
      "follow": true
    }
  ],
  "item": {
//item和rules同级并列
    "class": "MovieItem",
    "loader": "MovieItemLoader",
//分别代表Item和ItemLoader的所使用的类
    "attrs": {
//定义attrs属性来定义每个字段的提取规则
      "name": [
        {
          "method": "css",
          "arg": ".item h2::text"
        }
      ],
      "categories": [
        {
          "method": "css",
          "args": ".categories button span::text"
        }
      ],
      "cover": [
        {
          "method": "css",
          "arg": ".cover::attr(src)"
        }
      ],
      "published_at": [
        {
          "method": "css",
          "arg": ".info span::text",
          "re": "(\\d{4}-\\d{2}-\\d{2})\\s?上映"
        }
      ],
      "score": [
        {
          "method": "css",
          "arg": ".score::text"
        }
      ],
      "drama": [
        {
         "method": "css",
          "arg": ".drama p::text"
        }
      ]
    }
  }
}

14.将以上配置动态加载到parse_detail方法里

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..utils import get_config
from .. import items
from .. import loaders

class UniversalSpider(CrawlSpider):
    name = 'universal'

    def __init__(self, name, *args, **kwargs):
        -省略-

    def parse_detail(self, response):
        item = self.config.get('item')
        # 通过utils(get_config)获取JSON文件中的item配置信息
        if item:
            cls = getattr(items, item.get('class'))()
            # 获取class的配置(代表Item使用的类),将Item进行初始化
            loader = getattr(loaders, item.get('loader'))(cls, response=response)
            # 获取loader的配置(代表ItemLoader使用的类),将ItemLoader进行初始化
            for key, value in item.get('attrs').items():
                # 遍历Item的attrs代表的各个属性
                for extractor in value:
                    if extractor.get('method') == 'xpath':
                        # 判断method字段,调用对应的处理方法进行处理
                        loader.add_xpath(key, extractor.get('arg'), **{'re': extractor.get('re')})
                    if extractor.get('method') == 'css':
                        loader.add_css(key, extractor.get('arg'), **{'re': extractor.get('re')})
                    if extractor.get('method') == 'value':
                        loader.add_value(key, extractor.get('arg'), **{'re': extractor.get('re')})
                yield loader.load_item()
                # 所有配置动态加载完毕之后,调用load_item方法将Item提取出来


和前面的普通爬虫相比,主要增加了这几个部分:movie.json,universal.py,loaders.py,utils.py,run.py

movie.json:其包含的内容其实都是从原来的movie.py迁移过来的

universal.py:是一只通用爬虫

utils.py:用来帮助爬虫去读取JSON文件的

loaders.py:相当于爬虫和Item之间的“中间件”,对数据作进一步处理(因为在JSON里面,只定义了数据怎么爬取;而在爬虫中,为了保持通用性,不能把处理方法写死,所以要靠loaders来打补丁——我是这么理解的)

run.py:项目启动器,用来匹配爬虫和JSON配置文件的。


<完>

猜你喜欢

转载自blog.csdn.net/weixin_58695100/article/details/122793427
今日推荐