数据提取
什么是数据提取? 简单的来说,数据提取就是从响应中获取我们想要的数据的过程
数据分类
- 非结构化数据: html , 文本等
处理方法:正则表达式,xpath语法
2.结构化数据:json,xml等
处理方法:转换为python数据类型
复习JSON知识
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
为什么要使用JSON?
把json格式字符串转化为python字典类型很简单,所以爬虫中,如果我们能够找到返回json数据格式字符串的url,就会尽量使用这种url 如何找到返回json的url呢?
1、使用浏览器/抓包工具进行分析 wireshark(windows/linux),tcpdump(linux)
2、抓包手机app的软件
json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构
- 对象:对象在js中表示为{ }括起来的内容,数据结构为 { key:value, key:value, ... }的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是数字、字符串、数组、对象这几种
- 数组:数组在js中是中括号[ ]括起来的内容,数据结构为 ["Python", "javascript", "C++", ...],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。
Json在数据交换中起到了一个载体的作用,承载着相互传递的数据
1. json.loads()
把Json格式字符串解码转换成Python对象
2.json.dumps()
实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串
3.json.dump()
将Python内置类型序列化为json对象后写入文件
4. json.load()
读取文件中json形式的字符串元素 转化成python类型
import json
# python中支持单引号,但转换数据类型的时候,需要使用双引号
dict_data = '{"neuedu":"沈阳_python24", "pub_date": "2019-4-14 17:00:00"}'
print(dict_data)
print(type(dict_data))
# 转成json字符串 默认使用ascii编码,需要设置ensure_ascii为False
json_data = json.dumps(dict_data,ensure_ascii=False)
print(json_data)
print(type(json_data))
# 转成字典
dict_data_str = json.loads(dict_data)
print(dict_data_str)
print(type(dict_data_str))
# 把字典转成json写入文件,写文件如果遇到编码ascii错误,可以指定编码格式为utf8
f = open('data.json','w',encoding='utf8')
json.dump(dict_data_str,f,ensure_ascii=False)
# 读取json文件中的内容,转成字典
f = open('data.json','r',encoding='utf8')
dict_data = json.load(f)
print (dict_data)
案例 爬取豆瓣网电视信息
打开ajax请求页会展示如下代码
import requests
import json
"""
目标:
爬取豆瓣网电视信息
1、电视名称、封面信息、评分等
"""
class Douban(object):
def __init__(self):
self.url = 'https://movie.douban.com/j/search_subjects?type=tv&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=200&page_start=1'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}
# 创建文件对象
self.file = open('douban.json','w',encoding='utf8')
# 发送请求,获取数据
def get_data(self):
response = requests.get(url=self.url,headers=self.headers)
# 把响应数据转成str类型
return response.content.decode()
# 解析数据
def parse_data(self,data):
# 把电视数据转成字典
dict_data = json.loads(data)
# print(dict_data)
result_list = dict_data['subjects']
data_list = []
# 获取电视列表数据
for result in result_list:
temp = {}
temp['title'] = result['title']
temp['rate'] = result['rate']
temp['url'] = result['url']
data_list.append(temp)
# 返回数据列表
return data_list
# 保存电视数据
def save_data(self,data_list):
# 遍历列表
for data in data_list:
str_data = json.dumps(data,ensure_ascii=False) + ',\n'
self.file.write(str_data)
# 关闭文件对象
def __del__(self):
self.file.close()
def run(self):
# 1、构建请求头和url
# 2、发送请求,获取数据
data = self.get_data()
# 3、解析数据
data_list = self.parse_data(data)
# 4、保存文件
self.save_data(data_list)
if __name__ == '__main__':
douban = Douban()
douban.run()
工作空间生成douban,.json文件
正则表达式
就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑
使用正则进行猫眼经典影片电影页面信息爬取
地址: https://maoyan.com/board/4?offset=0
import requests
import re
import json
from multiprocessing import Pool
from requests.exceptions import RequestException\
def get_one_page(url):
try:
headers={'User-Agent':'Mozilla/5.0'}
r=requests.get(url,headers=headers)
r.encoding=r.apparent_encoding
r.raise_for_status()
#print(r.text)
return r.text
except requests.exceptions.RequestException:
print('requests.exceptions.RequestException')
return None
def parse_one_page(html):
pattern=re.compile('<dd>.*?<i class="board-index.*?">(\d+)</i>'
+'.*?title="(.*?)".*?'
+'<img data-src="(.*?)".*?'
+ 'class="star">(.*?)</p>.*?'
+ 'releasetime">(.*?)</p>'
+ '.*?integer">(\d+.)</i>'
+ '.*?fraction">(\d).*?</dd>'
,re.S)
items=pattern.findall(html)
for item in items:
yield {
'sequence':item[0],
'title':item[1],
'picture':item[2],
'actors':item[3].strip()[3:],
'date':item[4][5:],
'score':item[5]+item[6]}
def write_to_file(content):
with open('movie.txt','a',encoding='utf-8') as f:
jsondumps=json.dumps(content,ensure_ascii=False)
#print(jsondumps)
f.write(jsondumps+'\n')
f.close()
def main(i):
urlbasic='http://maoyan.com/board/4?offset='
url=urlbasic+str(10*i)
print(url)
html=get_one_page(url)
content=parse_one_page(html)
#print(type(content))
for i in [0,1,2,]:
write_to_file(next(content))
if __name__ == '__main__':
pool=Pool()
pool.map(main, [i for i in range(0, 11)])
存储成如下格式的txt文件
Xpath语法(必须掌握)
首先我们来明确一下lxml和XPath的定义
lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息
XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。
那么 HTML和XML 有什么区别呢?
相对于BeautifulSoup相比 XPath是C语言实现哦 :)所以效率高哦
节点的概念:每个XML的标签我们都称之为节点
常用节点选择工具
Chrome插件 XPath Helper(下载crx扩展程序进行安装)
节点选择语法
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
使用chrome插件选择标签时候,选中时,选中的标签会添加属性class="xh-highlight"
查找某个特定的节点或者包含某个指定的值的节点
选择未知节点
选取若干路径
lxml库
使用入门:
安装
pip3 install lxml
导入lxml 的 etree 库
from lxml import etree
利用etree.HTML,将html字符串转化为Element对象
html = etree.HTML(response.content)
Element对象具有xpath的方法
html.xpath(xpath语句)
使用etree.tostring(html)将element对象转换成html文档
lxml 可以自动修正 html 代码
demo
from lxml import etree
text = '''
<div>
<ul>
<li class="item-1"><a href="link1.html"></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>
'''
# 使用etree,创建对象,把文本转换成elements对象
html = etree.HTML(text)
# 提取多条数据,需要对提取的结果进行遍历,或者说再次xpath
data_list = html.xpath('//div/ul/li/a')
# print(dir(data_list))
# print(data_list)
# 需要遍历的节点列表中如果没有数据,提取指定节点的数据会发生异常,
for data in data_list:
# print(dir(data))
# 三元表达式
a_text = data.xpath('./text()')[0] if len(data.xpath('./text()')) > 0 else None
a_href = data.xpath('./@href')[0]
print(a_text,a_href)
print(html)
print(type(html))
print(dir(html))
# 返回的是对象
print(html.xpath('//div/ul/li[1]/a'))
# 提取链接,返回的是列表
print(html.xpath('//div/ul/li[1]/a/@href'))
# 提取文本,返回的是列表
print(html.xpath('//div/ul/li[1]/a/text()'))
# 提取文本,返回的是文本内容
print(html.xpath('//div/ul/li[1]/a/text()')[0])
百度校花吧爬取并保存图片案例
import requests
from lxml import etree
import os
class Baidu(object):
def __init__(self, name):
self.url = 'http://tieba.baidu.com/f?ie=utf-8&kw={}'.format(name)
# 使用较老版本的请求头,该浏览器不支持js
self.headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) '
}
# 发送请求,获取响应
def get_data(self, url):
response = requests.get(url,headers=self.headers)
return response.content
# 解析列表页数据,获取列表页面帖子的标题和链接
def parse_list_page(self, data):
with open('baidu.html','wb') as f:
f.write(data)
# 实例化etree对象
html = etree.HTML(data)
# 使用xpath语法,提取网页数据
node_list = html.xpath("//*[@id='thread_list']/li[@class=' j_thread_list clearfix']/div/div[2]/div[1]/div[1]/a")
# 判断获取结果
# print(len(node_list))
data_list = []
# 遍历node_list
for node in node_list:
temp = {}
temp['url'] = 'http://tieba.baidu.com' + node.xpath('./@href')[0]
temp['title'] = node.xpath('./text()')[0]
data_list.append(temp)
# 提取下一页的节点
next_node = html.xpath('//*[@id="frs_list_pager"]/a[last()-1]/@href')[0]
# print(next_node)
# 拼接下一页的完整url
next_url = 'http:' + next_node
# print(next_url)
return data_list,next_url
def parse_detail_page(self, data_list):
html = etree.HTML(data_list)
# 提取详情页面的图片链接
image_list = html.xpath("//cc/div[contains(@class,'d_post')]/img[@class='BDE_Image']/@src")
# 返回图片节点列表
print(image_list)
return image_list
# 下载图片,保存图片文件
# 创建文件夹
def download(self, image_list):
if not os.path.exists('images'):
os.makedirs('images')
for image in image_list:
# os.sep在mac系统中是/,如果是windows系统,\\,跨平台
file_name = 'images'+ os.sep + image.split('/')[-1]
image_data = self.get_data(image)
with open(file_name,'wb') as f:
f.write(image_data)
def run(self):
# 构造url和请求头
# 发送请求,获取响应
next_url = self.url
# 开启循环,
while next_url:
data = self.get_data(next_url)
# 解析列表页数据,返回的列表数据、下一页的的数据
data_list,next_url = self.parse_list_page(data)
# 解析详情页的数据,获取详情页的图片的链接地址
for data in data_list:
url = data['url']
result_list = self.get_data(url)
image_list = self.parse_detail_page(result_list)
# 保存数据,下载图片
self.download(image_list)
if __name__ == '__main__':
# 爬取百度校花吧所有图片并存在文件夹中
baidu = Baidu('校花吧')
baidu.run()