python3-Scrapy教程

本文将涉及以下内容:

    0. Scrapy安装;

    1. 创建Scrapy项目;

    2. 写一个spider来爬取网页并提取数据;

    3. 用命令行来输出爬到的数据;

    4. 修改spider来递归跟踪连接;

    5. 使用spider参数;

安装:

如果你是使用Anaconda,可以通过conda-forge通道安装:

conda install -c conda-forge scrapy

创建Scrapy项目:

在开始爬虫之前,你得先创建一个新的Scrapy工程。进入你想存放代码的目录然后运行:

scrapy startproject tutorial

这将创建一个包含了以下内容的tutorial目录:

tutorial/
    scrapy.cfg            # deploy configuration file

    tutorial/             # project's Python module, you'll import your code from here
        __init__.py

        items.py          # project items definition file

        middlewares.py    # project middlewares file

        pipelines.py      # project pipelines file

        settings.py       # project settings file

        spiders/          # a directory where you'll later put your spiders
            __init__.py

第一个Spider(爬行器):

Spider是一个类,这个类由你定义并由Scrapy用于从网站爬取信息。你必须继承scrapy.Spider定义好初始请求,如何在一个网页中跟踪连接(可选),如何解释下载的页面以提取数据(可选)。

下面是我们的第一个Spider代码,保存在你工程的totorial/spiders目录下,一个名为quotes_spider.py的文件中:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

正如你看到的,我们的Spider继承了scrapy.Spider定义了一些属性和方法:

  • name:Spidder的标识。在工程中必须唯一,也就是说你不能为不同的Spiders取同一个名字。
  • start_requests():必须返回一个可迭代的请求(你可以返回一个请求列表,或者写一个生成器函数),Spider将从这些请求开始爬取。
  • parse():一个用于处理响应的方法。响应参数是一个TextResponse对象,它包含了页面的内容和一些额外的辅助处理方法。parse()方法通常用于解析响应、将提取爬取数据转为字典、发现新的URL来跟踪、并从中创建新的请求(Request)。

如果运行我们的spider:

为了主我们的spider开始工作,进入工程的根目录并运行:

scrapy crawl quotes

这个命令用我们刚刚添加的quotes这个名字来运行spider,这将向quotes.toscrape.com这个域名发送一些请求。你将看到类似以下的输出:

... (omitted for brevity)
2018-03-18 09:12:35 [scrapy.core.engine] INFO: Spider opened
2018-03-18 09:12:35 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-03-18 09:12:35 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6024
2018-03-18 09:12:35 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2018-03-18 09:12:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2018-03-18 09:12:36 [quotes] DEBUG: Saved file quotes-1.html
2018-03-18 09:12:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2018-03-18 09:12:36 [quotes] DEBUG: Saved file quotes-2.html
2018-03-18 09:12:36 [scrapy.core.engine] INFO: Closing spider (finished)
...

现在检查一下你的当前目录,你会发现多了两个新文件:quotes-1.html和quotes-2.html,它们分别包含了URLs中的内容——正如我们的解析方法所提示的一样。

注:为什么我们还不解析HTML呢?如果你对此感到疑惑,请不要着急,我们将在后面讲解。
面纱下面到底发生了什么?

Scrapy调度从start_questts方法返回的scrapy.Reqest对象。在收到每个响应后,它实例化响应对象并调用与请求相关联的回调方法(在本例中,是parse()方法),并以响应作为参数。

start_requests方法的快捷方式:

实现start_requests()方法以根据URLs生成scrapy.Request的替代方案:用一个URL列表来定义start_usrls这个类属性。这个列表将被start_requests()的默认实现方法调用,并为你的spider生成初始请求:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)

parse()方法将被调用以处理每一个由这些urls请求(产生的响应),然而我们并没有显式地告诉Scrapy这么做。这是因为parse()是Scrapy的默认回调方法,没有显式指定回调它将被请求调用。

提取数据:

学习使用Scrapy提取数据的最好方法是用Scrapy shell来练习selectors(选择器)。运行:

scrapy shell 'http://quotes.toscrape.com/page/1/'
注意:在windows中,要用双引号:

scrapy shell "http://quotes.toscrape.com/page/1/"
你将看到类似以下输出:
2018-03-18 09:54:52 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2018-03-18 09:54:52 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-03-18 09:54:52 [scrapy.core.engine] INFO: Spider opened
2018-03-18 09:54:53 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2018-03-18 09:54:54 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2018-03-18 09:54:55 [traitlets] DEBUG: Using default logger
2018-03-18 09:54:55 [traitlets] DEBUG: Using default logger
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x000002901A26BBE0>
[s]   item       {}
[s]   request    <GET http://quotes.toscrape.com/page/1/>
[s]   response   <200 http://quotes.toscrape.com/page/1/>
[s]   settings   <scrapy.settings.Settings object at 0x000002901C90CA90>
[s]   spider     <DefaultSpider 'default' at 0x2901cb17f60>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

使用shell,你可以尝试使用CSS与响应对象选择元素:

>>>  response.css('title')
Out[1]: [<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

调用response.css('title')的返回值看起来像一个列表,它叫SelectorList,代表一系列的Selector对象,这些对象被XML/HTML元素包裹着,这允许你进行进一步查询,使选择或提取数据更加细化。

如果要提取title的文本,可以这样:

>>> response.css('title::text').extract()
Out[1]: ['Quotes to Scrape']

这里有有两点要注意:一是我们把::text加入到CSS查询中,以表示我们想直接选中<title>元素中的文件。如果我们没有指定::text,我们将取得整个title元素,包括它的tags。

>>> response.css('title').extract()
Out[2]: ['<title>Quotes to Scrape</title>']

另外一点是:调用.extract()返回的结果是一个列表,因为我们正在处理的是一个SelectorList对象。当你确定只是想要第一个结果时,比如本例中,你可以这样:

>>>  response.css("title::text").extract_first()
Out[3]: 'Quotes to Scrape'

或者这样:

>>>   response.css("title::text")[0].extract()
Out[4]: 'Quotes to Scrape'

然而,用.extract_first()可以避免IndexError错误,并在无法找到任何匹配元素的时候返回None。

一个经验教训:对于大多数的爬虫代码,一般是希望它因无法在网页找到东西而产生错误时可以恢复正常,这样即使局部失败仍然可以取得一些部分数据。

除了extract()和exract_first()之外,你还可以用re()方法——通过正则表达式来提取:

>>> response.css('title::text').re(r'Quotes.*')
Out[5]: ['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w')
Out[6]: ['Qu']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
Out[7]: ['Quotes', 'Scrape']

为了找到完整的CSS选择器,你可以在shell中通过view(response)指令使响应页面在浏览器中打开。你还可以利用你的浏览器的开发者工具或者诸如Firebug的插件。

Selector Gadget也是一款很nice的工具,用于快速查找CSS选择器,可以可视化选择元素,很多浏览器都支持。

Xpath 简介:

除了CSS之外,Scrapy选择器还支持XPath表达式:

>>> response.xpath('//title')
Out[9]: [<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>>  response.xpath('//title/text()').extract_first()
Out[10]: 'Quotes to Scrape'

XPath表达式非常强大,是Scrapy选择器的基础。实际上,CSS选择器在底层是转换为XPath。如果你仔细阅读shell中的文本就会发现这一点。

XPath表达式虽然没有CSS那么流行,但是它更加强大,因为除了导航结构它还可以查看内容。使用XPath,你可以这样选择东西:select the link that contains the text "Next Page"。这使得XPath非常适合爬虫,我们也鼓励你去学习XPath,即使你已经知道如何构造CSS选择器,这样将会使爬虫更加轻松。

这里不想太过详细的讲解XPath,但是你可以在这里取得更多关于在Scrapy选择器中使用Xpath的用法。关于学习XPath,我们推荐这个通过例子来学习XPath教程,和如何用XPath思考

提取引用和作者:

现在你已经对选择和提取有所了解了,我们将完成我们的spider,并写代码从网页中提取引用。

http://quotes.toscrape.com中的每一个引用都是类似下面的HTML元素来表述的:

<div class="quote">
    <span class="text">“The world as we have created it is a process of our
    thinking. It cannot be changed without changing our thinking.”</span>
    <span>
        by <small class="author">Albert Einstein</small>
        <a href="/author/Albert-Einstein">(about)</a>
    </span>
    <div class="tags">
        Tags:
        <a class="tag" href="/tag/change/page/1/">change</a>
        <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
        <a class="tag" href="/tag/thinking/page/1/">thinking</a>
        <a class="tag" href="/tag/world/page/1/">world</a>
    </div>
</div>

让我们打开scrapy shell来演示如何提取我们想要的数据:

$ scrapy shell "http://quotes.toscrape.com"

我们像这样获取quote HTML元素选择器:

>>> response.css("div.quote")

对以上查询返回的每一个选择器我们都可以对它们的子元素进行进一步的查询。我们把第一个选择器放到一个变量里,这样我们就可以用一种特定的quote运行我们的CSS选择器了:

>>> quote = response.css("div.quote")[0]

现在,让我们从刚刚创建的quote中提取标题、作者和标签:

>>> title = quote.css("span.text::text").extract_first()
>>> title
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
>>> author = quote.css("small.author::text").extract_first()
>>> author
'Albert Einstein'

由于标签是字符串列表,我们用.extract()方法来提取它们:

>>> tags = quote.css("div.tags a.tag::text").extract()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
已经搞清楚如何提取每一个比特,我们现在可以 现在我们可以遍历所有的引号元素并将它们组合到一个Python字典中:
>>> for quote in response.css("div.quote"):
...     text = quote.css("span.text::text").extract_first()
...     author = quote.css("small.author::text").extract_first()
...     tags = quote.css("div.tags a.tag::text").extract()
...     print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'}
    ... a few more of these, omitted for brevity
>>>

在我们的spader中提取数据:

回到我们的spider,现在为止它并未提取任何特别的数据,只是把整个HTML页面保存到本地。让我们把上述提取逻辑集成到我们的spider里面去。

一个典型的爬虫spider一般会生成许多包含了从页面所提取的数据的字典。为了达到这个效果,我们回调中使用Python的关键字yield,像下面看到的这样:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

运行这个spider,它将在日志中输出所提取的数据:

2018-03-18 21:15:42 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/2/>
{'text': '“Good friends, good books, and a sleepy conscience: this is the ideal life.”', 'author': 'Mark Twain', 'tags': ['books', 'contentment', 'friends', 'friendship', 'life']}
2018-03-18 21:15:42 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/2/>
{'text': '“Life is what happens to us while we are making other plans.”', 'author': 'Allen Saunders', 'tags': ['fate', 'life', 'misattributed-john-lennon', 'planning', 'plans']}

保存爬取到的数据:

最简单的方法是用以下命令”喂养输出“:

scrapy crawl quotes -o quotes.json

这将会生成一个quotes.json,里面包含了爬取到的所有记录。

出于历史原因,Scrapy是以追加而非的方式输出到给定文件。如果你运行了再次这个命令,且在第二次运行前没有将前面生成的文件删除,你将得到一个破损的JSON文件。

你也可以用其它的格式,像JSON Lines:

scrapy crawl quotes -o quotes.jl

JSON Line格式是非常有用的,因为它是”流式“的,很容易增加新记录。重复运行时不存在像JSON那样的问题。而且每一条记录是一个单行,你在处理大文件时不必把所有东西都腾到内存中,还有JQ等工具来帮助你在命令行这样做。

在小工程中(如本例中的tutorial),这已经足够了。然而,如果你想在爬取的记录中执行更加复杂的事情,你可以写一个记录管道(Item Pipeline)。在你创建工程的时候,一个Item Pipleline的点位文件已经构造好了,在tutorial/pipelines.py中。但如果你仅仅是想保存爬取的内容,那你不必实现任何item pipelines。

连接跟踪:

假设,如果你不仅仅是想从http://quotes.toscrape.com中爬取前面两面,而是想要网站的所有引用。

现在已经知道如何从页面中提取数据,接下来我们来看看怎么跟踪连接。

首先,要提取我们要跟踪的页面。检查我们的页面,在下面的标识中,我们可以发现一个指向下一页面的连接:

<ul class="pager">
    <li class="next">
        <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a>
    </li>
</ul>

我们可以尝试在shell中把它提取出来:

>>> response.css('li.next a').extract_first()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'

这是一个锚元素,而我们想要的是其中的href属性。为此,Scrapy支持一个CSS扩展方便我们选择属性内容,如下:

>>> response.css('li.next a::attr(href)').extract_first()
'/page/2/'

看如何修改我们的spider来递归跟踪下一个页面,并从中提取数据:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)

现在,parse()方法在提取数据后还查找指向下一页面的连接,并用urljoin()方法构建一个完整的绝对URL(由于连接有可能是相对的)并生成一个指向下一页面的新请求,将自身注册为回调以处理下一页的数据提取, 并保持看待遍历所有页面。

从Scrapy的跟踪机制中你可以看到:当你在回调中生成一个请求时,Scrapy将安排发送这个请求,并注册一个回调方法,当该请求完成时,将执行回调方法。

创建请求的快捷方式:

作为创建请求对象的快捷方式,你可以使用response.follow:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('span small::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)

跟scrapy.Request不一样的是,response.follow支持相对URL路径——不必调用urljoin。注意,response.follow只返回一个请求对象;你还是要生成这个请求。

你还可以用一个选择器代替字符串传给response.follow,但这个选择器需要提取必要的属性:

for href in response.css('li.next a::attr(href)'):
    yield response.follow(href, callback=self.parse)

注意:response.follow(response.css('li.next a'))是不行的,因为response.css返回的是一个包含所有结果的选择器列表,不是一个单一的选择器。上例中的for循环,或者response.foolow(response.css('li.next a')[0]则是可以的。

更多的例程及模式:

这是另外一个说明回调和跟踪连接的spider例程,这次提取的是作者信息:

import scrapy


class AuthorSpider(scrapy.Spider):
    name = 'author'

    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        # follow links to author pages
        for href in response.css('.author + a::attr(href)'):
            yield response.follow(href, self.parse_author)

        # follow pagination links
        for href in response.css('li.next a::attr(href)'):
            yield response.follow(href, self.parse)

    def parse_author(self, response):
        def extract_with_css(query):
            return response.css(query).extract_first().strip()

        yield {
            'name': extract_with_css('h3.author-title::text'),
            'birthdate': extract_with_css('.author-born-date::text'),
            'bio': extract_with_css('.author-description::text'),
        }

这个spider从主页面开始,它将跟踪所有指向作者页面的连接,并以parse_author作为每次跟踪的回调,而页码连接的回调仍然是我们之前看到的parse。

parse_author回调为提取定义了一个辅助方法,以从CSS查询中提取和清理数据,并使用作者数据生成Python代码。

这个spider展示的另一个有趣的事情是, 即使同一作者有很多引用,我们也不需要担心多次访问同一作者页面。默认地,Scrapy会过滤掉已经访问过的url的重复请求,以避免由于编程错误而导致频繁访问服务器。这可以通过DUPEFILTER_CLASS进行配置。

希望你现在已经很好地理解如何在Scrapy中使用跟踪机制和回调。

同样,一个常见的模式是用来自多个页面的数据构建一个项目,使用一个技巧将额外的数据传递给回调函数

使用spider参数:

你可以在运行时使用-a选项向爬行器提供命令行参数:

scrapy crawl quotes -o quotes-humor.json -a tag=humor
这些参数传递给了spider的的__init__方法,并默认成为spider的属性。

在本例中,标签参数提供的值将通过 self.tag提供。你可以用这个来让你的爬行器只抓取带有特定标签的引用,根据参数构建URL:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        url = 'http://quotes.toscrape.com/'
        tag = getattr(self, 'tag', None)
        if tag is not None:
            url = url + 'tag/' + tag
        yield scrapy.Request(url, self.parse)

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

如果你把tag=humor参数传给这个爬行器,你会看到它将只访问humor标签中的URL,如http://quotes.toscrape.com/tag/humor。

你可以在这里学到更多关于处理爬行器参数的知识。

下一步:

本教程只包含了Scrapy的基础,但还有很多其它的特性没有这里提到,选择器和教程没有涉及到其它内容,比如对爬取的数据进行建模。如果你使用示例项目,请查看示例部分。



  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Scrapy是一个基于Python的爬虫框架,它可以帮助我们快速高效地抓取网站数据。在这里,我将介绍Scrapy的基本用法,让您能够快速入门。 安装Scrapy ----------------------- 在安装Scrapy之前,我们需要先安装Python。然后,我们可以通过以下命令来安装Scrapy: ``` pip install scrapy ``` 创建Scrapy项目 ----------------------- 创建Scrapy项目的命令是: ``` scrapy startproject project_name ``` 这个命令将会在当前目录下创建一个名为project_name的文件夹,其中包含了Scrapy项目的基本结构。 编写Spider ----------------------- 在Scrapy中,Spider是用来定义爬取网站的规则的。我们可以通过以下命令来创建一个Spider: ``` scrapy genspider spider_name domain_name ``` 其中,spider_name是我们自己定义的Spider名称,domain_name是我们要抓取的网站域名。 接下来,我们需要在Spider中定义如何爬取网站。这里我们以爬取“http://quotes.toscrape.com/”网站上的名言警句为例。我们可以在Spider中定义如下规则: ```python import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').get(), 'author': quote.css('span small::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), } next_page = response.css('li.next a::attr(href)').get() if next_page is not None: yield response.follow(next_page, self.parse) ``` 在上述代码中,我们首先定义了Spider的名称,接着定义了我们要爬取的起始URL,最后定义了如何解析网页的函数parse()。在parse()函数中,我们使用了Scrapy的选择器来提取网页中的名言警句,并将其保存到字典中。接着,我们使用response.follow()函数来获取下一页的URL,并继续解析。 运行Spider ----------------------- 要运行我们刚才创建的Spider,我们可以使用以下命令: ``` scrapy crawl spider_name ``` 其中,spider_name是我们之前创建的Spider名称。 Scrapy会自动去抓取我们定义的起始URL,并根据我们定义的规则来解析网页。解析完成后,Scrapy会将结果保存到我们指定的位置。 总结 ----------------------- Scrapy是一个非常强大的Python爬虫框架,它可以帮助我们快速高效地抓取网站数据。在本教程中,我们介绍了Scrapy项目的创建、Spider的定义以及如何运行Spider。如果您想更深入地学习Scrapy,可以参考官方文档:https://docs.scrapy.org/en/latest/。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值