目录
1. 解析HTML格式
2.解析JSON格式
3.解析二进制格式
4. 实战
1. 解析HTML格式
解析HTML格式主要有以下几种方法,我们在之后的学习中重点关注前两种:
1)lxml库:第三方库,支持HTML和XML格式解析,支持XPath解析方式,解析效率非常高。
2)BeautifulSoup库:第三方库,支持HTML/XML的解析,支持Python标准库中的HTML解析器,也支持 lxml的 XML解析器。
3)Pyquery库:第三方库,相当于jQuery的python实现,可以用于解析HTML网页等。它的语法与jQuery几乎完全相同。
4)HtmlParser,XML:python自带的,用于解析Html和XML。
- lxml
1)XPath基础
XPath, XML 路径语言,用于在XML 文档中查找信息,同样也适用于HTML 文档的搜索。参考文档
XML:可扩展标记语言(eXtensibleMarkupLanguage)
HTML:超文本标记语言(HyperText Markup Language)
XML和HTML形式上差不太多,都是由一对对层级嵌套的标签构成,如<a>...</a>,标签内部可以有属性、文本等。XML的标签名称可以自己随意取,而HTML的标签大多都是固定的,比如<a>为链接标签,<li>为列表标签等。
在XPath中,有七种类型的节点:文档(根)、元素、属性、文本、命名空间、处理指令、注释节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
节点关系:
父(Parent) -- book
子(Children) -- title
同胞(Sibling) -- year
先辈(Ancestor) -- bookstore
后代(Descendant) -- price
2) 常用路径表达式
XML实例1:
book.xml文件内容:
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="ch">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
from lxml import etree
xml=etree.parse('./book.xml',etree.XMLParser())
result=etree.tostring(xml)
print(xml.xpath("/bookstore/book[1]"))
print(xml.xpath("/bookstore/book[1]/title/text()"))
#[<Element book at 0x1deba754648>](列表)
#[‘Harry Potter’](文本内容)
3)通配符(用来选取未知的XML元素)
XML实例2:
book.xml文件内容:
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="ch">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
from lxml import etree
xml=etree.parse('./book.xml',etree.XMLParser())
result=etree.tostring(xml)
print(xml.xpath("/bookstore/*"))
print(xml.xpath("//*"))
#| 是逻辑或 若前一个条件为真时 后一个就不看了 短路效应
print(xml.xpath("/bookstore/title|//book/price"))
print(xml.xpath("//price|title"))
print(xml.xpath("//title|price"))
print(xml.xpath("/bookstore/book/title|//price"))
HTML实例1:
test.html文件内容:
<div>
<ul>
<li id="flask" class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a')
print(result) #1
result = html.xpath('//a/@href')
print(result) #2
result = html.xpath('//a[@href]')
print(result) #2
result = html.xpath('//a[@href="link4.html"]/text()')
print(result) #3
result = html.xpath('//li[contains(@class, "item-1")]')
print(result) #4
result = html.xpath('//@class')
print(result) #5
- BeautifulSoup
BeautifulSoup是python的HTML/XML解析库,最主要的功能是从网页抓取数据。
1)它提供了很多函数,用于处理页面的导航、搜索、修改分析树等功能,通过解析文档为用户提供需要抓取的数据。
2)它的代码简单,不需要多少代码就可以写出一个完整的应用程序。
3)它自动将输入文档转换为Unicode编码,输出文档为utf-8编码。
4)它在解析时依赖解析器,支持Python 标准库中的HTML 解析器及第三方解析器(比如 lxml)。由于lxml解析器功能强大,速度快,通常推荐使用lxml解析器
1) 基本对象
BeautifulSoup将复杂HTML文档解析为树形结构对象进行处理,每个节点为一个对象(所有对象可以归纳为以下4种:Tag , NavigableString, BeautifulSoup, Comment),可以方便地获取指定标签的对应属性,将全部页面转变为字典或者数组,相对于正则表达式的方式,可以简化处理过程。
2)操作方法
一个Tag可能包含多个字符串或其它Tag,这些都是Tag的子节点。BeatifulSoup提供了很多操作和遍历子节点的属性和方法:
节点选择器:通过遍历文档树,获取标签名或属性信息(如soup.article.li,soup.p.attrs)。
方法选择器:通过搜索文档树,使用find等方法查找相应内容(如soup.find_all(‘li’) )。
CSS选择器—通过搜索文档树,采用CSS格式使用select方法选取特定元素(如soup.select(a[class=‘xxx’]))
节点选择器:
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>;
</p>
and they lived at the bottom of a well.
<p class="story">...</p>
</body>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print("-----1.节点选择器(读取字符串soup)-------")
print("soup.p------------")
print(soup.p) # 选择节点, 第一个匹配的节点 <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
print("soup.p.name------------")
print(soup.p.name) # 节点名称 p
print("soup.p.attrs------------")
print(soup.p.attrs) # 节点属性 {'class': ['title'], 'name': 'dromouse'}
print("soup.p.attrs['class']------------")
print(soup.p.attrs['class']) # 特定属性 ['title']
print("soup.p.string------------")
print(soup.p.string) # 节点的文本内容 The Dormouse's story
print("soup.p.b------------")
print(soup.p.b) # 嵌套选择 <b>The Dormouse's story</b>
print("soup.p.contents------------")
print(soup.p.contents) # 直接子节点,列表类型 [<b>The Dormouse's story</b>]
print("soup.p.parent------------")
print(soup.p.parent) # 直接父节点<body> <p.../p> </body>
print("list(soup.p.children)------------")
print(list(soup.p.children)) # 直接子节点,生成器类型 [<b>The Dormouse's story</b>]
print("list(soup.p.descendants)------------")
print(list(soup.p.descendants)) # 所有子孙节点 [<b>The Dormouse's story</b>, "The Dormouse's story"]
print("list(soup.p.parents)------------")
print(list(soup.p.parents)) # 所有祖先节点[<body>...</body>, <html><head>...</body></html>,<html><head>...</body></html>]
print("list(soup.a.next_siblings)------------")
print(list(soup.a.next_siblings)) # 后面的兄弟节点 [',\n ', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, ' and\n ', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, ';\n ']
print("list(soup.a.previous_siblings)------------")
print(list(soup.a.previous_siblings)) # 前面的兄弟节点 ['Once upon a time there were three little sisters; and their names were\n ']
print("soup.a.next_sibling------------")
print(soup.a.next_sibling) # 下一个兄弟节点 ,
print("soup.a.previous_sibling------------")
print(soup.a.previous_sibling) # 上一个兄弟节点 Once upon a time there were three little sisters; and their names were
方法选择器:
find_all(name, attrs, recursive, text, **kwargs)
1)功能:返回所有匹配元素组成的列表。
2)主要参数:
name:可以传入字符串查找所有名字为 name 的tag,如‘b’ ;可以传入正则表达式作为参数,如re.compile(“^b”);可以传入列表参数,如[“a”, “b”];可以传入True(匹配所有tag,不会返回字符串节点)
recursive:是否需要搜索所有子孙节点,默认为True,表示会检索当前tag的所有子孙节点
keyword:把该参数当作指定名字tag的属性来搜索,如id=‘link2’或href=re.compile("elsie"), id='link1’
limit:当结果到达 limit 值时停止搜索,如limit=3,只返回前3个元素
注意:如果想用class做过滤,但是 class 是 python 的关键词,这时需要在class后加个下划线,如soup.find_all("a", class_=“title")
find(name, attrs, recursive, text, **kwargs)
功能:返回第一个匹配的元素,其余同find_all。
find_parents()和find_parent()
功能:前者返回所有祖先节点, 后者返回直接父节点
find_next_siblings()和find_next_sibling()
功能:前者返回后面所有的兄弟节点, 后者返回后面第一个兄弟节点
find_previous_siblings()和find_previous_sibling()
功能:前者返回前面所有的兄弟节点, 后者返回前面第一个兄弟节点
find_all_next()和find_next()
功能:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点
find_all_previous()和find_previous()
功能:前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点
实例:
test.html内容:
<div>
<ul>
<li id="flask" class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
print("-----2.方法选择器(读取文件soup2)-------")
htmlfile = open('./test.html', 'r', encoding='utf-8')
soup2 = BeautifulSoup(htmlfile.read(), 'lxml')
print("-----find():返回发现的第一个------------")
li = soup2.find('li')
print('find_li:',li) #find_li: <li class="item-0" id="flask"><a href="link1.html">first item</a></li>
print('li.text(返回标签的内容):',li.text) #li.text(返回标签的内容): first item
print('li.attrs(返回标签的属性):',li.attrs) #li.attrs(返回标签的属性): {'id': 'flask', 'class': ['item-0']} 字典形式
print('li.string(返回标签内容为字符串):',li.string) #li.string(返回标签内容为字符串): first item
li = soup2.find(id='flask')
print(li) #<li class="item-0" id="flask"><a href="link1.html">first item</a></li>
# 因为class是python的保留关键字,所以无法直接查找class这个关键字。有两种方法可以进行class属性查询
# 第一种:在attrs属性用字典进行传递参数
find_class = soup2.find(attrs={'class':'item-1'})
print('findclass:',find_class) #findclass: <li class="item-1"><a href="link2.html">second item</a></li>
# 第二种:使用BeautifulSoup中的特别关键字参数class_
beautifulsoup_class_ = soup2.find(class_ = 'item-1')
print('BeautifulSoup_class_:',beautifulsoup_class_) #BeautifulSoup_class_: <li class="item-1"><a href="link2.html">second item</a></li>
print("-----find_all():返回发现的全部------------")
# find_all 查找所有
li_all = soup2.find_all('li')
for li in li_all: #1...5(最后一个自动补齐了</li>)
print('匹配到的li:',li) #1.<li class="item-0" id="flask"><a href="link1.html">first item</a></li>。。。
print('li的内容:',li.text) #1.first item。。。
print('li的属性:',li.attrs) #1.{'id': 'flask', 'class': ['item-0']}。。。
# 一种灵活的使用方式
li_quick = soup2.find_all(attrs={'class':'item-1'})
for li in li_quick:
print('灵活查找:',li)
#灵活查找: <li class="item-1"><a href="link2.html">second item</a></li>
#灵活查找: <li class="item-1"><a href="link4.html">fourth item</a></li>
CSS选择器:
可以采用CSS的语法格式来筛选元素。
select() :
print("-----3.CSS选择器-------")
print(soup.select('title')) # title标签 [<title>The Dormouse's story</title>]
print(soup.select("p:nth-of-type(3)")) # 第三个p节点 [<p class="story">...</p>]
print(soup.select('body a')) # body下的所有子孙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>]
print(soup.select('p > a')) # 所有p节点下的所有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>]
print(soup.select('p > #link1')) # 所有p节点下的id=link1的直接子节点 [<a class="sister" href="http://example.com/elsie" id="link1"><!--Elsie--></a>]
print(soup.select('#link1 ~ .sister')) # id为link1的节点后面class=sister的所有兄弟节点 [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.select('#link1 + .sister')) # id为link1的节点后面class=sister的第一个兄弟节点 [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
print(soup.select('.sister')) # class=sister的所有节点 [<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>]
print(soup.select('[class="sister"]')) # class=sister的所有节点 [<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>]
print(soup.select("#link1")) # id=link1的节点 [<a class="sister" href="http://example.com/elsie" id="link1"><!--Elsie--></a>]
print(soup.select("a#link1")) # a节点,且id=link1的节点 [<a class="sister" href="http://example.com/elsie" id="link1"><!--Elsie--></a>]
print(soup.select('a[href]')) # 所有的a节点,有href属性 [<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>]
print(soup.select('a[href="http://example.com/elsie"]')) # 指定href属性值的所有a节点 [<a class="sister" href="http://example.com/elsie" id="link1"><!--Elsie--></a>]
print(soup.select('a[href^="http://example.com/"]')) # href属性以指定值开头的所有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>]
print(soup.select('a[href$="tillie"]')) # href属性以指定值结尾的所有a节点 [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.select('a[href*=".com/el"]')) # 支持正则匹配 [<a class="sister" href="http://example.com/elsie" id="link1"><!--Elsie--></a>]
2.解析JSON格式
json.dumps()和json.loads()是json格式处理函数。
json.loads():将已编码的json字符串解码为字典类型。
json.dumps():将Python数据类型列表(字典)转化为json字符串。
import json
#json.dumps将字典转化为字符串
dict1 = {"age": "18"}
json_info = json.dumps(dict1)
print("dict1的类型:"+str(type(dict1))) #dict
print("通过json.dumps()函数处理:")
print(“json_info "+ json_info) #{"age": "18"}
print("json_info的类型:"+str(type(json_info))) #str
dict2 = json.loads(json_info)
print("dict2的类型:"+str(type(dict2))) #dict
print(dict2) #{'age': '18'}
#URL请求-响应结果是json
url='http://httpbin.org/get”
r = requests.get(url)
result = json.loads(r.text)
json.dump()和json.load()主要用来读写json文件函数。
#写文件
json_info = "{'age': '12'}"
file = open('1.json','w',encoding='utf-8')
json.dump(json_info,file)
#读文件
file = open('1.json','r',encoding='utf-8')
print(json.load(file)) #{'age': '12'}
3.解析二进制格式
图片、音频、视频这些文件实际上都是由二进制码组成的。
import os
os.makedirs('./image/', exist_ok=True)
url = "https://www.python.org/static/img/python-logo.png"
#方法1 直接将远程数据下载到本地文件
from urllib.request import urlretrieve
urlretrieve(url, './image/img1.png')
#方法2 将响应结果以二进制形式写入到指定文件中
import requests
r = requests.get(url)
with open('./image/img2.png', 'wb') as f:
f.write(r.content)
#方法3 相对内置的open()来说,codecs.open方法比较不容易在编码上出现问题,返回unicode编码
import urllib
import requests
import os
import codecs
bytes = urllib.request.urlopen(url)
f=codecs.open('./image/img3.png','wb')
f.write(bytes.read())
f.flush()
f.close()
4. 实战
- 爬取万方数据库文献摘要
输入指定关键词,分别爬取与该关键词相关的期刊、学位、会议论文的摘要、题目、作者等信息。
网址:http://www.wanfangdata.com.cn
- 爬取百度图片
指定搜索词,爬取与搜索词相关的图片。
- 爬取搜狗图片
指定搜索词,爬取与搜索词相关的图片。