文章目录
爬虫_chap2数据提取
XPath语法和lxml模块
什么是XPath?
xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。
XPath开发工具
- Chrome插件XPath Helper。
- Firefox插件Try XPath。
XPath语法
选取节点:
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
表达式 | 描述 | 示例 | 结果 |
---|---|---|---|
nodename | 选取此节点的所有子节点 | bookstore | 选取bookstore下所有的子节点 |
/ | 如果是在最前面,代表从根节点选取。否则选择某节点下的某个节点 | /bookstore | 选取根元素下所有的bookstore节点 |
// | 从全局节点中选择节点,随便在哪个位置 | //book | 从全局节点中找到所有的book节点 |
@ | 选取某个节点的属性 | //book[@price] | 选择所有拥有price属性的book节点 |
. | 当前节点 | ./a | 选取当前节点下的a标签 |
子节点
子孙节点
谓词/谓语:
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 描述 |
---|---|
/bookstore/book[1] | 选取bookstore下的第一个子元素 |
/bookstore/book**[last()**] | 选取bookstore下的倒数第一个book元素。 |
bookstore/book[position()❤️] | 选取bookstore下前面两个子元素。 |
//book[@price] | 选取拥有price属性的book元素 |
//book[@price=10] | 选取所有属性price等于10的book元素**(// 为选取子孙节点)** |
通配符
*表示通配符。
通配符 | 描述 | 示例 | 结果 |
---|---|---|---|
* | 匹配任意节点 | /bookstore/* | 选取bookstore下的所有子元素。 |
@* | 匹配节点中的任何属性 | //book[@*] | 选取所有带有属性的book元素。 |
选取多个路径:
通过在路径表达式中使用“|”运算符,可以选取若干个路径。
示例如下:
//bookstore/book | //book/title
# 选取所有book元素以及book元素下所有的title元素
运算符:
|
+
-
*
div
=
!=
<
<=
>
>=
or
and
mod
疑惑:
|
表示计算两个节点集?//book|//cd
返回 所有拥有book和
cd元素的节点集
还是 所有拥有book或
cd元素的节点集?
Note:
获得标签再使用谓词提取
//div[@class='abc']
contains:
//div[contains(@class,'job_detail')]
谓词的下标是从1而不是0开始
lxml库:解析器
lxml 是 一个HTML/XML的解析器,主要的功能是解析和提取 HTML/XML 数据。(与正则一样由C实现)
解析的目的:将纯文本处理成html对象,html中存在各种父子关系,容易提取诸如
标签
,属性
等信息
lxml python 官方文档:http://lxml.de/index.html
需要安装C语言库,可使用 pip 安装:pip install lxml
先etree.HTML(text)
解析再 etree.tostring(html)
转化为文本:
我们可以利用他来解析HTML代码,并且在解析HTML代码的时候,
**如果HTML代码不规范,他会自动的进行补全。**示例代码如下:(其中text是用string格式存储的HTML代码)
# 使用 lxml 的 etree 库
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><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>
'''
#利用etree.HTML,将字符串解析为HTML文档 这里etree.HTML是一个对象
html = etree.HTML(text)
# 按字符串序列化HTML文档
result = etree.tostring(html)
print(result)
输出结果如下:
<html><body>
<div>
<ul>
<li class="item-0"><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会自动修改HTML代码。例子中不仅补全了li标签,还添加了body,html标签。
从文件中读取html代码:etree.parse()
htmlElement=etree.parse("tencent.heml")
print(etre.tostring(htmlElement,encoding='utf-8').decode('utf-8'))
#etree.parse默认使用XML解析器,所以如果有些HTML代码不规范,会导致解析错误.此时可使用其他HTML解析器
parser=etree.HTMLParse(encoding='utf-8')#这是一个解析器,使用utf-8编码??
htmlElement=etree.parse("lagou.html",parser=parser)
print(etree.tostring(htmlElement,encoding='utf-8').decode('utf-8'))
除了直接使用字符串进行解析,lxml还支持从文件中读取内容。我们新建一个hello.html文件:
<!-- hello.html -->
是一个html注释
<!-- hello.html -->
<div>
<ul>
<li class="item-0"><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"><span class="bold">third item</span></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.parse()
方法来读取文件。示例代码如下:
tostring
的pretty_print
使得打印结果更加优美
from lxml import etree
# 读取外部文件 hello.html
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
输入结果和之前是相同的。
Note:
etree.parse返回一个对象,不是一个字符串,不能直接打印
etree.HTML返回的也是一个对象上面的代码有必要吗?已经使用parser将文本编成utf-8了,tostring还需要再次将其编成utf-8? 直接在打印的时候使用utf-8解码就行了吧~
像这样:print(etree.tostring(htmlElement).decode('utf-8'))
注意:没有decode的话,可能没办法print,也可能print出来但是排布杂乱无章
tostring之后文本以bytes编码存储,将其encoding为utf-8容易读取
encoding='utf-8'
将文本编码成utf-8
,.decode('utf-8')
以utf-8
读取文本
ctrl+d复制上一行
ctrl+c,当光标在行开头的时候也是复制一行
etree.parse
没有encoding参数,只有parser参数;
etree.tostring
才有encoding参数
遇到编码错误的问题,不要etree.HTML
网页的text,去etree.HTML
网页的content(当网页中出现中文,很容易遇到这种问题)
在lxml中使用XPath举例:
1.获取所有li标签:
print(htmlElement.xpath('//li'))
只要 from lxml import etree 就可以了2.获取所有li元素下的所有class属性的值:
print(htmlElement.xpath('//li/@class'))
3.获取li标签下href为
www.baidu.com
的a标签:
print(htmlElement.xpath('//li/a[@href='www.baidu.com']))
html文档
=>li标签
=>a标签
=>href属性
4.获取li标签下所有span标签:
print(htmlElement.xpath('//li//span')
5.获取li标签下的a标签里的所有class:
print(htmlElement.xpath('//li/a[@class]'))
print(htmlElement.xpath('//li/a//@class'))
后者正确,a[@class]
表示拥有class属性的a标签,a//@class
表示a标签里面的class属性
前者主体为标签,后者主体为属性6.获取最后一个li的a的href属性对应的值:(ie获取属性)
print(htmlElement.xpath('//li[last()]/a/@href'))
7.获取倒数第二个li元素的内容:
print(htmlElement.xpath('//li[last()-1]/text()'))
Tips:
last()
方法不用传参数,[last(2)]
是错误的
没有text()
返回的是[<Element li at 0x17991c0>]
不管检索结果1个还是几个,都以list形式返回,使用text()
方法一样
代码示例:
print(html.xpath('//li[last()-1]')[0].text)
或者
print(html.xpath('//li[last()-1]/text()')[0])
或者
print(html.xpath('//li/text()')[-2])
或者
print(html.xpath('//li)[-2].text)
8.获得从第二个开始的tr标签
print(html.xpath('//tr[position()>1]'))
9.用li存储第一个li标签,从这个标签中获取所有的a标签的值:
li=html.xpath('//li')[0] print(li.xpath('./a/text()'))
Tip:
li.xpath('//a/text()')
返回的a标签的值是在整个html里面搜索的,超过了当前li的范围10.获取父节点
print(html.xpath('//li/a/..')) browser.find_element_by_xpath(xpath_input_exp_mark)
Note:
.xpath
获得的经常是一个list
html元素才有.xpath
方法,普通文本没有
使用etree.HTML()
解析变量,使用etree.parse
解析本地文件
如何返回:拥有a标签的href属性为blabla的li标签呢
requests
和xpath
实战
爬取电影天堂
示例代码如下:
import requests
from lxml import etree
BASE_DOMAIN = 'http://www.dytt8.net'
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
'Referer': 'http://www.dytt8.net/html/gndy/dyzz/list_23_2.html'
}
def spider():
url = 'http://www.dytt8.net/html/gndy/dyzz/list_23_1.html'
resp = requests.get(url,headers=HEADERS)
# resp.content:经过编码后的字符串
# resp.text:没有经过编码,也就是unicode字符串
# text:相当于是网页中的源代码了
text = resp.content.decode('gbk')
# tree:经过lxml解析后的一个对象,以后使用这个对象的xpath方法,就可以
# 提取一些想要的数据了
tree = etree.HTML(text)
# xpath/beautifulsou4
all_a = tree.xpath("//div[@class='co_content8']//a")
for a in all_a:
title = a.xpath("text()")[0]
href = a.xpath("@href")[0]
if href.startswith('/'):
detail_url = BASE_DOMAIN + href
crawl_detail(detail_url)
break
def crawl_detail(url):
resp = requests.get(url,headers=HEADERS)
text = resp.content.decode('gbk')
tree = etree.HTML(text)
create_time = tree.xpath("//div[@class='co_content8']/ul/text()")[0].strip()
imgs = tree.xpath("//div[@id='Zoom']//img/@src")
# 电影海报
cover = imgs[0]
# 电影截图
screenshoot = imgs[1]
# 获取span标签下所有的文本
infos = tree.xpath("//div[@id='Zoom']//text()")
for index,info in enumerate(infos):
if info.startswith("◎年 代"):
year = info.replace("◎年 代","").strip()
if info.startswith("◎豆瓣评分"):
douban_rating = info.replace("◎豆瓣评分",'').strip()
print(douban_rating)
if info.startswith("◎主 演"):
# 从当前位置,一直往下面遍历
actors = [info]
for x in range(index+1,len(infos)):
actor = infos[x]
if actor.startswith("◎"):
break
actors.append(actor.strip())
print(",".join(actors))
if __name__ == '__main__':
spider()
lambda匿名函数
文本{},文本.format()
如果可能编码出错,就用response.text就好了,对text中需要的部分再进行encoding
enumerate
函数与下标:
获得演员信息:
for之前:将第一个演员加入actors,for之后:循环到 ◎
def spider()
:
爬豆瓣:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a38S1r3N-1581082359023)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1580919780185.png)]
正常来说,ul下面应该是li
爬腾讯招聘:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogXBXJ0L-1581082359036)(https://bucket-of-chenxiaohai.oss-cn-shenzhen.aliyuncs.com/typora/20200206083553-93992.png)]
chrome相关问题:
在62版本(目前最新)中有一个bug,在页面302重定向的时候不能记录FormData数据。这个是这个版本的一个bug。详细见以下链接:https://stackoverflow.com/questions/34015735/http-post-payload-not-visible-in-chrome-debugger。
在金丝雀版本中已经解决了这个问题,可以下载这个版本继续,链接如下:https://www.google.com/chrome/browser/canary.html
作业:
使用requests和xpath爬取腾讯招聘网信息。要求为获取每个职位的详情信息。
BeautifulSoup4库
和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。
bs底层还是lxml
安装和文档:
- 安装:
pip install bs4
。 - 中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
几大解析工具对比:
解析工具 | 解析速度 | 使用难度 |
---|---|---|
BeautifulSoup | 最慢 | 最简单 |
lxml | 快 | 简单 |
正则 | 最快 | 最难 |
需要pip install html5lib
简单使用:
from bs4 import BeautifulSoup
html = """
<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>
"""
#创建 Beautiful Soup 对象
# 使用lxml来进行解析
soup = BeautifulSoup(html,"lxml")
print(soup.prettify())
以上比较麻烦,因此使用strings属性=>strings得到的是一个生成器,格式转换为list方便查看=>使用stripped_strings属性获取无空格结果
生成器示例:
<generator object _all_strings at 0x03606A50>
Note:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wf7mJJC-1581082359055)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1580951713518.png)]
属性可以称为成员变量,方法可以称为成员函数。抽象的类,具体对象
四个常用的对象:
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
1.Tag
2.NavigatableString
3.BeautifulSoup
4.Comment
1. Tag:标签
Tag 通俗点讲就是 HTML 中的一个个标签。示例代码如下:
from bs4 import BeautifulSoup
html = """
<html><head><title>The Dormouse's story</title></head>
<body> se'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>
"""
#创建 Beautiful Soup 对象
soup = BeautifulSoup(html,'lxml')
print soup.title#soup对象有属性title
# <title>The Dormouse's story</title>
print soup.head#head标签
# <head><title>The Dormouse's story</title></head>
print soup.a#a标签
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print soup.p#p标签
# <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
print type(soup.p)
# <class 'bs4.element.Tag'>
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag,且只返回第一个符合条件的标签。如果要查询所有的标签,后面会进行介绍。
对于Tag,它有两个重要的属性,分别是 name
和 attrs
。示例代码如下:
print soup.name
# [document] #soup 对象本身比较特殊,它的 name 即为 [document]
print soup.head.name
# head #对于其他内部标签,输出的值便为标签本身的名称
print soup.p.attrs
# {'class': ['title'], 'name': 'dromouse'}
# 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
print soup.p['class'] # soup.p.get('class')
# ['title'] #还可以利用get方法,传入属性的名称,二者是等价的
soup.p['class'] = "newClass"
print soup.p # 可以对这些属性和内容等等进行修改
# <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
2. NavigableString:标签中的内容
如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string
获取标签中的文字。示例代码如下:
print soup.p.string
# The Dormouse's story
print type(soup.p.string)
# <class 'bs4.element.NavigableString'>thon
3. BeautifulSoup:一个文档的全部内容
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法.
因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name
soup.name
# '[document]'
4. Comment:注释
Tag , NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.容易让人担心的内容是文档的注释部分:
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
Comment 对象是一个特殊类型的 NavigableString 对象:
comment
# 'Hey, buddy. Want to buy a used parser'
遍历文档树:
1. contents
和 children
:???
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<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')
head_tag = soup.head
# 返回所有子节点的列表
print(head_tag.contents)
# 返回所有子节点的迭代器
for child in head_tag.children:
print(child)
2. strings
和 stripped_strings
如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取:
for string in soup.strings:
print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
for string in soup.stripped_strings:
print(repr(string))
# u"The Dormouse's story"
# u"The Dormouse's story"
# u'Once upon a time there were three little sisters; and their names were'
# u'Elsie'
# u','
# u'Lacie'
# u'and'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'...'
搜索文档树:
1. find和find_all方法:
搜索文档树,一般用得比较多的就是两个方法,一个是find
,一个是find_all
。find
方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。find_all
方法是把所有满足条件的标签都选到,然后返回回去。使用这两个方法,最常用的用法是出入name
以及attr
参数找出符合要求的标签。
soup.find_all("a",attrs={"id":"link2"})
或者是直接传入属性的的名字作为关键字参数:
soup.find_all("a",id='link2')
2. select方法:
使用以上方法可以方便的找出元素。但有时候使用css
选择器的方式可以更加的方便。使用**css
选择器的语法,应该使用select
方法**。以下列出几种常用的css
选择器方法:
(1)通过标签名查找:
print(soup.select('a'))
(2)通过类名查找:
通过类名,则应该在类的前面加一个.
。比如要查找class=sister
的标签。示例代码如下:
print(soup.select('.sister'))
(3)通过id查找:#id
通过id查找,应该在id的名字前面加一个#号。示例代码如下:
print(soup.select("#link1"))
(4)组合查找:
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开:
print(soup.select("p #link1"))
直接子标签查找,则使用 > 分隔:
print(soup.select("head > title"))
(5)通过属性查找:标签[属性]
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。示例代码如下:
print(soup.select('a[href="http://example.com/elsie"]'))
(6)获取内容
以上的 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()
列表的元素是什么格式呢???
Note:
Note:
soup.select(.even)
和soup.select(tr.even)
或者soup.select("tr[class='even']")
都可以需求
将所有id等于test,class也等于test标签提取出来
用css选择器实现不了(只能一个条件获取标签的属性:
a['href']
或者a.attrs['href']
tr.stripped_strings是一个迭代器,不能直接打印,可以遍历,也可以将其转换为列表再打印
css选择器做的事情很少,只是将需要的文本选择出来,如果想要获得标签下面的值,需要用beautifulsoup提供的api对css选择器得到的结果进行进一步处理,例如第五个需求:先soup.select(‘tr’), 再使用beautifulsoup提供的stripped_strings
BeautifulSoup直接继承自Tag,使用ctrl+b进入定义的地方查看
NavigableString继承自string类型
Comment继承自NavigableString如果有多行文本,string无法获取
contents返回列表,children返回迭代器div=sout.find('div') print(type(div.children))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TGUVZ6w3-1581082359070)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1580979737232.png)]
爬取中国天气网
香港的table标签只有开头没有结尾,但是在浏览器里面
shift+F12
的时候看到的却是有头有尾的=>浏览器的解析器容错能力高,将他补齐了,源代码是没有的
lxml解析器容错能力不高,所以识别不出来;
解决:用html5lib解析器//可以使用pip list查看已经安装的库
html5lib解析速度比较慢
def sorr_key(data): min_temp=data['min_temp'] return min_temp ALL_DATA.sort(key=sorr_key)#这里的sorr_key不用传参数?? 等价于: ALL_DATA.sort(key=lambda data:int(data['min_temp']))#冒号后面就是匿名函数的返回值,前面是参数,data是个字典? #注意:转换成int再排序
使用pyechats可视化,可以在github里面搜索pyecharts查找相应文档
ALL_DATA.sort()操作获得已排序的字典之后,将字典前十项赋值给data
data=ALL_DATA[0:10]
data是个列表,元素是字典,
[{"city":"北京","min_temp":-33},{...},...]
,为了方便数据可视化,将城市和气温分开存储,构造cities和temps列表.
列表的两种构造方法:
1.cities=[] for city_temp in data: city=city_temp['city'] cities.append(city)
cities=list(map(lambda x:x['city'],data)#需要转换格式,从map到list
temp同理,因为已经经过排序,所以构造后的city和temperature是对应的
chart.add(名字
bar
,横坐标args
,纵坐标kwargs
)
chart.render(‘temperature.html’) 渲染结果并保存在html文件里面
正则表达式和 re模块
:
什么是正则表达式:
通俗理解:按照一定的规则,从某个字符串中匹配出想要的数据。这个规则就是正则表达式。
标准答案:https://baike.baidu.com/item/正则表达式/1700215?fr=aladdin
一个段子:
世界是分为两种人,一种是懂正则表达式的,一种是不懂正则表达式的。
正则表达式常用匹配规则:
匹配单个字符串:
text = 'hello'
ret = re.match('he',text)
print(ret.group())
>> he
以上便可以在hello
中,匹配出he
。
点(.)匹配任意的一个字符:
text = "ab"
ret = re.match('.',text)
print(ret.group())
>> a
但是点(.)不能匹配不到换行符。示例代码如下:
text = "\n"
ret = re.match('.',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute 'group'
\d匹配任意的数字:
text = "123"
ret = re.match('\d',text)
print(ret.group())
>> 1
\D匹配任意的非数字:
text = "a"
ret = re.match('\D',text)
print(ret.group())
>> a
而如果text是等于一个数字,那么就匹配不成功了。示例代码如下:
text = "1"
ret = re.match('\D',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute 'group'
\s匹配的是空白字符(包括:\n,\t, \r
和空格):
text = "\t"
ret = re.match('\s',text)
print(ret.group())
>> 空白
\w匹配的是a-z
和A-Z
以及数字和下划线:
text = "_"
ret = re.match('\w',text)
print(ret.group())
>> _
而如果要匹配一个其他的字符,那么就匹配不到。示例代码如下:
text = "+"
ret = re.match('\w',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute
\W匹配的是和\w相反的:
text = "+"
ret = re.match('\W',text)
print(ret.group())
>> +
而如果你的text是一个下划线或者英文字符,那么就匹配不到了。示例代码如下:
text = "_"
ret = re.match('\W',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute
[]组合的方式,只要满足中括号中的某一项都算匹配成功:
text = "0731-88888888"
ret = re.match('[\d\-]+',text)
print(ret.group())
>> 0731-88888888
note:
横线需要转义
'\-'
,否则表示 到,例如[0-9]
匹配0~9
下划线-
不需要转义?
之前讲到的几种匹配规则,其实可以使用中括号的形式来进行替代:
- \d:[0-9]
- \D:0-9
- \w:
[0-9a-zA-Z_]
- \W:[^0-9a-zA-Z_]
匹配多个字符:
1.*
:可以匹配0或者任意多个字符。示例代码如下:
text = "0731"
ret = re.match('\d*',text)//'\s*'匹配任意多个空白字符
print(ret.group())
>> 0731
以上因为匹配的要求是\d
,那么就要求是数字,后面跟了一个星号,就可以匹配到0731这四个字符。
如果没有,不会反悔none,即不会报错=>因为可以匹配0个
re.match('9*?',text)
表示匹配0个或者任意多个9,但是非贪婪,所以匹配0个
2.+
:可以匹配1个或者多个字符。最少一个
。示例代码如下:
text = "abc"
ret = re.match('\w+',text)
print(ret.group())
>> abc
因为匹配的是\w
,那么就要求是英文字符,后面跟了一个加号,意味着最少要有一个满足\w
的字符才能够匹配到。如果text是一个空白字符或者是一个不满足\w的字符,那么就会报错。示例代码如下:
text = ""
ret = re.match('\w+',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute
3.?
:匹配的字符可以出现一次或者不出现( 0或者1
)
示例代码如下:
text = "123"
ret = re.match('\d?',text)
print(ret.group())
>> 1
4.{m}
:匹配m个字符。示例代码如下:
text = "123"
ret = re.match('\d{2}',text)
print(ret.group())
>> 12
5.{m,n}
:匹配m-n个字符。在这中间的字符都可以匹配到。示例代码如下:
text = "1200345"
ret = re.match('\d{1,3}',text)
prit(ret.group())
>> 120
如果text只有一个字符,那么也可以匹配出来。示例代码如下:
text = "1"
ret = re.match('\d{1,2}',text)
prit(ret.group())
>> 1
小案例:
1.验证手机号码:
手机号码的规则是以1
开头,第二位可以是34587
,后面那9位就可以随意了。示例代码如下:
text = "18570631587"
ret = re.match('1[34587]\d{9}',text)
print(ret.group())
>> 18570631587
而如果是个不满足条件的手机号码。那么就匹配不到了。示例代码如下:
text = "1257063158"
ret = re.match('1[34587]\d{9}',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute
2.验证邮箱:
邮箱的规则是邮箱名称是用数字、数字、下划线
组成的,然后是@
符号,后面就是域名了。示例代码如下:
text = "hynever@163.com"
ret = re.match('\w+@\w+\.[a-zA-Z\.]+',text)# +表示至少有一个
print(ret.group())
3.验证URL:
URL的规则是前面是http
或者https
或者是ftp
然后再加上一个冒号,再加上一个斜杠,再后面就是可以出现任意非空白字符 [^\s]+
了。示例代码如下:
text = "http://www.baidu.com/"
ret = re.match('(http|https|ftp)://[^\s]+',text)
print(ret.group())
4.验证身份证:
身份证的规则是,总共有18位,前面17位都是数字=> \d{17}
后面一位可以是数字,也可以是小写的x,也可以是大写的X。=> [\dxX]
示例代码如下:
text = "3113111890812323X"
ret = re.match('\d{17}[\dxX]',text)
print(ret.group())
^(脱字号):以…开始 或者 取反
text = "hello"
ret = re.match('^h',text)
print(ret.group())
match本来就是从头开始检索的,所以上述与
re.match('h',text)
无异
search则有区别,re.search('e',"hello")
返回e
,re.search('^e',"hello")
则取不到值
如果是在中括号中,那么代表的是取反操作.
$(dollar符):表示以…结束:
# 匹配163.com的邮箱
text = "xxx@163.com"
ret = re.search('\w+@163\.com$',text)
print(ret.group())
>> xxx@163.com
|:匹配多个表达式或者字符串:
text = "hello"
ret = re.search('hello|world',text)
print(ret.group())
>> hello
贪婪模式和非贪婪模式:
贪婪模式:正则表达式会匹配尽量多的字符**。默认是贪婪模式**。
非贪婪模式 ?
:正则表达式会尽量少的匹配字符。即上文讲到的 0个或1个
示例代码如下:
text = "0123456"
ret = re.match('\d+',text)
print(ret.group())
# 因为默认采用贪婪模式,所以会输出0123456
**非贪婪模式 ?
**:
text = "0123456"
ret = re.match('\d+?',text)
print(ret.group())
text2="<h1>标题</h1>"
ret=re.match('<.+?>',text2)#匹配前面的<...>
案例:匹配0-100
之间的数字:
text = '99'
print(ret.group())
#ret = re.match('[1-9]?\d$|100$',text)匹配不了 100
#>> AttributeError: 'NoneType' object has no attribute 'group'
>> 99
解释:
[1-9]?
表示可以有1~9,$
表示必须以[1-9]?\d
结尾,否则 101是以1~9开头的,中间的0是\d,所以就会取出10;如果加了$
则因为101不是以取出来的10结尾的,所以101不能通过;
010也通不过:match本来就是从头开始检索的
用search的话,可以^[(1-9]?\d)$|^100$
敲代码心得:
由于敲入中括号圆括号引号等需要成对出现的符号,要么分次键入,要么一次键入再通过方向键将光标移到中间.
但是,前者比较别扭:一般需要分次键入的符号在键盘的角落,难以操作,而且可能出现键入(
之后忘了键入)
的情况; 后者同样,方向键一般shift右下角,因此需要将右手离开键盘去按方向键,同样麻烦.
My idea:将方向键迁移到空格键下方,用左右大拇指分别控制左/上,右/下
转义字符和原生字符串:
在正则表达式中,有些字符是有特殊意义的字符。因此如果想要匹配这些字符,那么就必须使用反斜杠进行转义。比如$
代表的是以…结尾,如果想要匹配$
,那么就必须使用\$
。示例代码如下:
text = "apple price is \$99,orange paice is $88"
ret = re.search('\$(\d+)',text)
print(ret.group())
>> $99
原生字符串:
在正则表达式中,\
是专门用来做转义的。
在Python中\
也是用来做转义的。
因此如果想要在普通的字符串中匹配出\
,那么要给出四个\
。
即:
对
text="\nabc" ret=re. match('\\\\n')
,re中的'\\\\n'
首先是python的字符串,
经过python的转义=>'\\n'
,
再经过re的转义,=>'\n'
也就是,python中的正则表达式如果想表示一个'\'
,需要敲入四个'\'
,经过python和正则两次转义之后才表示'\'
示例代码如下:
text = "apple \c"
ret = re.search('\\\\c',text)#其实就是想要匹配'\c'
print(ret.group())
因此要使用原生字符串 r'string'
就可以解决这个问题:
text = "apple \c"
ret = re.search(r'\\c',text)
print(ret.group())
r
表示 raw,不会经过python的转义,直接进行正则的转义,因此原来四个反斜杠,现在只需要两个
re模块中常用函数:
match:
从开始的位置进行匹配。如果开始的位置没有匹配到。就直接失败了。示例代码如下:
text = 'hello'
ret = re.match('h',text)
print(ret.group())
>> h
如果第一个字母不是h
,那么就会失败。示例代码如下:
text = 'ahello'
ret = re.match('h',text)
print(ret.group())
>> AttributeError: 'NoneType' object has no attribute 'group'
如果想要匹配换行的数据,那么就要传入一个flag=re.DOTALL
,就可以匹配换行符了。示例代码如下:
text = "abc\nabc"
ret = re.match('abc.*abc',text,re.DOTALL)
print(ret.group())
search:
在字符串中找满足条件的字符。如果找到,就返回。说白了,就是只会找到第一个满足条件的。
text = 'apple price $99 orange price $88'
ret = re.search('\d+',text)
print(ret.group())
>> 99
用 ()
分组:
在正则表达式中,可以对过滤到的字符串进行分组。分组使用圆括号的方式。
1.
group
:和group(0)
是等价的,返回的是整个满足条件的字符串。
2.groups
:返回的是里面的子组。索引从1开始。 相当于group(1,2,3,...)
3.group(1)
:返回的是第一个子组,可以传入多个如group(1,3
)。
示例代码如下:
text = "apple price is $99,orange price is $10"
ret = re.search(r".*(\$\d+).*(\$\d+)",text)
print(ret.group())#>>apple price is $99,orange price is $10
print(ret.group(0))#>>apple price is $99,orange price is $10
print(ret.group(1))#>>$99
print(ret.group(2))#>>$10
print(ret.groups())#>>$99,$10
findall:
找出所有满足条件的,返回的是一个列表。
text = 'apple price $99 orange price $88'
ret = re.findall('\d+',text)
print(ret)
>> ['99', '88']
sub:
用来替换字符串。将匹配到的字符串替换为其他字符串。
text = 'apple price $99 orange price $88'
ret = re.sub('\d+','0',text)
print(ret)
>> apple price $0 orange price $0
sub
函数的案例,获取拉勾网中的数据:
html = """
<div>
<p>基本要求:</p>
<p>1、精通HTML5、CSS3、 JavaScript等Web前端开发技术,对html5页面适配充分了解,熟悉不同浏览器间的差异,熟练写出兼容各种浏览器的代码;</p>
<p>2、熟悉运用常见JS开发框架,如JQuery、vue、angular,能快速高效实现各种交互效果;</p>
<p>3、熟悉编写能够自动适应HTML5界面,能让网页格式自动适应各款各大小的手机;</p>
<p>4、利用HTML5相关技术开发移动平台、PC终端的前端页面,实现HTML5模板化;</p>
<p>5、熟悉手机端和PC端web实现的差异,有移动平台web前端开发经验,了解移动互联网产品和行业,有在Android,iOS等平台下HTML5+CSS+JavaScript(或移动JS框架)开发经验者优先考虑;6、良好的沟通能力和团队协作精神,对移动互联网行业有浓厚兴趣,有较强的研究能力和学习能力;</p>
<p>7、能够承担公司前端培训工作,对公司各业务线的前端(HTML5\CSS3)工作进行支撑和指导。</p>
<p><br></p>
<p>岗位职责:</p>
<p>1、利用html5及相关技术开发移动平台、微信、APP等前端页面,各类交互的实现;</p>
<p>2、持续的优化前端体验和页面响应速度,并保证兼容性和执行效率;</p>
<p>3、根据产品需求,分析并给出最优的页面前端结构解决方案;</p>
<p>4、协助后台及客户端开发人员完成功能开发和调试;</p>
<p>5、移动端主流浏览器的适配、移动端界面自适应研发。</p>
</div>
"""
ret = re.sub('</?[a-zA-Z0-9]+>',"",html)
print(ret)
split:
使用正则表达式来分割字符串。
text = "hello world ni hao"
ret = re.split('\W',text)
print(ret)
>> ["hello","world","ni","hao"]
compile:
对于一些经常要用到的正则表达式,可以使用compile
进行编译,后期再使用的时候可以直接拿过来用,执行效率会更快。而且compile
还可以指定flag=re.VERBOSE
,在写正则表达式的时候可以做好注释。示例代码如下:
text = "the number is 20.50"
r = re.compile(r"""
\d+ # 小数点前面的数字
\.? # 小数点
\d* # 小数点后面的数字
""",re.VERBOSE)
ret = re.search(r,text)
print(ret.group())
Note:
zip;.*?;解包
爬古诗文网
Note:
使用正则表达式的时候,html代码就是字符串,没有相互之间没有附级关系了
.
不能识别\
,所以要re.findall(r'string',text,re.DOTALL)
或者用re.s