二、数据提取(2)——BeautifulSoup4库
BeautifulSoup4库
和lxml一样,BeautifulSoup也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML数据。
lxml只会局部遍历,而BeautifulSoup是基于HTML DOM的,会载入整个目标,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup用来解析 HTML 比较简单,API 非常人性化,支持 CSS 选择器,Python标准库中的 HTML 解析器,也支持 lxml 的XML解析器。
BeautifulSoup3目前已经停止开发,推荐现在的项目使用 BeautifulSoup 4。
安装和文件
- 安装:
pip install bs4 - 中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
几大解析工具对比
| 解析工具 | 解析速度 | 使用难度 |
|---|---|---|
| BeautifulSoup | 最慢 | 最简单 |
| lxml | 快 | 简单 |
| 正则 | 最快 | 最难 |
简单使用
from bs4 import BeautifulSoup html = """ <table class="tablelist" cellpadding="0" cellspacing="0"> <tbody> <tr class="h"> <td class="l" width="374">职位名称</td> <td>职位类别</td> <td>人数</td> <td>地点</td> <td>发布时间</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=48569&keywords=&tid=87&lid=2175">26699-智慧零售业务中心前端开发(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=48556&keywords=&tid=87&lid=2175">22989-高级AI后台开发工程师(上海/深圳)</a> </td> <td>技术类</td> <td>2</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=48537&keywords=&tid=87&lid=2175">25666-腾讯云华东区域解决方案支持</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=48534&keywords=&tid=87&lid=2175">CSIG02-腾讯云行业方案架构师(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=48428&keywords=&tid=87&lid=2175">25929-高级机器学习工程师</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=48425&keywords=&tid=87&lid=2175">30628-腾讯广告投放平台PHP开发工程师(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=48393&keywords=&tid=87&lid=2175">CSIG16-车联网系统工程师(自研基础产品运维负责人)(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=48394&keywords=&tid=87&lid=2175">CSIG16-车联网IT运维经理(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=48380&keywords=&tid=87&lid=2175">22989-腾讯云-区域交付leader(上海)</a> </td> <td>技术类</td> <td>1</td> <td>上海</td> <td>2019-03-15</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=48349&keywords=&tid=87&lid=2175">27570-高级引擎工程师</a> </td> <td>技术类</td> <td>2</td> <td>上海</td> <td>2019-03-15</td> </tr> </tbody> </table> """ # 创建 Beautiful Soup对象 # 使用 lxml来进行解析 soup = BeautifulSoup(html,"lxml") # bs4 => lxml print(soup.prettify())
四个常用的对象
BeautifulSoup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag
- NavigableString
- BeautifulSoup
- Comment
1.Tag:
Tag通俗点讲就是 HTML 中的一个个标签。示例代码如下:
from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><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> """ #创建 Beautiful Soup 对象 soup = BeautifulSoup(html,'lxml') print soup.title # <title>The Dormouse's story</title> print soup.head # <head><title>The Dormouse's story</title></head> print soup.a # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> print soup.p # <p class="title" name="dromouse"><b>The Dormouse's story</b></p> print type(soup.p) # <class 'bs4.element.Tag'>
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。如果要查询所有的标签,后面会进行介绍。 对于Tag,它有两个重要的属性,分别是name和attrs。示例代码如下:
print soup.name # [document] #soup 对象本身比较特殊,它的 name 即为 [document] print soup.head.name # head #对于其他内部标签,输出的值便为标签本身的名称 print soup.p.attrs # {'class': ['title'], 'name': 'dromouse'} # 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。 print soup.p['class'] # soup.p.get('class') # ['title'] #还可以利用get方法,传入属性的名称,二者是等价的 soup.p['class'] = "newClass" print soup.p # 可以对这些属性和内容等等进行修改 # <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
2. NavigableString:
如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string获取标签中的文字。示例代码如下:
print soup.p.string # The Dormouse's story print type(soup.p.string) # <class 'bs4.element.NavigableString'>thon
3. BeautifulSoup:
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法. 因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name
soup.name # '[document]'
4. Comment:
Tag , NavigableString , 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'>
Comment 对象是一个特殊类型的 NavigableString 对象:
comment # 'Hey, buddy. Want to buy a used parser'
遍历文档树
1. contents和children:
html_doc = """ <html><head><title>The Dormouse's story</title></head> <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> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc,'lxml') head_tag = soup.head # 返回所有子节点的列表 print(head_tag.contents) # 返回所有子节点的迭代器 for child in head_tag.children: print(child)
2. Strings 和 stripped_strings
如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取:
for string in soup.strings: print(repr(string)) # u"The Dormouse's story" # u'\n\n' # u"The Dormouse's story" # u'\n\n' # u'Once upon a time there were three little sisters; and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n' # u'...' # u'\n'
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
for string in soup.stripped_strings: print(repr(string)) # u"The Dormouse's story" # u"The Dormouse's story" # u'Once upon a time there were three little sisters; and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...'
搜索文档树
1.find和find_all方法
搜索文档时,一般用得比较多的就是两个方法,一个是find,一个是find_all。find方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。find_all方法是把所有满足条件的标签都选到,然后返回回去。使用这两个方法,最常用的用法是出入name以及attr参数找出符合要求的标签。
soup.find_all("a",attrs={"id":"link2"})
或者是直接传入属性的名字作为关键字参数:
soup.find_all("a",id="link2")
2. select方法
使用以上方法可以方便的找出元素。但有时候使用css选择器的方式可以更加的方便。使用css选择器的语法,应该使用select方法。以下列出几种常用 的css选择器方法:
(1) 通过标签名查找
print(soup.select('a'))
(2) 通过类名查找
通过类名,则应该在类的前面加一个 . 。比如要查找 **class=sister()**的标签。示例代码如下:
print(soup.select('.sister'))
(3)通过id查找
通过id查找,应该在id的名字前面加一个#号。示例代码如下:
print(soup.select("#link1"))
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开:
print(soup.select("p #link1"))
直接子标签查找,则使用 > 分隔:
print(soup.select("head > title"))
(5)通过属性查找
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。示例代码如下:
print(soup.select('a[href="http://example.com/elsie"]'))
以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。
soup = BeautifulSoup(html, 'lxml') print type(soup.select('title')) print soup.select('title')[0].get_text() for title in soup.select('title'): print title.get_text()
实战:爬取中国天气网华北地区的天气
import requests from bs4 import BeautifulSoup def parse_page(url): headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } response = requests.get(url,headers=headers) text = response.content.decode('utf-8') soup = BeautifulSoup(text,'lxml') conMidtab = soup.find('div',class_='conMidtab') tables = conMidtab.find_all('table') for table in tables: trs = table.find_all('tr')[2:] for tr in trs: tds = tr.find_all('td') city_td = tds[0] city = list(city_td.stripped_strings)[0] temp_td = tds[-2] min_temp = list(temp_td.stripped_strings)[0] print({"city":city,"min_temp":min_temp}) def main(): url = 'http://www.weather.com.cn/textFC/hb.shtml' parse_page(url) if __name__ == '__main__': main()
实战2:爬取全国的最低气温并导出前十排行榜
1 import requests 2 from bs4 import BeautifulSoup 3 from pyecharts import Bar 4 5 ALL_DATA = [] 6 7 def parse_page(url): 8 headers = { 9 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' 10 } 11 response = requests.get(url,headers=headers) 12 text = response.content.decode('utf-8') 13 # html5lib 14 # pip install html5lib 15 soup = BeautifulSoup(text,'html5lib') 16 conMidtab = soup.find('div',class_='conMidtab') 17 tables = conMidtab.find_all('table') 18 for table in tables: 19 trs = table.find_all('tr')[2:] 20 for index,tr in enumerate(trs): 21 tds = tr.find_all('td') 22 city_td = tds[0] 23 if index == 0: 24 city_td = tds[1] 25 city = list(city_td.stripped_strings)[0] 26 temp_td = tds[-2] 27 min_temp = list(temp_td.stripped_strings)[0] 28 ALL_DATA.append({"city":city,"min_temp":min_temp}) 29 #print({"city":city,"min_temp":min_temp}) 30 31 32 def main(): 33 #url = 'http://www.weather.com.cn/textFC/hb.shtml' 34 #url = 'http://www.weather.com.cn/textFC/db.shtml' 35 #url = 'http://www.weather.com.cn/textFC/hd.shtml' 36 #url = 'http://www.weather.com.cn/textFC/hz.shtml' 37 #url = 'http://www.weather.com.cn/textFC/hn.shtml' 38 #url = 'http://www.weather.com.cn/textFC/xb.shtml' 39 #url = 'http://www.weather.com.cn/textFC/xn.shtml' 40 #url = 'http://www.weather.com.cn/textFC/gat.shtml' 41 urls = { 42 'http://www.weather.com.cn/textFC/hb.shtml', 43 'http://www.weather.com.cn/textFC/db.shtml', 44 'http://www.weather.com.cn/textFC/hd.shtml', 45 'http://www.weather.com.cn/textFC/hz.shtml', 46 'http://www.weather.com.cn/textFC/hn.shtml', 47 'http://www.weather.com.cn/textFC/xb.shtml', 48 'http://www.weather.com.cn/textFC/xn.shtml', 49 'http://www.weather.com.cn/textFC/gat.shtml' 50 } 51 for url in urls: 52 parse_page(url) 53 54 #分析数据 55 # 根据最低气温进行排序 56 ALL_DATA.sort(key = lambda data:int(data['min_temp'])) 57 58 data = ALL_DATA[0:10] 59 cities = [] 60 cities = list(map(lambda x:x['city'],data)) 61 temps = list(map(lambda x:x['min_temp'],data)) 62 63 chart = Bar("中国天气最低气温排行榜") 64 chart.add('',cities,temps) 65 chart.render('temperature.html') 66 67 68 if __name__ == '__main__': 69 main()
浙公网安备 33010602011771号