Maison  >  Article  >  développement back-end  >  Tutoriel d'introduction au Scrapy Crawler 4 Spider (crawler)

Tutoriel d'introduction au Scrapy Crawler 4 Spider (crawler)

PHPz
PHPzoriginal
2017-04-04 10:35:372760parcourir

PythonGestion des versions : pyenv et pyenv-virtualenv
Tutoriel d'introduction au robot Scrapy 1Installation et Utilisation de base
Tutoriel d'introduction à Scrapy Crawler 2 Démo fournie officiellement
Tutoriel d'introduction à Scrapy Crawler 3 Introduction et exemples de l'outil de ligne de commande
Tutoriel d'introduction à Scrapy Crawler 4 Spider (crawler)
Tutoriel d'introduction à Scrapy Crawler 5 Sélecteurs (sélecteurs)
Tutoriel d'introduction à Scrapy Crawler 6 Articles (projets)
Tutoriel d'introduction à Scrapy Crawler 7 Chargeurs d'éléments (chargeur de projet)
Tutoriel d'introduction à Scrapy Crawler Tutoriel 8 Shell interactif pour un Débogage pratique
Tutoriel d'introduction au robot Scrapy 9 Pipeline d'éléments (pipeline de projet)
Tutoriel d'introduction au robot Scrapy 10 Exportations de flux (fichiers d'exportation)
Tutoriel d'introduction au robot Scrapy 11 Demande et Réponse (demande et réponse)
Tutoriel d'introduction au robot Scrapy douze Lien Extraitors (extracteur de lien)

[toc]

Environnement de développement :
Python 3.6.0 版本 (actuellement le dernier)
Scrapy 1.3.2 版本 (actuellement le dernier)

Spider

Un robot définit comment explorer un site Web (ou une classe A pour un groupe de sites Web), y compris comment effectuer une exploration (c'est-à-dire suivre des liens) et comment extraire des données structurées de ses pages Web (c'est-à-dire des éléments d'exploration). un site Web (ou dans un certain site Web). Dans certains cas, un ensemble de sites Web) explore et analyse une page Web avec un comportement personnalisé où

Pour le robot d'exploration, la boucle passe par quelque chose comme ceci :

  1. Vous commencez par générer des requêtes initiales pour gratter la première URL, puis spécifiez une fonction de rappel à appeler avec les réponses téléchargé à partir de ces requêtes. >La première requête exécutée est obtenue en appelant start_requests() (par défaut) La requête génère la méthode d'analyse de

    pour l'URL

    spécifiée dans start_urls et cette méthode sert de fonction de rappel de la requête <.>Dans la fonction de rappel, vous analyserez la réponse (page Web) et renverrez un objet avec les données extraites, un objet Item

  2. , un objet Request ou un itérable de ces objets. les demandes contiendront également des rappels (qui peuvent être les mêmes), qui sont ensuite téléchargés par Scrapy, et leurs réponses traitées par le rappel spécifié.
  3. Dans la fonction de rappel, vous utilisez généralement des sélecteurs pour analyser le contenu de la page (mais vous pouvez également utiliser Beaut

    if
  4. ulSoup, l
  5. xml

    ou quel que soit le mécanisme que vous préférez) et générez le projet avec les données analysées. Enfin, les éléments renvoyés par le robot d'exploration sont généralement conservés dans une base de données (dans certains pipelines d'éléments) ou écrits dans un fichier à l'aide d'une exportation de flux.

  6. Même si cette boucle fonctionne pour (plus ou moins) n'importe quel type de robot d'exploration, il existe différents types de robots d'exploration par défaut regroupés dans Scrapy à des fins différentes. Nous parlerons de ces types ici.

  7. <a href="http://www.php.cn/wiki/164.html" target="_blank">class</a>
scrapy.spiders.Spider

Il s'agit du robot le plus simple, tous les autres robots doivent

hériter du robot de <a href="http://www.php.cn/wiki/164.html" target="_blank">class</a> scrapy.spiders.Spider (y compris les robots fournis avec Scrapy et les robots que vous écrivez vous-même). Il n'offre aucune fonctionnalité particulière. Il fournit simplement une implémentation

par défaut qui envoie des requêtes à partir de l'attribut

et start_requests() appelle la méthode start_urlsspider pour chaque réponse résultante. parsespiderUne

chaîne

qui définit le nom de ce robot. Le nom du robot indique la façon dont le robot est localisé (et instancié) par Scrapy, il name doit être unique
. Cependant, rien ne vous empêche d’instancier plusieurs instances du même robot. Il s’agit de l’attribut le plus important du robot et il est obligatoire. Si un robot explore un seul nom de domaine, la pratique courante consiste à nommer le robot d'après le domaine. Ainsi, par exemple, un robot qui explore mon site Web.com sera généralement appelé mon site Web.

Remarque

Dans Python 2, cela doit être ASCII.

allowed_do<a href="http://www.php.cn/wiki/646.html" target="_blank">principaux</a>s
autorisés Une liste facultative de chaînes de domaines explorés par ce robot d'exploration Spécifiez une liste à explorer, les

autres

ne seront pas explorés. allowed_do<a href="http://www.php.cn/wiki/646.html" target="_blank">main</a>s
Lorsqu'aucune URL spécifique n'est spécifiée, une liste d'URL que le robot commencera à explorer.

custom_<a href="http://www.php.cn/code/8209.html" target="_blank">set</a>tings
Un dictionnaire des paramètres qui seront remplacés par la configuration à l'échelle du projet lors de l'exécution de ce robot. Il doit être défini comme un attribut de classe car les paramètres sont mis à jour avant l'instanciation.

Pour une liste des paramètres intégrés disponibles, voir : Référence des paramètres intégrés.

crawlerCet attribut est défini par la méthode de classe from_crawler() après l'initialisation de la classe et relie le Crawler à l'objet auquel cette instance de robot est liée.

Les robots d'exploration encapsulent de nombreux composants du projet pour un accès à entrée unique (tels que des extensions, des

middleware, des gestionnaires de signaux, etc.). Consultez l'API CrawlerAPI pour plus de détails.

settings Configuration pour exécuter ce robot. Il s'agit d'une instance de paramètres, voir Thèmes de paramètres pour une introduction détaillée à ce sujet.

loggerEnregistreur Python créé avec Spider
. Vous pouvez l'utiliser pour envoyer des messages de journal via celui-ci, comme décrit dans Journalisation d'un robot d'exploration. name

(crawler, from_crawler args, * kwargs) est la méthode de classe utilisée par Scrapy pour créer des robots.

Vous n'aurez peut-être pas besoin de remplacer cela directement, car l'implémentation par défaut agit comme un proxy pour la méthode,

l'appelant avec l'argument args donné et l'argument nommé kwargs. init()

Néanmoins, cette méthode définit les propriétés du robot d'exploration et des paramètres dans la nouvelle instance afin qu'elles soient accessibles ultérieurement dans le robot d'exploration.

  • Paramètres :

    • crawler (Crawlerinstance) - Le robot auquel le robot sera lié

    • args (list) – arguments passés à la méthode

      init()

    • kwargs (dict) – passés à la

      init() Arguments de mots clés de la méthode

start_requests()Cette méthode doit renvoyer un itérable de la première requête pour explorer ce robot.

Avec start_requests(), start_urls n'est pas écrit, et il est inutile même s'il est écrit.

L'implémentation par défaut est : start_urls, mais la méthode start_requests peut être remplacée.

Par exemple, si vous devez lancer en vous connectant à l'aide d'une
requête POST vous pouvez :

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        return [scrapy.FormRequest("http://www.example.com/login",
                                   formdata={'user': 'john', 'pass': 'secret'},
                                   callback=self.logged_in)]

    def logged_in(self, response):
        # here you would extract links to follow and return Requests for
        # each of them, with another callback
        pass

make_requests_from_url(url)Une méthode qui reçoit une URL et renvoie une méthode d'objet Request (ou de liste d'objets Request) pour la récupération. Cette méthode est utilisée pour construire la requête initiale dans la méthode start_requests() et est généralement utilisée pour convertir les URL en requêtes.

Sauf substitution, cette méthode renvoie des requêtes avec la méthode parse() comme fonction de rappel et le paramètre dont_filter activé (voir la classe Request pour plus d'informations).

parse(response)Il s'agit du rappel par défaut de Scrapy pour gérer les réponses téléchargées lorsque leur demande ne spécifie pas de rappel.

La méthode d'analyse est responsable du traitement de la réponse et du renvoi des données explorées ou de plusieurs URL. Les autres rappels de requêtes ont les mêmes exigences que la classe Spider.

Cette méthode et tout autre rappel de requête doivent renvoyer un itérable d'objets Request et dicts ou Item.

  • Paramètres :

    • réponse (Response) - la réponse analysée

log(message[, level, component])Wrapper pour envoyer des messages de journal à l'enregistreur via le robot d'exploration, en maintenant la compatibilité ascendante. Voir Journalisation depuis Spider pour plus de détails.

closed(reason) Invoqué lorsque le robot se ferme. Cette méthode fournit un raccourci vers signaux.connect() pour le signal spider_closed.

Regardons un exemple :

import scrapy


class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        self.logger.info('A response from %s just arrived!', response.url)
Renvoi de plusieurs requêtes et éléments à partir d'un seul rappel :

import scrapy

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield {"title": h3}

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)
Vous pouvez utiliser start_requests() directement, tandis que Not start_urls; Le projet peut obtenir plus facilement des données :

import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/1.html', self.parse)
        yield scrapy.Request('http://www.example.com/2.html', self.parse)
        yield scrapy.Request('http://www.example.com/3.html', self.parse)

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield MyItem(title=h3)

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)
Arguments du Spider

Le robot peut recevoir des paramètres qui modifient son comportement. Certaines utilisations courantes des paramètres du robot consistent à définir une URL de départ ou à limiter l'exploration à certaines parties d'un site Web, mais ils peuvent être utilisés pour configurer n'importe quelle fonctionnalité du robot.

Le paramètre d'analyse Spider est transmis via la commande à l'aide de l'option -a. Par exemple :

scrapy crawl myspider -a category=electronics

Les robots d'exploration peuvent accéder aux paramètres dans leur méthode

init :

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def init(self, category=None, *args, **kwargs):
        super(MySpider, self).init(*args, **kwargs)
        self.start_urls = ['http://www.example.com/categories/%s' % category]
        # ...
par défaut

init La méthode prendra tous les paramètres du robot et les copiera dans le robot en tant que propriétés. L'exemple ci-dessus pourrait également s'écrire comme suit :

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/categories/%s' % self.category)
N'oubliez pas que le paramètre spider n'est qu'une chaîne. Le robot d’exploration n’effectuera aucune analyse par lui-même. Si vous souhaitez définir la propriété start_urls à partir de la ligne de commande, vous devez l'analyser vous-même sous forme de liste, en utilisant quelque chose comme ast.literal_eval ou

json.loads, puis la définir comme propriété. Sinon, vous finissez par parcourir une chaîne start_urls (un piège Python très courant), ce qui fait que chaque caractère est traité comme une URL distincte.

有效的用例是设置使用的http验证凭据HttpAuthMiddleware 或用户代理使用的用户代理UserAgentMiddleware:
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassw<a href="http://www.php.cn/wiki/1360.html" target="_blank">ord</a> -a user_agent=mybot

Spider参数也可以通过Scrapyd schedule.jsonAPI 传递。请参阅Scrapyd文档。


通用爬虫

Scrapy附带一些有用的通用爬虫,你可以使用它来子类化你的爬虫。他们的目的是为一些常见的抓取案例提供方便的功能,例如根据某些规则查看网站上的所有链接,从站点地图抓取或解析XML / CSV Feed。

对于在以下爬虫中使用的示例,我们假设您有一个TestItemmyproject.items模块中声明的项目:

import scrapy

class TestItem(scrapy.Item):
    id = scrapy.Field()
    name = scrapy.Field()
    description = scrapy.Field()

抓取爬虫

类 scrapy.spiders.CrawlSpider
这是最常用的爬行常规网站的爬虫,因为它通过定义一组规则为下列链接提供了一种方便的机制。它可能不是最适合您的特定网站或项目,但它是足够通用的几种情况,所以你可以从它开始,根据需要覆盖更多的自定义功能,或只是实现自己的爬虫。

除了从Spider继承的属性(你必须指定),这个类支持一个新的属性:

rules
它是一个(或多个)Rule对象的列表。每个都Rule定义了抓取网站的某种行为。规则对象如下所述。如果多个规则匹配相同的链接,则将根据它们在此属性中定义的顺序使用第一个。

这个爬虫还暴露了可覆盖的方法:

parse_start_url(response)
对于start_urls响应调用此方法。它允许解析初始响应,并且必须返回Item对象,Request对象或包含任何对象的迭代器。

抓取规则

class scrapy.spiders.Rule(link_extractor,callback = None,cb_kwargs = None,follow = None,process_links = None,process_request = None )
link_extractor是一个链接提取程序对象,它定义如何从每个爬网页面提取链接。

callback是一个可调用的或字符串(在这种情况下,将使用具有该名称的爬虫对象的方法),以便为使用指定的link_extractor提取的每个链接调用。这个回调接收一个响应作为其第一个参数,并且必须返回一个包含Item和 Request对象(或它们的任何子类)的列表。

警告
当编写爬网爬虫规则时,避免使用parse作为回调,因为CrawlSpider使用parse方法本身来实现其逻辑。所以如果你重写的parse方法,爬行爬虫将不再工作。

cb_kwargs 是包含要传递给回调函数的关键字参数的dict。

follow是一个布尔值,它指定是否应该从使用此规则提取的每个响应中跟踪链接。如果callbackNone follow默认为True,否则默认为False

process_links是一个可调用的或一个字符串(在这种情况下,将使用具有该名称的爬虫对象的方法),将使用指定从每个响应提取的每个链接列表调用该方法link_extractor。这主要用于过滤目的。

process_request 是一个可调用的或一个字符串(在这种情况下,将使用具有该名称的爬虫对象的方法),它将被此规则提取的每个请求调用,并且必须返回一个请求或无(过滤出请求) 。

抓取爬虫示例

现在让我们来看一个CrawlSpider的例子:

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

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # Extract links matching 'category.php' (but not matching 'subsection.php')
        # and follow links from them (since no callback means follow=True by default).
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # Extract links matching 'item.php' and parse them with the spider's method parse_item
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

这个爬虫会开始抓取example.com的主页,收集类别链接和项链接,用parse_item方法解析后者。对于每个项目响应,将使用XPath从HTML中提取一些数据,并将Item使用它填充。

XMLFeedSpider

class scrapy.spiders.XMLFeedSpider
XMLFeedSpider设计用于通过以特定节点名称迭代XML订阅源来解析XML订阅源。迭代器可以选自:iternodes,xml和html。iternodes为了性能原因,建议使用迭代器,因为xml和迭代器html一次生成整个DOM为了解析它。但是,html当使用坏标记解析XML时,使用作为迭代器可能很有用。

要设置迭代器和标记名称,必须定义以下类属性:

  • iterator
    定义要使用的迭代器的字符串。它可以是:

    • 'iternodes' - 基于正则表达式的快速迭代器

    • 'html'- 使用的迭代器Selector。请记住,这使用DOM解析,并且必须加载所有DOM在内存中,这可能是一个大饲料的问题

    • 'xml'- 使用的迭代器Selector。请记住,这使用DOM解析,并且必须加载所有DOM在内存中,这可能是一个大饲料的问题
      它默认为:'iternodes'

itertag
一个具有要迭代的节点(或元素)的名称的字符串。示例:
itertag = 'product'

namespaces
定义该文档中将使用此爬虫处理的命名空间的元组列表。在 与将用于自动注册使用的命名空间 的方法。(prefix, uri)prefixuriregister_namespace()

然后,您可以在属性中指定具有命名空间的itertag 节点。

例:

class YourSpider(XMLFeedSpider):

    namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
    itertag = 'n:url'
    # ...

除了这些新的属性,这个爬虫也有以下可重写的方法:

adapt_response(response)
一种在爬虫开始解析响应之前,在响应从爬虫中间件到达时立即接收的方法。它可以用于在解析之前修改响应主体。此方法接收响应并返回响应(它可以是相同的或另一个)。

parse_node(response, selector)
对于与提供的标记名称(itertag)匹配的节点,将调用此方法。接收Selector每个节点的响应和 。覆盖此方法是必需的。否则,你的爬虫将不工作。此方法必须返回一个Item对象,一个 Request对象或包含任何对象的迭代器。

process_results(response, results)
对于由爬虫返回的每个结果(Items or Requests),将调用此方法,并且它将在将结果返回到框架核心之前执行所需的任何最后处理,例如设置项目ID。它接收结果列表和产生那些结果的响应。它必须返回结果列表(Items or Requests)。

XMLFeedSpider示例

这些爬虫很容易使用,让我们看一个例子:

from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem

class MySpider(XMLFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.xml']
    iterator = 'iternodes'  # This is actually unnecessary, since it's the default value
    itertag = 'item'

    def parse_node(self, response, node):
        self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

基本上我们做的是创建一个爬虫,从给定的下载一个start_urls,然后遍历每个item标签,打印出来,并存储一些随机数据Item。

CSVFeedSpider

class scrapy.spiders.CSVF
这个爬虫非常类似于XMLFeedSpider,除了它迭代行,而不是节点。在每次迭代中调用的方法是parse_row()。

delimiter
CSV文件中每个字段的带分隔符的字符串默认为','(逗号)。

quotechar
CSV文件中每个字段的包含字符的字符串默认为'"'(引号)。

<a href="http://www.php.cn/html/html-HEAD-2.html" target="_blank">head</a>ers
文件CSV Feed中包含的行的列表,用于从中提取字段。

parse_row(response, row)
使用CSV文件的每个提供(或检测到)标头的键接收响应和dict(表示每行)。这个爬虫还给予机会重写adapt_response和process_results方法的前和后处理的目的。

CSVFeedSpider示例

让我们看一个类似于前一个例子,但使用 CSVFeedSpider:

from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem

class MySpider(CSVFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.csv']
    delimiter = ';'
    quotechar = "'"
    headers = ['id', 'name', 'description']

    def parse_row(self, response, row):
        self.logger.info('Hi, this is a row!: %r', row)

        item = TestItem()
        item['id'] = row['id']
        item['name'] = row['name']
        item['description'] = row['description']
        return item

SitemapSpider

class scrapy.spiders.SitemapSpider
SitemapSpider允许您通过使用Sitemaps发现网址来抓取网站。

它支持嵌套Sitemap和从robots.txt发现Sitemap网址 。

sitemap_urls
指向您要抓取的网址的网站的网址列表。

您还可以指向robots.txt,它会解析为从中提取Sitemap网址。

sitemap_rules
元组列表其中:(regex, callback)

  • regex是与从Sitemap中提取的网址相匹配的正则表达式。 regex可以是一个str或一个编译的正则表达式对象

  • callback是用于处理与正则表达式匹配的url的回调。callback可以是字符串(指示蜘蛛方法的名称)或可调用的。

例如:
sitemap_rules = [('/product/', 'parse_product')]

规则按顺序应用,只有匹配的第一个将被使用。
如果省略此属性,则会在parse回调中处理在站点地图中找到的所有网址。

sitemap_follow
应遵循的网站地图的正则表达式列表。这只适用于使用指向其他Sitemap文件的Sitemap索引文件的网站。

默认情况下,将跟踪所有网站地图。

sitemap_alternate_links
指定是否url应遵循一个备用链接。这些是在同一个url块中传递的另一种语言的同一网站的链接。

例如:

<url>
    <loc>http://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>

使用sitemap_alternate_linksset,这将检索两个URL。随着 sitemap_alternate_links禁用,只有http://example.com/将进行检索。

默认为sitemap_alternate_links禁用。

SitemapSpider示例

最简单的示例:使用parse回调处理通过站点地图发现的所有网址 :

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']

    def parse(self, response):
        pass # ... scrape item here ...

使用某个回调处理一些网址,并使用不同的回调处理其他网址:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']
    sitemap_rules = [
        ('/product/', 'parse_product'),
        ('/category/', 'parse_category'),
    ]

    def parse_product(self, response):
        pass # ... scrape product ...

    def parse_category(self, response):
        pass # ... scrape category ...

关注robots.txt文件中定义的sitemaps,并且只跟踪其网址包含/sitemap_shop以下内容的Sitemap :

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]
    sitemap_follow = ['/sitemap_shops']

    def parse_shop(self, response):
        pass # ... scrape shop here ...

将SitemapSpider与其他来源网址结合使用:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]

    other_urls = ['http://www.example.com/about']

    def start_requests(self):
        requests = list(super(MySpider, self).start_requests())
        requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
        return requests

    def parse_shop(self, response):
        pass # ... scrape shop here ...

    def parse_other(self, response):
        pass # ... scrape other here ...

       

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn