精通Python爬虫-03-狩猎大师

声明:html

本系列文章原创于慕课网,做者秋名山车神,任何人不得以任何形式在不经做者容许的状况下,进行任何形式的印刷以及销售,转载需注明出处及此声明。前端

本系列文章更新至少每周一更,将涉及Python爬虫基础,Requests,Scrapy等主流爬虫技术。同时会介绍图片验证码,语音验证码的识别以及我本身设计的一个高并发可扩展易维护的集群爬虫架构。python

对文章有任何问题请在下面留言,我会不按期的回复你们。面试

人非圣贤,若是文章有错别字请你们自行区分或指正出来,我将不按期修改错误的地方。算法

本系列可否持久更新下去离不开你们的支持与鼓励,以及对原创版权的尊重。json

做者想说的话浏览器

最近一段时间特别的忙,事情也不少。有几家出版社找着写书,都让我给推了,昨天闲着没事在翻慕课网的手记时,发现了这个系列的文章。看到那么多人浏览,那么多人评论让快点更新,我以为不能让你们失望,因此我开始更新了。网络

我想作的事情很很普通,就是但愿我所知道的技术可以以一种力所能及的方式带给你们,我但愿慕课网是一个积极进取的社区,每一个人都能毫无保留的对待别人。也但愿我作的这件普通的事情,可以帮助到每个想学习python,学习爬虫的人。谢谢你们的支持。数据结构

大师的嗅觉架构

在上一章中,咱们已经学习了如何使用 urllib 来从网络上获取信息,这一章咱们来学习若是从这些信息中提取咱们想要的内容。

依然以咱们的慕课网为例,顺便让咱们回顾一下上一章的代码。

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 建立一个Request对象 req = urllib.request.Request('http://www.imooc.com') # 使用Request对象发送请求 response = urllib.request.urlopen(req) print(response.read())

若是运行上面的代码,就能够看到慕课网首页的静态代码了,也就是咱们一般所说的HTML代码。

Python爬虫

那这么多的数据,咱们怎么能从中获取到想要的内容呢?好比咱们想要获取慕课网首页实战推荐里面全部推荐的实战课名称,该怎么办呢?

Python爬虫

此时咱们就可使用另一个库 BeautifulSoup

安装BeautifulSoup

打开咱们的命令行工具,输入:

pip install beautifulsoup4==4.6.0

若是你那里下载的速度特别慢,可使用下面这条命令,指定本次使用豆瓣的pip镜像源来安装:

pip install beautifulsoup4==4.6.0 -i https://pypi.douban.com/simple

看到以下的信息就表示安装成功了:

Successfully installed beautifulsoup4-4.6.0

有的小伙伴可能还会看到一些黄色的文字:

You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

这是说咱们的pip版本过低了,提示咱们可使用 python -m pip install --upgrade pip 这条命令来升级到最新版本,通常不须要升级,若是想要升级的可使用这条命令,升级之后就不会再出这个提示了。

在实战中学习

不用读系列:我并不想把本教程作成一个官方文档,或者是像其余书那样,把官方文档抄下来。这样又有什么意义呢?学了之后仍是不知道怎么应用,因此我但愿每个库咱们都是经过一个小项目来学习的,用到的每个方法我都会详细的说明。而用不到的方法,它都用不到了,咱们还学它干吗?

你们跟着我一步一步学习怎么狩猎吧

首先咱们打开慕课网的首页:https://www.imooc.com

建议使用Chrome浏览器

而后找到咱们的实战推荐,在其中一个推荐的实战课图片上,右键点击图片,选择检查。

Python爬虫

而后会打开浏览器的开发者工具,若是还不知道这个工具怎么用的,你们能够去看一下个人免费课程:

浏览器开发者工具使用技巧

接着咱们选择开发者工具上面的选择器,使用鼠标左键点击咱们想要获取的地方:

Python爬虫

能够看到下面的代码就是咱们想要获取的内容:

<h3 class="course-card-name">全网最热Python3入门+进阶 更快上手实际开发</h3>

那么其余的几个也是这样的吗?重复上面的步骤点击每一个实战推荐的名字,看到以下结果:

Python爬虫

第一个狩猎技巧

想要获取同类型的内容,就寻找他们相同的部分。

那么上面的实战推荐中,很显然他们相同的部分就是 <h3 class="course-card-name"></h3>,那接下来就直接修改上面的代码来试着获取这部分的内容。

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 导包 from bs4 import BeautifulSoup # 建立一个Request对象 req = urllib.request.Request('http://www.imooc.com') # 使用Request对象发送请求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read()) course_names = soup.find_all('h3', {'class':'course-card-name'}) print(course_names)

from bs4 import BeautifulSoup 这句代码呢,意思就是从bs4这个模块里,导入咱们的 BeautifulSoup 方法,没什么其余的含义。

BeautifulSoup() 方法接收两个参数,第一个参数是一个str类型的,就是咱们获取到的HTML代码。第二个参数也是一个str类型的,表示咱们但愿使用哪一个库来解析HTML的代码,经常使用的都 html.parser 和 lxml,其中lxml 效率更高一些。而咱们上面的代码并无指定第二个参数,因此它会输出一个警告信息。

UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line 5 of the file C:\dream\python\Anaconda3\Scripts\ipython-script.py. To get rid of this warning, change code that looks like this:

BeautifulSoup(YOUR_MARKUP})

to this:

BeautifulSoup(YOUR_MARKUP, "lxml")

markup_type=markup_type))

关键在于倒数第二行 BeautifulSoup(YOUR_MARKUP, "lxml"),这里提示的很明显,让咱们增长第二个参数 lxml。固然了,这是由于个人电脑里安装了 lxml 库,没有安装的小伙伴,这里提示的多是 html.parser,无论提示的是什么,你都按照BeautifulSoup提示你的来进行修改就能够了~

我这里按照提示修改之后的那句代码就成了:

soup = BeautifulSoup(response.read(), "lxml")

这个方法还会给咱们返回一个bs4.BeautifulSoup的对象,咱们可使用一个变量来接收,这个变量能够是任意名字,这里咱们用 soup 来表示。

soup.find_all() 这个方法接收两个参数,表明从 soup 这个对象里,搜索所有符合条件的内容。第一个参数是str类型的,表示咱们要从哪一个HTML标签中获取,这里咱们是要从 h3 这个标签里获取。第二个dict类型的,接收key和value的键值对。表明咱们要获取的这个h3标签,有那些属性能够来标识它。

<h3 class="course-card-name">全网最热Python3入门+进阶 更快上手实际开发</h3>

能够看到咱们想获取的h3标签,只有一个class属性,那第二个参数就是:

{"class": "course-card-name"}

该方法返回一个 bs4.element.ResultSet 类型的对象,相似于咱们python的list列表,咱们能够像操做列表同样来操做这个对象。一样使用一个变量去接收它。

最终上面的代码运行之后,可能看到以下的结果:

[<h3 class="course-card-name">前端进阶:响应式开发与经常使用框架</h3>, <h3 class="course-card-name">揭秘一线互联网企业 前端JavaScript高级面试</h3>, ...省略N条... <h3 class="course-card-name">Python Flask 构建微电影视频网站</h3>, <h3 class="course-card-name">AWS云-深度学习&amp;amp;机器学习的AI业务应用</h3>]

提示:因为慕课网是不断在更新的,因此当你看到这篇教程的时候,相关的内容可能已经更换了名称,好比class不叫course-card-name。我但愿你可以本身修改上面的代码,来达到符合你看到教程时,慕课网的样子。

咱们发现,结果和咱们预想的不太同样,这是为何呢?咱们再次回到慕课网,右键在网页空白的部分单击,而后选择查看网页源码:

Python爬虫

而后在弹出来的窗口中,按下 ctrl + f 的组合快捷键,接着搜索 course-card-name

嗯,我相信你们知道组合键的意思就是先按下键盘上的 Ctrl 键,而后不松开这个键的状况下,再按键盘上的 F 键。

看到的搜索结果以下:

Python爬虫

出现了76个搜索结果,这是为何呢?咱们明明只是想获取实战推荐里面的课程名称,为何出现了这么多的搜索结果?

简单思考一下咱们就能够知道,咱们刚才搜索是从 response.read() 里面搜索的,这表明了整个慕课网首页的全部内容。而慕课网全部的课程名称,使用的都是下面的样式:

<h3 class="course-card-name"></h3>

因此咱们不只获取了实战推荐的课程名称,还获取了其余不想要的课程名称。

爬虫并非获取的越多越好,而是精准获取咱们想要的数据,其余不想要的数据越少越好。

那么很显然,是咱们狩猎的范围太大了,试着来缩小咱们的狩猎范围吧。

一样使用浏览器的开发者工具,慢慢的移动咱们的鼠标,直到鼠标指向的内容,恰好包裹咱们想要获取的那一部分。

Python爬虫

如图所示,最终咱们指向了一个div的标签,它覆盖了咱们想要获取的全部内容,而没有覆盖其余咱们不想获取的内容。而且这个div也有一个class属性,经过上面学习到的,来修改一下咱们的代码,看看是否是能达到咱们的目的。

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 导包 from bs4 import BeautifulSoup # 建立一个Request对象 req = urllib.request.Request('http://www.imooc.com') # 使用Request对象发送请求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先获取咱们刚才找到的div course_div = soup.find_all('div', {'class':'clearfix types-content'}) # 再从咱们的div里获取想要的内容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) # 结果是不是咱们想要的呢? print(course_names)

结果就报错了~

AttributeError: ResultSet object has no attribute 'find_all'. You're probably treating a list of items like a single item. Did you call find_all() when you meant to call find()?

其实你们看到报错,不要惧怕,要慢慢的看这个报错的内容,通常报错都是从倒数第一行开始看的,也只有倒数第一行每每才是有用的东西。

那么上面的这个报错很显然,find_all()方法返回的是一个ResultSet对象,这个对象不具备find_all()方法,因此在咱们下面使用 course_div.find_all('h3', {'class':'course-card-name'}) 时,就报错了。

可是不要慌,BeautifulSoup已经给出修改意见了,它说若是咱们想要获取的是一个单一的内容,能够尝试使用find()方法,修改一下咱们的代码,再次运行。

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 导包 from bs4 import BeautifulSoup # 建立一个Request对象 req = urllib.request.Request('http://www.imooc.com') # 使用Request对象发送请求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先获取咱们刚才找到的div course_div = soup.find('div', {'class':'clearfix types-content'}) # 再从咱们的div里获取想要的内容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) # 此次还会报错吗? print(course_names)

结果如咱们所料,没有报错,而且输出了以下内容:

[<h3 class="course-card-name">全网最热Python3入门+进阶 更快上手实际开发</h3>, <h3 class="course-card-name">玩转数据结构 从入门到进阶</h3>, <h3 class="course-card-name">Java企业级电商项目架 构演进之路 Tomcat集群与Redis分布式</h3>, <h3 class="course-card-name">React Native技术精讲与高质量上线APP开发</h3>, <h3 class="course-card-name"> Vue2.5开发去哪儿网App 从零基础入门到实战项目</h3>]

soup.find() 这个方法,惟一和咱们find_all不一样的点在于,他们返回的对象不一样。find_all返回的是一个bs4.element.ResultSet列表对象。而find返回的是bs4.element.Tag。而find_all返回的列表中,包含的就是一个个的bs4.element.Tag对象。bs4.element.Tag对象,就具备了find或者是find_all的方法,以便咱们不断的缩小范围,最终获取咱们想要的结果。

你可能认为咱们本章到此结束了,并无结束。由于咱们的目的还没达到,我上面说过了,一个好的爬虫就是除了咱们想要的内容之外,别的什么都不要有。那咱们输出的结果里,显然还包含了 <h3 class="course-card-name"> 这些东西,那怎么样把它过滤掉呢?

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 导包 from bs4 import BeautifulSoup # 建立一个Request对象 req = urllib.request.Request('http://www.imooc.com') # 使用Request对象发送请求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先获取咱们刚才找到的div course_div = soup.find('div', {'class':'clearfix types-content'}) # 再从咱们的div里获取想要的内容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) for course_name in course_names: print(course_name.text)

只须要从咱们的ResultSet列表对象中,取出每个Tag对象,而后调用它的text属性,就能获取到标签中的文本内容了~

时间老是过的很快,你们能够在评论中告诉我,用本节学到的知识作了哪些有意思的事情。你们对本系列课程有任何的建议和疑问,均可以经过下方的留言告诉我,每一个人的留言我都会看的。

同时你们也能够加入下面两个Python的交流群:

慕课网Python讨论群①:221828022

也能够加入我建的一个Python交流群:685024920

两个群我都有在里面,你们有任何的问题均可以在里面 @秋名山车神~

学习一下咱们 liuyubobobo 老师的口头禅,你们加油~电动叉车

同时也但愿你们多多支持 liuyubobobo 老师的实战课,多多强化本身的算法内功,早日成功一个优秀的 攻城狮。

相关文章
相关标签/搜索