数据处理:
一、数据提取的概念和数据的分类:
1 爬虫中数据的分类
在爬虫爬取的数据中有很多不同类型的数据,我们需要了解数据的不同类型来又规律的提取和解析数据.
- 1.结构化数据:json,xml等
- 处理方式:直接转化为python类型
- 2.非结构化数据:HTML
- 处理方式:正则表达式、xpath
2.小结:
- 1.爬虫中数据分类之结构化数据: json,xml
- 2.爬虫中数据分类之非结构化数据:Html,字符串
- 3.结构化数据处理的方式有:jsonpath,xpath,转换python类型处理,bs4
- 4.非结构化数据处理方式有:正则表达式,xpath,bs4
二、json的数据提取之json
1. 复习什么是json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
2. json模块中方法的学习:
其中类文件对象的理解:
具有read()或者write()方法的对象就是类文件对象,比如f = open(“a.txt”,”r”) f就是类文件对象
具体使用方法:
# -*-coding:utf-8-*-
# json模块的使用
import json
mydict = {
"store": {
"book": [
{"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
],
}
}
# json.dumps 实现Python类型转化为json字符串
# indent 实现换行和空格
# ensure_ascii=False 实现让中文写入是保持中文
json_str = json.dumps(mydict, indent=2, ensure_ascii=False)
print('json.dumps: python_type ==> json_str:{}'.format(type(json_str)))
# json.dumps: python_type ==> json_str:<class 'str'>
# json.loads 实现json字符串转化为python的数据类型
my_dict = json.loads(json_str)
print("json.loads: json_str ==> python_type:{}".format(type(my_dict)))
# json.loads: json_str ==> python_type:<class 'dict'>
# json.dump 实现把python字符串转化为python类型病写入文件对象
with open("json实例.txt", "w") as f:
# f.write(json.dumps(mydict, indent=2, ensure_ascii=False))
json.dump(mydict, f, ensure_ascii=False, indent=2)
print("json.dump已写入成功...")
# json.load实现累文件对象中的json字符串转化为python类型
with open("json实例.txt", "r") as f:
# my_dict = f.read()
my_dict = json.load(f)
print("json.load:读取文件 ==> {}: {}".format(type(my_dict), my_dict))
# json.load:读取文件 ==> <class 'dict'>: {'store': {'book': [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}]}}
3.jsonpath模块的学习:
3.1 jaonpath介绍:
用来解析多层嵌套的json数据;JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, PHP 和 Java。
3.2 JsonPath 对于 JSON 来说,相当于 XPath 对于 XML
安装方法:
pip install jsonpath
官方文档:http://goessner.net/articles/JsonPath
3.3 JsonPath语法:
JSONPath | 描述 |
---|---|
$ | 根节点 |
@ | 现行结点 |
. or [] | 取子节点 |
n/a | 取父节点,jsonpath未支持 |
.. | 不管位置, 选择所有符合条件的 |
* | 匹配所有元素节点 |
n/a | 根据属性访问, json不支持,因为json是个key-value递归架构,不需要属性访问 |
[] | 迭代器表示(可以在里边做简单的迭代操作,如数组下标,根据内容选值等) |
[ , ] | 支持迭代器中做多选 |
?() | 支持过滤操作 |
() | 支持表达式计算 |
n/a | 分组,jsonpath不支持 |
3.4 语法使用实例:
# -*-coding:utf-8-*-
# jsonpath语法练习
from jsonpath import jsonpath
book_dict = {
"store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
print(jsonpath(book_dict, "$..author")) # 获取所有作者 # 返回列表,如果取不到将返回False
# ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
print(jsonpath(book_dict, "$.store.book[*].author")) # 获取store中的所有的book的作者
# -----> 0 4
# -----> 1 4
# -----> 2 4
# -----> 3 4
# ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
print(jsonpath(book_dict, "$.store..price")) # store中的所有的内容的价格
# [8.95, 12.99, 8.99, 22.99, 19.95]
print(jsonpath(book_dict, "$.store.*")) # store下的所有的元素
# [[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}], {'color': 'red', 'price': 19.95}]
print(jsonpath(book_dict, "$..book[2]")) # 获取第二本书
# -----> 2 4
# [{'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}]
print(jsonpath(book_dict, "$..book[(@.length-1)]")) # 获取最后一本书
# -----> 3 4
# [{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]
print(jsonpath(book_dict,"$..book[-1:]")) # 获取最后一本书
# -----> 3 4
# [{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]
print(jsonpath(book_dict, "$..book[0,1]")) # 获取前两本书
# -----> 0 4
# -----> 1 4
# [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}]
print(jsonpath(book_dict, "$..book[:2]")) # 获取前两本书
# -----> 0 4
# -----> 1 4
# [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}]
#
print(jsonpath(book_dict, "$..book[?(@.isbn)]")) # 获取有isbn的所有数
# -----> 2 4
# -----> 3 4
# [{'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]
print(jsonpath(book_dict, "$..book[?(@.price>10)]")) # 获取价格大于10的所有的书
# -----> 1 4
# -----> 3 4
# [{'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]
print(jsonpath(book_dict, "$..*")) # 获取所有的数据
3.5代码示例:
我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市的名字的列表,并写入文件。
# -*-coding:utf-8-*-
# 我们以拉勾网城市JSON文件
# http://www.lagou.com/lbs/getAllCitySearchLabels.json
# 为例,获取所有城市的名字的列表,并写入文件。
import requests
import jsonpath
import json
#获取拉勾网城市json字符串
url = "http://www.lagou.com/lbs/getAllCitySearchLabels.json"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"
}
resp = requests.get(url,headers=headers)
html_str = resp.content.decode()
# print(html_str)
# 把json格式字符穿转换成python对象
json_obj = json.loads(html_str)
# print(json_obj)
# 从根节点开始,获取所有key为name的值
citylist = jsonpath.jsonpath(json_obj,"$..name")
print(citylist)
# 写入文件
with open("city_name.txt", "w") as f:
content = json.dumps(citylist, ensure_ascii=False)
f.write(content)
6. 小结:
- 1.json的概念(JavaScript Object Notation)和json的作用数据交互时的一种数据格式
- 2.json模块中操作字符串和python类型互转的方法是dump,load
- 3.json模块中操作文件和python类型互转的方法是dumps,loads
- 4.jsonpath模块的安装 pip install jsonpath
- 5.jsonpath的解析根节点:$
- 6.jsonpath的解析子节点:.
三、数据提取之正则:
1.什么是正则表达式
用事先定义好的一些特定字符、及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。
2.正则表达式的常见语法
知识点
- 正则中的字符
- 正则中的预定义字符集
- 正则中的数量词
练习:执行以下代码:
# -*-coding:utf-8-*-
import re
string_a = '<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\n\t\t<meta http-equiv="content-type" content="text/html;charset=utf-8">\n\t\t<meta content="always" name="referrer">\n <meta name="theme-color" content="#2932e1">'
ret = re.findall("<.*>",string_a)
print(ret)
输出:
['<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">', '<meta http-equiv="content-type" content="text/html;charset=utf-8">', '<meta content="always" name="referrer">', '<meta name="theme-color" content="#2932e1">']
3 re模块的常见方法
- re.match(从头找一个)
- re.search(找一个)
- re.findall(找所有)
- 返回一个列表,没有就是空列表
- re.findall("\d",“chuan1zhi2”) >> [“1”,“2”]
- re.sub(替换)
- re.sub("\d","",“chuan1zhi2”) >> ["chuan_zhi"]
- re.compile(编译,提升匹配速度)
- 返回一个模型P,具有和re一样的方法,但是传递的参数不同
- 匹配模式需要传到compile中
p = re.compile("\d",re.S)
p.findall("chuan1zhi2")
4.python中原始字符串r的用法:
原始字符串定义(raw string):所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符,原始字符串往往针对特殊字符而言。例如"\n"的原始字符串就是"\n"
- 原始字符串的长度:
In [19]: len("\n")
Out[19]: 1
In [20]: len(r"\n")
Out[20]: 2
In [21]: r"\n"[0]
Out[21]: '\\'
- 正则中原始字符串的使用:
In [13]: r"a\nb" == "a\\nb"
Out[13]: True
In [14]: re.findall("a\nb","a\nb")
Out[14]: ['a\nb']
In [15]: re.findall(r"a\nb","a\nb")
Out[15]: ['a\nb']
In [16]: re.findall("a\\nb","a\nb")
Out[16]: ['a\nb']
In [17]: re.findall("a\\nb","a\\nb")
Out[17]: []
In [18]: re.findall(r"a\\nb","a\\nb")
Out[18]: ['a\\nb']
以上情况说明了:
正则中使用原始字符串r能够忽略转义符号带来的影响,加上原始字符串r之后,待匹配的字符串中有多少个\,正则中就添加多少个\即可。
- windows中原始字符串r的使用
5 匹配中文
在某些情况下,我们想匹配文本中的汉字,有一点需要注意的是,中文的 unicode 编码范围 主要在 [u4e00-u9fa5],这里说主要是因为这个范围并不完整,比如没有包括全角(中文)标点,不过,在大部分情况下,应该是够用的。
假设现在想把字符串 title = u’你好,hello,世界’ 中的中文提取出来,可以这么做:
import re
title = u'你好,hello,世界'
pattern = re.compile(ur'[\u4e00-\u9fa5]+')
result = pattern.findall(title)
print result
# 注意点: 中文匹配 需要设置unicode字符才可以匹配
6.如何非贪婪的去匹配内容
import re
s = '123xxxxxx456'
result_1 = re.findall('\d+', s)
result_2 = re.findall('\d+?', s)
print(result_1) # ['123', '456']
print(result_2) # ['1', '2', '3', '4', '5', '6']
7.动手:
通过正则匹配果壳问答上面的精彩回答的地址和标题
地址:https://www.guokr.com/ask/highlight/?page=1
class GuoKeTalk(object):
"""爬去果壳问答的精彩回答"""
def __init__(self):
self.title_list = [] # 保存结果
self.url = "https://www.guokr.com/ask/highlight/"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"
}
def get_resp(self, page):
"""
获取请求响应
:param page: 请求页码
:return:
"""
kw = {"page":page}
while True:
resp = requests.get(self.url, headers=self.headers, params={"page": page})
if resp.status_code == 200:
print(resp.status_code)
return resp
def get_res(self, resp):
"""
获取目标数据添加到title_list中
:param resp:
:return:
"""
html_str = resp.content.decode()
href_title = re.findall(r'<h2><a target="_blank" href="(.*)">(.*)</a></h2>', html_str)
# print(href_title)
for content in href_title:
dict = {}
dict[content[0]] = content[1]
print(dict)
self.title_list.append(dict)
def write_file(self,filename, connent):
"""
写入文件
:param filename:
:param connent:
:return:
"""
with open(filename, "w") as f:
f.write(str(self.title_list))
def run(self):
"""
启动方法
:return:
"""
for i in range(1, 11):
resp = self.get_resp(i)
self.get_res(resp)
print("第%d页成功..." % i)
self.write_file("果壳问答.txt",self.title_list)
if __name__ == '__main__':
guoke = GuoKeTalk()
guoke.run()
四、数据提取之xpath
1.为什么要学习xpath和lxml
lxml是一款高性能的 Pythokn HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息
2.什么是xpath:
XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。
W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
3. 认识xml
知识点:
- html和xml的区别;
- xml中各个元素的关系和属性;
3.1 html和xml的区别:
数据格式 | 描述 | 设计目标 |
---|---|---|
XML | Extensible Markup Language(可扩展标记语言) | 被设计为传输和存储数据,其焦点是数据的内容 |
HTML | HyperText Markup Language(超文本标记语言) | 显示数据以及如何更好的显示数据 |
3.2 xml的树结构:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
上面的xml内容可以表示为下面的树结构:
上面的这种结构关系在xpath被进一步细化
4.xpath的节点关系:
知识点:
- 认识xpath中的节点;
- 了解xpath中节点之间的关系
4.1 xpath中的节点是什么:
每个XML的标签我们都称之为节点,其中最顶层的节点称为根节点。
4.2 xpath中节点的关系:
5. xpath中节点选择的工具
- Chrome插件 XPath Helper
- 下载地址:https://pan.baidu.com/s/1UM94dcwgus4SgECuoJ-Jcg 密码:337b
- 把文件的后缀名crx改为rar,然后解压到xpath_…
- 把解压后的文件夹拖入到已经开启开发者模式的chrome浏览器扩展程序界面
- 重启浏览器
- Firefox插件 XPath Checker
注意: 这些工具是用来学习xpath语法的,他们都是从elements中匹配数据,elements中的数据和url地址对应的响应不相同,所以在代码中,不建议使用这些工具进行数据的提取。
6.xpath语法:
知识点
- 掌握元素路径的相关方法
- 掌握获取获取属性的方法
- 掌握获取文本的方法
我们将在下面的例子中使用这个 XML 文档。
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
6.1 选取节点:
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
使用chrome插件选择标签时候,选中时,选中的标签会添加属性class="xh-highlight"
下面列出了最有用的表达式:
表达式 | 描述 |
---|---|
nodename | 选中该元素。 |
/ | 从根节点选取、或者是元素和元素间的过渡。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
… | 选取当前节点的父节点。 |
@ | 选取属性。 |
text() | 选取文本。 |
实例
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式 | 结果 |
---|---|
bookstore | 选择bookstore元素。 |
/bookstore | 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//book/title/@lang | 选择所有的book下面的title中的lang属性的值。 |
//book/title/text() | 选择所有的book下面的title的文本。 |
xpath基础语法练习:
豆瓣电影top250的页面来练习上述语法
https://movie.douban.com/top250
- 选择所有的h1下的文本
- //h1/text()
- 获取所有的a标签的href
- //a/@href
- 获取html下的head下的title的文本
- /html/head/title/text()
- 获取html下的head下的link标签的href
- /html/head/link/@href
但是当我们需要选择所有的电影名称的时候会特别费力,通过下一小节的学习,就能够解决这个问题.
- /html/head/link/@href
6.2 查找塔顶的节点
路径表达式 | 结果 |
---|---|
//title[@lang=“eng”] | 选择lang属性值为eng的所有title元素 |
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()>1] | 选择bookstore下面的book元素,从第二个开始选择 |
//book/title[text()=‘Harry Potter’] | 选择所有book下的title元素,仅仅选择文本为Harry Potter的title元素 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
注意点: 在xpath中,第一个元素的位置是1,最后一个元素的位置是last(),倒数第二个是last()-1
xpath基础语法练习2:
从豆瓣电影top250的页面中:选择所有的电影的名称,href,评分,评价人数
# -*-coding:utf-8-*-
# 豆瓣电影top250的页面所有的电影的名称,href,评分,评价人数
# https://movie.douban.com/top250
import requests
from lxml import etree
class DouBanTOP250(object):
"""爬去豆瓣top250"""
def __init__(self):
"""
初始化
"""
self.base_url = "https://movie.douban.com/top250"
self.headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"
}
self.movies = []
def get_comment(self,nexturl=""):
"""获取数据"""
url = self.base_url + nexturl
resp = requests.get(url, headers=self.headers)
html_str = resp.content.decode()
# xpath
html = etree.HTML(html_str)
li_list = html.xpath("//ol[@class='grid_view']/li")
for li in li_list:
movie = {}
movie['detail_href'] = li.xpath("div/div[1]/a/@href")[0]
movie['img_src'] = li.xpath("div/div[1]/a/img/@src")[0]
movie['title'] = li.xpath("div/div[2]/div/a/span[1]/text()")[0]
movie['score'] = li.xpath("div/div[2]/div[2]/div/span[@class='rating_num']/text()")[0]
movie['commentaries'] = li.xpath("div/div[2]/div[2]/div/span[last()]/text()")[0][:-3]
self.movies.append(movie)
try:
next_href = html.xpath("//span/a[text()='后页>']/@href")[0]
print(next_href)
self.get_comment(nexturl=next_href)
except:
return
if __name__ == '__main__':
douban = DouBanTOP250()
douban.get_comment()
print(douban.movies)
print(len(douban.movies))
**注意:**使用了递归的方法, 如果数据量比较大,并不推荐用这种方法
6.3 选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
6.4 选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。 |
//title | //price 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
7.小结
- xpath的概述XPath (XML Path Language),解析查找提取信息的语言
- xml是和服务器交互的数据格式和json的作用一致
- html是浏览器解析标签数据显示给用户
- xpath的节点关系:根节点,子节点,父节点,兄弟节点,子节点,后代节点
- xpath的重点语法获取任意节点://
- xpath的重点语法根据属性获取节点:标签[@属性 = ‘值’]
- xpath的获取节点属性值:@属性值
- xpath的获取节点文本值:text()
五、数据提取之lxml模块
1. lxml的安装:
安装方式:
pip install lxml
2. lxml的使用:
2.1 lxml模块的入门使用:
- 导入lxml的etree库(导入没有提示不代表不能用)
from lxml import etree
- 利用etree.HTML,将字符串转换为Element对象,Element对象具有西帕挺好的方法,返回结果的列表,能够接受bytes类型的数据和str类型的数据
html = etree.HTML(text)
ret_list = html.xpath("xpath字符串")
- 把转化后的element对象转化为字符串,返回bytes类型结果 etree.tostring(element)
假设我们现在有如下的html字符串,尝试对他进行操作:
<div> <ul>
<li class="item-1"><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> # 注意,此处缺少一个 </li> 闭合标签
</ul> </div>
# -*-coding:utf-8-*-
from lxml import etree
html_str = """
<div> <ul>
<li class="item-1"><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> # 注意,此处缺少一个 </li> 闭合标签
</ul> </div>
"""
html = etree.HTML(html_str)
print(html)
handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)
输出为:
<Element html at 0x7f383d59ff88>
<html><body><div> <ul>
<li class="item-1"><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></li>
</ul> </div>
</body></html>
可以发现,lxml确实能够把标签补充完成稿,但是请注意lxml是人写的,很多时候由于网页不够规范,或者是lxml的bug,即使参考url地址对应的响应去提取数据,仍然获取不到,这个时候我们需要使用etree.tostring的方法,观察etree到底把html转化成了什么样子,即使根据转化后的html字符串去进行数据的提取。
2.2 lxml的深入练习:
接下来我们继续操作,假设每个class为item-1的li标签是1条新闻数据,如何把这条新闻数据组成一个字典。
from lxml import etree
text = ''' <div> <ul>
<li class="item-1"><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> '''
html = etree.HTML(text)
#获取href的列表和title的列表
href_list = html.xpath("//li[@class='item-1']/a/@href")
title_list = html.xpath("//li[@class='item-1']/a/text()")
#组装成字典
for href in href_list:
item = {}
item["href"] = href
item["title"] = title_list[href_list.index(href)]
print(item)
输出为:
{'href': 'link1.html', 'title': 'first item'}
{'href': 'link2.html', 'title': 'second item'}
{'href': 'link4.html', 'title': 'fourth item'}
假设在某种情况下,某个新闻的href没有,那么会怎样呢?
from lxml import etree
text = ''' <div> <ul>
<li class="item-1"><a>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> '''
...
结果是:
{'href': 'link2.html', 'title': 'first item'}
{'href': 'link4.html', 'title': 'second item'}
数据的对应全部错了,这不是我们想要的,接下来通过下一小节来解决这个问题
2.3 lxml模块的进阶使用:
前面我们取到属性,或者是文本的时候,返回字符串 但是如果我们取到的是一个节点,返回什么呢?
返回的事element对象,可以继续使用xpath方法 ,对此我们可以在后面的数据提取过程中:先根据某个标签进行分组,分组之后再进行数据的提取 ==> 先分组,在提取
实例如下:
from lxml import etree
text = ''' <div> <ul>
<li class="item-1"><a>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> '''
html = etree.HTML(text)
li_list = html.xpath("//li[@class='item-1']")
print(li_list)
结果为:
[<Element li at 0x7f42b7e01f48>, <Element li at 0x7f42b7e01f08>, <Element li at 0x7f42b7e10048>]
可以发现结果是一个element对象,这个对象能够继续使用小path方法
先根据li标签进行分组,之后再进行是数据的提取
from lxml import etree
text = ''' <div> <ul>
<li class="item-1"><a>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> '''
#根据li标签进行分组
html = etree.HTML(text)
li_list = html.xpath("//li[@class='item-1']")
#在每一组中继续进行数据的提取
for li in li_list:
item = {}
item["href"] = li.xpath("./a/@href")[0] if len(li.xpath("./a/@href"))>0 else None
item["title"] = li.xpath("./a/text()")[0] if len(li.xpath("./a/text()"))>0 else None
print(item)
结果为:
{'href': None, 'title': 'first item'}
{'href': 'link2.html', 'title': 'second item'}
{'href': 'link4.html', 'title': 'fourth item'}
前面的代码中,进行数据提取需要判断,可能某些一面不存在数据的情况,对应的可以使用三元运算符来解决
以上提取数据的方式:先分组再提取,都会是我们进行数据的提取的主要方法
3.小结:
- lxml库的安装: pip install lxml
- lxml的导包:from lxml import etree;
- lxml转换解析类型的方法:etree.HTML(text)
- lxml解析数据的方法:data.xpath("//div/text()")
- 需要注意lxml提取完毕数据的数据类型都是列表类型
- 如果数据比较复杂:先提取大节点, 在遍历小节点操作,即先分组再提取数据
六、BeautifulSoup4:
1.BeautifulSoup4的介绍和安装:
CSS 选择器:BeautifulSoup4,和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。使用 pip 安装即可:
pip install beautifulsoup4
官方文档:http://beautifulsoup.readthedocs.io/zh_CN/v4.4.0
抓取工具 | 速度 | 使用难度 | 安装难度 |
---|---|---|---|
正则 | 最快 | 困难 | 无(内置) |
BeautifulSoup | 慢 | 最简单 | 简单 |
lxml | 快 | 简单 | 一般 |
1.bs4的基本使用实例:
首先必须导入bs4库:
# -*-coding:utf-8-*-
from bs4 import BeautifulSoup
html_str="""
<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>
"""
# 创建BeautifulSoup对象
soup = BeautifulSoup(html_str)
# soup = BeautifulSoup(html_str, "lxml") # 显式地指定解析器
# 打开本地HTML文件的方式来创建对象
# soup = BeautifulSoup(open("index.html"))
# 格式化输出soup对象的内容
print(soup.prettify())
运行结果:
<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 class="sister" href="http://example.com/elsie" id="link1">
<!-- Elsie -->
</a>
,
<a class="sister" href="http://example.com/lacie" id="link2">
Lacie
</a>
and
<a class="sister" href="http://example.com/tillie" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class="story">
...
</p>
</body>
</html>
注意:
如果我们在python下执行,会看到这样过的一段警告:
意思是,如果我们没有显示的指定解释器,所以默认使用这个系统柜的最佳可用HTML解释器(“lxml”)
如果你在另一个系统中运行这段代码,或者在不同的虚拟环境中,使用不同的解释器造成的行为不同。
但是我们可以通过soup = BeautifulSoup(html_str, ‘lxml’)方式指定lxml解析器。
2.搜索文档树:
2.1 find_all(name, sttrs,recursive, text, **kwargs)
1) name参数:
name参数可以查找所有名字为name的tag
A 传字符串
最简单的过滤器是字符串,在搜索方法中传入衣蛾字符串参数,Beautiful Soup会查找与字符串完整匹配的内容。
此例子用于查找文档中所有的标签:
print(soup.find_all("b"))
# [<b>The Dormouse's story</b>]
print(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>]
B 传正则表达式
如果传入正则表达式作为参数,BeautifulSoup会通过正则表达式的match()来匹配内容。
下面例子中找出所有以b开头的标签,这表示和标签都应该被找到
import re
for tag in soup.find_all(re.compile(r"^b")):
print(tag)
C 传列表
1)如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有标签和标签:
print(soup.find_all(["a", "b"]))
# [<b>The Dormouse's story</b>,
# <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>]
2)keyword 参数
print(soup.find_all(class_="sister")) # 注意 这里是class_ 最后有个下划线!
#[<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.find_all(id='link2'))
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
3)text 参数
通过 text 参数可以搜索文档中的字符串内容,与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表
import re
print(soup.find_all(text=" Elsie ")) # 注释内容不能少了空格!
# [' Elsie ']
print(soup.find_all(text=["Tillie", "Elsie", "Lacie"])) # 这就找不到注释内容!
# ['Lacie', 'Tillie']
print(soup.find_all(text=re.compile("Dormouse")))
["The Dormouse's story", "The Dormouse's story"]
2.2 find
find的用法与find_all一样,区别在于find返回 第一个符合匹配结果,find_all则返回 所有匹配结果的列表
2.3 CSS选择器
这就是另一种与 find_all 方法有异曲同工之妙的查找方法,也是返回所有匹配结果的列表。
-
写 CSS 时,标签名不加任何修饰,类名前加.,id名前加#
-
在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
(1)通过标签选择器查找
print(soup.select('title'))
#[<title>The Dormouse's story</title>]
print(soup.select('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('b'))
#[<b>The Dormouse's story</b>]
(2)通过类选择器查找
print(soup.select('.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>]
(3)通过 id 选择器查找
print(soup.select('#link1'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
(4)层级选择器 查找
print(soup.select('p #link1'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
(5)通过属性选择器查找
print(soup.select('a[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('a[href="http://example.com/elsie"]'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
(6) 获取文本内容 get_text()
以上的 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())
(7) 获取属性 get(‘属性的名字’)
soup = BeautifulSoup(html, 'lxml')
print(type(soup.select('a')))
print(soup.select('a')[0].get('href'))
3.小结:
3.1bs4的其他方法
bs4获取标签的某个属性
soup.select('a')[0]['href'] # bs4 获取a标签的href属性
bs4根据文本内容定位元素
soup.find_all('a', text='下一页') # bs4 根据a标签的文本内容获取a标签
bs4根据标签名和标签的class属性来获取标签
soup.find_all('img', class_='BDE_Image') # 注意这里又是class_ 后边有个下划线
- 安装beautifulsoup4:pip install beautifulsoup4
- beautifulsoup导包: from bs4 import BeautifulSoup
- beautifulsoup转换类型: BeautifulSoup(html)
- find 方法返回一个解析完毕的对象
- findall 方法返回的是解析列表list
- select 方法返回的是解析列表list
- 获取属性的方法: get(‘属性名字’)
- 和获取文本的方法: get_text()