第十一课:scrapy 分布式爬虫(1—古诗文爬取)

143 阅读5分钟

1.scrapy框架介绍

写一个爬虫,需要做很多的事情。比如:发送网络请求、数据解析、数据存储、反反爬虫机制(更换ip代理、设置请求头等)、异步请求等。这些工作如果每次都要自己从零开始写的话,比较浪费时间。因此Scrapy把一些基础的东西封装好了,在他上面写爬虫可以变的更加的高效(爬取效率和开发效率)。因此真正在公司里,一些上了量的爬虫,都是使用Scrapy框架来解决。

2.scrapy安装

  • pip install scrapy。

  • 可能会出现问题:

    • 在ubuntu下要先使用以下命令安装依赖包:sudo apt-get install python3-dev build-essential python3-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev,安装完成后再安装scrapy
    • 在windows下安装可能会提示No module named win32api,这时候先使用命令:pip install pypiwin32,安装完成后再安装scrapy
    • 在windows下安装Scrapy可能会提示twisted安装失败,那么可以到这个页面下载twisted文件:https://www.lfd.uci.edu/~gohlke/pythonlibs/,下载的时候要根据自己的Python版本来选择不同的文件。下载完成后,通过pip install xxx.whl
  • 官方文档:

    中文:

    [Scrapy 中文文档 — Scrapy 文档 (scrapy-16.readthedocs.io)](https://scrapy-16.readthedocs.io/zh_CN/1.6/)

    英文:

    [Scrapy 2.9 documentation — Scrapy 2.9.0 documentation](https://docs.scrapy.org/en/latest/index.html)

3.scrapy框架架构

image.png

  1. Scrapy Engine(引擎):Scrapy框架的核心部分。负责在Spider和ItemPipeline、Downloader、Scheduler中间通信、传递数据等。
  2. Spider(爬虫):发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发送给爬虫,爬虫就去解析想要的数据。这个部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是需要的,都是由程序员自己决定。
  3. Scheduler(调度器):负责接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。
  4. Downloader(下载器):负责接收引擎传过来的下载请求,然后去网络上下载对应的数据再交还给引擎。
  5. Item Pipeline(管道):负责将Spider(爬虫)传递过来的数据进行保存。具体保存在哪里,应该看开发者自己的需求。
  6. Downloader Middlewares(下载中间件):可以扩展下载器和引擎之间通信功能的中间件。
  7. Spider Middlewares(Spider中间件):可以扩展引擎和爬虫之间通信功能的中间件。

4.scrapy快速入门

  • 创建项目
  1. 创建项目:scrapy startproject [项目名称].
  2. 创建爬虫:cd到项目中->scrapy genspider [爬虫名称] [域名].
  • 项目中文件的作用
  1. settings.py:用来配置爬虫的。
  2. middlewares.py:用来定义中间件。
  3. items.py:用来提前定义好需要下载的数据字段。
  4. pipelines.py:用来保存数据。
  5. scrapy.cfg:用来配置项目的。
  • 项目结构

image.png

5.scrapy框架爬取古诗文网站实战

  • 第一步

在settings.py文件里面将ROBOTSTXT_OBEY设置为False

ROBOTSTXT_OBEY = False

然后在DEFAULT_REQUEST_HEADERS里面设置请求头

DEFAULT_REQUEST_HEADERS = {
   "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
   "Accept-Language": "en",
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63'
}

为了方便运行代码,在项目根目录下创建MyRun.py文件

from scrapy import cmdline

cmds = "scrapy crawl gsww_spider"

cmdline.execute(cmds.split(" "))
  • 第二步

在gsww_spider.py文件里面编写提取数据得逻辑,细节见代码里面的注释

import scrapy

from scrapy.http.response.html import HtmlResponse      # 鼠标右键,进入可以看到模块里面存在的那些方法

from scrapy.selector.unified import SelectorList        # 鼠标右键,进入可以看到模块里面存在的那些方法

class GswwSpiderSpider(scrapy.Spider):

    # 配置url请求的一些信息
    name = "gsww_spider"
    # 指定去爬取的网站
    allowed_domains = ["gushiwen.cn"]
    # 请求时,页面跳转到那个页面
    start_urls = ["https://www.gushiwen.cn/default_1.aspx"]

    # 为了方便学习,添加一个打印的方法
    def myPrint(self, value):
        print('*'*30)
        print(value)
        print('*' * 30)

    def parse(self, response):
        # self.myPrint(type(response))      scrapy.http.response.html.HtmlResponse
        gsw_list = response.xpath('.//div[@class="left"]/div[@class="sons"]')
        # self.myPrint(type(gsw_list))      <class 'scrapy.selector.unified.SelectorList'>
        # response.xpath()返回的都是selectorList对象
        # selectorList里面返回的都是selector对象
        # selector.getall()方法获取selector对象里面指定的值
        # selector.get()方法获取selector对象里面第一个值
        for gws_div in gsw_list:
            # 获取标题
            title = gws_div.xpath(".//b/text()").get()
            # self.myPrint(title)

            # 获取朝代和姓名
            sources = gws_div.xpath(".//p[@class='source']/a/text()").getall()
            source = "".join(sources).split()
            print(source)

            # 获取内容
            contents = gws_div.xpath(".//div[@class='contson']//text()").getall()
            content = "".join(contents).split()
            # print(content)
  • 第三步

在items.py文件里面罗列出需要的数据

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class GswwItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    source = scrapy.Field()
    content = scrapy.Field()

然后在gsww_spider.py文件里面导入Items.py,然后创建对象,传入参数。

from ..items import GswwItem

image.png

  • 第四步

将组装后的数据发送给pipeline, 使用yield生成器返回给pipeline

yield item
  • 第五步

在settings.py里面配置pipeline

ITEM_PIPELINES = {
   "gsww.pipelines.GswwPipeline": 300,
}

在pipeline里面测试一下item传递过来了没用

  • 第六步

编辑pipeline,让传递过来的数据存储到文件当中。

rom itemadapter import ItemAdapter

import json

class GswwPipeline:
    # 爬虫开始的时候打开文件
    def open_spider(self, spider):
        self.fp = open('gsw_spider.txt', 'w', encoding='utf-8')

    # 陆续写入内容到文件中
    def process_item(self, item, spider):
        self.fp.write(json.dump(dict(item)) + "\n")
        return item

    # 爬虫结束的时候关闭文件
    def close_spider(self, spider):
        self.fp.close()
  • 第七步

获取多页数据

# 获取多页数据的请求
next_href = response.xpath("//a[@id='amore']/@href").get()
if next_href:
    # urljoin()方法可以让网站域名和我们给定的值自动组成一个网址
    next_url = response.urljoin(next_href)
    request = response.Request(next_url)
    yield request

5.gsww_spider.py里面完整的代码

# -*- coding: utf-8 -*-
import scrapy
from ..items import GswwItem
from scrapy.http.response.html import HtmlResponse
from scrapy.selector.unified import Selector

class GswwSpiderSpider(scrapy.Spider):
    name = 'gsww_spider'
    allowed_domains = ['gushiwen.org', 'gushiwen.cn']
    start_urls = ['https://www.gushiwen.org/default_1.aspx']

    def myprint(self,value):
        print("="*30)
        print(value)
        print("="*30)

    def parse(self, response):
        # self.myprint(type(response))
        # respsone.xpath返回的都是SelectorList对象
        # SelectorList:里面存储的都是Selector对象
        # SelectorList.getall:可以直接获取xpath中指定的值。
        # SelectorList.get:可以直接提取第一个值。

        gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
        for gsw_div in gsw_divs:
            title = gsw_div.xpath('.//b/text()').get()
            source = gsw_div.xpath(".//p[@class='source']/a/text()").getall()
            try:
                dynasty = source[0]
                author = source[1]
                # 下面的//text()代表的是获取class='contson'下的所有子孙文本
                content_list = gsw_div.xpath(".//div[@class='contson']//text()").getall()
                content = "".join(content_list).strip()
                item = GswwItem(title=title,dynasty=dynasty,author=author,content=content)
                yield item
             exception:
                 print(title)

        # next_href = response.xpath("//a[@id='amore']/@href").get()
        # if next_href:
        #     next_url = response.urljoin(next_href)
        #     request = scrapy.Request(next_url)
        #     yield request

        # 获取多页数据的请求
        next_href = response.xpath("//a[@id='amore']/@href").get()
        if next_href:
            # urljoin()方法可以让网站域名和我们给定的值自动组成一个网址
            next_url = response.urljoin(next_href)
            request = response.Request(next_url)
            yield request