上一篇演示了如何使用requests模块向网站发送http请求,获取到网页的HTML数据。这篇来演示如何使用BeautifulSoup模块来从HTML文本中提取咱们想要的数据。html
update on 2016-12-28:以前忘记给BeautifulSoup的官网了,今天补上,顺便再补点BeautifulSoup的用法。html5
update on 2017-08-16:不少网友留言说Unsplash网站改版了,不少内容是动态加载的。因此建议动态加载的内容使用PhantomJS而不是Request库进行请求,若是使用PhantomJS请看个人下一篇博客,若是是定位html文档使用的class等名字更改的话,建议你们根据更改后的内容进行定位,学爬虫重要的是爬取数据的逻辑,逻辑掌握了网站怎么变都不重要啦。web
个人运行环境以下:chrome
系统版本
Windows10。浏览器
Python版本
Python3.5,推荐使用Anaconda 这个科学计算版本,主要是由于它自带一个包管理工具,能够解决有些包安装错误的问题。去Anaconda官网,选择Python3.5版本,而后下载安装。网络
IDE
我使用的是PyCharm,是专门为Python开发的IDE。这是JetBrians的产品,点我下载。函数
BeautifulSoup 有多个版本,咱们使用BeautifulSoup4。详细使用看BeautifuSoup4官方文档。
使用管理员权限打开cmd命令窗口,在窗口中输入下面的命令便可安装:
conda install beautifulsoup4
工具
直接使用Python3.5 没有使用Anaconda版本的童鞋使用下面命令安装:
pip install beautifulsoup4
测试
而后咱们安装lxml,这是一个解析器,BeautifulSoup可使用它来解析HTML,而后提取内容。网站
Anaconda 使用下面命令安装lxml:
conda install lxml
使用Python3.5 的童鞋们直接使用pip安装会报错(因此才推荐使用Anaconda版本),安装教程看这里。
若是不安装lxml,则BeautifulSoup会使用Python内置的解析器对文档进行解析。之因此使用lxml,是由于它速度快。
文档解析器对照表以下:
解析器 | 使用方法 | 优点 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup,"html.parser") | 1. Python的内置标准库 2. 执行速度适 3. 中文档容错能力强 |
Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup,"lxml") | 1. 速度快 2. 文档容错能力强 |
须要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup,["lxml-xml"]) BeautifulSoup(markup,"xml") |
1. 速度快 2. 惟一支持XML的解析器 |
须要安装C语言库 |
html5lib | BeautifulSoup(markup,"html5lib") | 1. 最好的容错性 2. 以浏览器的方式解析文档 3. 生成HTML5格式的文档 |
速度慢,不依赖外部扩展 |
网上找到的几个官方文档:BeautifulSoup4.4.0中文官方文档,BeautifulSoup4.2.0中文官方文档。不一样版本的用法差很少,几个经常使用的语法都同样。
首先来看BeautifulSoup的对象种类,在使用的过程当中就会了解你获取到的东西接下来应该如何操做。
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每一个节点都是Python对象。全部对象能够概括为4种类型: Tag , NavigableString , BeautifulSoup , Comment 。下面咱们分别看看这四种类型都是什么东西。
这个就跟HTML或者XML(还能解析XML?是的,能!)中的标签是同样同样的。咱们使用find()方法返回的类型就是这个(插一句:使用find-all()返回的是多个该对象的集合,是能够用for循环遍历的。)。返回标签以后,还能够对提取标签中的信息。
tag.name
tag['attribute']
咱们用一个例子来了解这个类型:
from bs4 import BeautifulSoup html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ soup = BeautifulSoup(html_doc, 'lxml') #声明BeautifulSoup对象 find = soup.find('p') #使用find方法查到第一个p标签 print("find's return type is ", type(find)) #输出返回值类型 print("find's content is", find) #输出find获取的值 print("find's Tag Name is ", find.name) #输出标签的名字 print("find's Attribute(class) is ", find['class']) #输出标签的class属性值
NavigableString就是标签中的文本内容(不包含标签)。获取方式以下:
tag.string
仍是以上面那个例子,加上下面这行,而后执行:
print('NavigableString is:', find.string)
BeautifulSoup对象表示一个文档的所有内容。支持遍历文档树和搜索文档树。
这个对象其实就是HTML和XML中的注释。
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string type(comment) # <class 'bs4.element.Comment'>
有些时候,咱们并不想获取HTML中的注释内容,因此用这个类型来判断是不是注释。
if type(SomeString) == bs4.element.Comment: print('该字符是注释') else: print('该字符不是注释')
可使用子节点、父节点、 及标签名的方式遍历:
soup.head #查找head标签 soup.p #查找第一个p标签 #对标签的直接子节点进行循环 for child in title_tag.children: print(child) soup.parent #父节点 # 全部父节点 for parent in link.parents: if parent is None: print(parent) else: print(parent.name) # 兄弟节点 sibling_soup.b.next_sibling #后面的兄弟节点 sibling_soup.c.previous_sibling #前面的兄弟节点 #全部兄弟节点 for sibling in soup.a.next_siblings: print(repr(sibling)) for sibling in soup.find(id="link3").previous_siblings: print(repr(sibling))
最经常使用的固然是find()和find_all()啦,固然还有其余的。好比find_parent() 和 find_parents()、 find_next_sibling() 和 find_next_siblings() 、find_all_next() 和 find_next()、find_all_previous() 和 find_previous() 等等。
咱们就看几个经常使用的,其他的若是用到就去看官方文档哦。
find_all( name , attrs , recursive , string , **kwargs )
soup.find_all("title") # [<title>The Dormouse's story</title>] # soup.find_all("p", "title") # [<p class="title"><b>The Dormouse's story</b></p>] # soup.find_all("a") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] # soup.find_all(id="link2") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] # import re soup.find(string=re.compile("sisters")) # u'Once upon a time there were three little sisters; and their names were\n'
name 参数:能够查找全部名字为 name 的tag。
attr 参数:就是tag里的属性。
string 参数:搜索文档中字符串的内容。
recursive 参数: 调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的全部子孙节点。若是只想搜索tag的直接子节点,可使用参数 recursive=False 。
find( name , attrs , recursive , string , **kwargs )
soup.find('title') # <title>The Dormouse's story</title> # soup.find("head").find("title") # <title>The Dormouse's story</title>
基本功已经练完,开始实战!
继续上一篇的网站Unsplash,咱们在首页选中图片,查看html代码。发现全部的图片都在a标签里,而且class都是cV68d,以下图。
经过仔细观察,发现图片的连接在style中的background-image中有个url。这个url就包含了图片的地址,url后面跟了一堆参数,能够看到其中有&w=XXX&h=XXX,这个是宽度和高度参数。咱们把高度和宽度的参数去掉,就能获取到大图。下面,咱们先获取到全部的含有图片的a标签,而后在循环获取a标签中的style内容。
其实在图片的右下方有一个下载按钮,按钮的标签中有一个下载连接,可是该连接并不能直接请求到图片,须要跳转几回,经过获取表头里的Location才能获取到真正的图片地址。后续我再以这个角度获取图片写一篇博文,我们现根据能直接获取到的url稍作处理来获取图片。小伙伴儿们也可能会发现其余的方式来获取图片的url,都是能够的,尽情的尝试吧!
import requests #导入requests 模块 from bs4 import BeautifulSoup #导入BeautifulSoup 模块 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'} #给请求指定一个请求头来模拟chrome浏览器 web_url = 'https://unsplash.com'r = requests.get(web_url, headers=headers) #像目标url地址发送get请求,返回一个response对象 all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #获取网页中的class为cV68d的全部a标签 for a in all_a: print(a['style']) #循环获取a标签中的style
这里的find_all('a', class_='cV68d') 是找到全部class为cV68d的a标签,返回的是一个list,因此能够用for循环获取每一个a标签。
还有,get请求使用了headers参数,这个是用来模拟浏览器的。如何知道‘User-Agent’是什么呢?
在你的Chrome浏览器中,按F12,而后刷新网页,看下图就能够找到啦。
OK,咱们来执行如下上面的代码,结果以下:
接下来的任务是在一行的文本中取到图片的url。仔细看每一行的字符串,两个双引号之间的内容就是图片的url了,因此咱们Python的切片功能来截取这中间的内容。
改写for循环中的内容:
for a in all_a: img_str = a['style'] #a标签中完整的style字符串 print(img_str[img_str.index('"')+1 : img_str.index('"',img_str[img_str.index('"')+1)]) #使用Python的切片功能截取双引号之间的内容
获取到url后还要把宽度和高度的参数去掉。
for a in all_a: img_str = a['style'] #a标签中完整的style字符串 print('a标签的style内容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] print('高度和宽度数据字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') print('截取后的图片的url是:', img_url_final)
有了这些图片的url,就能够经过继续发请求的方式获取图片啦。接下来咱们先来封装一下发请求的代码。
先建立一个类:
class BeautifulPicture(): def __init__(self): #类的初始化操做 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'} #给请求指定一个请求头来模拟chrome浏览器 self.web_url = 'https://unsplash.com' #要访问的网页地址 self.folder_path = 'D:\BeautifulPicture' #设置图片要存放的文件目录
而后封装request请求:
def request(self, url): #返回网页的response r = requests.get(url) # 像目标url地址发送get请求,返回一个response对象 return r
咱们在文件目录下保存图片的话,要先建立文件目录。因此再添加一个建立目录的方法:
要先引入os库哦。
import os
而后是方法定义:
def mkdir(self, path): ##这个函数建立文件夹 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夹') os.makedirs(path) print('建立成功!') else: print(path, '文件夹已经存在了,再也不建立')
再而后是保存图片啦。
def save_img(self, url, name): ##保存图片 print('开始保存图片...') img = self.request(url) time.sleep(5) file_name = name + '.jpg' print('开始保存文件') f = open(file_name, 'ab') f.write(img.content) print(file_name,'文件保存成功!') f.close()
工具方法都已经准备完毕,开始咱们的逻辑部分:
def get_pic(self): print('开始网页get请求') r = self.request(self.web_url) print('开始获取全部a标签') all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #获取网页中的class为cV68d的全部a标签 print('开始建立文件夹') self.mkdir(self.folder_path) #建立文件夹 print('开始切换文件夹') os.chdir(self.folder_path) #切换路径至上面建立的文件夹 i = 1 #后面用来给图片命名 for a in all_a: img_str = a['style'] #a标签中完整的style字符串 print('a标签的style内容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] print('高度和宽度数据字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') print('截取后的图片的url是:', img_url_final) self.save_img(img_url_final, str(i)) i += 1
最后就是执行啦:
beauty = BeautifulPicture() #建立一个类的实例 beauty.get_pic() #执行类中的方法
最后来一个完整的代码,对中间的一些部分进行了封装和改动,并添加了每部分的注释,一看就明白了。有哪块有疑惑的能够留言~~
import requests #导入requests 模块 from bs4 import BeautifulSoup #导入BeautifulSoup 模块 import os #导入os模块 class BeautifulPicture(): def __init__(self): #类的初始化操做 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'} #给请求指定一个请求头来模拟chrome浏览器 self.web_url = 'https://unsplash.com' #要访问的网页地址 self.folder_path = 'D:\BeautifulPicture' #设置图片要存放的文件目录 def get_pic(self): print('开始网页get请求') r = self.request(self.web_url) print('开始获取全部a标签') all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #获取网页中的class为cV68d的全部a标签 print('开始建立文件夹') self.mkdir(self.folder_path) #建立文件夹 print('开始切换文件夹') os.chdir(self.folder_path) #切换路径至上面建立的文件夹 for a in all_a: #循环每一个标签,获取标签中图片的url而且进行网络请求,最后保存图片 img_str = a['style'] #a标签中完整的style字符串 print('a标签的style内容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容 #获取高度和宽度的字符在字符串中的位置 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉 print('高度和宽度数据字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') #把高度和宽度的字符串替换成空字符 print('截取后的图片的url是:', img_url_final) #截取url中参数前面、网址后面的字符串为图片名 name_start_pos = img_url.index('photo') name_end_pos = img_url.index('?') img_name = img_url[name_start_pos : name_end_pos] self.save_img(img_url_final, img_name) #调用save_img方法来保存图片 def save_img(self, url, name): ##保存图片 print('开始请求图片地址,过程会有点长...') img = self.request(url) file_name = name + '.jpg' print('开始保存图片') f = open(file_name, 'ab') f.write(img.content) print(file_name,'图片保存成功!') f.close() def request(self, url): #返回网页的response r = requests.get(url, headers=self.headers) # 像目标url地址发送get请求,返回一个response对象。有没有headers参数均可以。 return r def mkdir(self, path): ##这个函数建立文件夹 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夹') os.makedirs(path) print('建立成功!') else: print(path, '文件夹已经存在了,再也不建立') beauty = BeautifulPicture() #建立类的实例 beauty.get_pic() #执行类中的方法
执行的过程当中可能会有点慢,这是由于图片自己比较大!若是仅仅是为了测试爬虫,则能够不把图片的宽度和高度替换掉,图片就没那么大啦,运行过程会快不少。
伙伴儿们是否是发现,咱们只获取到了10张图片,并无把网站全部照片都下载下来。
这是由于我们爬取的网站是下拉刷新的,下拉一次,刷新10张照片。那么,该如何爬取这种下拉刷新的网页呢?请看下一篇喽。