数据提取
响应内容的分类
结构化的响应内容
- json字符串
- 可以使用re、json等模块来提取特定数据
xml字符串
- 可以使用re、lxml等模块来提取特定数据
非结构化的响应内容
- html字符串
- 可以使用re、lxml等模块来提取特定数据
xml和html的区别
xml
xml是一种可扩展标记语言,样子和html很像,功能更专注于对传输和存储数据
xml内容可以表示为下面的树结构:
xml和html的区别
- html:
- 超文本标记语言
- 为了更好的显示数据,侧重点是为了显示
- xml:
- 可扩展标记语言
- 为了传输和存储数据,侧重点是在于数据内容本身
常用数据解析方法
jsonpath模块
jsonpath模块就能解决一个多层嵌套的复杂字典,批量提取value。jsonpath可以按照key对python字典进行批量数据提取
使用方法
-
jsonpath是第三方模块,需要额外安装
pip install jsonpath -
jsonpath模块提取数据的方法
from jsonpath import jsonpath
ret = jsonpath(a, ‘jsonpath语法规则字符串’) -
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
}
}
}
from jsonpath import jsonpath
print(jsonpath(book_dict, '$..author')) # 如果取不到将返回False # 返回列表,如果取不到将返回False
练习
拉勾网城市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 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"}
response =requests.get(url, headers=headers)
html_str = response.content.decode()
# 把json格式字符串转换成python对象
jsonobj = json.loads(html_str)
# 从根节点开始,获取所有key为name的值
citylist = jsonpath.jsonpath(jsonobj,'$..name')
# 写入文件
with open('city_name.txt','w') as f:
content = json.dumps(citylist, ensure_ascii=False)
f.write(content)
lxml模块和xpath语法
- lxml模块可以利用XPath规则语法,来快速的定位HTML\XML 文档中特定元素以及获取节点信息(文本内容、属性值)
- XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。
- 提取xml、html中的数据需要lxml模块和xpath语法配合使用
谷歌浏览器xpath helper插件
作用:在谷歌浏览器中对当前页面测试xpath语法规则
安装
- 下载Chrome插件 XPath Helper
- 可以在chrome应用商城进行下载,如果无法下载,也可以从下面的链接进行下载
- 下载地址:https://pan.baidu.com/s/1UM94dcwgus4SgECuoJ-Jcg 密码:337b
- 把文件的后缀名crx改为rar,然后解压到同名文件夹中
- 把解压后的文件夹拖入到已经开启开发者模式的chrome浏览器扩展程序界面
- 重启浏览器后,访问url之后在页面中点击xpath图标,就可以使用了
- 如果是linux或macOS操作系统,无需操作上述的步骤2,直接将crx文件拖入已经开启开发者模式的chrome浏览器扩展程序界面
xpath的节点关系
每个html、xml的标签都称之为节点,其中最顶层的节点称为根节点。以xml为例,html也是一样的。
xpath中节点的关系
author是title的第一个兄弟节点
xpath语法
基础节点选择语法
- XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
- 这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
- 使用chrome插件选择标签时候,选中时,选中的标签会添加属性class=“xh-highlight”
xpath定位节点以及提取属性或文本内容的语法
语法练习
- 选择所有的h2下的文本
- //h2/text()
- 获取所有的a标签的href
- //a/@href
- 获取html下的head下的title的文本
- /html/head/title/text()
- 获取html下的head下的link标签的href
- /html/head/link/@href
节点修饰语法
xpath的下标
- 在xpath中,第一个元素的位置是1
- 最后一个元素的位置是last()
- 倒数第二个是last()-1
语法练习
-
所有的学科的名称
- //div[@class=“nav_txt”]//a[@class=“a_gd”]
-
第一个学科的链接
- //div[@class=“nav_txt”]/ul/li[1]/a/@href
-
最后一个学科的链接
- //div[@class=“nav_txt”]/ul/li[last()]/a/@href
-
通过包含修饰
- //div[contains(@id,“qiushi_tag_”)]
- //span[contains(text(),‘一页’)]
其他常用节点选择语法
- 全部的标签
- //*
- 全部的属性
- //node()
lxml模块
安装 pip/pip3 install lxml
爬虫对html提取的内容
- 提取标签中的文本内容
- 提取标签中的属性的值
- 比如,提取a标签中href属性的值,获取url,进而继续发起请求
使用
- 导入lxml 的 etree 库
from lxml import etree - 利用etree.HTML,将html字符串(bytes类型或str类型)转化为Element对象,Element对象具有xpath的方法,返回结果的列表
html = etree.HTML(text)
ret_list = html.xpath(“xpath语法规则字符串”) - xpath方法返回列表的三种情况
- 返回空列表:根据xpath语法规则字符串,没有定位到任何元素
- 返回由字符串构成的列表:xpath字符串规则匹配的一定是文本内容或某属性的值
- 返回由Element对象构成的列表:xpath规则字符串匹配的是标签,列表中的Element对象可以继续进行xpath
- 旧方法报错
from lxml import html #局部导入,避免报错
etree = html.etree
htmlDiv = etree.HTML(data)
title = htmlDiv.xpath("//meta[1]/@content")
print(title)
使用示例
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)
练习
将每个class为item-1的li标签作为1条新闻数据。提取a标签的文本内容以及链接,组装成一个字典。
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> '''
注意:
- 先分组,再提取数据,可以避免数据的错乱
- 对于空值要进行判断
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>'''
#根据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)
etree.tostring函数
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>
</ul> </div> '''
html = etree.HTML(html_str)
handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)
现象和结论
打印结果和原来相比:
- 自动补全原本缺失的li标签
- 自动补全html等标签
<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.etree.HTML(html_str)可以自动补全标签
- lxml.etree.tostring函数可以将转换为Element对象再转换回html字符串
- 爬虫如果使用lxml来提取数据,应该以lxml.etree.tostring的返回结果作为提取数据的依据
贴吧爬取练习
import requests
class Tieba(object):
def __init__(self,name):
self.url='https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(name)
self.headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
# "User-Agent":"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)" #换个low的浏览器
}
def get_data(self,url):
resp=requests.get(url,headers=self.headers)
return resp.content
def parse_data(self,data):
data=data.decode().replace("<!--","").replace("-->","") #将注释内容替换,否则不显示
# 创建element对象
from lxml import html
etree = html.etree
html = etree.HTML(data)
el_list=html.xpath('//li[@class=" j_thread_list clearfix"]/div/div[2]/div[1]/div[1]/a')
print(len(el_list))
data_list=[]
for el in el_list:
temp={}
temp["title"]=el.xpath('./text()')[0]
temp['link']="http://tieba.baidu.com/" +el.xpath('./@href')[0]
data_list.append(temp)
#获取下一页url,找翻页url尽量不要用索引
try:
# next_url= "https:"+html.xpath('//a[@class="last pagination-item"]/@href')[0] #xpath语法错了
next_url= "https:"+html.xpath('//a[contains(text(),"下一页>")]/@href')[0]
except:
next_url=None
return data_list,next_url
def save_data(self,data_list):
for data in data_list:
print(data)
def run(self):
next_url=self.url
while True:
data=self.get_data(next_url)
data_list,next_url =self.parse_data(data)
self.save_data(data_list)
if next_url==None:
break
if __name__ == '__main__':
tieba=Tieba('一人之下')
tieba.run()