xinbiancheng.cn

Scrapy教程

在本教程中,我们假设您的系统上已经安装了Scrapy。如果不是这种情况,请参阅《安装指南》。

判断是否已经安装好了命令如下:

scrapy

如果提示信息如下,就证明安装好了

Scrapy 2.4.0 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench         Run quick benchmark test
  commands
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command

我们将准备用scrape来爬取这个网站 quotes.toscrape.com,该网站列出了很多著名作家的名言。

感谢 quotes.toscrape.com 该网站为为我们提供了测试和学习的数据

本教程将指导您完成以下任务:

  1. 创建一个新的Scrapy项目

  2. 编写spider以爬网站点并提取数据

  3. 使用命令行导出抓取的数据

  4. 更改spider以递归地跟随链接

  5. 使用spider参数

Scrapy用Python编写。如果您是该语言的新手,则可能首先要了解该语言的外观,以充分利用Scrapy。

如果您已经熟悉其他语言,并且想快速学习Python,那么Python教程是一个很好的资源。

如果您不熟悉编程并且想开始使用Python,那么以下书籍可能对您有用:

  • 使用Python自动执行无聊的工作

  • 如何像计算机科学家一样思考

  • 学习Python 3的艰难方法

您还可以查看针对非程序员的Python资源列表,以及learningpython-subreddit中的建议资源。

1.1创建scrapy项目

在开始抓取之前,您将必须创建一个新的Scrapy项目。输入命令(解释tutorial这个是项目的名称)

scrapy startproject tutorial

提示信息如下:

New Scrapy project 'tutorial', using template directory 'c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\scrapy\templates\project', created in:
    C:\Users\Administrator\Scrapy\tutorial

You can start your first spider with:
    cd tutorial
    scrapy genspider example example.com

以上的提示信息,就证明scrapy的项目创建成功了

1.2根据上面的提示信息,是要切换到tutorial这个项目目录接着输入命令:

cd tutorial

scrapy genspider example example.com这个提示,这句话是什么意思呢?这个命令是给我们创建spider模板的,example是spider名,example.com是要爬的网站名,因我们想爬quotes.toscrape.com这个网站

1.3根据以上提示命令如下(创建爬虫模板):

scrapy genspider quotes quotes.toscrape.com

提示信息如下:

Created spider 'quotes' using template 'basic' in module:
  {spiders_module.__name__}.{module}

以上提示说明爬虫的模板已经创建好了

这将创建一个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
              quotes.py

模板创建的quotes.py代码如下:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        pass

如何运行我们的爬虫

要使我们的爬虫工作,请转到项目的顶级目录并运行:

以上是创建了空的模板Spider可以正常运行我们发一个命令爬一下试试

scrapy crawl quotes

显示信息如下:

2020-10-30 20:44:34 [scrapy.utils.log] INFO: Scrapy 2.4.0 started (bot: tutorial)
2020-10-30 20:44:34 [scrapy.utils.log] INFO: Versions: lxml 4.6.1.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 20.3.0, Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1h  22 Sep 2020), cryptography 3.1.1, Platform Windows-10-10.0.17763-SP0
2020-10-30 20:44:34 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.selectreactor.SelectReactor
2020-10-30 20:44:34 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'tutorial',
 'NEWSPIDER_MODULE': 'tutorial.spiders',
 'ROBOTSTXT_OBEY': True,
 'SPIDER_MODULES': ['tutorial.spiders']}
2020-10-30 20:44:34 [scrapy.extensions.telnet] INFO: Telnet Password: cb75f063c9d1ee9c
2020-10-30 20:44:34 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.logstats.LogStats']
2020-10-30 20:44:34 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-10-30 20:44:34 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-10-30 20:44:34 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-10-30 20:44:34 [scrapy.core.engine] INFO: Spider opened
2020-10-30 20:44:34 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-10-30 20:44:34 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-10-30 20:44:35 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2020-10-30 20:44:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/> (referer: None)
2020-10-30 20:44:36 [scrapy.core.engine] INFO: Closing spider (finished)
2020-10-30 20:44:36 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 448,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 2561,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 1,
 'downloader/response_status_count/404': 1,
 'elapsed_time_seconds': 2.000908,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2020, 10, 30, 12, 44, 36, 405167),
 'log_count/DEBUG': 2,
 'log_count/INFO': 10,
 'response_received_count': 2,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/404': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2020, 10, 30, 12, 44, 34, 404259)}
2020-10-30 20:44:36 [scrapy.core.engine] INFO: Spider closed (finished)

以上提示信息显示已经爬完了是成功的([scrapy.core.engine] INFO: Spider closed (finished))。

我们的第一只爬虫就弄好了

QuotesSpider是您定义的类,Scrapy用于从网站(或一组网站)中获取信息。 
他们必须Spider继承并定义要发出的初始请求,可以选择如何遵循页面中的链接,
以及如何解析下载的页面内容以提取数据。

以上的我们第一个Spider的代码。只是顺利的执行完成了,并没有爬下什么,没有成就感,用下面的代码去替换一下之前的代码:

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 = f'quotes-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log(f'Saved file {filename}')

如您所见,我们的Spider子类化scrapy.Spider 并定义了一些属性和方法:

  • name:标识蜘蛛。它在一个项目中必须是唯一的,也就是说,不能为不同的Spider设置相同的名称。

  • start_requests():必须返回一个可迭代的请求(您可以返回一个请求列表或编写一个生成器函数),Spider将从中开始爬行。随后的请求将根据这些初始请求连续生成。

  • parse():一种方法,将调用该方法来处理针对每个请求下载的响应。response参数是一个实例,TextResponse它保存页面内容,并具有其他有用的方法来处理它。

    parse()方法通常解析响应,将提取的数据作为dict提取,还查找要遵循的新URL并Request从中创建新请求()。

再次执行命令如下命名

scrapy crawl quotes

此命令使用quotes我们刚刚添加的名称运行Spider ,它将发送对该quotes.toscrape.com域的一些请求。您将获得类似于以下的输出:

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

现在,检查当前目录中的文件。您应该注意,已经创建了两个新文件:quotes-1.htmlquotes-2.html,按照我们的parse方法说明,其内容分别为URL 。

注意

如果您想知道为什么我们还没有解析HTML,请稍候,我们将尽快解决。

到底发生了什么?

Scrapy计划scrapy.Request通过start_requests Spider方法返回的对象。在收到每个响应时,它实例化Response对象并调用与请求关联的回调方法(在本例中为该 parse方法),并将响应作为参数传递。

start_requests方法的快捷方式

无需实现从URLstart_requests()生成scrapy.Request对象的方法,您只需定义start_urls带有URL列表的类属性即可。然后,默认实现的将使用此列表start_requests()为您的蜘蛛创建初始请求:

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 = f'quotes-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)

parse()即使我们没有明确告诉Scrapy这样做,也将调用该方法来处理对这些URL的每个请求。发生这种情况的原因parse()是Scrapy的默认回调方法,该方法针对没有显式分配的回调的请求被调用。

提取数据

学习如何使用Scrapy提取数据的最好方法是使用Scrapy shell尝试选择器。跑:

scrapy shell 'http://quotes.toscrape.com/page/1/'

注意

请记住,从命令行运行Scrapy shell时,请始终将网址括在引号中,否则包含参数(即&字符)的网址将不起作用。

在Windows上,请使用双引号代替:

scrapy shell "http://quotes.toscrape.com/page/1/"

您将看到类似以下内容:

[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7fa91d888c90>
[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 0x7fa91d888c10>
[s]   spider     <DefaultSpider 'default' at 0x7fa91c8af990>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

使用外壳,您可以尝试使用带有响应对象的CSS选择元素:

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

运行的结果response.css('title')是一个名为的类似列表的对象 SelectorList,该Selector对象表示围绕XML / HTML元素的对象的列表, 并允许您运行进一步的查询以细化选择或提取数据。

要从上面的标题中提取文本,您可以执行以下操作:

>>> response.css('title::text').getall()
['Quotes to Scrape']

这里有两点需要注意:一是我们已经添加::text到CSS查询中,这意味着我们只想直接在<title>element内部选择文本元素 。如果不指定::text,我们将获得完整的title元素,包括其标签:

>>> response.css('title').getall()
['<title>Quotes to Scrape</title>']

另一件事是调用的结果.getall()是一个列表:选择器可能返回多个结果,因此我们将它们全部提取出来。当您知道只需要第一个结果时,在这种情况下,您可以执行以下操作:

>>> response.css('title::text').get()
'Quotes to Scrape'

或者,您可以编写:

>>> response.css('title::text')[0].get()
'Quotes to Scrape'

但是,.get()直接在SelectorList 实例上使用避免了anIndexErrorNone在找不到与选择匹配的任何元素时返回。

这里有一个教训:对于大多数抓取代码,您希望它能够对由于页面上找不到内容而导致的错误具有弹性,因此即使某些部分未能被抓取,您也至少可以获取一些数据。

除了getall()和 get()方法之外,您还可以使用re()方法使用正则表达式进行提取 :

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

为了找到合适的CSS选择器,您可能会发现使用从Web浏览器中的Shell打开响应页面很有用view(response)。您可以使用浏览器的开发人员工具检查HTML并提供一个选择器(请参阅使用浏览器的开发人员工具进行抓取)。

Selector Gadget还是一个不错的工具,可以快速为视觉选择的元素找到CSS选择器,该选择器可在许多浏览器中使用。

XPath:简要介绍

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

>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'

XPath表达式非常强大,并且是Scrapy Selectors的基础。实际上,CSS选择器是在后台转换为XPath的。您可以看到,如果您仔细阅读了外壳中选择器对象的文本表示形式。

尽管XPath表达式可能不如CSS选择器流行,但它提供了更多功能,因为除了导航结构之外,它还可以查看内容。使用XPath,您可以选择以下内容:选择包含文本“下一页”的链接。这使XPath非常适合于抓取任务,并且即使您已经知道如何构造CSS选择器,我们也鼓励您学习XPath,这将使抓取更加容易。

我们不会在这里介绍XPath,但是您可以在此处阅读有关将XPath与Scrapy Selectors结合使用的更多信息。要了解有关XPath的更多信息,我们建议本教程通过示例学习XPath,并建议本教程学习“如何在XPath中思考”。

提取报价和作者

现在您对选择和提取有所了解,让我们通过编写代码从网页中提取引号来完善蜘蛛程序。

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'

我们获得带有HTML报价的选择器的列表,其中包括:

>>> response.css("div.quote")
[<Selector xpath="descendant-or-self::div[@class and contains(concat(' ', normalize-space(@class), ' '), ' quote ')]" data='<div class="quote" itemscope itemtype...'>,
 <Selector xpath="descendant-or-self::div[@class and contains(concat(' ', normalize-space(@class), ' '), ' quote ')]" data='<div class="quote" itemscope itemtype...'>,
 ...]

上面的查询返回的每个选择器都允许我们在其子元素上运行进一步的查询。让我们将第一个选择器分配给一个变量,以便我们可以在特定的引号上直接运行CSS选择器:

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

现在,让我们使用刚刚创建的对象从中提取textauthortags从该引号中提取quote

>>> text = quote.css("span.text::text").get()
>>> text
'“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").get()
>>> author
'Albert Einstein'

假设标签是一个字符串列表,我们可以使用.getall()方法来获取所有标签:

>>> tags = quote.css("div.tags a.tag::text").getall()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']

在弄清楚如何提取每一位之后,我们现在可以遍历所有引号元素并将它们放到Python字典中:

>>> for quote in response.css("div.quote"):
...     text = quote.css("span.text::text").get()
...     author = quote.css("small.author::text").get()
...     tags = quote.css("div.tags a.tag::text").getall()
...     print(dict(text=text, author=author, tags=tags))
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
{'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'author': 'J.K. Rowling', 'tags': ['abilities', 'choices']}
...

在我们的spider中提取数据

让我们回到蜘蛛。到目前为止,它没有特别提取任何数据,只是将整个HTML页面保存到本地文件中。让我们将上面的提取逻辑集成到我们的Spider中。

Scrapy蜘蛛通常会生成许多字典,其中包含从页面提取的数据。为此,我们yield在回调中使用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('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

如果运行此蜘蛛,它将输出提取的数据和日志:

2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}

存储抓取的数据

存储已抓取数据的最简单方法是使用Feed输出,并使用以下命令:

scrapy crawl quotes -O quotes.json

这将生成一个quotes.json文件,其中包含所有以JSON序列化的刮掉的物品。

-O命令行开关覆盖任何现有的文件; 使用-o而不是新的内容附加到任何现有文件。但是,附加到JSON文件会使文件内容无效JSON。附加到文件时,请考虑使用其他序列化格式,例如JSON Lines:

scrapy crawl quotes -o quotes.jl

该JSON行格式是因为它的流状,你可以很容易地追加新的记录,它是有用的。当您运行两次时,它不会出现相同的JSON问题。另外,由于每条记录都是单独的一行,因此您可以处理大文件而不必将所有内容都放入内存中,因此有类似JQ的工具可以在命令行上帮助完成此任务。

在小型项目中(例如本教程中的项目),这应该足够了。但是,如果要对已刮除的物料执行更复杂的操作,则可以编写“物料管道”。在创建项目时,已为您设置了“项目管道”的占位符文件 tutorial/pipelines.py。虽然如果您只想存储已刮除的项目,则无需实现任何项目管道。

使用蜘蛛参数

您可以通过-a 在运行蜘蛛时使用选项来为蜘蛛提供命令行参数:

scrapy crawl quotes -O quotes-humor.json -a tag=humor

这些参数将传递给Spider的__init__方法,并在默认情况下成为Spider属性。

在此示例中,为参数提供的值tag可通过访问self.tag。您可以使用它使您的Spider只获取带有特定标记的引号,并根据以下参数构建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').get(),
                'author': quote.css('small.author::text').get(),
            }

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

如果您将tag=humor参数传递给该蜘蛛,则会注意到它只会访问humor标记中的网址,例如 http://quotes.toscrape.com/tag/humor

您可以在此处了解有关处理蜘蛛参数的更多信息。

下一步

本教程仅介绍了Scrapy的基础知识,但这里没有提到很多其他功能。检查还有什么?在部分 一目了然Scrapy为最重要的简要概述章节。

您可以从“基本概念”部分继续,以进一步了解命令行工具,蜘蛛网,选择器以及本教程未涵盖的其他内容,例如对抓取的数据进行建模。如果您喜欢玩示例项目,请查看“示例”部分。