社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  python开源

Python爬虫零基础入门(系列)

果金Python • 5 年前 • 1297 次点击  

一、前言 上一篇演示了如何使用requests模块向网站发送http请求,获取到网页的HTML数据。这篇来演示如何使用BeautifulSoup模块来从HTML文本中提取我们想要的数据。

update on 2016-12-28:之前忘记给BeautifulSoup的官网了,今天补上,顺便再补点BeautifulSoup的用法。

update on 2017-08-16:很多网友留言说Unsplash网站改版了,很多内容是动态加载的。所以建议动态加载的内容使用PhantomJS而不是Request库进行请求,如果使用PhantomJS请看我的下一篇博客,如果是定位html文档使用的class等名字更改的话,建议大家根据更改后的内容进行定位,学爬虫重要的是爬取数据的逻辑,逻辑掌握了网站怎么变都不重要啦。

二、运行环境 我的运行环境如下:

系统版本 Windows10。

Python版本 Python3.5,推荐使用Anaconda 这个科学计算版本,主要是因为它自带一个包管理工具,可以解决有些包安装错误的问题。去官网,选择Python3.5版本,然后下载安装。

IDE 我使用的是PyCharm,是专门为Python开发的IDE。这是JetBrians的产品。

三、模块安装 BeautifulSoup 有多个版本,我们使用BeautifulSoup4。详细使用看官方文档。 使用管理员权限打开cmd命令窗口,在窗口中输入下面的命令即可安装: conda install beautifulsoup4Python入门到精通学习教程请加219再加上539然后519内有大量学习教程,欢迎大家加入

直接使用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格式的文档 速度慢,不依赖外部扩展 四、BeautifulSoup 库的使用 网上找到的几个官方文档,不同版本的用法差不多,几个常用的语法都一样。

首先来看BeautifulSoup的对象种类,在使用的过程中就会了解你获取到的东西接下来应该如何操作。

4.1 BeautifulSoup对象的类型 Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象。所有对象可以归纳为4种类型: Tag , NavigableString , BeautifulSoup , Comment 。下面我们分别看看这四种类型都是什么东西。

4.1.1 Tag 这个就跟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属性值 4.1.2 NavigableString NavigableString就是标签中的文本内容(不包含标签)。获取方式如下: tag.string 还是以上面那个例子,加上下面这行,然后执行: print('NavigableString is:', find.string)

4.1.3 BeautifulSoup BeautifulSoup对象表示一个文档的全部内容。支持遍历文档树和搜索文档树。

4.1.4 Comment 这个对象其实就是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('该字符不是注释') 4.2 BeautifulSoup遍历方法 4.2.1 节点和标签名 可以使用子节点、父节点、 及标签名的方式遍历:

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)) 4.2.2 搜索文档树 最常用的当然是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() 搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。返回值类型是bs4.element.ResultSet。 完整的语法: 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() 与find_all()类似,只不过只返回找到的第一个值。返回值类型是bs4.element.Tag。 完整语法: 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')  #获取网页中的classcV68d的所有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')  #获取网页中的classcV68d的所有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张照片。那么,该如何爬取这种下拉刷新的网页呢?请看下一篇喽

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/46828
 
1297 次点击