从零开始学Scrapy网络爬虫:视频教学版(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-27 06:01:41

点击下载

作者:张涛

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

从零开始学Scrapy网络爬虫:视频教学版

从零开始学Scrapy网络爬虫:视频教学版试读:

前言

随着人工智能浪潮的到来,笔者身边有越来越多的人投入到人工智能和大数据的学习与研究中。他们来自不同的行业,有高校老师和学生,有AI研究专家,有物理或数学专业人才。他们都迫切希望能够获取大量相关领域的数据,用于学习和研究。而互联网中源源不断的海量数据为他们提供了一个既经济又可靠的来源。如何简单、高效、快捷地获取这些数据呢?笔者试图为他们推荐几本能快速入手的书籍。经过一番了解,发现目前市场上关于网络爬虫的图书主要分为两类:一类是翻译成中文的外版图书,其定位相对高端,且翻译质量参差不齐,阅读难度较大,不易上手,故不适合初学者学习;另一类是国内原创的一些关于网络爬虫的图书,这些书大多要求读者具备一定的Python编程基础,虽然书中对各种网络爬虫框架都有介绍,但是不深入也不成体系,对于零基础或非计算机专业的人员来说,显然也不太适合。

于是,他们就“怂恿”我,希望我能编写一本从零基础开始学起的网络爬虫书籍。虽然我从事网络爬虫教学工作多年,但我深知教学跟写书是两码事。教学注重临场发挥,思维比较发散;而写书要求文笔流畅、逻辑严谨缜密。我实在没有信心接受这个挑战。直到有一天,机械工业出版社的编辑联系到了我,认为我从事教育和研究工作,能讲、会说、有技术,对写书来说正是最大的优势。于是在编辑的鼓励和指导下,我开始构思和梳理文章脉络:首先,本书受众要广,即使是零基础或非计算机专业的“小白”也能上手;其次,本书内容不追求多和杂,只选用最流行、最好用、最强大的网络爬虫框架介绍即可;最后,本书的可操作性和实用性要强,通过迭代案例加深读者对知识的理解与应用,以典型的、知名的网站为爬取目标,提高读者解决实际问题的能力。本书正是遵循这样的思路逐步推进,不断优化,最后顺利地完成了写作。本书有何特色

1.由浅入深,循序渐进

本书从零开始,先介绍Python语言、网络爬虫基础、Scrapy框架结构等基础内容;再介绍Scrapy的数据库存储、动态页面爬取、突破反爬虫技术等核心技术;接着介绍分布式爬虫的实现、部署和管理等高级技术;最后介绍了一个完整的综合项目的开发过程。

2.视频教学,讲解详尽

为了便于读者高效、直观地学习,书中每一章的重点内容都专门录制了配套教学视频。读者可以将图书内容和教学视频结合起来,深入、系统地学习,相信一定会取得更好的学习效果。

3.注释详细,一目了然

无论是在Python程序设计,还是在Scrapy爬虫实现部分,本书均对代码做了详细的注释,读者理解起来会更加顺畅。另外,对于多步骤的操作过程,本书在图例中使用数字做了标注,便于读者准确操作。

4.案例丰富,实用易学

本书提供了14个实用性很强的项目案例,这些案例爬取的目标均是知名的、具有代表性的、应用价值较高的网站。读者通过实际操练这些项目案例,可以更加透彻地理解Scrapy网络爬虫的相关知识。

5.提供课件,方便教学

笔者专门为本书制作了专业的教学PPT,以方便相关院校或培训机构的教学人员讲课时使用。本书内容

第1篇 基础篇

第1章 Python基础

本章介绍了Python环境搭建,并详细介绍了Python基本语法、Python内置数据结构及Python模块化设计,为Scrapy网络爬虫开发打下坚实的编程基础。

第2章 网络爬虫基础

本章介绍了与网络爬虫技术相关的HTTP基本原理、网页基础,以及使用XPath提取网页信息的方法,为Scrapy网络爬虫开发打下坚实的理论基础。

第3章 Scrapy框架介绍

本章首先介绍了网络爬虫的原理;然后介绍了Scrapy框架的结构及执行流程,并实现了Scrapy的安装;最后结合案例,实现了第一个Scrapy网络爬虫功能。

第4章 Scrapy网络爬虫基础

本章深入Scrapy框架内部,介绍了使用Spider提取数据、使用Item封装数据、使用Pipeline处理数据的方法,并通过一个项目案例,演示了一个功能完备的Scrapy项目的实现过程。

第2篇 进阶篇

第5章 数据库存储

本章介绍了关系型数据库MySQL、非关系型数据库MongoDB和Redis的下载、安装及基本操作,并通过3个项目案例,实现了将爬取来的数据分别存储于这3个数据库中的方法。

第6章 JavaScript与AJAX数据爬取

本章通过两个项目案例,介绍了使用Scrapy爬取通过JavaScript或AJAX加载的数据的方法和技巧。

第7章 动态渲染页面的爬取

本章介绍了使用Selenium和Splash这两个工具来模拟浏览器进行数据爬取的方法,并通过两个项目案例,进一步巩固使用Selenium和Splash的方法与技巧。

第8章 模拟登录

本章介绍了某些需要登录才能访问的页面爬取方法,并介绍了模拟登录、验证码识别和Cookie自动登录等知识,还通过一个项目案例,进一步巩固了实现模拟登录的方法和技巧。

第9章 突破反爬虫技术

本章介绍了突破反爬虫的几种技术,主要有降低请求频率、修改请求头、禁用Cookie、伪装成随机浏览器及更换IP地址等,通过这些举措,可以有效避免目标网站的侦测,提高爬虫成功率。

第10章 文件和图片下载

本章介绍了使用Scrapy的中间件批量下载文件和图片的方法,并通过两个项目案例,进一步巩固了文件和图片下载的方法与技巧。

第3篇 高级篇

第11章 Scrapy-Redis实现分布式爬虫

本章介绍了使用Scrapy-Redis实现分布式爬虫的方法。首先介绍了分布式爬虫的原理,然后介绍了实现分布式爬虫的思路和核心代码,最后通过一个图片下载的项目案例,构造了一个分布式爬虫系统。

第12章 Scrapyd部署分布式爬虫

本章介绍了分布式系统的部署和管理。首先介绍了使用Scrapyd和Scrapyd-Client部署分布式爬虫,然后介绍了使用Docker批量部署分布式爬虫,最后介绍了如何使用Gerapy管理分布式爬虫。

第13章 综合项目:抢票软件的实现

本章通过全面分析12306购票网站的特点,结合Scrapy网络爬虫框架和Selenium浏览器工具,使用Python面向对象的设计模式,完成了一个综合性和实用性都较强的项目:抢票软件。本书配套资源获取方式

本书涉及以下配套资源:

·配套教学视频;

·实例源代码文件;

·教学PPT。

这些配套资源需要读者自行下载。请登录华章公司网站www.hzbook.com,在该网站上搜索到本书,然后单击“资料下载”按钮,在本书页面上找到下载链接即可下载。适合阅读本书的读者

·网络爬虫初学者;

·网络爬虫爱好者;

·网络爬虫从业人员;

·数据工程师;

·高等院校的老师和学生;

·相关培训机构的学员。本书作者

笔者毕业于中国科学技术大学软件工程专业,获硕士学位。现就职于知名的智能语音技术公司,有10余年软件项目管理经验。在高等院校担任网络爬虫及机器学习方面的授课工作。

本书能够顺利出版,首先要感谢本书编辑欧振旭!他花费了大量时间和精力对本书提出了有价值的修改意见和建议;还要感谢其他为本书的出版提供过帮助的编辑和朋友!没有他们的大力支持,本书也很难与读者见面。

由于笔者水平所限,加之成书时间有限,书中可能还存在一些疏漏和不当之处,敬请各位读者斧正。联系邮箱:hzbook2017@163.com。张涛第1篇 基础篇

·第1章 Python基础

·第2章 网络爬虫基础

·第3章 Scrapy框架介绍

·第4章 Scrapy网络爬虫基础第1章 Python基础

Scrapy网络爬虫框架是用Python编写的,因此掌握Python编程基础是更好地学习Scrapy的前提条件。即使你从未接触过Python,通过本章的学习,也能很熟练地进行Scrapy网络爬虫开发,因为Python的设计哲学是优雅、明确、简单,用最少的代码完成更多的工作。1.1 Python简介

在开发者社群流行一句话“人生苦短,我用Python”。看似一句戏言,其实十分恰当地说明了Python独特的魅力及其在开发者心目中的地位。1.1.1 Python简史

要说近几年最受关注的编程语言,非Python莫属。根据2019年3月Tiobe发布的编程语言排行榜显示,Python以惊人的速度上升到了第三位。这门“古老”的语言,之所以能够焕发新生,得益于人工智能的崛起。因为Python是人工智能的首选编程语言,这已是业界的共识,也是必然的选择。

Python是一门解释型的高级编程语言,创始人为荷兰人Guido van Rossum(吉多·范罗苏姆)。1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节无聊的时间,决心开发一个简单易用的新语言,它介于C和Shell之间,同时吸收了ABC语言的优点。之所以起Python这个名字,是因为他喜欢看英国电视秀节目《蒙提·派森的飞行马戏团(Monty Python's Flying Circus)》。

Python主要有以下几个特点。

·初学者的语言:结构简单、语法优雅、易于阅读和维护。

·跨平台:支持主流的操作系统,如Windows、Mac OS和Linux。

·内置电池:极其丰富和强大的第三方库,让编程工作看起来更像是在“搭积木”。

·胶水语言:就像使用胶水一样把用其他编程语言(尤其是C/C++)编写的模块黏合过来,让整个程序同时兼备其他语言的优点,起到了黏合剂的作用。1.1.2 搭建Python环境

一提到环境的搭建,相信很多人都有过痛苦的经历,除了需要安装一堆软件,还要忍受一系列复杂的步骤及天书般的配置命令,稍有不慎,就会功亏一篑。本节将为大家介绍使用Anaconda“傻瓜式”地搭建Python编程环境的方法。

1.Anaconda介绍

Anaconda是最受欢迎的数据科学Python发行版,它集成了Python环境,包含了一千多个Python/R数据科学包,并能有效地管理包、依赖项和环境,更重要的是它包含了Scrapy框架的各种依赖包,因此以后安装Scrapy框架时,基本不会出现任何问题。

2.安装Anaconda(1)下载Anaconda。

官方网站下载网址为https://www.anaconda.com/download/,如图1-1所示。图1-1 Anaconda下载页面

网速慢的读者可在清华大学开源软件镜像站下载,网址为https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/,如图1-2所示。(2)Anaconda是跨平台的,有Windows、Linux和Mac OS版本,请根据自己的操作系统及系统类型(32/64位),下载最新版本的Anaconda。图1-2 清华大学开源软件Anaconda下载页面(3)安装过程比较简单,直接双击安装包,按照提示安装即可。在安装过程中,有两处需要注意:

一是勾选Add Anaconda to my PATH environment variable复选框,将Anaconda注册到环境变量中,如图1-3所示。图1-3 设置环境变量

二是忽略下载VSCode,即单击Skip按钮,如图1-4所示。VSCode(Visual Studio Code),是微软推出的一款轻量级代码编辑器,这里用不到。

3.验证安装是否成功

如何验证Anaconda是否已经安装成功了呢?很简单,打开控制台,输入命令:python。如果显示如图1-5所示的Python版本的信息,说明Anaconda已经成功安装,这时即可进入Python的解释器界面。图1-4 忽略安装VSCode图1-5 验证安装是否成功

4.编写第一行Python代码

在解释器界面就可以进行Python编程了,如输入print("hello Python!"),回车,就会打印出字符串"hello Python!",如图1-6所示。自己动手试一试吧。图1-6 第一行Python代码1.1.3 安装PyCharm集成开发环境

如果仅仅是基本的Python程序开发,安装Anaconda就足够了。但是对于Scrapy网络爬虫开发,就显得力不从心了,我们需要功能更强大的集成开发环境,来帮助我们整合资源,减少错误,提高效率。

PyCharm是一种Python编程的集成开发环境(IDE),带有一整套可以帮助用户在使用Python语言开发时提高效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试和版本控制等。当然PyCharm对于专业的Python Web开发,也提供了Django框架(用于开发Python Web的框架)的支持。

1.下载PyCharm

PyCharm官方网站下载网址为https://www.jetbrains.com/pycharm/download。

2.选择版本

PyCharm分Professional(专业版)和Community(社区版)。专业版拥有全部功能,但是收费;社区版是个较轻量级的IDE,免费开源。对于开发者来说,使用社区版完全够用了。

3.安装PyCharm

PyCharm的安装也是“傻瓜式”的,只要按照提示执行“下一步”即可。不过,在选择操作系统类型(32/64位)时,需要根据操作系统的实际情况选择对应的系统类型,如图1-7所示。图1-7 选择自己的操作系统类型

4.编写第一个Python代码hello Python!

下面在PyCharm中编写Python程序。首先新建一个名为hello的项目(Project),一个项目中可以包含多个Python源文件,然后在hello项目中新建一个名为hello.py的源文件,在源文件中输入print("hello Python!"),最后在源文件中右击,在弹出的快捷菜单中选择run‘hello’选项,即可执行程序。结果显示在信息显示区,如图1-8所示。图1-8 PyCharm编程1.2 Python基本语法

Python语法简单、优雅,如同阅读英语一般,即使是非科班出身的用户,也能很快理解Python语句的含义。1.2.1 基本数据类型和运算

1.变量

有计算机基础的读者都知道,计算机在工作过程中所产生的数据都是在内存中存储和读取的。内存类似于工厂的仓库,数据作为零件存储在仓库(内存)中不同的地方。仓库那么大,怎样才能快速找到这些零件(数据)呢?我们可以给每个零件贴上“标签”,根据标签名称,就可以找到它们了。这些标签,就是传说中的“变量”。使用“变量”可以快速定位数据、操作数据。比如,存储同学cathy的个人信息:name = "cathy" #变量名name,存储姓名age = 10 #变量名age,存储年龄height = 138.5 #变量名height,存储身高is_student = True #变量名is_student,存储是否是学生的标记score1 = None #变量名score1,存储成绩【重点说明】

·变量名包含英文、数字及下划线,但不能以数字开头。

·=用来给变量赋值,如变量name的值为cathy。

·变量在使用前必须赋值。

·#代表行内注释。

2.数据类型

Python的基本数据类型包括整数、浮点数、布尔型和字符串,可以使用type()函数查看一个变量的类型。比如查看同学cathy的各个变量的类型:type(name) #字符串:type(age) #整数:type(height) #浮点数:type(is_student) #布尔型:type(score1) #NoneType:【重点说明】

·注释中的尖括号是执行type()函数后输出的结果。

·结果中的class意味着Python中一切皆对象,后面会讲到。

·score1的类型是NoneType,不是0,也不是空。很多情况下,API执行失败会返回None。

·变量不需要声明类型,Python会自动识别。1.2.2 运算符和表达式

Python中数值的基本运算和其他语言差不多,运算符及其使用说明如表1-1所示。表1-1 运算符及其使用说明1.2.3 条件判断语句

条件语句是指根据条件表达式的不同,使程序跳转至不同的代码块。Python的条件语句有:if、if-else和if-elif-else。下面来看几个判断成绩的例子。(1)判断成绩是否合格:score = 95 #成绩#二选一if score >= 60: #如果成绩60分及以上,则输出“合格” print("合格")else: #否则,输出“不合格” print("不合格")

运行以上代码后,输出“合格”。(2)判断成绩是优秀、良好、及格还是不及格:#多选一if score >= 90: #如果成绩大于等于90 print("优秀") print("再接再厉")elif score <90 and score >=70: #如果成绩在70~90之间 print("良好")elif score < 70 and score >= 60: #如果成绩在60~70之间 print("及格")else: #其他情况 print("不及格")

运行以上代码后,输出“优秀”和“再接再厉”。【重点说明】

·关键字if、elif和else后面的冒号(:)不能缺,这是语法规则。

·每个判断条件下的代码块都必须缩进,这是Python的一大特点,即通过强行缩进来表明成块的代码。这样做的好处是代码十分清晰工整,坏处是稍不注意,代码块就会对不齐,运行就会出错。

·Python中用于比较大小的关系运算符,跟其他语言类似,如表1-2所示。表1-2 关系运算符

·Python中用于连接多个条件的逻辑运算符,如表1-3所示。表1-3 逻辑运算符

下面来看一个判断闰年的例子。要判断是否是闰年,只要看年份是否满足条件:能被4整除,并且不能被100整除,或者能被4整除,并且又能被400整除。

实现代码如下:year = input("请输入年份:") #通过命令行输入年份year = int(year) #转换为整型if (year%4==0 and year%100!=0) or (year%4==0 and year%400==0): print("%d年是闰年"%year) #闰年else: print("%d年不是闰年"%year) #非闰年【重点说明】

·第一行通过input( )函数实现从命令行中动态输入年份。

·if后面是判断闰年的条件表达式,由于and的优先级高于or,也可以省略圆括号。条件表达式还可以简写为:if year%4==0 and (year%100!=0 or year%400==0):

·通过print输出字符串文字。这是一个经过格式化的字符串,双引号中是将要格式化的字符串,其中的%d是格式化符号,表示整数。双引号后面跟%year,表示将变量year的值转换为整数后插入到%d的位置上。1.2.4 循环语句

生活中有许多重复的劳动,如cathy做错事被罚抄课文5遍等。代码的世界也是如此,对于重复的功能,如果通过简单的复制、粘贴,代码就会变得沉重冗余,难以理解。Python中使用while和for循环来实现代码的重复利用,通常用于遍历集合或累加计算。

1.while循环

while循环的语法结构为:while <条件>: 循环体

在给定的判定条件为True时执行循环体,否则,退出循环。循环的流程图如图1-9所示。图1-9  循环流程图

以下代码实现了打印5遍字符串的功能:#1.使用while执行5次循环n = 1 #记录次数while n<=5: #n<=5为循环条件 print("cathy正在努力抄第%d遍课文"%n) #每次循环输出的文字 n += 1 #自增1【重点说明】

·while语句后要有半角冒号(:)。

·循环体要有缩进。

·每次循环n都会自增1,否则就会死循环。

2.for循环

for循环的语法结构为:for <目标对象> in <对象集合>: 循环体

当执行for循环时,会逐个将对象集合中的元素赋给目标对象,然后为每个元素执行循环体。以下代码使用for循环实现了计数和遍历集合的功能:#1.使用for执行5次循环for n in range(1,6): #range()函数生成整数集合(1,2,3,4,5) print("cathy正在努力抄第%d遍课文"%n) #每次循环输出文字#2.遍历字符串所有字符name = "cathy"for n in name: print(n) #每次循环分别输出c、a、t、h、y#3.遍历列表中的所有项目student = ["cathy",10,25] #记录姓名、年龄、体重for item in student: print(item) #每次循环分别输出cathy 10 25

3.break和continue

在循环过程中,有时需要终止循环或者跳过当前循环。Python使用break和continue来分别表示终止循环和跳过当前循环。

来看一个break的例子:实现在1~100之间,找到第一个能被3整除且能被8整除的整数。实现代码如下:a = 1 #初始为1while a<=100: #循环100次 if a%3==0 and a%8==0: print("第一个能被3整除且能被8整除的整数是:%d"%a) break #终止循环 a+=1 #每次循环自增1

再来看一个continue的例子:实现在1~100之间,找到所有不能被3和8整除的数。实现代码如下:for i in range(1,101): if i%3==0 and i%8==0: continue print("%d "%i)【重点说明】

·range(1,101)函数生成了一个包含1~100的整数集合,注意,不包括101。

·if语句判断的是能被3和8整除的数,使用continue跳过for循环剩下的代码,继续执行下一次循环。

4.while和for使用场景

一般情况下,while和for循环可以互相代替,但也有一些使用原则:

·如果循环变量的变化,伴随着一些条件判断等因素,推荐使用while循环。

·如果仅仅是遍历集合中所有的数据,没有一些条件判断因素,推荐使用for循环。1.2.5 字符串

1.引号

字符串是Python中最常见的数据类型,它包含在一对双引号(" ")或单引号(' ')中。单引号和双引号没有任何区别。name = "cathy" #双引号字符串like = 'english' #单引号字符串

当单引号中含有单引号(或者叫撇号)时,程序运行就会出错,解释器会“犯迷糊”,如下面的代码所示。它会将'i'看成一个字符串,后面的m就不知道如何处理了。age = 'i'm ten ' #单引号中包含单引号错误信息:SyntaxError: invalid syntax

针对上述问题,有以下两种修改方式。

方式一:将字符串改为双引号括起来。age = "i'm ten " #使用双引号

方式二:使用反斜杠(\)将字符串中的单引号进行转义。age = 'i\'m ten ' #加转移字符:\

2.访问字符串

Python访问字符串,可以使用方括号([ ])下标法来截取字符串,代码如下:hello = "hello,Python!"hello[0] #获取第1个值:hhello[1:4] #获得第2~5个(不包括)范围的值:ellhello[-1] #获取最后一个值:!【重点说明】

·字符串的下标是由左往右,从0开始标记的。

·截取任意范围内容,其格式为:起始下标:终止下标,这叫做切片。需要注意的是,终止下标是不包含在截取范围内的,如hello[1:4]得到ell。

·下标为负数时,从右往左标记,如-1就是获取最后一个值,-2获取倒数第二个值,以此类推。

3.字符串方法

字符串自带很多处理方法,通过简单的调用,就可以实现对自身的处理。以下为字符串最常用的几种处理方法,读者可以打印出来看一下效果。cathyStr =" Hello,cathy! " #两边有空格的字符串cathyStr.strip(" ") #去除字符串两边的空格cathyLst = cathyStr.split(",") #以逗号作为分隔符,切分字符串,保存为列表cathyStr.replace("!",".") #将字符串中所有感叹号替换为句号cathyStr.lower() #将字符串中所有字母都转换为小写字母cathyStr.upper() #将字符串中所有字母都转换为大写字母

4.格式化输出

字符串的格式化输出有3种方法:

第1种是我们一直在print( )函数中使用的%格式法。例如,要输出字符串“我的名字叫XX,今年X岁了。”,其中名字和年龄都是动态输入的。实现代码如下:name = input("请输入姓名:")age = int(input("请输入年龄:"))message = "我的名字叫%s,今年%d岁了。"%(name,age)print(message)【重点说明】

·在message字符串中,%s和%d是格式化符号,%s代表字符串,%d代表整数。它们与后面的name和age一一对应,功能是将name设为字符串,将age设为整数,再插入到%s和%d对应的位置上。

程序运行后,根据提示输入cathy和10,输出的结果如下:>请输入姓名:cathy>请输入年龄:10我的名字叫cathy,今年10岁了。

这种方法有个特点,就是格式化符号和后面的变量要一一对应,位置一旦搞错,就会出现错乱。这时候可以考虑使用第2种格式化输出方法。先看一下代码:name = input("请输入姓名:")age = int(input("请输入年龄:"))message = "我的名字叫%(i_name)s,今年%(i_age)d岁了。"%{"i_name":name,"i_age":age}print(message)【重点说明】

·%s和%d的中间添加了i_name和i_age这两个参数。在后面的字典({}括起来的部分)中可以找到参数对应的值,这些值会替换参数形成完整的字符串。

程序运行后,根据提示输入tom和15,输出结果如下:>请输入姓名:tom>请输入年龄:15我的名字叫tom,今年15岁了。

第3种格式化输出的方法是使用字符串的format()函数,用法与第2种方法类似。还是先来看代码:name = input("请输入姓名:")age = int(input("请输入年龄:"))message = "我的名字叫{i_name},今年{i_age}岁了。".format(i_name=name,i_age=age)print(message)【重点说明】

·字符串中的{}中定义了参数,这些参数可以在format()函数中找到对应的值,这些值会替换参数形成完整的字符串。

程序运行后,根据提示输入lili和8,输出的结果如下:>请输入姓名:lili>请输入年龄:8我的名字叫lili,今年8岁了。1.3 Python内置数据结构

1.2.5节使用了变量存储同学cathy的个人信息,但是如果她的个人信息很多,就需要定义更多的变量来存储,这就会产生以下问题:

·变量定义多,容易混淆。

·数据各自独立,没有关联性。

·代码量大。

·可读性不强。

使用Python容器就可以解决上述问题。容器可以用来盛放一组相关联的数据,并对数据进行统一的功能操作。容器主要分为列表(list)、字典(dict)和元组(tuple),这些结构和其他语言中的类似结构本质上是相同的,但Python容器更简单、更强大。1.3.1 列表

列表是一组元素的集合,可以实现元素的添加、删除、修改和查找等操作。现将同学cathy的个人信息统一放到列表中,代码如下:cathy = ["cathy",10,138.5,True,None] #cathy的个人信息 score = [90,100,98,95] #各科成绩name = list("cathy") #利用list()函数初始化一个列表print(name) #输出结果:['c', 'a', 't', 'h', 'y']【重点说明】

·列表内的元素用方括号([ ])包裹。

·列表内不同元素之间使用逗号(,)分隔。

·列表内可以包含任何数据类型,也可以包含另一个列表。

·可以使用list()函数生成一个列表。

可以使用列表自带的方法实现列表的访问、增加、删除和倒序等操作。仔细阅读以下代码及注释。cathy = ["cathy",10,138.5,True,None] #cathy的个人信息a = cathy[0] #下标法获取第1个元素(姓名):cathyb = cathy[1:3] #使用切片获取下标1到下标3之前的子序列:[10, 138.5]c = cathy[1:-2] #切片下标也可以倒着数,-1对应最后一个元素:[10, 138.5]d = cathy[:3] #获取从开始到下标3之前的子序列:['cathy', 10, 138.5]e = cathy[2:] #获取下标2开始到结尾的子序列:[138.5, True, None]cathy[2] = 140.2 #将第3个元素修改为140.210 in cathy #判断10是否在列表中,Truecathy.append(28) #将体重添加到列表末尾print(cathy) #['cathy', 10, 140.2, True, None, 28]cathy.insert(2,"中国") #将国籍插入到第2个元素之后print(cathy) #['cathy', 10, '中国', 140.2, True, None, 28]cathy.pop() #默认删除最后一个元素print(cathy) #['cathy', 10, '中国', 140.2, True, None]cathy.remove(10) #删除第1个符合条件的元素print(cathy) #['cathy', '中国', 140.2, True, None]cathy.reverse() #倒序print(cathy) #[None, True, 140.2, '中国', 'cathy']

现在要使用列表存储另一个同学terry的信息,已知除了姓名以外,其他的信息跟cathy一样。通过以下操作就可以得到同学terry的列表。#cathy的个人信息cathy_list = ["cathy",10,138.5,True,None]terry_list = cathy_list #将cathy_list赋给变量terry_listterry_list[0] = "terry" #修改terry的姓名print(terry_list) #打印terry信息:['terry', 10, 138.5, True, None]print(cathy_list) #打印cathy信息:['terry', 10, 138.5, True, None]

和大家的预期不同的是,cathy_list中的姓名也变成terry了,但是我们并未修改cathy_list的姓名,这是什么原因呢?原来在执行terry_list=cathy_list时,程序并不会将cathy_list的值复制一遍,然后赋给terry_list,而是简单地为cathy_list的值即["cathy",10,138.5,True,None]建立了一个引用,相当于cathy_list和terry_list都是指向同一个值的指针,所以当terry_list中的值改变后,cathy_list的值也会跟着变。可以通过id()函数来获取变量的地址。实现代码如下:print(id(cathy_list)) #获取cathy_list的地址:2011809417032print(id(terry_list)) #获取terry_list的地址:2011809417032

结果显示,cathy_list和terry_list这两个变量均指向同一个地址。如何解决这个问题呢?可以使用copy()函数将值复制一份,再赋给terry_list,实现代码如下:#terry_list = cathy_list #删除该条语句terry_list = cathy_list.copy() #将值复制一份赋给变量terry_list1.3.2 字典

将同学cathy各科的成绩保存于列表score中,实现代码如下:score = [90,100,98,95] #成绩

如果想要获取cathy的语文成绩,如何做到呢?除非事先将每门课的位置都做了记录,否则无论如何是获取不到语文成绩的。当需要对数据做明确的标注,以供别人理解和处理时,使用列表就不太方便了,这时字典就派上用场了。

字典是一种非常常见的“键-值”(key-value)映射结构,它为每一个元素分配了一个唯一的key,你无须关心位置,通过key就可以获取对应的值。下面来看一下使用字典保存的成绩:score1 = {"math":90,"chinese":100,"english":98,"PE":95} #成绩字典print(score1["chinese"])【重点说明】

·字典内的元素用大括号({})包裹。

·使用key:value的形式存储一个元素,如"math":90,字符串math是分数90的key。

·字典内不同键值对之间采用逗号(,)分隔。

·字典是无序的,字典中的元素是通过key来访问的,如score1["chinese"]得到语文成绩。

也可以使用dict()函数初始化字典,实现代码如下:score2 = dict(math=90,chinese=100,english=98,PE=95)print(score1["chinese"]) #根据key获取语文成绩:100if "PE" in score1: #判断字典中是否包含"PE"的key print(score1["PE"]) #得到体育成绩:95#获取所有的key并保存于列表中,输出结果:['math', 'chinese', 'english', 'PE']print(score1.keys())#获取所有的value并保存于列表中,输出结果:[90, 100, 98, 95]print(score1.values())#获取key和value对转化为列表#输出结果:[('math', 90), ('chinese', 100), ('english', 98), ('PE', 95)]print(score1.items())1.3.3 元组

元组和列表最大的区别就是不可变的特性,即元组的值一旦确定了,就无法进行任何改动,包括修改、新增和删除。sex1 = ("male","female") #使用括号生成并初始化元组sex2 = tuple(["male","female"]) #从列表初始化sex3 = ("male",) #只有一个元素时,后面也要加逗号sex4 = "male","female" #默认是元组类型("male","female")【重点说明】

·元组中元素的访问方法和列表一样,都可以使用下标和切片。

·圆括号(( ))表示元组,方括号([ ])代表列表,大括号({ })代表字典。

·初始化只包含一个元素的元组时,也必须在元素后加上逗号,如sex3。

·直接用逗号分隔多个元素的赋值默认是元组,如变量sex4。

·元组内的数据一旦被初始化,就不能更改。1.3.4 遍历对象集合

for循环用于遍历一个对象集合,依次访问集合中的每个项目。前面提到的列表、字典和元组,均可通过for循环遍历。下面来看几个例子。

1.遍历列表cathy = ["cathy",10,138.5,True,None]#依次输出:"cathy",10,138.5,True,Nonefor a in cathy: print(a)

可以通过下标遍历列表。用len()函数获得列表长度,再用range()函数获得所有下标的集合,实现代码如下:#依次输出:"cathy",10,138.5,True,Nonefor i in range(len(cathy)): print(cathy[i])

2.遍历字典score = {"math":90,"chinese":100,"english":98,"PE":95} #成绩字典#键的遍历,不按顺序输出:"math","chinese","english","PE"for key in score: print(key)#键和值的遍历,不按顺序输出:math : 90,chinese : 100,english : 98,PE : 95for key,value in score.items(): print(key,":",value)

程序如果执行多次,会发现输出的顺序不一定一致,这是因为字典是无序的。

3.遍历元组sex = ("male","female")#依次输出:"male","female"for b in sex: print(b)1.4 Python模块化设计

在编写程序的时候,读者会不会被一个问题所困扰?有些功能多处要用到,实现时不得不复制和粘贴相同的代码。这不但会使程序代码冗余、容易出错,而且维护起来十分困难。因此,可以将这段重复使用的代码打包成一个可重用的模块,根据需要调用这个模块,而不是复制和粘贴现有的代码,这个模块就是Python的函数。

另外,我们有时希望将模块和模块所要处理的数据相关联,就跟Python内置的数据结构一样,能有效地组织和操作数据。Python允许创建并定义面向对象的类,类可以用来将数据与处理的数据功能相关联。

Scrapy爬虫框架正是基于Python模块化(函数和类)的设计模式进行组织和架构的。而且几乎所有爬虫功能的实现,都是基于函数和类的。可以说,Python模块化设计是理解Scrapy爬虫框架及掌握爬虫编程技术的重要前提。1.4.1 函数

函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。它有一个入口,用于输入数据,还有一个出口,用于输出结果。当然,根据实际需求,入口和出口是可以省略的。下面先看几个例子。(1)判断闰年的函数。def is_leap(year): #函数定义 if (year % 4 == 0 and year % 100 != 0) or (year % 4 == 0 and year % 400 == 0): return 1 #闰年 else: return 0 #非闰年【重点说明】

·函数以def关键字开头,后接函数名、圆括号(( ))和冒号(:)。

·圆括号内用于定义参数(也可以没有参数)。

·代码块必须缩进。

·使用return结束函数,并将返回值传给调用方。

需要注意的是,函数只有被调用才会被执行。以下代码实现了函数的调用:year = int(input("请输入年份:")) #控制台输入年份result = is_leap(year) #函数调用,传递参数yearif result == 1: print("%d年是闰年"%year)else: print("%d年不是闰年"%year)【重点说明】

·通过is_leap(year)调用函数,其中,year是传递给函数的参数(叫做实参)。

·当函数执行完后,会通过return返回结果,赋给result。(2)实现打印任意同学信息的函数。def print_student(name,age,sex="女"): #性别使用了默认值,必须放最后面 print("name:",name) print("age:",age) print("sex:",sex)print_student("cathy",10) #函数调用,性别使用了默认设置print_student("terry",20,"男") #函数调用【重点说明】

·函数可以定义多个参数,用逗号隔开。

·参数可以设置默认值,但是必须放在最后。

·在调用函数时,要按定义时的顺序放置参数,函数会按照顺序将实参传递给形参。(3)求任意几门功课的平均成绩的函数。def get_avg(*scores): #scores前面加*,表示可变长参数 sum = 0 #总成绩,初始值为0 for one in scores: sum+=one return (sum/len(scores)) #计算出平均值,返回给调用方avg = get_avg(80,90,95) #调用函数,求3门课的平均分avg1 = get_avg(77,88) #调用函数,求2门课的平均分print(avg) #结果:88.33333333333333print(avg1) #结果:82.5【重点说明】

·在参数个数不确定的情况下,可以使用可变长参数。方法是在变量前面加上*号。

·可变长参数类似于一个列表,无论输入多少个数据,都会被存储于这个可变参数中。因此,可以使用for循环遍历这个可变参数,获取所有的数据。1.4.2 迭代器(iterator)

大家都知道,通过网络爬虫提取的数据,数据量往往都很大。如果将所有数据都保存到列表或字典中,将会占用大量的内存,严重影响主机的运行效率,这显然不是一个好方法。遇到这种情况,就需要考虑使用迭代器(iterator)了。

迭代器相当于一个函数,每次调用都可以通过next()函数返回下一个值,如果迭代结束,则抛出StopIteration异常。从遍历的角度看这和列表没什么区别,但它占用内存更少,因为不需要一下就生成整个列表。

能够使用for循环逐项遍历数据的对象,我们把它叫做可迭代对象。例如列表、字典和rang()函数都是可迭代对象。可以通过内置的iter()函数来获取对应的迭代器对象。例如,使用迭代器获取列表中的每个元素,代码如下:cathy = ["cathy",10,138.5,True,None]iter1 = iter(cathy) #生成迭代器对象print(next(iter1)) #得到下一个值:"cathy"print(next(iter1)) #得到下一个值:101.4.3 生成器(Generator)

在Python中,把使用了yield的函数称为生成器(generator)。生成器是一种特殊的迭代器,它在形式上和函数很像,只是把return换成了yield。函数在遇到return关键字时,会返回值并结束函数。而生成器在遇到yield关键字时,会返回迭代器对象,但不会立即结束,而是保存当前的位置,下次执行时会从当前位置继续执行。

下面来看一个著名的斐波那契数列,它以0、1开头,后面的数是前两个数的和,下面展示的是前20个斐波那契数列的数据。0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

下面分别使用普通函数和生成器实现斐波那契数列的功能,以此来说明它们的不同之处。(1)定义普通函数。#普通斐波那契函数定义def get_fibonacci(max): #max:数量 fib_list =[0,1] #保存斐波那契数列的列表,初始值为0和1 while len(fib_list) < max: fib_list.append(fib_list[-1]+fib_list[-2]) #最后两个值相加 return fib_list#主函数if __name__ == "__main__": #函数调用,输出前10个斐波那契数列的值:0 1 1 2 3 5 8 13 21 34 for m in get_fibonacci(10): print(m,end=" ")

因为函数只能返回一次,所以每次计算得到的斐波那契数必须全部存储到列表中,最后再使用return将其返回。(2)使用带yield的函数——生成器。#使用yield的斐波那契函数定义def get_fibonacci2(max): n1 = 0 #第一个值 n2 = 1 #第二个值 num = 0 #记录数量 while num < max: yield n1 n1,n2 = n2,n1+n2 num+=1#主函数if __name__ == "__main__": #输出前10个斐波那契数列的值:0 1 1 2 3 5 8 13 21 34 for n in get_fibonacci2(10): print(n,end=" ")

yield一次返回一个数,不断返回多次。先来看一下程序执行的流程图,如图1-10所示。图1-10 斐波那契数列流程图【重点说明】

·通过函数get_fibonacci2()实现斐波那契数列时,没有将其保存于列表中,而是通过yield实时将其返回。

·在主函数中,使用for循环遍历生成器。执行第一次循环,调用生成器函数get_fibonacci2(),函数运行到yield时,返回n1,函数暂停执行,并记录当前位置,然后执行for循环的循环体print(n,end=" "),打印n1的值。下一次循环,函数从上次暂停的位置继续执行,直到遇到yield,如此往复,直到结束。

·使用yield可以简单理解为:对大数据量的操作,能够节省内存。

·在使用Scrapy实现爬虫时,为了节省内存,总是使用yield提交数据。1.4.4 类和对象

1.类和对象

我们希望尽量将函数和函数所要处理的数据相关联,就跟Python内置的数据结构一样,能有效地组织和操作数据。Python中的类就是这样的结构,它是对客观事物的抽象,由数据(即属性)和函数(即方法)组成。

就像函数必须调用才会执行一样,类只有实例化为对象后,才可以使用。也就是说,类只是对事物的设计,对象才是成品。

以下为描述人这个类的代码示例:class People: #定义人的类 #构造函数,生成类的对象时自动调用 def __init__(self,my_name,my_age,my_sex): self.name = my_name #姓名 self.age = my_age #年龄 self.sex = my_sex #性别 #方法:获取姓名 def get_name(self): return self.name #方法:打印信息 def get_information(self): print("name:%s,age:%d,sex:%s"%(self.name,self.age,self.sex))【重点说明】

·使用class关键字定义一个类,其后接类名People,类名后面接冒号(:)。

·__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。注意,init两边分别有两个下划线。

·self代表类的实例。在定义类的方法时,self要作为参数传递进来,虽然在调用时不必传入相应的参数。

·类的属性有:name、age和sex。使用属性时要在前面要加上self。

·类的方法有:get_name(self)和get_information(self)。注意,这里要有参数self。

·类的方法与普通的函数只有一个区别,它们必须有一个额外的第一个参数名称,按照惯例它是self。

重申一遍,类只有实例化为对象后才可以使用。例如要生成同学cathy的对象,实现代码如下:#主函数if __name__ == "__main__": #生成类的对象,赋初值 cathy = People("cathy",10,"女") print(cathy.get_name()) #调用方法并打印,得到:"cathy"cathy.get_information() #调用方法,得到:name:cathy,age:10,sex:女【重点说明】

·使用类名People,生成该类的对象cathy,并传入参数cathy,10和“女”。

·实例化为对象cathy时,自动调用__init__()构造函数,并接收传入的参数。

·使用点号(.)来访问对象的属性和方法,如cathy.get_name()。

2.继承

刚才定义了人这个类,如果还想再实现一个学生的类,是否需要重新设计呢?显然这会浪费很多时间,因为学生首先是人,具有人的所有属性和功能,再加上学生独有的一些特性,如年级、学校等即可。因此,我们没有必要重复“造轮子”,只要将人的类继承过来再加上自己的特性就生成了学生的类,这种机制叫做继承,其中学生类叫做子类,人的类叫做父类。类似于“子承父业”,即子类继承了父类所有的属性和方法。

学生类实现代码如下:class Student(People): def __init__(self,stu_name,stu_age,stu_sex,stu_class): People.__init__(self,stu_name,stu_age,stu_sex) #初始化父类属性 self.my_class = stu_class #班级 #打印学生信息 def get_information(self): print("name:%s,age:%d,sex:%s,class:%s"%(self.name,self.age,self. sex,self.my_class))#主函数if __name__ == "__main__": #生成Student类的对象 cathy = Student("cathy",10,"女","三年二班") #打印结果name:cathy,age:10,sex:女,class:三年二班 cathy.get_information()

·Student为学生类的类名,圆括号内是继承的父类。这样,Student类就继承了父类所有的属性和方法。

·在构造函数中,为学生类新增了一个属性my_class,其余属性自动从父类继承而来。不过,需要调用父类的构造函数来初始化父类的属性。

·新增的方法get_information(self)用于输出学生的信息。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载