wjx-2005-07-01  

Scrapy 分布式爬虫

1、yield关键词

主要用途:

  • 异步处理:Scrapy是基于Twisted框架构建的,Twisted是一个时间驱动的网络框架,它允许Scrapy进行非阻塞操作。使用yield可以暂停当前的爬虫处理,等待某些异步操作完成后再继续执行。
  • 延迟处理:当你需要处理大量数据或者需要等待某些耗时的I/O操作(如数据库查询、API调用等)时,yield可以使得爬虫在等待期间去处理其它的任务,提高效率。
  • 生成器函数:yield用于创建生成器,这允许你编写一个函数,该函数可以再每一次迭代时产生一个值,而不是一次性返回所有值这样可以节省内存尤其是处理大量的数据时。
  • 中间数据处理:在Scrapy的Item Pipeline中,yield可以用来处理中间数据。例如,在一个管道阶段中使用yield来处理一个item,然后在下一个阶段中再次处理它。
  • 控制数据流:yield可以用来控制数据流,允许你在数据处理的不同阶段插入逻辑,比如在爬取过程中进行数据清洗、转换等操作。
  • 与Scrapy引擎集成:Scrapy框架内部使用yield来处理请求和响应。当你在爬虫中使用yield返回一个请求时,Scrapy引擎会处理这个请求,并将响应传递会回爬虫,以便进一步处理。
  • 提高可读性和组织性:使用yield可以让你的代码更加清晰和模块化,因为它允许你将数据处理分解成更小、更易于管理的块。
 yield scrapy.Request(url=url, callback=self.function)
"""
作用:
生成一个请求,并将其发送到队列中;
等待调度器安排下载器下载这个请求的URL对应的网页;
这个请求时一个异步操作,yield关键字的使用使得爬虫
可以在等待响应的过程中继续执行其它的任务。
"""

对这行代码的具体解释:

  • yield:在这里,它用来产生一个Scrapy的Request对象。
  • scrapy.Request:这是Scrapy框架中用于创建新的HTTP请求的类。当你想请求一个网页并对其进行解析时,你会创建一个Request对象。
  • url:代表你想要爬取的网页的URL。url参数指定了请求的目标地址。
  • callback=self.function:callback参数是一个函数,它指定了当请求的网页被下载器下载后,应该调用哪个方法来处理这个响应。

2、中间件作用

在Scrapy框架中,中间件提供了一个强大的机制来处理Scrapy引擎中的请求和响应。中间件位于Scrapy的下载器和爬虫之间,允许开发者在请求发送到网络之前和响应返回给爬虫之前进行修改和处理。一下是中间件的一些主要作用:

  • 请求处理:中间件可以在请求发送之前对其进行修改,例如添加或修改HTTP头部、处理Cookie、添加URL参数等。
  • 响应处理:中间件可以在响应被爬虫处理之前对其进行处理,例如处理重定向修改响应内容、处理异常等。
  • 异常处理:中间件可以要你用来捕获和处理请求过程中发生的异常。例如网络错误、超时等,从而提高爬虫的稳定性和健壮性。
  • 数据存储:中间件可以用来在请求和响应之间存储数据,例如将某些数据存储在数据库中,或者在请求之间传递状态信息。
  • 限速和延迟:中间件可用来控制爬虫爬取速度,以避免对目标网站造成过大压力,或者模拟正常用户的浏览行为。
  • 安全性增强:中间件可以用来增强爬虫的安全性,例如检测和防止CSRF攻击、添加安全头部等。
  • 用户代理管理:中间件可以用来管理用户代理。模拟不同的浏览器或设备,以适应不同的网站需求。
  • 代理服务器使用:中间件可以用来处理通过代理服务器发送请求的情况,这对于绕过IP封禁或访问受限制的内容非常有用。
  • 内容编码处理:中间件可以用来处理响应内容的编码问题,例如自动解码压缩的内容或转换编码格式。
  • 统计和日志记录:中间件可以用来记录请求和响应的统计信息,例如记录请求次数、响应状态码等,以便用于分析和调试。

3、Scrapy文件介绍

3.1、scrapy.cfg:

  • 这是 Scrapy 项目的配置文件。
  • 它通常包含项目设置,如设置默认的下载延迟、项目名称、版本等。
  • 也可以在这里配置项目的扩展和中间件。

3.2、items.py:

  • 定义了爬取的数据结构,即爬虫将要抓取的字段。
  • 通常继承自 scrapy.Item 类,并定义了各种字段及其类型。

3.3、pipeline.py:

  • 包含了数据处理管道,用于处理由爬虫抓取的数据。
  • 可以包含多个管道类,每个类都定义了数据如何被处理,例如清洗、验证、存储等。

3.4、settings.py:

  • 包含了项目的设置文件。
  • 可以在这里配置各种项目级别的设置,如下载延迟、并发请求数量、输出格式等。

3.5、middleware.py:

  • 包含了中间件,它们是在每个请求和响应之间处理的钩子函数。
  • 中间件可以用来处理异常、记录请求和响应、处理cookies等。

3.6、spiders/目录:

  • 包含所有的爬虫文件,每个爬虫都是一个.py文件。
  • 每个爬虫文件通常包含一个或多个爬虫类,这些类继承自scrapy.Spider。
  • 爬虫类定义了爬取的逻辑,如起始URL、如何遍历页面、如何提取数据等。

项目实战案例总结 (58同城)

# demo58.py 这是爬虫文件

import scrapy
from scrapy import cmdline
from Spider58.items import Spider58ItemZufang , Spider58ItemErshoufang


class Demo58Spider(scrapy.Spider):
    name = "demo58"
    allowed_domains = ["58.com"]
    start_urls = ["https://wh.58.com/?utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT"]

    def parse(self, response):
        """
        在直接使用 xpath 语法对 response 进行解析
        返回的是 SelectorList 对象
        :param response:
        :return:
        """
        # 首先获取租房和二手房的链接
        links = response.xpath('//*[@id="fcNav"]/em/a/@href').extract() # extract_first() 只取第一个元素
        for link in links:
            """
            使用 response.urljoin 方法:
            用于将一个相对的 URL 与 Response 对象当前页面的绝对 URL 合并。
            """
            href = response.urljoin(link)
            if 'chuzu' in href:
                yield scrapy.Request(url=href,callback=self.parse_zufang)
            if 'ershoufang' in href:
                yield scrapy.Request(url=href,callback=self.parse_ershoufang)

    """
    由于存在两个URL
    需要解析的方式也不一样
    需要创建两个 items 类
    """
    def parse_zufang(self,response):
        titleList = response.xpath('//div[@class="des"]/h2/a/text()').extract()
        priceList = response.xpath('//div[@class="list-li-right"]//div[@class="money"]/b/text()').extract()
        for title,price in zip(titleList,priceList):
            item = Spider58ItemZufang()
            item["title"] = title
            item["price"] = price
            print("租房信息:",title)
            yield item
    def parse_ershoufang(self,response):
        titleList = response.xpath('//div[@class="property-content-detail"]/div[@class="property-content-title"]/h3/text()').extract()
        priceList = response.xpath('//p[@class="property-price-total"]/span[@class="property-price-total-num"]/text()').extract()
        for title, price in zip(titleList, priceList):
            item = Spider58ItemErshoufang()
            item['title'] = title
            item['price'] = price
            print("二手房信息:", title)
            yield item


if __name__ == '__main__':
    """
    使用cmdline.execute
    第一个参数:scrapy
    第二个参数:crawl
    第三个参数:name(上面的名字)
    第四个参数:--nolog(不显示日志信息)
    """
    cmdline.execute(['scrapy', 'crawl', 'demo58', '--nolog'])

​ 以上文件首先从58同城首页进行获取租房和二手房链接,由于两个链接需要解析的语法并不一致,所以我们另外定义了两个函数分别用于解析相对应的链接内容,在这个实现过程中,学习到了两个方法。

  • extract()和extract_first() 方法:这两个都是针对 SelectorList对象,第一个是获得所有值放入列表中,第二个则是只取第一个值。
  • urljoin() 方法:用于将一个相对的URL和这个网站的绝对URL进行拼接。
# items.py文件

import scrapy

# 租房类
class Spider58ItemZufang(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field() # 标题
    price = scrapy.Field() # 价钱

# 二手房类
class Spider58ItemErshoufang(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()  # 标题
    price = scrapy.Field()  # 价钱

​ 以上代码定义了两个 item 类,分别表示租房和二手房爬取字段。

# pipeline.py文件

import csv
from Spider58.items import Spider58ItemZufang,Spider58ItemErshoufang



class Spider58PipelineZufang:
    def __init__(self):
        self.file = open('zufang.csv','w') # 打开文件用于写入
        self.writer = csv.writer(self.file) # 用于创建一个CSV写入器对象
        self.writer.writerow(["title","price"]) # 写入 csv 文件头部

    # def spider_opened(self,spider):
        # 爬虫打开时调用,可以执行爬虫级别的初始化操作
        # 例如,记录日志、设置信号处理器等
    def process_item(self, item, spider):
        """
        isinstance作用:
        用于检查一个对象是否是一个类的实例
        这个函数通常用于多态性检查,即在运行时确定对象的实际类型
        :param item:
        :param spider:
        :return:
        """
        if isinstance(item,Spider58ItemZufang):
            item["title"] = item["title"].strip()
            self.writer.writerow([item['title'], item['price']])
        return item

    def spider_closed(self,spider):
        self.file.close()


class Spider58PipelineErshou:
    def __init__(self):
        self.file = open('ershou.csv', 'w')  # 打开文件用于写入
        self.writer = csv.writer(self.file)  # 用于创建一个CSV写入器对象
        self.writer.writerow(["title", "price"])  # 写入 csv 文件头部

    # def spider_opened(self,spider):
    # 爬虫打开时调用,可以执行爬虫级别的初始化操作
    # 例如,记录日志、设置信号处理器等
    def process_item(self, item, spider):
        """
        isinstance作用:
        用于检查一个对象是否是一个类的实例
        这个函数通常用于多态性检查,即在运行时确定对象的实际类型
        :param item:
        :param spider:
        :return:
        """
        if isinstance(item, Spider58ItemErshoufang):
            self.writer.writerow([item['title'], item['price']])
        return item

    def spider_closed(self, spider):
        self.file.close()

​ 首先,对 pipeline.py 文件进行说明。在 Scrapy 的管道中,方法名 spider_opened 和 spider_closed 是由 Scrapy 框架预定义的信号处理器,它们与 Scrapy 的生命周期时间相关联。这些方法名是特定的,不能随意修改,因为 Scrapy 依赖这些特定的方法名来调用相应的处理逻辑。

  • spider_opened:当爬虫启动时,这个信号会被触发。你可以在这个方法中执行一些初始化操作,比如建立数据库连接、打开文件等。

  • spider_closed:当爬虫关闭时,这个信号会被触发,你可以在这个方法中执行一些清理操作,比如关闭文件、提交数据到数据库等。

    ​ 接下来,讲一下 CSV。它可以使用 write 方法,这是 Python 标准库 csv 模块中的一个工厂函数,它用于创建一个 CSV 写入器对象。

    • isinstance:这是 Python 中的一个内置函数,用于检查一个对象是否是一个类的实例,或者是否是一个子类的实例。这个函数通常用于多态性检查,即在运行时确定对象的实际类型。
    # 语法
    
    isinstance(object, classinfo)
    
    # object:要检查的对象
    #classinfo:可以是一个类名、类的类型对象或者是一个包含这些类的元组
    
    """
    以下是一个简单的例子
    """
    
    # 假设我们有一个基类 Animal 和两个子类 Dog 和 Cat
    class Animal:
        pass
    
    class Dog(Animal):
        pass
    
    class Cat(Animal):
        pass
    
    # 创建 Dog 和 Cat 的实例
    dog = Dog()
    cat = Cat()
    
    # 使用 isinstance 检查实例的类型
    print(isinstance(dog, Dog))  # 输出 True,因为 dog 是 Dog 的实例
    print(isinstance(cat, Animal))  # 输出 True,因为 cat 是 Animal 的子类的实例
    print(isinstance(dog, Cat))  # 输出 False,因为 dog 不是 Cat 的实例或其子类的实例
    

    ​ 最后,讲一下 (return item)。在 Scrapy 的管道中,return item 语句将处理后的 Item 对象返回给 Scrapy 框架。这个返回的 Item 对象随后会被传递给后续的管道(如果有的话),或者是进行最终的存储。这里需要注意的是,以上代码由于写了两个 pipeline 类,所以返回的 item 必须放在外面返回,就是不能在 if 内部返回。

# settings.py 文件

# Scrapy settings for Spider58 project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = "Spider58"

SPIDER_MODULES = ["Spider58.spiders"]
NEWSPIDER_MODULE = "Spider58.spiders"


# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
#    "Accept-Language": "en",
#}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    "Spider58.middlewares.Spider58SpiderMiddleware": 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    "Spider58.middlewares.Spider58DownloaderMiddleware": 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    "scrapy.extensions.telnet.TelnetConsole": None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   "Spider58.pipelines.Spider58PipelineZufang": 300,
   "Spider58.pipelines.Spider58PipelineErshou": 300,
}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = "httpcache"
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"

# Set settings whose default value is deprecated to a future-proof value
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"

以上是对配置文件做出来一些修改,供大家参考。希望大家多多支持,谢谢!!!

posted on 2024-03-26 21:50  星辰与Python  阅读(4)  评论(0编辑  收藏  举报