1 爬虫中数据的分类
在爬虫爬取的数据中有很多不同类型的数据, 需要了解数据的不同类型来又规律的提取和解析数据.
- 结构化数据:json,xml等
- 处理方式:直接转化为python类型
- 非结构化数据:HTML
- 处理方式:正则表达式、xpath
下面以今日头条的首页为例,介绍结构化数据和非结构化数据
- 结构化数据例子:
- 非结构化数据:
- 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数据也是结构非常明显的
json的数据提取
2 什么是json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于的进行阅读和编写。同时也方便机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
3 json模块中方法的学习
其中类文件对象的理解:
具有read()或者write()方法的对象就是类文件对象,比如f = open(“a.txt”,”r”) f就是类文件对象
具体使用方法:
#json.dumps 实现python类型转化为json字符串
#indent实现换行和空格
#ensure_ascii=False实现让中文写入的时候保持为中文
json_str = json.dumps(mydict,indent=2,ensure_ascii=False)
#json.loads 实现json字符串转化为python的数据类型
my_dict = json.loads(json_str)
#json.dump 实现把python类型写入类文件对象
with open("temp.txt","w") as f:
json.dump(mydict,f,ensure_ascii=False,indent=2)
# json.load 实现类文件对象中的json字符串转化为python类型
with open("temp.txt","r") as f:
my_dict = json.load(f)
4 jsonpath模块的学习
4.1 jsonpath介绍
用来解析多层嵌套的json数据; JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, PHP 和 Java。
4.2 JsonPath 对于 JSON 来说,相当于 XPath 对于 XML。
安装方法:pip install jsonpath
官方文档:http://goessner.net/articles/JsonPath
4.3 JsonPath与XPath语法对比:
4.4 语法使用示例
{ "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
}
}
}
JSONPath | Result |
---|---|
$.store.book[*].author | store中的所有的book的作者 |
$..author | 所有的作者 |
$.store.* | store下的所有的元素 |
$.store..price | store中的所有的内容的价格 |
$..book[2] | 第三本书 |
$..book[(@.length-1)] | $..book[-1:] | 最后一本书 |
$..book[0,1] | $..book[:2] | 前两本书 |
$..book[?(@.isbn)] | 获取有isbn的所有数 |
$..book[?(@.price<10)] | 获取价格大于10的所有的书 |
$..* | 获取所有的数据 |
4.5 代码示例:
我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市。
import requests
import jsonpath
import json
url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json'
response =requests.get(url)
html_str = response.content.decode()
# 把json格式字符串转换成python对象
jsonobj = json.loads(html_str)
# 从根节点开始,匹配name节点
citylist = jsonpath.jsonpath(jsonobj,'$..name')
fp = open('city.json','w')
content = json.dumps(citylist, ensure_ascii=False)
fp.write(content.encode('utf-8'))
fp.close()
5 练习
爬取豆瓣电视剧上英剧和美剧两个分类的电视数据,地址:https://m.douban.com/tv/
6 小结
- json的概念(JavaScript Object Notation)和json的作用数据交互时的一种数据格式
- json模块中操作字符串和python类型互转的方法是dump,load
- json模块中操作文件和python类型互转的方法是dumps,loads
- jsonpath模块的安装 pip install jsonpath
- jsonpath的解析根节点:$
- jsonpath的解析子节点:.
数据提取之正则
1 什么是正则表达式
用事先定义好的一些特定字符、及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。
2 正则表达式的常见语法
知识点
- 正则中的字符
- 正则中的预定义字符集
- 正则中的数量词
正则的语法很多,不能够全部复习,对于其他的语法,可以临时查阅资料,比如:表示或还能使用|
练习: 下面的输出是什么?
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)
3 re模块的常见方法
- pattern.match(从头找一个)
- pattern.search(找一个)
- pattern.findall(找所有)
- 返回一个列表,没有就是空列表
re.findall("\d","chuan1zhi2") >> ["1","2"]
-
pattern.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 练习
- re.compile该如何使用?
- 如何非贪婪的去匹配内容?
re.findall(r“a.*bc”,”a\nbc”,re.DOTALL)
和re.findall(r“a(.*)bc”,”a\nbc”,re.DOTALL)
的区别?- 不分组时匹配的是全部,分组后匹配的是组内的内容
7 动手
-
通过正则匹配果壳问答上面的精彩回答的地址和标题https://www.guokr.com/ask/highlight/?page=1 思路:
-
寻找url地址的规律
-
寻找数据的位置
-
-
获取36kr上的所有新闻:http://36kr.com/
8 小结
- 熟悉正则表达式中字符:\d,\D,\w,\W,\,.等
- 熟悉正则表达式中量词:*,+,?,{}等
- 熟悉正则表达式中范围:['值1','值2']
- re模块的常见方法:match,search,find,findall)
- 原始字符串
r
的用法(保持原先字符串中所有的字符) - 匹配中文的操作[u4e00-u9fa5],中文比配需要注意:汉字和正则表达式都需要是unicode字符操作
数据提取之xpath
1 为什么要学习xpath和lxml
lxml是一款高性能的 Python 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的区别
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
- 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
但是当我们需要选择所有的电影名称的时候会特别费力,通过下一小节的学习,就能够解决这个问题
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,评分,评价人数
6.3 选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
6.3 选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//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模块的学习
在python爬虫代码中使用xpath呢,需要lxml。
1 lxml的安装
安装方式:pip install lxml
2 lxml的使用
2.1 lxml模块的入门使用
-
导入lxml 的 etree 库 (导入没有提示不代表不能用)
`from lxml import etree`
-
利用etree.HTML,将字符串转化为Element对象,Element对象具有xpath的方法,返回结果的列表,能够接受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>
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)
print(type(html))
handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)
输出为
<class 'lxml.etree._Element'>
<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小节的学习来解决这个问题
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 0x11106cb48>, <Element li at 0x11106cb88>, <Element li at 0x11106cbc8>]
可以发现结果是一个element对象,这个对象能够继续使用xpath方法
先根据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 动手
用XPath来做一个简单的爬虫,爬取某个贴吧里的所有帖子,获取每个帖子的标题,连接和帖子中图片
思路分析:
-
推荐使用极速版的页面,响应不包含js,elements和url地址对应的响应一样
-
获取所有的列表页的数据即连接和标题
2.1. 确定url地址,确定程序停止的条件
url地址的数量不固定,不能够去构造url列表,需要手动获取下一页的url地址进行翻页
有下一页的情况:
没有下一页的情况:
-
2.2. 确定列表页数据的位置由于没有js,可以直接从elements中进行数据的提取
-
获取帖子中的所有数据
3.1 确定url地址
url详情页的规律和列表页相似
-
3.2 确定数据的位置
-
4 小结
- lxml库的安装:
pip install lxml
- lxml的导包:
from lxml import etree
; - lxml转换解析类型的方法:
etree.HTML(text)
- lxml解析数据的方法:
data.xpath("//div/text()")
- 需要注意lxml提取完毕数据的数据类型都是列表类型
- 如果数据比较复杂:先提取大节点, 在遍历小节点操作