04爬虫-三种数据解析方式
数据解析方式
解析的方法:
-
正则
-
xpath
-
bs4
-
pyquery
数据解析原理:
- 标签的定位
- 提取标签中存储的文本数据或者标签属性中存储的数据
一、正则解析
引言
正则表达式(regular expression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征。比如 表达式“ab+” 描述的特征是“一个 'a' 和 任意个 'b' ”,那么 'ab', 'abb', 'abbbbbbbbbb' 都符合这个特征。
正则表达式可以用来:(1)验证字符串是否符合指定特征,比如验证是否是合法的邮件地址。(2)用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便。(3)用来替换,比普通的替换更强大。
正则表达式学习起来其实是很简单的,不多的几个较为抽象的概念也很容易理解。之所以很多人感觉正则表达式比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难;另一方面,各种引擎自带的文档一般都要介绍它特有的功能,然而这部分特有的功能并不是我们首先要理解的。
1. 正则表达式规则
1.1 普通字符
字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是"普通字符"。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。
1.2 简单的转义字符
一些不便书写的字符,采用在前面加 "\" 的方法。这些字符其实我们都已经熟知了。
表达式 |
可匹配 |
\r, \n |
代表回车和换行符 |
\t |
制表符 |
\\ |
代表 "\" 本身 |
还有其他一些有特殊用处的标点符号,在前面加 "\" 后,就代表该符号本身。比如:^, $ 都有特殊意义,如果要想匹配字符串中 "^" 和 "$" 字符,则表达式就需要写成 "\^" 和 "\$"。
表达式 |
可匹配 |
\^ |
匹配 ^ 符号本身 |
\$ |
匹配 $ 符号本身 |
\. |
匹配小数点(.)本身 |
这些转义字符的匹配方法与 "普通字符" 是类似的。也是匹配与之相同的一个字符。
1.3 能够与 '多种字符' 匹配的表达式
正则表达式中的一些表示方法,可以匹配 '多种字符' 其中的任意一个字符。比如,表达式 "\d" 可以匹配任意一个数字。虽然可以匹配其中任意字符,但是只能是一个,不是多个。这就好比玩扑克牌时候,大小王可以代替任意一张牌,但是只能代替一张牌。
表达式 |
可匹配 |
\d |
任意一个数字,0~9 中的任意一个 |
\w |
任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中任意一个 |
\s |
包括空格、制表符、换页符等空白字符的其中任意一个 |
. |
小数点可以匹配除了换行符(\n)以外的任意一个字符
|
1.4 自定义能够匹配 '多种字符' 的表达式
使用方括号 [ ] 包含一系列字符,能够匹配其中任意一个字符。用 [^ ] 包含一系列字符,则能够匹配其中字符之外的任意一个字符。同样的道理,虽然可以匹配其中任意一个,但是只能是一个,不是多个。
表达式 |
可匹配 |
[ab5@] |
匹配 "a" 或 "b" 或 "5" 或 "@" |
[^abc] |
匹配 "a","b","c" 之外的任意一个字符 |
[f-k] |
匹配 "f"~"k" 之间的任意一个字母 |
[^A-F0-3] |
匹配 "A"~"F","0"~"3" 之外的任意一个字符
|
1.5 修饰匹配次数的特殊符号
前面讲到的表达式,无论是只能匹配一种字符的表达式,还是可以匹配多种字符其中任意一个的表达式,都只能匹配一次。如果使用表达式再加上修饰匹配次数的特殊符号,那么不用重复书写表达式就可以重复匹配。
使用方法是:"次数修饰"放在"被修饰的表达式"后边。比如:"[bcd][bcd]" 可以写成 "[bcd]{2}"。
表达式 |
作用 |
{n} |
表达式重复n次,比如: "\w{2}" 相当于 "\w\w"; "a{5}" 相当于 "aaaaa" |
{m,n} |
表达式至少重复m次,最多重复n次,比如: "ba{1,3}"可以匹配 "ba"或"baa"或"baaa" |
{m,} |
表达式至少重复m次,比如: "\w\d{2,}"可以匹配 "a12","_456","M12344"... |
? |
匹配表达式0次或者1次,相当于 {0,1},比如: "a[cd]?"可以匹配 "a","ac","ad" |
+ |
表达式至少出现1次,相当于 {1,},比如: "a+b"可以匹配 "ab","aab","aaab"... |
* |
表达式不出现或出现任意次,相当于 {0,},比如: "\^*b"可以匹配 "b","^^^b"...
|
1.6 其他一些代表抽象意义的特殊符号
一些符号在表达式中代表抽象的特殊意义:
表达式 |
作用 |
^ |
与字符串开始的地方匹配,不匹配任何字符 |
$ |
与字符串结束的地方匹配,不匹配任何字符 |
\b |
匹配一个单词边界,也就是单词和空格之间的位置,不匹配任何字符
|
其他
表达式 |
作用 |
| |
左右两边表达式之间 "或" 关系,匹配左边或者右边 |
( ) |
(1). 在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰
|
表达式 |
匹配结果 |
"\w+" 将匹配第一个 "d" 之后的所有字符 "xxxdxxxd" |
|
"\w+" 将匹配第一个 "d" 和最后一个 "d" 之间的所有字符 "xxxdxxx"。虽然 "\w+" 也能够匹配上最后一个 "d",但是为了使整个表达式匹配成功,"\w+" 可以 "让出" 它本来能够匹配的最后一个 "d" |
表达式 |
匹配结果 |
"\w+?" 将尽可能少的匹配第一个 "d" 之后的字符,结果是:"\w+?" 只匹配了一个 "x" |
|
为了让整个表达式匹配成功,"\w+?" 不得不匹配 "xxx" 才可以让后边的 "d" 匹配,从而使整个表达式匹配成功。因此,结果是:"\w+?" 匹配 "xxx" |
形式 |
字符范围 |
\xXX |
编号在 0 ~ 255 范围的字符,比如: 空格可以使用 "\x20" 表示 |
\uXXXX |
任何字符可以使用 "\u" 再加上其编号的4位十六进制数表示,比如: "\u4E2D" |
表达式 |
可匹配 |
\S |
|
\D |
|
\W |
|
\B |
字符 |
说明 |
^ |
匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "\^" |
$ |
匹配输入字符串的结尾位置。要匹配 "$" 字符本身,请使用 "\$" |
( ) |
标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "\(" 和 "\)" |
[ ] |
用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "\[" 和 "\]" |
{ } |
修饰匹配次数的符号。要匹配大括号,请使用 "\{" 和 "\}" |
. |
匹配除了换行符(\n)以外的任意一个字符。要匹配小数点本身,请使用 "\." |
? |
修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "\?" |
+ |
修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "\+" |
* |
修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用 "\*" |
| |
左右两边表达式之间 "或" 关系。匹配 "|" 本身,请使用 "\|" |
表达式属性 |
说明 |
Ignorecase |
默认情况下,表达式中的字母是要区分大小写的。配置为 Ignorecase 可使匹配时不区分大小写。有的表达式引擎,把 "大小写" 概念延伸至 UNICODE 范围的大小写。 |
Singleline |
默认情况下,小数点 "." 匹配除了换行符(\n)以外的字符。配置为 Singleline 可使小数点可匹配包括换行符在内的所有字符。 |
Multiline |
默认情况下,表达式 "^" 和 "$" 只匹配字符串的开始 ① 和结尾 ④ 位置。如: |
Global |
主要在将表达式用来替换时起作用,配置为 Global 表示替换所有的匹配。
|
·需求:爬取糗事百科中所有的糗图图片数据
·实现:
检查页面中的图片数据是否为动态加载的。
将当前页面的源码数据请求到
使用正则表达式定位相关的img标签,然后获取img的src属性值
对src的属性值发起请求获取图片数据。
持久化存储图片数据
1 import requests 2 import re 3 from urllib import request 4 import os 5 if not os.path.exists("./tupian"): 6 os.mkdir("./tupian") 7 8 headers = { 9 "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36" 10 } 11 url = "https://www.qiushibaike.com/pic/" 12 page_text = requests.get(url=url,headers=headers).text 13 r = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>' 14 img_url = re.findall(r,page_text,re.S) 15 for url in img_url: 16 url = "https:"+url 17 img_name = url.split("/")[-1] 18 img_path = "./tupian/"+img_name 19 request.urlretrieve(url,img_path) 20 print(img_name,"下载成功")
1 ##################################################################### 2 #匹配出i开头的行 3 string = '''fall in love with you 4 i love you very much 5 i love she 6 i love her''' 7 8 re.findall('^.*',string,re.M) 9 ##################################################################### 10 #匹配全部行 11 string1 = """<div>静夜思 12 窗前明月光 13 疑是地上霜 14 举头望明月 15 低头思故乡 16 </div>""" 17 18 re.findall('.*',string1,re.S)
Beautiful Soup 4
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.
这篇文档介绍了BeautifulSoup4中所有主要特性,并且有小例子.让我来向你展示它适合做什么,如何工作,怎样使用,如何达到你想要的效果,和处理异常情况.
文档中出现的例子在Python2.7和Python3.2中的执行结果相同
官网的 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,推荐在现在的项目中使用Beautiful Soup 4。
安装 Beautiful Soup
$ pip install beautifulsoup4
但是如果你在编写新项目,那么你应该安装的 beautifulsoup4
如果你没有安装 pip
,那你也可以 下载BS4的源码 ,然后通过setup.py来安装.
$ Python setup.py install
如果上述安装方法都行不通,Beautiful Soup的发布协议允许你将BS4的代码打包在你的项目中,这样无须安装即可使用.
安装解析器
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml:
$ pip install lxml
实例化BeautifulSoup对象
1、 soup = BeautifulSoup(page_text,"lxml")#将互联网上的资源加载到对象中
2、soup = BeautifulSoup(file_page,"lxml")#将本地存储的加载到对象中
拿”爱丽丝梦游仙境”的文档来做例子:
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
1、tag的名字
操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取 <head> 标签,只要用 soup.head
这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取<body>标签中的第一个<b>标签:
soup.body.b
# <b>The Dormouse's story</b>
通过点取属性的方式只能获得当前名字的第一个tag:
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
如果想要得到所有的<a>标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 Searching the tree 中描述的方法,比如: find_all()
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>]
2、string
tag只有一个直系类型子节点,那么这个tag可以使用 .string
得到子节点:
3、find_all()、find()
find_all( name , attrs , recursive , string , **kwargs )
find_all()
方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
soup.find_all("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>]
4、select()
Beautiful Soup支持大部分的CSS选择器 , 在 Tag
或 BeautifulSoup
对象的 .select()
方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
soup.select("title")
# [<title>The Dormouse's story</title>]
soup.select("p nth-of-type(3)")
# [<p class="story">...</p>]
通过tag标签逐层查找:
soup.select("body a")
找到某个tag标签下的直接子标签 :
soup.select("head > title")
通过CSS的类名查找:
soup.select(".sister")
通过tag的id查找:
soup.select("#link1")
同时用多种CSS选择器查询元素:
soup.select("#link1,#link2")
通过属性的值来查找:
soup.select('a[href="http://example.com/elsie"]')
通过是否存在某个属性来查找:
soup.select('a[href]')
5、取文本
(1)根据标签名查找 - soup.a 只能找到第一个符合要求的标签 (2)获取属性 - soup.a.attrs 获取a所有的属性和属性值,返回一个字典 - soup.a.attrs['href'] 获取href属性 - soup.a['href'] 也可简写为这种形式 (3)获取内容 - soup.a.string - soup.a.text - soup.a.get_text() 【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容 (4)find:找到第一个符合要求的标签 - soup.find('a') 找到第一个符合要求的 - soup.find('a', title="xxx") - soup.find('a', alt="xxx") - soup.find('a', class_="xxx") - soup.find('a', id="xxx") (5)find_all:找到所有符合要求的标签 - soup.find_all('a') - soup.find_all(['a','b']) 找到所有的a和b标签 - soup.find_all('a', limit=2) 限制前两个 (6)根据选择器选择指定的内容 select:soup.select('#feng') - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器 - 层级选择器: div .dudu #lala .meme .xixi 下面好多级 div > p > a > .lala 只能是下面一级 【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
需求:使用bs4实现将诗词名句网站中三国演义小说的每一章的内容爬去到本地磁盘进行存储
http://www.shicimingju.com/book/sanguoyanyi.html
1 import requests 2 from bs4 import BeautifulSoup 3 #找到爬取的网址 4 url = "http://www.shicimingju.com/book/sanguoyanyi.html" 5 #伪造头部 6 headers = { 7 "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36" 8 } 9 #text是requests对象的属性 10 page_text = requests.get(url=url,headers=headers).text 11 #创建BeautifulSoup对象 12 soup = BeautifulSoup(page_text,"lxml") 13 #获取标题 14 name = soup.h1.text 15 #获取所有的url和章数标题 16 pages = soup.select(".book-mulu>ul>li>a") 17 #创建文件名 18 fileName = "./"+ name+".txt" 19 f = open(fileName,"w",encoding="utf-8") 20 for i in pages: 21 title = i.text 22 page_url = "http://www.shicimingju.com" + i["href"] 23 pg_text = requests.get(url=page_url,headers=headers).text 24 pg_text = BeautifulSoup(pg_text,"lxml") 25 #获取到所有的p标签 26 text = pg_text.find_all("p") 27 #先写入标题 28 f.write("\n"+title+"\n") 29 for i in text: 30 #写入每个p标签的内容 31 f.write(i.text) 32 #关闭文件 33 f.close()
3、Xpath解析(类似bs4)
解析效率比较高
通用性比较强
环境安装:
1 pip install lxml
解析原理:
实例化一个etree对象,然后将要解析的传入该对象中。
使用etree对象的xpath方法,结合xpath表达式对标签的定位以及数据的选取
实例化etree对象
etree.parse("本地文件路径")
etree.HTML(page_text)
导包:from lxml import etree
1 from lxml import etree 2 import requests 3 4 url='http://www.haoduanzi.com/category-10_2.html' 5 headers = { 6 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36', 7 } 8 url_content=requests.get(url,headers=headers).text 9 #使用xpath对url_conten进行解析 10 #使用xpath解析从网络上获取的数据 11 tree=etree.HTML(url_content) 12 #解析获取当页所有段子的标题 13 title_list=tree.xpath('//div[@class="log cate10 auth1"]/h3/a/text()') 14 15 ele_div_list=tree.xpath('//div[@class="log cate10 auth1"]')
1 属性定位: 2 #找到class属性值为song的div标签 3 //div[@class="song"] 4 层级&索引定位: 5 #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a 6 //div[@class="tang"]/ul/li[2]/a 7 逻辑运算: 8 #找到href属性值为空且class属性值为du的a标签 9 //a[@href="" and @class="du"] 10 模糊匹配: 11 //div[contains(@class, "ng")] 12 //div[starts-with(@class, "ta")] 13 取文本: 14 # /表示获取某个标签下的文本内容 15 # //表示获取某个标签下的文本内容和所有子标签下的文本内容 16 //div[@class="song"]/p[1]/text() 17 //div[@class="tang"]//text() 18 取属性: 19 //div[@class="tang"]//li[2]/a/@href
●将糗图中前5页的图片进行下载也可以适当使用其他网站
●爬取boss相关的岗位信息和详情页
●站长素材免费建立模板下载 htp://sc.chinaz.com/janli/free.html