使用Scrapy选择器提取豆瓣电影信息,并用正则表达式从介绍详情中获取指定信息

本文同步更新于博主个人博客:blog.buzzchat.top

一、Scrapy框架

1. 介绍

在当今数字化的时代,数据是一种宝贵的资源,而网络爬虫(Web Scraping)则是获取网络数据的重要工具之一。而在 Python 生态系统中,Scrapy 框架作为一种高效、灵活的网络爬虫框架,为开发者提供了强大的工具和功能,使他们能够轻松地从网站中提取所需的结构化数据。

1.1 Scrapy 框架简介

Scrapy 是一个基于 Python 的开源网络爬虫框架,旨在快速高效地爬取网站数据,并提取结构性数据。它提供了一套强大的工具和组件,使开发者能够专注于编写爬虫逻辑,而无需关注网络请求、页面解析等底层细节。Scrapy 的设计理念包括高度可配置、模块化和可扩展性,使其成为爬取大规模数据的理想选择。

1.2 本教程目的

本教程旨在帮助初学者了解如何使用 Scrapy 框架来爬取豆瓣电影 Top250 的信息。通过本教程,你将学习到如何创建一个 Scrapy 项目、编写爬虫逻辑、提取数据、保存数据以及处理网络请求等相关内容。无论是对 Scrapy 框架感兴趣的新手,还是想要学习如何从网站中获取数据的开发者,本教程都将提供有价值的知识和实用的技能。


2. 概念和工作原理

在开始使用 Scrapy 构建爬虫之前,了解其核心概念和工作原理是非常重要的。Scrapy 的设计思想是基于一系列组件的协同工作,以实现高效的网络数据爬取和处理。在本部分,我们将介绍 Scrapy 的主要组成部分以及其工作流程。

2.1 组件介绍

Scrapy 主要由以下组件组成:

  • 引擎(Engine): 负责控制整个爬虫系统的流程,包括调度器、下载器和爬虫之间的通信。它负责调度并协调各个组件的工作。

  • 调度器(Scheduler): 负责接收引擎发送过来的请求,并按照一定的策略将这些请求排队,然后发送给下载器。

  • 下载器(Downloader): 负责下载引擎发送过来的请求对应的页面,并将下载到的页面内容返回给引擎。

  • 爬虫(Spiders): 爬虫是用户编写的一组类,用于定义如何爬取特定网站(或者一组网站)的数据。每个爬虫都包含了一些用于从网页中提取数据的解析规则。

  • 管道(Pipeline): 管道负责处理爬虫从网页中抽取出来的数据,并进行后续的处理,比如数据清洗、存储等操作。

  • 下载中间件(Downloader Middleware): 下载中间件是介于引擎和下载器之间的组件,它可以对请求和响应进行预处理或后处理。

  • 爬虫中间件(Spider Middleware): 爬虫中间件是介于引擎和爬虫之间的组件,它可以对请求和数据进行预处理或后处理。

如图所示:

2.2 工作原理

Scrapy 的工作流程如下:

  1. 引擎接收到用户定义的初始请求,并将其传递给调度器。
  2. 调度器将请求排队,并按照一定的策略选择下一个要处理的请求,然后将其发送给下载器。
  3. 下载器下载网页内容,并将下载到的页面内容返回给引擎。
  4. 引擎将下载到的页面内容发送给相应的爬虫进行解析,提取出目标数据。
  5. 爬虫将提取到的数据提交给管道进行后续处理,比如数据清洗、存储等操作。
  6. 如果有新的请求需要处理,引擎将它们发送给调度器,重复上述过程,直到没有新的请求需要处理为止。

通过以上步骤,Scrapy 能够高效地从网站中爬取数据,并对其进行处理和存储,为用户提供了强大的数据获取和处理能力。


3. 创建新的 Scrapy 项目

要创建一个新的 Scrapy 项目,首先确保你已经安装了 Scrapy。然后在命令行中执行以下命令:

scrapy startproject myproject

这会在当前目录下创建一个名为 myproject 的新项目。在这个项目中,你会看到如下结构:

  • myproject/
    • myproject/
      • __init__.py
      • items.py: 定义爬取的数据结构(可选)。
      • middlewares.py: 可以在请求和响应之间进行操作的中间件(可选)。
      • pipelines.py: 对爬取到的数据进行处理的管道(可选)。
      • settings.py: 项目的设置文件,包含了各种配置选项。
      • spiders/: 存放爬虫代码的目录。
        • __init__.py
      • __init__.py
    • scrapy.cfg: Scrapy 部署配置文件。

执行这些步骤后,你将拥有一个全新的Scrapy项目,可以在其中编写和运行你的爬虫代码。如果你想要创建新的爬虫,可以使用 genspider 命令来生成。例如:

cd myproject
scrapy genspider example example.com

这将在 spiders 目录中创建一个名为 example 的新爬虫,用于爬取 example.com 网站的数据。如图所示:


4. 项目文件结构及作用

  • scrapy.cfg: 项目的配置文件,包含了 Scrapy 项目的配置信息,比如项目的名称以及项目中使用的设置文件位置等。

  • myproject/: 项目的 Python 包,包含了项目的代码和其他资源文件。

    • __init__.py: 表明该目录是一个 Python 包。

    • items.py: 可选的文件,用于定义爬取的数据结构,通常使用 Scrapy 的 Item 类来定义数据模型。

    • middlewares.py: 可选的文件,包含了自定义的中间件,用于在请求和响应之间进行操作,例如添加代理、用户代理等。

    • pipelines.py: 可选的文件,包含了自定义的管道,用于对爬取到的数据进行处理,例如数据清洗、存储等操作。

    • settings.py: 项目的设置文件,包含了各种配置选项,如爬虫的延迟时间、并发请求数、用户代理等。

    • spiders/: 存放爬虫代码的目录,每个爬虫通常都是一个单独的 Python 文件。

      • __init__.py: 表明该目录是一个 Python 包。

二、实践:使用 Scrapy 爬取豆瓣电影 Top250

1.定义数据模型

在 Scrapy 中,我们使用 Item 对象来表示爬取到的数据。为了清晰地组织数据,我们需要定义一个数据模型来描述我们感兴趣的信息。在这个项目中,我们主要关注豆瓣电影 Top250 页面中的电影信息,因此我们创建一个名为 MovieItem 的数据模型来存储这些信息。

# File: your_project/items.py

import scrapy

class MovieItem(scrapy.Item):
    # 定义了要爬取的关键信息字段
    ranking = scrapy.Field()    # 排名
    name = scrapy.Field()       # 电影名
    introduce = scrapy.Field()  # 简介
    star = scrapy.Field()       # 星级
    comments = scrapy.Field()   # 评论数
    describe = scrapy.Field()   # 描述

以上代码将这些信息定义为 Scrapy 的 Field 类型,这样可以方便后续爬取和处理。每个字段都对应了我们所感兴趣的电影信息,如排名、电影名、简介、星级、评论数和描述等。


2.编写爬虫

在这一部分,我们将创建一个 Spider 来爬取豆瓣电影 Top250 页面中的信息。我们将使用 Scrapy 提供的功能来轻松地提取页面中的数据,并将其存储到先前定义的数据模型中。

# File: your_project/spiders/douban_top250.py

import scrapy
from ..items import MovieItem

class DoubanTop250Spider(scrapy.Spider):
    """
    该 Spider 用于爬取豆瓣电影 Top250 页面的信息。
    """

    name = 'douban_top250'  # Spider 的名称
    allowed_domains = ['movie.douban.com']  # 允许爬取的域名
    start_urls = ['https://movie.douban.com/top250']  # 起始 URL

    def parse(self, response):
        """
        解析页面响应,提取电影信息并存储到 MovieItem 对象中
        :param response: 爬取到的页面响应
        :return: MovieItem 对象
        """
        # 使用 XPath 选择器提取电影信息
        movies = response.xpath('//div[@class="item"]')
        for movie in movies:
            item = MovieItem()  # 创建 MovieItem 对象来存储电影信息
            # 提取电影排名信息
            item['ranking'] = movie.xpath('div[@class="pic"]/em/text()').get()
            # 提取电影名称
            item['name'] = movie.xpath('div[@class="info"]/div[@class="hd"]/a/span[@class="title"]/text()').get()
            # 提取电影简介
            item['introduce'] = movie.xpath('div[@class="info"]/div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()').get()
            # 提取电影星级评分
            item['star'] = movie.xpath('div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').get()
            # 提取电影评论数
            item['comments'] = movie.xpath('div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[last()]/text()').get()
            # 提取电影描述信息
            item['describe'] = movie.xpath('div[@class="info"]/div[@class="bd"]/p[@class=""]/text()').get()
            yield item  # 将 MovieItem 对象传递给 Scrapy 引擎

        # 继续爬取下一页数据
        next_page = response.xpath('//span[@class="next"]/a/@href').get()
        if next_page:
            yield scrapy.Request(response.urljoin(next_page), callback=self.parse)

在上述代码中,我们使用了 XPath 选择器来定位 HTML 页面中的元素,并提取我们感兴趣的信息。XPath 是一种用于在 XML 文档中导航和选择节点的语言。通过编写 XPath 表达式,我们可以精确定位到页面中的特定元素,从而提取我们需要的数据。

至于 XPath 选择器的代码,是通过观察豆瓣电影 Top250 页面的 HTML 结构而来的,我们根据页面的标签和类名来编写 XPath 表达式,从而定位到我们需要的电影信息。如图所示:

电影的所有信息都存在于<div class="item">中,其中排名(rank)的位置为<div class="pic">下的<em>标签中,电影标题的位置位于<div class="info">下的<div class="hd">下的<span class="title">标签中,以此类推即可写出其余元素的xpath代码

换页的xpath代码如图所示:

因网站源码可能重构导致xpath代码改变,实际应用时应根据实际情况修改代码

Xpath代码解释如下:

  1. //div[@class="item"]: 这个 XPath 表达式选取了页面中所有 class 属性为 "item" 的 div 元素,这些元素包含了每部电影的信息。

  2. div[@class="pic"]/em/text(): 这个 XPath 表达式选取了电影排名信息,其中 div[@class="pic"] 选择了每部电影的海报 div 元素,em 选择了其中的 em 元素,然后使用 text() 方法提取其中的文本内容。

  3. div[@class="info"]/div[@class="hd"]/a/span[@class="title"]/text(): 这个 XPath 表达式选取了电影名称信息,首先选择了包含电影信息的 div 元素,然后进一步选择了 class 属性为 "hd" 的 div 元素,接着选择了其中的 a 元素,然后选择了 span 元素,最后使用 text() 方法提取了电影名称的文本内容。

  4. div[@class="info"]/div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text(): 这个 XPath 表达式选取了电影简介信息,依次选择了包含电影信息的 div 元素、class 属性为 "bd" 的 div 元素、p 元素、class 属性为 "quote" 的 p 元素、span 元素,并提取了其中的文本内容。

  5. div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text(): 这个 XPath 表达式选取了电影星级评分信息,选择了包含电影信息的 div 元素、class 属性为 "bd" 的 div 元素、class 属性为 "star" 的 div 元素、span 元素、class 属性为 "rating_num" 的 span 元素,并提取了其中的文本内容。

  6. div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[last()]/text(): 这个 XPath 表达式选取了电影评论数信息,选择了包含电影信息的 div 元素、class 属性为 "bd" 的 div 元素、class 属性为 "star" 的 div 元素、最后一个 span 元素,并提取了其中的文本内容。

  7. div[@class="info"]/div[@class="bd"]/p[@class=""]/text(): 这个 XPath 表达式选取了电影描述信息,选择了包含电影信息的 div 元素、class 属性为 "bd" 的 div 元素、p 元素、其中没有设置 class 属性的 p 元素,并提取了其中的文本内容。

此外,parse 方法是 Scrapy 中用于解析页面响应的方法。当 Scrapy 发起请求并获取到页面响应后,会自动调用 parse 方法来处理响应,并在其中编写代码来提取数据。parse 方法中的 response 参数包含了爬取到的页面响应,我们可以使用它来提取页面中的信息。


3.中间件设置

有时为了应对网站的反爬虫机制,我们需要对 Scrapy 的下载中间件进行一些设置,以伪装请求并防止被识别为爬虫。在这个项目中,我们可以通过设置随机的 User-Agent 和使用 IP 代理来模拟不同的用户和 IP 地址发送请求。

首先,我们需要在中间件中添加随机 User-Agent 的设置。我们可以在 middlewares.py 文件中创建一个类,名为 RandomUserAgentMiddleware,用于为每个请求随机选择一个 User-Agent,并将其添加到请求头中。

# File: your_project/middlewares.py


import random
class user_agent(object):
    def process_request(self, request, spider):
        # user agent 列表
        USER_AGENT_LIST = [
            'MSIE (MSIE 6.0; X11; Linux; i686) Opera 7.23',
            'Opera/9.20 (Macintosh; Intel Mac OS X; U; en)',
            'Opera/9.0 (Macintosh; PPC Mac OS X; U; en)',
            'iTunes/9.0.3 (Macintosh; U; Intel Mac OS X 10_6_2; en-ca)',
            'Mozilla/4.76 [en_jp] (X11; U; SunOS 5.8 sun4u)',
            'iTunes/4.2 (Macintosh; U; PPC Mac OS X 10.2)',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:5.0) Gecko/20100101 Firefox/5.0',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20120813 Firefox/16.0',
            'Mozilla/4.77 [en] (X11; I; IRIX;64 6.5 IP30)',
            'Mozilla/4.8 [en] (X11; U; SunOS; 5.7 sun4u)'
        ]
        agent = random.choice(USER_AGENT_LIST)  # 从上面列表中随机抽取一个代理
        request.headers['User-Agent'] = agent # 设置请求头的用户代理

在上述代码中,我们创建了一个名为 RandomUserAgentMiddleware 的类,它的 process_request 方法用于处理每个请求,为每个请求随机选择一个 User-Agent,并将其添加到请求头中。其中 USER_AGENT_LIST 是在 settings.py 中定义的用户代理列表。

我们还可以添加一个 IP 代理的设置,以应对可能的 IP 封锁或限制。这里不再详细列举代码,你可以在 middlewares.py 中创建一个类来实现该功能,并在 settings.py 中设置相应的 IP 代理列表。

最后,我们需要在 settings.py 文件中启用这些中间件,并设置它们的优先级:

# File: your_project/settings.py

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.user_agent': 543,
}

在上述代码中,我们将 RandomUserAgentMiddleware 设置为 DOWNLOADER_MIDDLEWARES 字典的值,并指定了优先级为 543。你也可以根据实际情况添加 IP 代理中间件,并设置相应的优先级。

这样,当我们运行 Scrapy 项目时,每个请求就会随机选择一个 User-Agent,并根据需要使用 IP 代理发送请求,以应对网站的反爬虫机制。


4.数据存储

当我们选择将数据存储到 CSV 文件中时,我们需要在 Scrapy 项目中编写一个专门的 Pipeline,负责将爬取到的数据写入 CSV 文件中。下面是一个示例:

# File: your_project/pipelines.py

import csv

class CSVPipeline:
    """
    Pipeline 类,用于将爬取到的数据存储到 CSV 文件中
    """

    def __init__(self, csv_file_path):
        """
        初始化方法,设置 CSV 文件路径
        """
        self.csv_file_path = csv_file_path

    @classmethod
    def from_crawler(cls, crawler):
        """
        类方法,从 Scrapy 配置中获取 CSV 文件路径
        """
        return cls(
            csv_file_path=crawler.settings.get('CSV_FILE_PATH')
        )

    def open_spider(self, spider):
        """
        在 Spider 开始爬取时调用,打开 CSV 文件并写入表头
        """
        self.csv_file = open(self.csv_file_path, 'w', newline='', encoding='utf-8')
        # 创建 CSV 写入器,并写入表头,这里的表头根据 MovieItem 的字段来确定
        self.csv_writer = csv.DictWriter(self.csv_file, fieldnames=['ranking', 'name', 'introduce', 'star', 'comments', 'describe'])
        self.csv_writer.writeheader()

    def close_spider(self, spider):
        """
        在 Spider 结束爬取时调用,关闭 CSV 文件
        """
        self.csv_file.close()

    def process_item(self, item, spider):
        """
        处理每个 Item 对象,将其写入 CSV 文件中
        """
        # 使用 CSV 写入器将 Item 写入 CSV 文件
        self.csv_writer.writerow(item)
        return item

在上述代码中,我们定义了一个名为 CSVPipeline 的 Pipeline 类,它负责将爬取到的数据存储到 CSV 文件中。在该 Pipeline 中,我们实现了四个方法:

  • __init__ 方法:用于初始化 CSV 文件路径。
  • from_crawler 类方法:用于从 Scrapy 的配置中获取 CSV 文件路径。
  • open_spider 方法:在 Spider 开始爬取时调用,用于打开 CSV 文件并写入表头。
  • close_spider 方法:在 Spider 结束爬取时调用,用于关闭 CSV 文件。
  • process_item 方法:处理每个 Item 对象,将其写入 CSV 文件中。

接下来,我们需要在 Scrapy 项目的 settings.py 文件中设置 CSV 文件的路径:

# File: your_project/settings.py

CSV_FILE_PATH = 'douban_movies.csv'

最后,我们需要在 settings.py 中启用该 Pipeline:

# File: your_project/settings.py

ITEM_PIPELINES = {
    'your_project.pipelines.CSVPipeline': 300,
}

这样,当我们运行 Scrapy 项目时,爬取到的电影信息就会被存储到名为 douban_movies.csv 的 CSV 文件中了


5.启动Scrapy项目

在实际应用中,启动Scrapy项目有多种方式,以下是其中一些:

5.1 使用命令行工具

Scrapy提供了命令行工具,可以让你直接在终端中启动项目。使用 scrapy crawl 命令,你可以指定要运行的Spider以及输出的文件格式和路径。例如:

scrapy crawl douban_top250

这里没有指定 -o douban_movies.csv 输出文件路径,因为数据存储已经在Pipeline中设置。

5.2 编写Python脚本

你也可以编写一个简单的Python脚本来启动Scrapy项目。在脚本中,导入你的Spider类,并调用Scrapy的 crawl() 函数来运行Spider。以下是一个示例:

from scrapy import cmdline

cmdline.execute("scrapy crawl douban_top250".split())

与命令行工具一样,在这个示例中我们也没有指定输出文件路径。

5.3 使用Scrapy的CrawlerProcess

使用Scrapy的 CrawlerProcess 类来编写自定义的启动脚本。这种方式更加灵活,可以更好地控制Scrapy的运行方式。以下是一个示例:

from scrapy.crawler import CrawlerProcess
from myproject.spiders import MySpider

# 创建一个 CrawlerProcess 实例
process = CrawlerProcess()

# 向进程中添加要运行的 Spider,这里使用了自定义的 MySpider
process.crawl(MySpider)

# 开始运行爬虫,直到所有爬虫完成
process.start()

这段代码将会启动指定的Spider,并且会按照它们在 process.crawl() 中被添加的顺序依次运行。你可以根据需要添加更多的Spider。

5.4 使用Scrapyd

Scrapyd是一个用于部署和管理Scrapy项目的工具,你可以将Scrapy项目部署到Scrapyd服务器上,并通过HTTP API来控制项目的启动和停止。

通过以上几种启动方法,你可以根据项目需求和个人偏好来选择最合适的方式。Scrapy提供了灵活而强大的工具来满足你的爬虫需求。

如图所示,均已准确爬取

这里给大家说明一个常见问题的解决方案,当你直接用excel打开csv代码乱码时,可通过以下方法解决,如图所示选择数据—>从文本/csv文件导入,选择文件即可

三、使用正则表达式从电影介绍详情中提取指定信息

目标:爬取电影详情页面中介绍详情内容中括号包裹的内容

在这个项目中,我们仍然可以套用第二部分的大致代码结构,但需要针对新的需求进行一些调整。主要区分点包括:

  1. 爬取的内容不同,需要重写 items 类
    由于我们要从电影详情页面提取的信息不同于之前的需求,我们需要重新定义 items.py 文件中的数据模型。这意味着我们需要修改数据模型中的字段,以便适应新的数据结构。例如,我们可能需要添加一个字段来存储电影简介中的括号内容。

  2. 根据网页具体情况重写 spider 文件
    鉴于我们现在要爬取的是豆瓣电影详情页面,而不是之前的 Top250 页面,我们需要对 spider.py 文件进行重写。这意味着我们需要更新爬取页面的 URL,修改解析函数以匹配电影详情页面的 HTML 结构,并调整数据提取逻辑以确保我们可以准确地提取电影简介中的括号内容。

除了这些主要区分点之外,我们的项目结构和工作流程将保持大致相同。我们仍然会使用 Scrapy 框架来构建我们的爬虫,利用其强大的功能来快速而高效地提取所需信息。

在下一步中,我们将详细讨论如何重写 items.py 和 spider.py 文件,以满足新的需求。

1.定义数据模型

对于本项目,我们打算爬取豆瓣电影页面,因此需要定义一个数据模型来存储电影信息。

首先,我们将创建一个名为 ZhengzeItem 的类,该类继承自 Scrapy 的 Item 类。这个类将包含两个字段:

  1. title:用于存储电影的标题。
  2. parenthesis_content:用于存储电影剧情简介中带括号的内容。

在添加完数据模型后,我们需要修改数据管道以适应新的数据模型并存储到 CSV 文件中。以下是对 items.py 和 pipelines.py 文件的修改说明:

在 items.py 文件中:

import scrapy

class ZhengzeItem(scrapy.Item):
    """
    数据模型:定义了爬取电影详情页面所需的字段

    Fields:
        title (str): 电影标题
        parenthesis_content (str): 电影剧情简介中带括号的内容
    """
    # 电影标题
    title = scrapy.Field()
    
    # 电影剧情简介中带括号的内容
    parenthesis_content = scrapy.Field()

这里我们定义了一个名为 ZhengzeItem 的数据模型,包含了电影标题和剧情简介中带括号的内容两个字段,以适应我们从豆瓣电影页面爬取的数据。

在 pipelines.py 文件中:

import csv

class CSVPipeline:
    """
    数据管道:将爬取的电影信息存储为 CSV 文件
    """
    def __init__(self, settings):
        self.settings = settings
        self.file = None

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(settings)

    def open_spider(self, spider):
        self.file = open(self.settings.get('CSV_FILE_PATH'), 'w', newline='', encoding='utf-8')
        self.writer = csv.writer(self.file)
        self.writer.writerow(['Title', 'Parenthesis Content'])

    def close_spider(self, spider):
        if self.file:
            self.file.close()

    def process_item(self, item, spider):
        title = str(item.get('title', ''))
        parenthesis_content = str(item.get('parenthesis_content', ''))

        self.writer.writerow([title, parenthesis_content])

        return item

在这里,我们修改了数据管道 CSVPipeline 的代码,使用了 csv 模块来将爬取的电影信息存储到 CSV 文件中。通过这样的修改,我们可以确保爬取的数据能够被正确地存储和处理。


2.编写爬虫

当编写爬虫时,首先要考虑的是如何访问每个电影的详情页面。在这个项目中,我们需要访问豆瓣电影网站上排名前250的电影的详情页面。为了实现这一目标,我们需要先找到每个电影的链接。

步骤:

1. 通过XPath找到每个电影的详情页链接:

  • 我们首先分析豆瓣电影Top250页面的HTML结构,确定每个电影链接所在的位置,并编写XPath表达式来定位这些链接。

2. 使用Scrapy发送HTTP请求访问每个电影的详情页:

  • 通过Scrapy的scrapy.Request()方法,我们可以根据之前获取到的链接,发送HTTP请求访问每个电影的详情页面。

3. 提取详情页面中的内容:

  • 一旦我们成功访问了每个电影的详情页,我们需要从页面中提取出电影标题和电影简介中用括号括起来的内容。为了实现这一步,我们可以使用XPath或其他解析方法来定位这些信息。

4. 使用正则表达式提取所需内容:

  • 在获取到详情页的HTML源码后,我们可以使用正则表达式来进一步提取所需内容。针对电影简介中用括号括起来的内容,我们可以编写正则表达式来匹配并提取出来。

5. 保存提取到的数据到CSV文件:

  • 最后一步是将提取到的数据保存到CSV文件中。我们可以使用Python的CSV模块来创建CSV文件,并将数据写入其中。确保数据格式的正确性和完整性,以便后续的分析和使用。

xpath寻找过程:

2.1 寻找每个电影的详情页链接(如图所示)

2.2 寻找详情页面中的内容(这里会遇到2个BUG)

BUG1:

在后续爬取过程中发现,爬取内容缺失,如下图所示,仅爬取到了第1段内容,第二段之后未爬取到,查看源码发现<br>之后的内容未爬取到,因此修改代码中的xpath,在前面加上normalize-space()即可解决

因为当网页中包含 `<br>` 标签时,提取文本内容可能会出现问题,因为 `<br>` 标签表示换行,但在XPath表达式中并不会被视为分隔符。因此,文本内容可能会被 `<br>` 标签打断,导致提取的结果不完整或不正确。

为了解决这个问题,我们可以使用 XPath 中的 `normalize-space()` 函数。这个函数的作用是移除文本字符串中开头和结尾的空白字符,并将字符串中的连续空白字符替换为单个空格,从而将文本内容规范化为一个整体。

在这个例子中,我们将 `normalize-space()` 函数应用在XPath表达式的结果上,这样就可以确保提取的文本内容不会被 `<br>` 标签打断,而是被规范化为一段连续的文本。因此,无论 `<br>` 标签在哪里,我们都可以正确提取简介文本,而不会受到其影响。

解决后如图所示,全部正确爬取:

BUG2:

解决BUG1之后发现,仍有部分文章爬取到的内容为空,查看网站源码即可发现,存在不同的xpath解析路径,如下图所示,图1电影详情全部展示并存在于<span property="v:summary" class="">之下,图2的电影详情存在(展开全部)按钮,并且文章内容存储在<span class="all hidden">之下,而之前的代码仅包含第一种情况,因此将修改xpath代码为:

normalize-space(//div[@id="link-report-intra"]/span[@class="all hidden" or @property="v:summary

即可同时匹配以上两种情况

图1:

图2:

2.3 正则表达式

当提取电影简介中的括号内内容时,我们需要使用正则表达式来匹配括号中的文本。但是,并非所有电影简介都包含括号,因此我们需要处理这种情况,以避免出现错误。

代码解析:

import re

if synopsis:
    # 定义正则表达式来匹配带括号的内容,使用多行模式(re.DOTALL)
    parenthesis_pattern = r'(([^)]*))'  # 匹配括号内的内容
    parenthesis_matches = re.findall(parenthesis_pattern, synopsis, re.DOTALL)

    # 对每个匹配到的括号内容字符串调用 strip() 方法去除空格,并生成新的列表
    parenthesis_matches_stripped = [match.strip() for match in parenthesis_matches]
else:
    parenthesis_matches_stripped = []

解释:

  • if synopsis::此条件语句检查电影简介是否存在内容。如果简介非空,则执行下一步操作。
  • parenthesis_pattern = r'(([^)]*))':这里定义了一个正则表达式模式,用于匹配括号中的内容。r 前缀表示原始字符串,以避免反斜杠转义问题。模式 (([^)]*)) 匹配括号内的任何字符,直到遇到右括号为止。
  • re.findall(parenthesis_pattern, synopsis, re.DOTALL):使用 re.findall 函数在 synopsis 中查找所有匹配 parenthesis_pattern 的内容,并使用 re.DOTALL 标志以匹配换行符。
  • [match.strip() for match in parenthesis_matches]:对于每个匹配到的括号内容字符串,使用 strip() 方法去除首尾的空格,并将结果存储在 parenthesis_matches_stripped 列表中。
  • 如果电影简介为空,则将 parenthesis_matches_stripped 设置为空列表,以避免出现错误。

通过这种方式,我们可以确保在提取电影简介中的括号内内容时,代码能够正确处理各种情况,包括简介为空或简介中没有括号的情况。

2.4 实际爬虫过程中遇到的问题和解决方案
2.4.1 爬取过程中重定向到登陆页面

如图所示:出现302重定向,点击去发现是跳转到登陆页面了

这是通常是因为网站对未登录用户进行了重定向以保护内容或者限制访问。302状态码表示临时重定向,通常用于指示需要进行授权或者登录才能访问资源。

解决这个问题的方法之一是设置请求头中的cookie,以模拟登录状态。登录网站后,浏览器会将一些身份验证信息存储在cookie中,并在之后的请求中发送给服务器。通过在爬虫中设置相同的cookie,可以让服务器认为爬虫是已登录的用户,从而避免跳转到登录页面。

如图所示,登录后在network中找到cookie,然后复制添加到爬虫文件中即可,代码放在本段最后

同时一定要注意!!!你的cookie是电脑端,所以UA池中不能存在安卓端的内容,如图所示就会被反爬机制查到!!!

2.4.2 被反爬机制查到的解决方案

如图所示,爬取过程中出现302重定向到了以下界面

跳转到机器人检测界面可能是由于网站的反爬机制检测到了你的爬虫行为。网站的反爬机制可以检测到访问模式、频率、请求头等与正常用户不同的行为,并将其识别为爬虫或机器人。

为了规避这些反爬机制,你可以尝试以下几种方法:

  1. 设置合理的请求头信息: 模拟浏览器的行为,包括设置User-Agent、Referer等请求头,使请求看起来更像是来自正常的浏览器而不是爬虫。

  2. 降低访问频率: 适当控制爬取的速度,避免短时间内发送过多的请求,模拟正常用户的访问行为。

  3. 使用代理IP: 使用代理IP进行请求,以隐藏你的真实IP地址,减少被网站识别为爬虫的可能性。

  4. 处理验证码: 如果跳转到机器人检测界面是因为验证码的原因,可以通过自动识别验证码的方式来解决。

  5. 模拟登录: 如果网站要求登录后才能访问内容,可以尝试模拟登录并保存登录状态,以维持登录状态进行后续的请求。

  6. 动态切换请求头和IP: 定期更换请求头信息和IP地址,增加反爬机制的识别难度。

需要注意的是,绕过反爬机制可能违反网站的使用条款,造成不必要的麻烦。建议在进行爬取之前,先查看网站的使用政策,并尊重网站所有者的规定。

我这里使用了如下方法来成功绕过反爬虫检测:

  1. 设置随机User-Agent: 通过中间件 RandomUserAgentMiddleware 在每次请求中随机选择一个User-Agent,以模拟不同浏览器或设备的访问行为,降低被识别为爬虫的可能性。

  2. 设置随机请求延迟: 通过设置随机的下载延迟时间,即 DOWNLOAD_DELAY_MIN 和 DOWNLOAD_DELAY_MAX 之间的随机时间,可以避免请求过于频繁,减少被网站识别为爬虫的概率。

  3. 启用Cookie: 通过将 COOKIES_ENABLED 设置为True,启用了Cookie,这可以在一定程度上帮助维持登录状态或者处理网站基于Cookie的认证机制。

另外,如果同一个ip下爬取次数过多可能会造成IP封禁,如果你和博主一样使用笔记本电脑的话,可以尝试切换不同手机热点的方式去改变IP

尝试切换不同手机热点是一种常见的方式来改变IP地址。使用不同的网络连接(比如不同的WiFi网络或者移动数据网络)可以改变你的出口IP地址,从而规避一些网站的IP封禁。然而,需要注意的是,一些网站可能会采取更复杂的反爬虫措施,不仅仅会封禁单个IP地址,还可能会监测用户的行为模式,比如频繁的爬取请求,从而对整个IP段进行封禁或者采取其他限制措施。

因此,尽管切换IP地址是一种常见的应对措施,但是并不一定总是有效的。在进行网络爬取时,最好遵守网站的robots.txt协议和使用适当的爬取速率来避免被封禁。

settings.py文件内容如下:

BOT_NAME = "Zhengze"
ROBOTSTXT_OBEY = False

SPIDER_MODULES = ["Zhengze.spiders"]
NEWSPIDER_MODULE = "Zhengze.spiders"
COOKIES_ENABLED = True

# 中间件配置
DOWNLOADER_MIDDLEWARES = {
    'Zhengze.middlewares.RandomUserAgentMiddleware': 543,  # 设置随机User-Agent中间件的优先级
   # 'Zhengze.middlewares.ProxyMiddleware': 544,  # 设置随机User-Agent中间件的优先级
}

import random

# 设置一个随机延迟范围,比如 1 到 3 秒之间
DOWNLOAD_DELAY_MIN = 0
DOWNLOAD_DELAY_MAX = 1

# 生成随机延迟时间
delay = random.uniform(DOWNLOAD_DELAY_MIN, DOWNLOAD_DELAY_MAX)

# 使用动态延迟时间
DOWNLOAD_DELAY = delay


# CSV输出路径设置
CSV_FILE_PATH = 'E:/doubantop250/Zhengze.csv'

# 数据处理管道设置
ITEM_PIPELINES = {
    'Zhengze.pipelines.CSVPipeline': 300,
}

最终爬虫文件如下:

import scrapy
import re
from ..items import ZhengzeItem
from bs4 import BeautifulSoup

class DoubanTop250Spider(scrapy.Spider):
    name = "Zhengze"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]

    # 设置你的 cookie
    custom_cookies = {
        'cookie1_name': 'bid=_qJ5CNM-f7c; _pk_id.100001.4cf6=caa05f47c90379e0.1711352781.; __utmz=30149280.1711352781.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmz=223695111.1711352781.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __yadk_uid=IQXFNT2vnxeGjwf9deJz78a9ve5HsqW4; ll="118237"; _vwo_uuid_v2=DE4B5B1FD6BE6E2F26C4053D0E1C20C4F|317f8a090233bf021d2e70cae61a1186; dbcl2="279763592:IvXurz8qqIA"; push_noty_num=0; push_doumail_num=0; ck=RQhm; ap_v=0,6.0; _pk_ses.100001.4cf6=1; __utma=30149280.702551814.1711352781.1713138121.1713168043.7; __utmb=30149280.0.10.1713168043; __utmc=30149280; __utma=223695111.807567155.1711352781.1713138121.1713168043.7; __utmb=223695111.0.10.1713168043; __utmc=223695111; __gads=ID=1dbab480dc4dd324:T=1713083660:RT=1713168046:S=ALNI_MZx40l60gyeAJfeyr7ZTtIkDtbj3A; __gpi=UID=00000debc09129bc:T=1713083660:RT=1713168046:S=ALNI_MbBQgg5LtDLxueprj31quRIChjIug; __eoi=ID=e28a5d511c0bcc8a:T=1713083660:RT=1713168046:S=AA-AfjZDyydCQqURUR0ZSKESl3mU'

    }

    def start_requests(self):
        for url in self.start_urls:
            # 发送请求时,添加 cookie
            yield scrapy.Request(url=url, cookies=self.custom_cookies, callback=self.parse,dont_filter=True)

    def parse(self, response):
        # 获取当前页面的电影信息
        for item in self.parse_page(response):
            yield item

        # 检查是否有下一页,并发送请求
        next_page = response.xpath('//span[@class="next"]/a/@href').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

    def parse_page(self, response):
        # 获取每部电影的链接
        movie_links = response.xpath('//div[@class="hd"]/a/@href').extract()
        for movie_link in movie_links:
            yield scrapy.Request(movie_link, callback=self.parse_movie)

    import re

    def parse_movie(self, response):
        # 获取电影标题
        title = response.xpath('//h1/span/text()').get()

        synopsis = response.xpath('normalize-space(//div[@id="link-report-intra"]/span[@class="all hidden" or @property="v:summary"])').get()
        # 如果简介不为空,则进行括号内容的正则匹配
        if synopsis:
            # 定义正则表达式来匹配带括号的内容,使用多行模式(re.DOTALL)
            parenthesis_pattern = '(([^)]*))'  # 匹配括号内的内容
            parenthesis_matches = re.findall(parenthesis_pattern, synopsis, re.DOTALL)

            # 对每个匹配到的括号内容字符串调用 strip() 方法去除空格,并生成新的列表
            parenthesis_matches_stripped = [match.strip() for match in parenthesis_matches]
        else:
            parenthesis_matches_stripped = []

        # 创建 Item 对象并存储提取的内容
        item = {
            'title': title,
            'parenthesis_content': parenthesis_matches_stripped
        }

        yield item

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/548479.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle和PG数据库临时表的差异,PG数据库如何删除临时表

现实的开发过程中使用 PG 数据库删除临时表发现如下报错&#xff0c;提示表 xxx 不存在&#xff1a; 问题原因&#xff1a; 调用删除语句&#xff0c;但是临时表不存在了。 解决方案&#xff1a; PG下用下面的方式来删除临时表或不进行删除&#xff08;会话级临时表会自动删除…

线性表的链式存储

文章目录 前言一、概念及特点二、链表术语及分类三、单链表1.特点2.C语言实现3.头结点作用4.基本操作的具体实现 总结 前言 T_T此专栏用于记录数据结构及算法的&#xff08;痛苦&#xff09;学习历程&#xff0c;便于日后复习&#xff08;这种事情不要啊&#xff09;。所用教材…

Cannot access ‘androidx.activity.FullyDrawnReporterOwner‘

Android Studio新建项目就报错&#xff1a; Cannot access ‘androidx.activity.FullyDrawnReporterOwner’ which is a supertype of ‘cn.dazhou.osddemo.MainActivity’. Check your module classpath for missing or conflicting dependencies 整个类都报错了。本来原来一直…

文献学习-37-动态场景中任意形状针的单目 3D 位姿估计:一种高效的视觉学习和几何建模方法

On the Monocular 3D Pose Estimation for Arbitrary Shaped Needle in Dynamic Scenes: An Efficient Visual Learning and Geometry Modeling Approach Authors: Bin Li,† , Student Member, IEEE, Bo Lu,† , Member, IEEE, Hongbin Lin, Yaxiang Wang, Fangxun Zhong, Me…

使用arthas查看java项目resources目录下面的文件内容

有一次在测试环境想看resources下面的mapper文件内容&#xff08;代码执行和预期不一致&#xff0c;所以想排查一下是不是打上去的包有问题&#xff0c;没有通过下载jar的方式解压查看&#xff09;&#xff0c;然后想到了使用arthas来弄&#xff0c;这里记录一下怎么个查看法。…

【Textin.com】智能文档处理系列 - 电子文档解析技术全格式解析

一、引言 在当今的数字化时代&#xff0c;电子文档已成为信息存储和交流的基石。从简单的文本文件到复杂的演示文档&#xff0c;各种格式的电子文档承载着丰富的知识与信息&#xff0c;支撑着教育、科研、商业和日常生活的各个方面。随着信息量的爆炸性增长&#xff0c;如何高效…

listpack

目录 为什么有listpack? listpack结构 listpack的节点entry 长度length encoding编码方式 listpack的API 1.创建listpack 2.遍历操作 正向遍历 反向遍历 3.查找元素 4.插入/替换/删除元素 总结 为什么有listpack? ziplist是存储在连续内存空间&#xff0c;节省…

Spring Boot 2.x 将 logback 1.2.x 升级至 1.3.x

场景 安全部门针对代码进行漏洞扫描时&#xff0c;发现 logback-core 和 logback-classic 都属于 1.2.x 版本&#xff0c;这个版本存在 CVE 漏洞&#xff0c;并且建议升级到 1.3.x 版本。 问题 将两个包直接升级到 1.3.x 版本时&#xff0c;Spring Boot Web 服务启动直接出现…

基于Springboot+Vue+mysql仓库管理系统仓库进销存管理系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

nfs服务器详解

nfs&#xff08;网络文件系统&#xff09;---------- 其实就是通过网络将文件共享出去。 通过TCP/IP网络去共享资源的。在NFS的应用中&#xff0c;本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件&#xff0c;就像访问本地文件一样。 客户端和服务端需要去读写共…

五分钟搞定什么是系统的平均负载

平均负载定义 平均负载是指单位时间内&#xff0c;系统处于可运行状态和不可中断状态的平均进程数&#xff0c;也就是平均活跃进程数&#xff0c;和CPU使用率没有直接关系。简单理解就是平均负载其实就是平均活跃进程数。 使用uptime命令查看系统平均负载 在linux中&#xf…

【环境】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 一、培养项目系统性思维 1.1 系统性思维 1.2 系统性思维的价值 1.3 建模和推演&数字孪生 二、项目的复杂性和如何驾驭复杂性 2.1 复杂性的三个维度 2.2 如何驾驭复杂性 三、…

Qt实现XYModem协议(一)

1 概述 Kermit文件运输协议提供了一条从大型计算机下载文件到微机的途径。它已被用于进行公用数据传输。 其特性如下: Kermit文件运输协议是一个半双工的通信协议。它支持7位ASCII字符。数据以可多达96字节长度的可变长度的分组形式传输。对每个被传送分组需要一个确认。Kerm…

如何利用纯前端技术,实现一个网页版视频编辑器?

纯网页版视频编辑器 一、前言二、功能实现三、所需技术四、部分功能实现4.1 素材预设4.2 多轨道剪辑 一、前言 介绍&#xff1a;本篇文章打算利用纯前端的技术&#xff0c;来实现一个网页版的视频编辑器。为什么突然想做一个这么项目来呢&#xff0c;主要是最近一直在利用手机…

KITTI结果领先地位!Progressive LiDAR Adaptation for Road Detection——PLARD算法

描述 详解一篇基于激光视觉融合的道路检测文章&#xff0c;发表在2019年自动化学报英文版&#xff08;我所主编的业界顶刊&#xff09;中&#xff0c;第三作者是陶大程&#xff0c;业界大佬&#xff0c;可自行进行百度。 为什么选择这篇文章进行分析呢。查看KITTI数据集的分数…

分布式数据库Polardb-X架构及特点

PolarDB-X架构 计算节点&#xff08;Compute Node&#xff0c;CN&#xff09;是系统的入口&#xff0c;采用无状态设计的sql引擎提供分布式路由和计算&#xff0c;包括SQL解析器、优化器、执行器等模块。负责数据分布式路由、计算及动态调度&#xff0c;负责分布式事务2PC协调…

CFDPro雾化仿真 | 专为雾化过程与液滴属性研究设计的仿真模块

雾化是一种将液体转化为微小液滴的技术&#xff0c;通过不同的雾化方法实现液体的高效分散、蒸发、燃烧、吸附或沉积等目的。 雾化仿真在多个工业领域中具有极其重要的地位。无论是内燃机中燃油的高效燃烧&#xff0c;还是化工生产中的喷雾干燥&#xff0c;以及农业喷雾中农药…

[linux]进程控制——进程终止

一、main函数的返回值 我们在编写C语言的程序时&#xff0c;通常会这样写&#xff1a; int main() {return 0; } 那么我们为什么要返回&#xff08;return&#xff09;0 呢&#xff1f; 其实&#xff0c;main函数也是一个函数&#xff0c;它也会被调用&#xff0c;所以谁调…

【力扣 Hot100 | 第四天】4.15(括号生成)

文章目录 4.括号生成4.1题目4.2解法&#xff1a;回溯4.2.1回溯思路&#xff08;1&#xff09;函数返回值以及参数&#xff08;2&#xff09;终止条件&#xff08;3&#xff09;遍历过程 4.2.2代码 4.括号生成 4.1题目 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数…

计算机笔记(11)续20个

180&#xff0e;时钟频率2.0GHz表示一秒有2*10的9次方个时钟周期&#xff0c;若执行一条指令需要2个时钟周期&#xff0c;则每秒执行的指令数为2*10的9次方/21*10的9次方 181.同轴电缆粗缆采用AUI头作为连接器件 182. 183.win7中的回收站&#xff0c;存放的是硬盘上被删除的…