大家好,感谢邀请,今天来为大家分享一下《Scrapy入门教程》第三章:爬虫基础知识的问题,以及和的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!
本章非常重要,您可能需要阅读几遍或寻找问题的解决方案。我们将从如何安装Scrapy开始,然后通过案例讲解如何编写爬虫。在我们开始之前,有几点需要注意。
由于我们即将进入编程的有趣部分,因此使用本书中的代码片段非常重要。当你看到:
$ 回显你好世界
Hello world是让你在终端输入echo hello world(忽略$),第二行是看结果。
当你看到:
打印“嗨”
hi让你进入Python或Scrapy界面(忽略)。同样,第二行是输出结果。
您还需要编辑该文件。编辑工具取决于您的计算机环境。如果您使用Vagrant(强烈推荐),则可以使用文本编辑器,例如Notepad、Notepad++、Sublime Text、TextMate、Eclipse 或PyCharm。如果你对Linux/Unix比较熟悉,可以使用控制台自带的vim或者emacs。这两个编辑器功能强大,但有一定的学习曲线。如果您是初学者,可以选择适合初学者的nano编辑器。
安装ScrapyScrapy的安装比较简单,但是也取决于读者的电脑环境。为了支持更多的人,本书安装和使用Scrapy的方法是使用Vagrant,它可以让你在一个Linux盒子里使用所有工具,而不管操作系统是什么。下面提供了Vagrant 和一些常见操作系统的说明。
MacOS为了轻松阅读本书,请参阅以下Vagrant 说明。如果你想在MacOS上安装Scrapy,只需在控制台输入:
$ easy_install scrapy 然后,一切就可以交给电脑了。安装过程中可能会询问你的密码或者是否安装Xcode,同意即可。
Windows在Windows 中安装Scrapy 有点棘手。另外,本书的所有软件在Windows上安装起来非常麻烦。我们已经为您想到了可能会出现的问题。 Vagrant with Virtualbox 可在所有64 位计算机上流畅运行。通读相关章节,只需几分钟即可安装完毕。如果你确实想在Windows中安装它,请参考本书网站http://scrapybook.com/上的信息。
Linux您可以在各种Linux服务器上安装Scrapy。步骤如下:
提示:确切的安装依赖关系变化很快。在撰写本文时,Scrapy 版本为1.0.3(翻译时为1.4)。以下只是针对不同服务器的建议方法。
Ubuntu或Debian Linux为了使用apt在Ubuntu(测试机是Ubuntu 14.04 Trusty Tahr - 64位)或其他服务器上安装Scrapy,可以使用以下三个命令:
$ sudo apt-get 更新
$ sudo apt-get install python-pip python-lxml python-crypto python-
cssselect python-openssl python-w3lib python-twisted python-dev libxml2-
dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
$ sudo pip install scrapy 此方法需要编译,并且可能随时中断,但您可以在PyPI 上安装最新版本的Scrapy。如果想避免编译并且安装的不是最新版本,可以搜索“安装Scrapy Ubuntu packages”并按照官方文档进行安装。
Red Hat或CentOS Linux使用yum 在Linux 上安装Scrapy 也很简单(测试机器是Ubuntu 14.04 Trusty Tahr - 64 位)。只需三个命令:
须藤百胜更新
sudo yum -y 安装libxslt-devel pyOpenSSL python-lxml python-devel gcc
sudo easy_install scrapy从GitHub安装按照前面的说明安装Scrapy 的依赖项。 Scrapy 是用纯Python 编写的。如果您想编辑源代码或测试最新版本,可以从https://github.com/scrapy/scrapy 克隆最新版本。只需在命令行输入:
$ git clonehttps://github.com/scrapy/scrapy.git
$ cd scrapy
$ python setup.py install 我想如果你是这种类型的用户,你不需要我提醒你安装virtualenv。
升级ScrapyScrapy 升级相当频繁。如果需要升级Scrapy,可以使用pip、easy_install或aptitude:
$ sudo pip install --upgrade Scrapy 或
$ sudo easy_install --upgrade scrapy 如果您想降级或安装特定版本的Scrapy,您可以:
$ sudo pip install Scrapy==1.0.0 或
$ sudo easy_install scrapy==1.0.0Vagrant:本书案例的运行方法本书中的一些示例比较复杂,并且一些示例使用了很多东西。无论您的水平如何,请尝试运行所有示例。只需一条命令,即可使用Vagrant搭建运行环境。
在本书使用的Vagrant 系统中,你的计算机被称为“主机”。 Vagrant 在主机上创建一个虚拟机。这使得我们可以忽略主机软件和硬件并运行该案例。
本书的大部分章节都使用了两种服务,即——开发机和网络机。我们登录在开发机上运行Scrapy,在网络机上进行爬虫。后面的章节会用到更多的服务,包括数据库和大数据处理引擎。
按照附录A的安装要求安装Vagrant,直至安装好git和Vagrant。打开命令行输入以下命令即可获取本书的代码:
$ git克隆https://github.com/scalingexcellence/scrapybook.git
$ cd scrapybook 打开Vagrant:
$ vagrant up --no-parallel 首次打开Vagrant 可能需要一些时间,具体取决于您的网络。第二次打开会更快。打开后,通过以下方式登录到您的虚拟机:
$vagrant ssh 代码已从主机复制到开发机器,现在可以在本书的目录中看到:
$ CD 书
$ls
$ ch03 ch04 ch05 ch07 ch08 ch09 ch10 ch11.可以打开几个窗口,输入vagrant ssh,这样就可以打开多个终端了。输入vagrant stop 关闭系统,输入vagrantstatus 检查状态。 vagrant stop 无法关闭虚拟机。如果你在VirtualBox 中遇到问题,可以手动关闭它,或者使用vagrant global-status 查找id,并使用vagranthalt 暂停它。大多数示例都可以离线运行,这是Vagrant 的一大优势。
安装好环境后,就可以开始学习Scrapy了。
UR2IM——基础抓取过程每个网站都是不同的,对每个网站进行额外的研究是不可避免的。如果您遇到特别不常见的问题,您可能还需要查阅Scrapy 邮件列表。要找到答案,在哪里找到答案,如何找到答案,你必须熟悉整个流程和相关术语。 Scrapy的基本流程可以写成缩写UR2IM,如下图所示。
The URL一切都从URL 开始。您需要目标网站的URL。我的例子是https://www.gumtree.com/,一个Gumtree分类网站。
例如,访问伦敦房地产主页http://www.gumtree.com/flats-houses/london,你会发现很多房子的网址。右键单击链接地址以复制URL。其中一个URL 可能如下所示:https://www.gumtree.com/p/studios-bedsits-rent/split-level。但是,Gumtree网站发生变化后,URL的XPath表达式将变得无效。如果不添加用户标头,Gumtree 将不会响应。我稍后再讲,但现在如果你想加载网页,你可以使用Scrapy 终端,如下所示:
scrapy shell -s USER_AGENT="Mozilla/5.0" 进行调试时,可以在Scrapy语句后添加--pdb,例如:
scrapy shell --pdb https://gumtree.com 我们不希望大家如此频繁地点击Gumtree 网站,而且Gumtree 网站上的URL 很快就会过期,所以不适合用作示例。我们也希望大家能够离线练习书中的例子。这就是为什么Vagrant开发环境内置了一个Web服务器,可以生成类似于Gumtree的网页。这些网页可能看起来不太好,但从爬虫开发者的角度来看,它们是完全合格的。如果想在Vagrant上访问Gumtree,可以在Vagrant开发机上访问http://web:9312/,或者在浏览器中访问http://localhost:9312/。
让我们在这个网页上尝试一下Scrapy。在Vagrant 开发机器上,输入:
$ scrapy shell http://web:9312/properties/property_000000.html
.
[s] 可用的Scrapy 对象:
[s]抓取工具[s]项{}
[s]请求[s]响应200 http://web:9312/./property_000000.html[s]设置[s]蜘蛛[s]有用的快捷方式:
[s] help() Shell 帮助(打印此帮助)
[s] fetch(req_or_url) 获取请求(或URL)并更新本地.
[s] view(response) 在浏览器中查看响应
获取一些输出,加载页面,然后输入Python(可以使用Ctrl+D 退出)。
请求和响应在前面的输出日志中,Scrapy自动帮我们做了一些工作。我们输入一个地址,Scrapy 发出GET 请求,并得到成功响应值为200。这意味着网页信息已成功加载并可供使用。如果我们想打印response.body的前50个字母,我们会得到:
响应.body[:50]
"nnn这是此Gumtree 网页的HTML 文档。有时请求和响应可能很复杂。它们将在第5 章中解释,但现在我们只讨论最简单的情况。
抓取对象下一步是从响应文件中提取信息并将其输入到Item 中。由于这是一个HTML 文档,因此我们使用XPath 来完成它。首先看一下这个页面:
页面上有很多信息,但大部分都是关于布局的:标志、搜索框、按钮等。从爬行的角度来看,它们并不重要。例如,我们关注列表的标题、地址、电话号码。它们都对应于HTML 中的元素。我们需要在HTML 中找到它们,并使用我们在上一章中学到的知识来提取它们。我们先从标题开始吧。
右键单击标题并选择检查元素。再次右键单击自动定位的HTML,然后选择复制XPath。 Chrome 提供的XPath 总是很复杂并且容易失败。我们将简化它。我们只取最后一个h1。这是因为从SEO 的角度来看,每页HTML 最好只有一个h1。事实上,大多数网页只有一个h1,所以不必担心重复。
提示:SEO的意思是搜索引擎优化:通过优化网页代码、内容和链接来提高对搜索引擎的支持。
让我们看看h1 标签是否有效:
response.xpath("//h1/text()").extract()
[u"set unique family well"]太棒了,完全有效。我在h1后面添加了text(),这意味着只会提取h1标签中的文本。如果不添加text(),它看起来像这样:
response.xpath("//h1").extract()
[u"
set unique family well
"]我们已经成功获得了称号,但是如果我们仔细看看,我们可以找到更简单的方法。
Gumtree给标签添加了一个属性,即itemprop=name。所以XPath可以简化为//*[@itemprop="name"][1]/text()。在XPath 中,请记住数组从1 开始,因此这里[] 是1。
选择属性itemprop="name" 是因为Gumtree 使用该属性来命名许多其他内容,例如“你可能也喜欢”,并且使用数组序号提取它非常方便。
接下来看价格。价格在HTML 中的位置如下:
334.39pw我们又看到了属性itemprop="name",XPath表达式为//*[@itemprop="price"][1]/text()。验证一下:
response.xpath("//*[@itemprop="price"][1]/text()").extract()
[u"xa3334.39pw"] 请注意Unicode 字符( 符号)和价格350.00pw。这表明数据需要清理。在此示例中,我们使用正则表达式来提取数字和小数点。使用常规方法如下:
response.xpath("//*[@itemprop="price"][1]/text()").re("[.0-9]+")
[u"334.39"]提取房屋描述的文字和房屋地址也很相似,如下:
//*[@itemprop="描述"][1]/text()
与//*[@itemtype="http://schema.org/Place"][1]/text()类似,您可以使用//img[@itemprop="image"][1]/@src来抓取图像。请注意,此处未使用text(),因为我们只需要图像的URL。
如果这就是我们想要提取的所有信息,那么它的组织方式如下:
目标XPath表达式标题//*[@itemprop="name"][1]/text()
示例value: [u"set unique family well"]Price//*[@itemprop="price"][1]/text()
示例值(使用re()):[u"334.39"]description//*[@itemprop="description"][1]/text()
示例值: [u"网站法院仓库rnpool."]地址//*[@itemtype="http://schema.org/Place"][1]/text()
示例值: [u"Angel, London"]Image_URL//*[@itemprop="image"][1]/@src
示例value: [u"./images/i01.jpg"]这个表很重要,因为稍微改变一下表达式也许就可以爬取其他页面。另外,如果要爬取几十个网站,可以用这样的表格来区分。
到目前为止,我只使用过HTML 和XPath。接下来我会用Python来做一个项目。
一个Scrapy项目到目前为止,我们只在Scrapy shell 中工作。学习完前面的知识后,现在启动一个Scrapy项目,Ctrl+D退出Scrapy shell。 Scrapy shell 只是一个操作网页、XPath 表达式和Scrapy 对象的工具。不要在上面浪费太多,因为一旦退出,你写的代码就会消失。我们创建一个名为properties的项目:
$ scrapy startproject 属性
$ cd 属性
$ 树。
属性
__init__.py
items.py
pipelines.py
设置.py
蜘蛛
__init__.py
scrapy.cfg
2个目录,6个文件我们先看一下这个Scrapy项目的文件目录。该文件夹包含一个同名文件夹,其中包含三个文件items.py、pipelines.py 和settings.py。还有一个子文件夹Spiders,目前为空。后面的章节将详细讨论设置、管道和scrapy.cfg 文件。
定义items使用编辑器打开items.py。里面已经有代码了,我们需要修改一下。使用上表的内容重新定义类PropertiesItem。
另外补充一些以后要用到的内容。稍后将对此进行深入解释。这里需要注意的是,声明一个字段并不需要填写它。因此,请随意添加您认为需要的字段,并且稍后可以修改它们。
Python 表达式imagespipeline 字段将根据image_URL 自动填充到此处。稍后会详细介绍。位置地理编码将在此处填充。稍后会详细介绍。我们还会添加一些杂务字段,这些字段可能与当前的项目关系不大,但我个人对它们非常感兴趣,将来也许能够使用它们。您可以选择添加或不添加。观察这些项目,你就会明白这些项目是如何帮助我找到在哪里(服务器、url)、何时(日期)以及如何(爬虫)进行爬行的。它们帮助我取消项目、计划新的重复爬网或忽略爬网程序错误。这里不懂也没关系,稍后我会详细解释。
Chore 字段Python 表达式urlresponse.url
示例value: "http://web./property_000000.html"projectself.settings.get("BOT_NAME")
示例value: "properties"spiderself.name
示例value: "basic"serverserver socket.gethostname()
示例value: "scrapyserver1"datedatetime.datetime.now()
示例value: datetime.datetime(2015, 6, 25.) 使用此表修改PropertiesItem 类。修改文件properties/items.py如下:
从scrapy.item 导入项目、字段
类属性项(项目):
# 主要字段
标题=字段()
价格=字段()
描述=字段()
地址=字段()
图像_URL=字段()
# 计算字段
图像=字段()
位置=字段()
#管家领域
网址=字段()
项目=字段()
蜘蛛=字段()
服务器=字段()
date=Field() 这是我们的第一段代码。请注意,Python 使用空格进行缩进。每个字段名称前面都有四个空格或一个制表符。如果一行有四个空格,另一行有三个空格,则会报语法错误。如果一行有四个空格,另一行有制表符,也会报错。空格字符指定这些项目位于PropertiesItem 下。其他一些语言使用大括号{},有些使用begin-end,而Python则使用空格。
编写爬虫完成一半。现在我们来编写爬虫。一般来说,每个网站或大型网站的一部分只有一个爬虫。爬虫代码成为UR2IM进程。
当然,你可以使用文本编辑器逐句编写爬虫,但更方便的方法是使用scrapy genspider 命令,如下所示:
$ scrapy genspider basic web 使用模块中的模板“basic”创建一个爬虫“basic”:
properties.spiders.basic 爬虫文件basic.py 出现在目录properties/spiders中。刚才的命令是生成一个名为basic的默认文件,该文件仅限于抓取网络上的URL。我们可以取消这个限制。该爬虫使用基本模板。可以使用scrapy genspider -l 查看所有模板,然后使用参数-t 使用模板生成想要的爬虫。稍后会介绍一个例子。
查看properties/spiders/basic.py文件,其代码如下:
导入scrapy
类BasicSpider(scrapy.Spider):
名称="基本"
allowed_domains=["网络"]
开始网址=(
"http://www.web/",
)
def 解析(自身,响应):
passimport命令允许我们使用Scrapy框架。然后定义一个类BasicSpider,它继承自scrapy.Spider。继承是指虽然我们没有写任何代码,但是这个类继承了Scrapy框架中Spider类的很多特性。这使得我们只需几行代码就可以拥有一个功能齐全的爬虫。然后我们看到了爬虫的一些参数,比如名称和爬取域字段名。最后,我们定义一个空函数parse(),它有两个参数self和response。通过self,你可以使用爬虫的一些有趣的功能。该响应看起来很熟悉,这是我们在Scrapy shell 中看到的响应。
让我们开始编辑这个爬虫。 start_URL 更改为Scrapy 命令行中使用的URL。然后使用爬虫预先准备好的log()方法输出内容。修改后的properties/spiders/basic.py文件为:
导入scrapy
类BasicSpider(scrapy.Spider):
名称="基本"
allowed_domains=["网络"]
起始网址=(
"http://web:9312/properties/property_000000.html",
)
def 解析(自身,响应):
self.log("title: %s" % response.xpath(
"//*[@itemprop="名称"][1]/text()").extract())
self.log("price: %s" % 响应.xpath(
"//*[@物品
prop="price"][1]/text()").re("[.0-9]+")) self.log("description: %s" % response.xpath( "//*[@itemprop="description"][1]/text()").extract()) self.log("address: %s" % response.xpath( "//*[@itemtype="http://schema.org/" "Place"][1]/text()").extract()) self.log("image_URL: %s" % response.xpath( "//*[@itemprop="image"][1]/@src").extract())总算到了运行爬虫的时间!让爬虫运行的命令是scrapy crawl接上爬虫的名字: $ scrapy crawl basic INFO: Scrapy 1.0.3 started (bot: properties) ... INFO: Spider opened DEBUG: Crawled (200)DEBUG: title: [u"set unique family well"] DEBUG: price: [u"334.39"] DEBUG: description: [u"website..."] DEBUG: address: [u"Angel, London"] DEBUG: image_URL: [u"../images/i01.jpg"] INFO: Closing spider (finished) ...成功了!不要被这么多行的命令吓到,后面我们再仔细说明。现在,我们可以看到使用这个简单的爬虫,所有的数据都用XPath得到了。 来看另一个命令,scrapy parse。它可以让我们选择最合适的爬虫来解析URL。用—spider命令可以设定爬虫: $ scrapy parse --spider=basic http://web:9312/properties/property_000001.html你可以看到输出的结果和前面的很像,但却是关于另一个房产的。填充一个项目接下来稍稍修改一下前面的代码。你会看到,尽管改动很小,却可以解锁许多新的功能。 首先,引入类PropertiesItem。它位于properties目录中的item.py文件,因此在模块properties.items中。它的导入命令是: from properties.items import PropertiesItem然后我们要实例化,并进行返回。这很简单。在parse()方法中,我们加入声明item = PropertiesItem(),它产生了一个新项目,然后为它分配表达式: item["title"] = response.xpath("//*[@itemprop="name"][1]/text()").extract()最后,我们用return item返回项目。更新后的properties/spiders/basic.py文件如下: import scrapy from properties.items import PropertiesItem class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] start_URL = ( "http://web:9312/properties/property_000000.html", ) def parse(self, response): item = PropertiesItem() item["title"] = response.xpath( "//*[@itemprop="name"][1]/text()").extract() item["price"] = response.xpath( "//*[@itemprop="price"][1]/text()").re("[.0-9]+") item["description"] = response.xpath( "//*[@itemprop="description"][1]/text()").extract() item["address"] = response.xpath( "//*[@itemtype="http://schema.org/" "Place"][1]/text()").extract() item["image_URL"] = response.xpath( "//*[@itemprop="image"][1]/@src").extract() return item现在如果再次运行爬虫,你会注意到一个不大但很重要的改动。被抓取的值不再打印出来,没有“DEBUG:被抓取的值”了。你会看到: DEBUG: Scraped from<200 http://...000.html>{"address": [u"Angel, London"], "description": [u"website ... offered"], "image_URL": [u"../images/i01.jpg"], "price": [u"334.39"], "title": [u"set unique family well"]}这是从这个页面抓取的PropertiesItem。这很好,因为Scrapy就是围绕Items的概念构建的,这意味着我们可以用pipelines填充丰富项目,或是用“Feed export”导出保存到不同的格式和位置。保存到文件试运行下面: $ scrapy crawl basic -o items.json $ cat items.json [{"price": ["334.39"], "address": ["Angel, London"], "description": ["website court ... offered"], "image_URL": ["../images/i01.jpg"], "title": ["set unique family well"]}] $ scrapy crawl basic -o items.jl $ cat items.jl {"price": ["334.39"], "address": ["Angel, London"], "description": ["website court ... offered"], "image_URL": ["../images/i01.jpg"], "title": ["set unique family well"]} $ scrapy crawl basic -o items.csv $ cat items.csv description,title,url,price,spider,image_URL... "...offered",set unique family well,,334.39,,../images/i01.jpg $ scrapy crawl basic -o items.xml $ cat items.xml334.39...不用我们写任何代码,我们就可以用这些格式进行存储。Scrapy可以自动识别输出文件的后缀名,并进行输出。这段代码中涵盖了一些常用的格式。CSV和XML文件很流行,因为可以被Excel直接打开。JSON文件很流行是因为它的开放性和与JavaScript的密切关系。JSON和JSON Line格式的区别是.json文件是在一个大数组中存储JSON对象。这意味着如果你有一个1GB的文件,你可能必须现在内存中存储,然后才能传给解析器。相对的,.jl文件每行都有一个JSON对象,所以读取效率更高。 不在文件系统中存储生成的文件也很麻烦。利用下面例子的代码,你可以让Scrapy自动上传文件到FTP或亚马逊的S3 bucket。 $ scrapy crawl basic -o "ftp://user:pass@ftp.scrapybook.com/items.json " $ scrapy crawl basic -o "s3://aws_key:aws_secret@scrapybook/items.json"注意,证书和URL必须按照主机和S3更新,才能顺利运行。 另一个要注意的是,如果你现在使用scrapy parse,它会向你显示被抓取的项目和抓取中新的请求: $ scrapy parse --spider=basic http://web:9312/properties/property_000001.html INFO: Scrapy 1.0.3 started (bot: properties) ... INFO: Spider closed (finished) >>>STATUS DEPTH LEVEL 1<<< # Scraped Items ------------------------------------------------ [{"address": [u"Plaistow, London"], "description": [u"features"], "image_URL": [u"../images/i02.jpg"], "price": [u"388.03"], "title": [u"belsize marylebone...deal"]}] # Requests ------------------------------------------------ []当出现意外结果时,scrapy parse可以帮你进行debug,你会更感叹它的强大。清洗——项目加载器和杂务字段恭喜你,你已经创建成功一个简单爬虫了!让我们让它看起来更专业些。 我们使用一个功能类,ItemLoader,以取代看起来杂乱的extract()和xpath()。我们的parse()进行如下变化:【《Scrapy入门教程》第三章:爬虫基础知识】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
学习Scrapy 第3章的内容简直太棒了!感觉对爬虫的基础有了更深的理解。
有20位网友表示赞同!
这节课讲得深入浅出,很好地介绍了爬虫的基本原理。
有8位网友表示赞同!
终于明白Scrapy框架的强大之处了,可以高效地构建各种类型的爬虫。
有13位网友表示赞同!
之前对爬虫的概念一直比较模糊,看完这章感觉豁然开朗!
有7位网友表示赞同!
有好多实用的爬虫实例,能够直接应用到实际项目中。
有13位网友表示赞同!
学习爬虫很重要啊,可以从互联网上获取大量的数据分析。
有19位网友表示赞同!
以后想尝试自己用Scrapy爬一些感兴趣的网站信息了。
有8位网友表示赞同!
第3章的内容讲解得非常清晰,图片和代码都很容易理解。
有18位网友表示赞同!
这类基础知识还是需要花时间好好学习一下的。
有7位网友表示赞同!
感觉这个课程学来学去就是基础基础!但扎实掌握的基础才能更上一层楼啊!
有5位网友表示赞同!
"Learning Scrapy" 中文版的教程太好了,非常适合入门学习。
有7位网友表示赞同!
想通过爬虫实现一些自动化任务,这章的内容很有帮助。
有12位网友表示赞同!
之前尝试过其他的爬虫框架,Scrapy 真是太强大了!
有14位网友表示赞同!
感谢作者制作这样的优秀教程,学习起来更加方便了!
有11位网友表示赞同!
学习一个编程框架的开始总是需要从基础知识入手的,这章内容很棒!
有7位网友表示赞同!
对爬虫技术一直充满好奇,这次正好来学习一下Scrapy。
有10位网友表示赞同!
希望后续课程也能一样精彩有趣,期待深入了解Scrapy框架。
有16位网友表示赞同!
这堂课帮助我建立了爬虫的基础知识体系,打下了一个坚实的基矗!
有12位网友表示赞同!