丰富的线上&线下活动,深入探索云世界
做任务,得社区积分和周边
最真实的开发者用云体验
让每位学生受益于普惠算力
让创作激发创新
资深技术专家手把手带教
遇见技术追梦人
技术交流,直击现场
海量开发者使用工具、手册,免费下载
极速、全面、稳定、安全的开源镜像
开发手册、白皮书、案例集等实战精华
为开发者定制的Chrome浏览器插件
编写第一个网络爬虫笔者是一个喜欢学习的人,自学了各方面的知识,总结发现:学习的动力来自于兴趣,兴趣则来自于动手做出成果的快乐。因此,笔者特意将动手的乐趣提前。在第2章,读者就可以体会到通过完成一个简单的Python网络爬虫而带来的乐趣。希望这份喜悦能让你继续学习本书的其他内容。本章主要介绍如何安装Python和编辑器Jupyter、Python的一些基础语法以及编写一个最简单的Python网络爬虫。
Python是一种计算机程序语言,由于其简洁性、易学性和可扩展性,已成为最受欢迎的程序语言之一。在2016年最受欢迎的编程语言中,Python已经超过C++排名第3位。另外,由于Python拥有强大而丰富的库,因此可以用来处理各种工作。在网络爬虫领域,由于Python简单易学,又有丰富的库可以很好地完成工作,因此很多人选择Python进行网络爬虫。
Python的安装主要有两种方式:一是直接下载Python安装包安装,二是使用Anaconda科学计算环境下载Python。根据笔者的经验,这两种方式也对应着用Python来爬虫的两类人群:如果你希望成为Python开发人员或者爬虫工程师,笔者推荐你直接下载Python安装包,配合着Pycharm编辑器,这将提升你的开发效率;如果你希望成为数据分析师或者商业分析师,爬虫只是方便之后做数据分析,笔者推荐你使用Anaconda,配合着自带的JupyterNotebook,这会提升你的分析效率。由于网络爬虫需要较多的代码调试,因此我推荐初学者使用Anaconda。因为Anaconda除了包含了Python安装包,还提供了众多科学计算的第三方库,如Numpy、Scipy、Pandas和Matplotlib等,以及机器学习库,如Scikit-Learn等。而且它并不妨碍你之后使用Pycharm开发。请读者选择一种下载,不要两种都用,不然会带来Python版本管理的混乱。第一种方法:Anaconda的安装十分简单,只需两步即可完成。下面将介绍在Windows下安装Anaconda的步骤,在Mac下的安装方法与此类似。
步骤二:安装Anaconda。双击打开Anaconda安装文件,就像安装普通软件一样,直接单击Install安装即可。注意,在图2-2所示的对话框中勾选第一个和第二个复选框。按照提示操作后,安装即可。
步骤二:安装Python。双击打开Python安装文件,选择AddPython3.7toPATH,之后单击InstallNow安装即可。
pip是Python安装各种第三方库(package)的工具。对于第三方库不太理解的读者,可以将库理解为供用户调用的代码组合。在安装某个库之后,可以直接调用其中的功能,使得我们不用自己写代码也能实现某个功能。这就像你为计算机杀毒时,会选择下载一个杀毒软件,而不是自己写一个杀毒软件,直接使用杀毒软件中的杀毒功能来杀毒就可以了。这个比方中的杀毒软件就像是第三方库,杀毒功能就是第三方库中可以实现的功能,你可以调用第三方库实现某个功能。由于Anaconda或者Python安装包自带了pip,因此不用再安装pip。在下面的例子中,我们将介绍如何用pip安装第三方库bs4,它可以使用其中的BeautifulSoup解析网页。步骤一:打开cmd.exe,在Windows中为cmd,在Mac中为terminal。在Windows中,cmd是命令提示符,输入一些命令后,cmd.exe可以执行对系统的管理。单击“开始”按钮,在“搜索程序和文件”文本框中输入cmd后按回车键,系统会打开命令提示符窗口,如图2-5所示。在Mac中,可以直接在“应用程序”中打开terminal程序。
步骤二:安装bs4的Python库。在cmd中键入pipinstallbs4后按回车键,如果出现successfullyinstalled,就表示安装成功,如图2-6所示。
除了bs4这个库,之后还会用到requests库、lxml库等其他第三方库,帮助我们更好地进行网络爬虫。正因为这些第三方库的存在,才使得Python在爬虫领域越来越方便、越来越活跃。
步骤二:创建Python文件。这时浏览器会开启一个页面,在页面中选择想创建文件的文件夹,单击右上角的New按钮,从下拉列表中选择Python3作为希望启动的Notebook类型,如图2-8所示。
步骤三:在新创建的文件中编写Python程序。键入print('helloworld!')后,可以按Shift+Enter快捷键执行刚刚的代码,结果如图2-9所示。
为什么本书使用JupyterNotebook学习和编写Python脚本呢?首先,JupyterNotebook的交互式编程可以分段运行Python,对于网络爬虫这种分阶段(获取网页-解析网页-存储数据)运行的脚本来说,在写代码和测试阶段可以边看边写,可以加快调试代码的速度,非常适合debug(代码纠错)。其次是展示,JupyterNotebook能够把运行和输出的结果保存下来,下次打开这个Notebook时也可以看到之前运行的结果。除了可以编写代码外,Jupyter还可以添加各种元素,比如图片、视频、链接等,同时还支持Markdown。在完成代码之后,还可以在Jupyter左上角点击File>Downloadas>Python,下载为.py文件,就可以放到其他编辑器里运行了。如果你对Python的其他自定义功能有要求的话,推荐下载Jupyter的插件nbextensions。具体指引可以到笔者知乎或本书官网www.santostang.com了解。
步骤二:安装Pycharm。双击打开Pycharm安装文件,根据自己电脑选择32bit还是64bit,记得在CreateAssociations勾选.py,安装即可,如图2-11所示。
步骤三:打开Pycharm。在开始页面,选择自己喜欢的主题,如图2-12所示。
步骤三:随后点击CreateNewProject创建一个新的项目,如图2-13所示。
步骤三:选择好存储项目的位置,这里我给项目起的名称是“WebScraping”,你可以按照自己的需求存放项目地址,如图2-14所示。
步骤三:进入Pycharm页面后,会看到如下页面。这时,点击File>New>PythonFile,并填上python文件名,例如“test”。创建完test.py文件后,打开test.py,键入print('helloworld!')。选中代码,右键选择Run‘test’,即可得到结果,如图2-15所示。
本节主要介绍Python的一些基础语法。如果你已经学会使用Python,可以跳过这一节,直接开始编写第一个Python网络爬虫。
Python是一种非常简单的语言,最简单的就是print,使用print可以打印出一系列结果。例如,键入print("HelloWorld!"),打印的结果如下(同图2-9):
HelloWorld!另外,Python要求严格的代码缩进,以Tab键或者4个空格进行缩进,代码要按照结构严格缩进,例如:
HelloWorld!如果需要注释某行代码,那么可以在代码前面加上“#”,例如:In[3]:#在前面加上#,代表注释print("HelloWorld!")HelloWorld!
PythonWebScrapingbySantos如果字符串包含单引号(')和双引号("),应该怎么办?可以在前面加上右斜杠(),例如以下案例:
I'mSantos.Ilove"python".2.数字(Number)数字用来存储数值,包含两种常用的数字类型:整数(int)和浮点数(float),其中浮点数由整数和小数部分组成。两种类型之间可以相互转换,如果要将整数转换为浮点数,就在变量前加上float;如果要将浮点数转换为整数,就在变量前加上int,例如:
7还有其他两种复杂的数据类型,即长整数和复数,由于不常用到,感兴趣的读者可以自己学习。3.列表(list)如果需要把上述字符串和数字囊括起来,就可以使用列表。列表能够包含任意种类的数据类型和任意数量。创建列表非常容易,只要把不同的变量放入方括号中,并用逗号分隔即可,例如:
怎么访问列表中的值呢?可以在方括号中标明相应的位置索引进行访问,与一般认知不一样的是,索引从0开始,例如:
list1[0]:Pythonlist2[1:3]:[2,3]
['Python','new','Scrappy']如果想要给列表添加值呢?可以用append()方法,例如:
['Python','new','Scrappy','bySantos']4.字典(Dictionaries)字典是一种可变容器模型,正如其名,字典含有“字”(直译为键值,key)和值(value),使用字典就像是自己创建一个字典和查字典的过程。每个存储的值都对应着一个键值key,key必须唯一,但是值不需要唯一。值也可以取任何数据类型,例如:
Alex{'Name':'Alex','Age':7,'Class':'First'}如何遍历访问字典中的每一个值呢?这里需要用到字典和循环的结合,例如:
NameAlexAge7ClassFirst如果想修改字典中的值或者加入新的键值呢?可以直接修改和加入,例如:
{'Name':'Tom','Age':7,'Class':'First','Gender':'M'}
条件语句可以使得当满足条件的时候才执行某部分代码。条件为布尔值,也就是只有True和False两个值。当if判断条件成立时才执行后面的语句;当条件不成立的时候,执行else后面的语句,例如:
Youarestudyingpython.如果需要判断的有多种条件,就需要用到elif,例如:
Youarestudyingjava.Python的条件语句注意不要少了冒号(:)。循环语句能让我们执行一个代码片段多次,循环分为for循环和while循环。for循环能在一个给定的顺序下重复执行,例如:
BeijingShanghaiGuangzhou除了对列表进行直接循环,有时我们还会使用range()进行循环,首先用len(citylist)得到列表的长度为3,然后range(3)会输出列表[0,1,2],从而实现循环,得到和上面一样的结果。例如:
BeijingShanghaiGuangzhouwhile循环能不断重复执行,只要能满足一定条件,例如:
012
在代码很少的时候,我们按照逻辑写完就能够很好地运行。但是如果代码变得庞大复杂起来,就需要自己定义一些函数(Functions),把代码切分成一个个方块,使得代码易读,可以重复使用,并且容易调整顺序。其实Python就自带了很多函数,例如下面的sum()和abs()函数,我们可以直接调用。
101此外,我们也可以自己定义函数。一个函数包括输入参数和输出参数,Python的函数功能可以用y=x+1的数学函数来理解,在输入x=2的参数时,y输出3。但是在实际情况中,某些函数输入和输出参数可以不用指明。下面定义一个函数:
3参数必须要正确地写入函数中,函数的参数也可以为多个,也可以是不同的数据类型,例如可以是两个参数,分别是字符串和列表型的。
applebananaorange
santos18看到这里,也许你有疑问,要实现上述代码的结果,使用函数式编程不是比面向对象编程更简单吗?例如,如果我们使用函数式编程,可以写成:
santos18此处确实是函数式编程更容易。使用函数式编程,我们只需要写清楚输入和输出变量并执行函数即可;而使用面向对象的编程方法,首先要创建封装对象,然后还要通过对象调用被封装的内容,岂不是很麻烦?但是,在某些应用场景下,面向对象编程能够显示出更大的优势。如何选择函数式编程和面向对象编程呢?可以这样进行选择,如果各个函数之间独立且无共用的数据,就选用函数式编程;如果各个函数之间有一定的关联性,那么选用面向对象编程比较好。下面简单介绍面向对象的两大特性:封装和继承。1.封装封装,顾名思义就是把内容封装好,再调用封装好的内容。封装分为两步:第一步为封装内容。第二步为调用被封装的内容。(1)封装内容下面为封装内容的示例。
self在这里只是一个形式参数,当执行obj1=Person('santos',18)时,self等于obj1,此处将santos和18分别封装到obj1及self的name和age属性中。结果是obj1有name和age属性,其中name="santos",age=18。(2)调用被封装的内容调用被封装的内容时有两种方式:通过对象直接调用和通过self间接调用。通过对象直接调用obj1对象的name和age属性,代码如下:
santos18通过self间接调用时,Python默认会将obj1传给self参数,即obj1.detail(obj1)。此时方法内部的self=obj1,即self.name='santos',self.age=18,代码如下:
santos18上述例子定义了一个Person的类。在这个类中,可以通过各种函数定义Person的各种行为和特性,要让代码显得更加清晰有效,就要在调用Person类各种行为的时候也可以随时提取。这比仅使用函数式编程更加方便。面对对象的编程方法不会像平时按照执行流程去思考,在这个例子中,是把Person这个类型视为一个对象,它拥有name和age两个属性,在调用过程中,让自己把自己打印出来。综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或self间接获取被封装的内容。2.继承继承是以普通的类为基础建立专门的类对象。面向对象编程的继承和现实中的继承类似,子继承了父的某些特性,例如:猫可以:喵喵叫、吃、喝、拉、撒狗可以:汪汪叫、吃、喝、拉、撒如果我们要分别为猫和狗创建一个类,就需要为猫和狗实现他们所有的功能,代码如下,这里为伪代码,无法在python执行:
从上述代码不难看出,吃、喝、拉、撒是猫狗共同的特性,我们没有必要在代码中重复编写。如果用继承的思想,就可以写成:动物:吃喝拉撒猫:喵喵叫(猫继承动物的功能)狗:汪汪叫(狗继承动物的功能)
小白家的小黑猫吃喵喵叫胖子家的小瘦狗吃汪汪叫对于继承来说,其实就是将多个类共有的方法提取到父类中,子类继承父类中的方法即可,不必一一实现每个方法。
在编程过程中,我们不免会遇到写出来的程序运行错误,所以程序员经常戏称自己是在“写bug(错误)而非写程序”。这些错误一般来说会使得整个程序停止运行,但是在Python中,我们可以用try/except语句来捕获异常。try/except使用try来检测语句块中的错误,如果有错误的话,except则会执行捕获异常信息并处理。以下是一个实例:
divisionbyzero上述代码首先执行try里面的语句,除以0产生运算错误后,会执行except里的语句,将错误打印出来。在网络爬虫中,它可以帮我们处理一些无法获取到数据报错的情况。此外,如果我们并不想打印错误,就可以用pass空语句。
步骤二:出现如图2-18所示的审查元素页面。单击左上角的鼠标键按钮,然后在页面上单击想要的数据,下面的Elements会出现相应的code所在的地方,就定位到想要的元素了。
存储到本地的txt文件非常简单,在第二步的基础上加上2行代码就可以把这个字符串保存在text中,并存储到本地。txt文件地址应该和你的Python文件放在同一个文件夹。返回文件夹,打开title.txt文件,其中的内容如图2-19所示。
试题1:请使用Python中的循环打印输出从1到100的所有奇数。
试题2:请将字符串“你好$$$我正在学Python@#@#现在需要&&&修改字符串”中的符号变成一个空格,需要输出的格式为:“你好我正在学Python现在需要修改字符串”。
试题3:输出9×9乘法口诀表。
试题4:请写出一个函数,当输入函数变量月利润为I时,能返回应发放奖金的总数。例如,输出“利润为100000元时,应发放奖金总数为10000元”。其中,企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10%;利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%;利润在20万元到40万元之间时,高于20万元的部分可提成5%;利润在40万元到60万元之间时,高于40万元的部分可提成3%;利润在60万元到100万元之间时,高于60万元的部分可提成1.5%;利润高于100万元时,超过100万元的部分按1%提成。
试题5:用字典的值对字典进行排序,将{1:2,3:4,4:3,2:1,0:0}按照字典的值从大到小进行排序。
试题6:请问以下两段代码的输出分别是什么?
试题7:请问以下两段代码的输出分别是什么?
试题1答案:
在上述代码中,range(1,101)返回的是从1到100所有整数的列表list,然后使用循环判断这个数字除以2的余数是否为1,i%2返回的是i除以2的余数。如果余数等于1,就输出该数字。
试题2答案:
在上述代码中,使用replace方法可以将字符串中的一些字符替换成想要的字符。例如,str1.replace('$$$','')就是把str1中的'$$$'替换成空格。其实还可以采用另一种更加简单的方法:
这里用到一个库re(正则表达式),使用其中的re.sub可以进行替换。正则表达式的功能将在第5章进行详细说明。试题3答案:
运行上述代码,得到的结果如图2-20所示。
上述代码使用了两个循环的嵌套,在第一个循环中i为1,在第二个循环中j为1。当j完成循环后,i会加1,变成2,j又从1开始一个新的循环,从而得到输出的这个9×9乘法表。试题4答案:
在上述代码中,计算应发奖金时,我们对不同的情况使用if和elif进行了不同的处理。还可以使用一个比较简洁的方式:
试题5答案:
运行上述代码,输出的结果是:[(0,0),(2,1),(1,2),(4,3),(3,4)]对字典进行排序是不可能的,只有把字典转换成另一种方式才能排序。字典本身是无序的,但是如列表元组等其他类型是有序的,所以需要用一个元组列表来表示排序的字典。
试题6答案:第一段代码输出的结果是:1第二段代码输出的结果是:[1]从结果发现,在第一段代码中,a为数字int,函数改变不了函数以外a的值,输出结果仍然为1;而在第二段代码中,a为列表,函数将函数以外的a值改变了。这是因为在Python中对象有两种,即可更改(mutable)与不可更改(immutable)对象。在Python中,strings字符串、tuples元组和numbers数字是不可更改对象,而list列表、dict字典等是可更改对象。在第一段代码中,当一个引用传递给函数时,函数自动复制一份引用。函数里和函数外的引用是不一样的。在第二段代码中,函数内的引用指向的是可变对象列表a,函数内的列表a和函数外的列表a是同一个。
试题7答案:第一段代码输出的结果是:bbbaaaaaa第二段代码输出的结果是:[1][1][1]代码中的p1.name="bbb"表示实例调用了类变量,其实就是函数传参的问题。p1.name一开始指向类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了,所以第一个答案是bbb。而后面的两个答案还是调用类变量name="aaa",所以还是aaa。第二段的答案因为正如上面所言,列表和字典是可更改对象,因此修改一个指向的对象时会把类变量也改变了。