python网络爬虫常用解析器
BeautifulSoup
BeautifulSoup 是一个HTML/XML 的解析器,主要用于解析和提取 HTML/XML 数据。
它基于HTML DOM 的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
目前最常用的版本是Beautiful Soup 4,也就是 bs4 ,所以在导入时 import bs4 就是在导入Beautiful Soup 4。
BeautifulSoup4安装
pip install beautifulsoup4
BeautifulSoup4解析器
BeautifulSoup做的工作就是对html标签进行解释和分类,不同的解析器对相同html标签会做出不同解释。
默认情况下,直接输入一个link解析时,python内部默认的解析器是"html.parser"。
BeautifulSoup4简单使用
假设有这样一个Html,具体内容如下:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta content="always" name="referrer" />
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css" />
<title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div id="u1">
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻 </a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123 </a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图 </a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频 </a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧 </a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品 </a>
</div>
</div>
</div>
</div>
</body>
</html>
创建beautifulsoup4对象:
import bs4.element
from bs4 import BeautifulSoup
import requests
#
# html = requests.get('https://www.cnblogs.com/xfeiyun/category/2213556.html')
# print(html.text)
fo = open('./test.html', 'rb')
text = fo.read()
# 解析器
soup = BeautifulSoup(text, 'html.parser')
print(soup.prettify()) # 格式化html结构
print(soup.title) # 获取title标签的名称
print(soup.title.name) # 获取title的name
print(soup.title.string) # 获取head标签的所有内容
print(soup.head)
print(soup.div) # 获取第一个div标签中的所有内容
print(soup.div["id"]) # 获取第一个div标签的id的值
print(soup.a)
print(soup.find_all("a")) # 获取所有的a标签
print(soup.find(id="u1")) # 获取id="u1"
for item in soup.find_all("a"):
print(item.get("href")) # 获取所有的a标签,并遍历打印a标签中的href的值
for item in soup.find_all("a"):
print(item.get_text())
print("|=============================================|")
# 根据id查找节点
wrapper = soup.select('#wrapper')[0]
if isinstance(wrapper, bs4.element.Tag):
u1 = wrapper.select('#u1')[0] # 查找ul节点,select()查找都是列表,这里只有一个,所以取下标0
for child in u1.children: # children不仅仅包含标签节点,也包括文本内容、特殊符号(如果\n)等
print(type(child))
# alist = u1.find_all("a") # 查找所有的a节点
print("---------> 属性匹配")
alist = u1.find_all(attrs={"class": "mnav"}) # 查找有指定属性的节点
for a in alist:
print(a.string) # 获取节点的内容
print(a.attrs) # 获取节点的属性字典
print("---------> 文本匹配")
textList = u1.find_all(text='地图 ') # 注意:必须与html的文本节点完成匹配,空格也不能省略,否则匹配不到
for atext in textList:
print(atext) # 这里输出的是文本
BeautifulSoup4四大对象种类
BeautifulSoup4将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag(标签)
- NavigableString(内容)
- BeautifulSoup(文档)
- Comment(注释)
Tag
Tag通俗点讲就是HTML中的一个个标签,例如:
from bs4 import BeautifulSoup
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
# 获取title标签的所有内容
print(bs.title)
# 获取head标签的所有内容
print(bs.head)
# 获取第一个a标签的所有内容
print(bs.a)
# 类型
print(type(bs.a))
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。
对于 Tag,它有两个重要的属性,是 name 和 attrs:
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
# [document] #bs 对象本身比较特殊,它的 name 即为 [document]
print(bs.name)
# head #对于其他内部标签,输出的值便为标签本身的名称
print(bs.head.name)
# 在这里,我们把 a 标签的所有属性打印输出了出来,得到的类型是一个字典。
print(bs.a.attrs)
# 还可以利用get方法,传入属性的名称,二者是等价的
print(bs.a['class']) # 等价 bs.a.get('class')
# 可以对这些属性和内容等等进行修改
bs.a['class'] = "newClass"
print(bs.a)
# 还可以对这个属性进行删除
del bs.a['class']
print(bs.a)
NavigableString
既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如:
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.title.string)
print(type(bs.title.string))
BeautifulSoup
BeautifulSoup对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性,例如:
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(type(bs.name))
print(bs.name)
print(bs.attrs)
Comment
Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.a)
# 此时不能出现空格和换行符,a标签如下:
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新闻--></a>
print(bs.a.string) # 新闻
print(type(bs.a.string)) # <class 'bs4.element.Comment'>
遍历文档树
.contents:获取Tag的所有子节点,返回一个list
# tag的.content 属性可以将tag的子节点以列表的方式输出
print(bs.head.contents)
# 用列表索引来获取它的某一个元素
print(bs.head.contents[1])
.children:获取Tag的所有子节点,返回一个生成器
for child in bs.body.children:
print(child)
.descendants:获取Tag的所有子孙节点,返回的是一个 (子孙节点)的迭代器,包含所有子孙节点,用于循环遍历。
.strings:如果Tag包含多个字符串,即在子孙节点中有内容,可以用此获取,而后进行遍历
.stripped_strings:与strings用法一致,只不过可以去除掉那些多余的空白内容
.parent:获取Tag的父节点
.parents:递归得到父辈元素的所有节点,返回一个生成器
.previous_sibling:获取当前Tag的上一个节点,属性通常是字符串或空白,真实结果是当前标签与上一个标签之间的顿号和换行符
.next_sibling:获取当前Tag的下一个节点,属性通常是字符串或空白,真实结果是当前标签与下一个标签之间的顿号与换行符
.previous_siblings:获取当前Tag的上面所有的兄弟节点,返回一个生成器
.next_siblings:获取当前Tag的下面所有的兄弟节点,返回一个生成器
.previous_element:获取解析过程中上一个被解析的对象(字符串或tag),可能与previous_sibling相同,但通常是不一样的
.next_element:获取解析过程中下一个被解析的对象(字符串或tag),可能与next_sibling相同,但通常是不一样的
.previous_elements:返回一个生成器,可以向前访问文档的解析内容
.next_elements:返回一个生成器,可以向后访问文档的解析内容
.has_attr:判断Tag是否包含属性
搜索文档树
find_all(name, attrs, recursive, text, **kwargs)
(1)name参数:
字符串过滤:会查找与字符串完全匹配的内容
a_list = bs.find_all("a")
print(a_list)
正则表达式过滤:如果传入的是正则表达式,那么BeautifulSoup4会通过search()来匹配内容
from bs4 import BeautifulSoup
import re
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.find_all(re.compile("a"))
for item in t_list:
print(item)
列表:如果传入一个列表,BeautifulSoup4将会与列表中的任一元素匹配到的节点返回
t_list = bs.find_all(["meta", "link"])
for item in t_list:
print(item)
方法:传入一个方法,根据方法来匹配
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
def name_is_exists(tag):
return tag.has_attr("name")
t_list = bs.find_all(name_is_exists)
for item in t_list:
print(item)
(2)kwargs参数:
from bs4 import BeautifulSoup
import re
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
# 查询id=head的Tag
t_list = bs.find_all(id="head")
print(t_list)
# 查询href属性包含ss1.bdstatic.com的Tag
t_list = bs.find_all(href=re.compile("http://news.baidu.com"))
print(t_list)
# 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
t_list = bs.find_all(class_=True)
for item in t_list:
print(item)
(3)attrs参数:
并不是所有的属性都可以使用上面这种方式进行搜索,比如HTML的data-*属性:
t_list = bs.find_all(data-foo="value")
如果执行这段代码,将会报错。我们可以使用attrs参数,定义一个字典来搜索包含特殊属性的tag:
t_list = bs.find_all(attrs={"data-foo":"value"})
for item in t_list:
print(item)
(4)text参数:
通过text参数可以搜索文档中的字符串内容,与name参数的可选值一样,text参数接受 字符串,正则表达式,列表
from bs4 import BeautifulSoup
import re
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.find_all(attrs={"data-foo": "value"})
for item in t_list:
print(item)
t_list = bs.find_all(text="hao123")
for item in t_list:
print(item)
t_list = bs.find_all(text=["hao123", "地图", "贴吧"])
for item in t_list:
print(item)
t_list = bs.find_all(text=re.compile("\d"))
for item in t_list:
print(item)
当我们搜索text中的一些特殊属性时,同样也可以传入一个方法来达到我们的目的:
def length_is_two(text):
return text and len(text) == 7
t_list = bs.find_all(text=length_is_two)
for item in t_list:
print(item)
(5)limit参数:
以传入一个limit参数来限制返回的数量,当搜索出的数据量为5,而设置了limit=2时,此时只会返回前2个数据
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.find_all("a", limit=2)
for item in t_list:
print(item)
find_all除了上面一些常规的写法,还可以对其进行一些简写:
# 两者是相等的
# t_list = bs.find_all("a") => t_list = bs("a")
t_list = bs("a") # 两者是相等的
# t_list = bs.a.find_all(text="新闻") => t_list = bs.a(text="新闻")
t_list = bs.a(text="新闻")
find()
find()将返回符合条件的第一个Tag,有时我们只需要找一个Tag时,我们就可以用到find()方法了。当然了,也可以使用find_all()方法,传入一个limit=1,然后再取出第一个值也是可以的,不过未免繁琐。
from bs4 import BeautifulSoup
file = open('./test.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
# 返回只有一个结果的列表
t_list = bs.find_all("title", limit=1)
print(t_list)
# 返回唯一值
t = bs.find("title")
print(t)
# 如果没有找到,则返回None
t = bs.find("abc")
print(t)
从结果可以看出find_all,尽管传入了limit=1,但是返回值仍然为一个列表,当我们只需要取一个值时,远不如find方法方便。但是如果未搜索到值时,将返回一个None。
在上面介绍BeautifulSoup4的时候,我们知道可以通过bs.div来获取第一个div标签,如果我们需要获取第一个div下的第一个div,
我们可以这样:
t = bs.div.div
# 等价于
t = bs.find("div").find("div")
CSS选择器
BeautifulSoup支持发部分的CSS选择器,在Tag获取BeautifulSoup对象的.select()方法中传入字符串参数,即可使用CSS选择器的语法找到Tag:
通过标签名查找
print(bs.select('title'))
print(bs.select('a'))
通过类名查找
print(bs.select('.mnav'))
通过id查找
print(bs.select('#u1'))
组合查找
print(bs.select('div .bri'))
属性查找
print(bs.select('a[class="bri"]'))
print(bs.select('a[href="http://tieba.baidu.com"]'))
直接子标签查找
t_list = bs.select("head > title")
print(t_list)
兄弟节点标签查找
t_list = bs.select(".mnav ~ .bri")
print(t_list)
获取内容
t_list = bs.select("title")
print(bs.select('title')[0].get_text())
XPath
XPath,全称 XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。
- XPath 使用路径表达式在 XML 文档中进行导航
- XPath 包含一个标准函数库
- XPath 是 XSLT 中的主要元素
- XPath 是一个 W3C 标准
要使用XPath,需要安装一个第三方库lxml:
pip install lxml
节点(Node)
在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
xpath中节点的关系:
xpath语法
from lxml import etree
import requests
# 本地文件解析 -> 将源码转化为能被XPath匹配的格式
print("本地文件解析......")
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('test.html', parser=parser)
# 远程html url解析 -> 将源码转化为能被XPath匹配的格式
print("远程html url解析......")
resp = requests.get('https://www.cnblogs.com/xfeiyun/category/2213556.html')
tree2 = etree.HTML(resp.text)
实例:
<?xml version="1.0" encoding="UTF-8"?>
<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>
选取节点
XPath 使用路径表达式在 XML 文档中选取节点,节点是沿着路径或者 step 来选取的。
表达式 |
描述 |
nodename |
选取当前节点的所有子节点 |
/ |
从根节点选取 |
// |
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. |
选取当前节点 |
.. |
选取当前节点的父节点 |
@ |
选取属性 |
text() |
选取文本 |
示例:
路径表达式 |
结果 |
bookstore |
选取 bookstore 元素的所有子节点 |
/bookstore |
选取根元素 bookstore |
bookstore/book |
选取bookstore 下名字为 book的所有子元素。 |
//book |
选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book |
选取bookstore 下名字为 book的所有后代元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang |
选取所有名为 lang 的属性。 |
from lxml import etree
# 本地文件解析
parser = etree.XMLParser(encoding='utf-8')
tree = etree.parse('demo.xml', parser=parser)
# 常用的表达式
# 从根节点选取bookstore元素, bookstore本例中只有一个
bookStoreNode = tree.xpath("/bookstore")[0]
print(bookStoreNode)
# 选取当前节点
print(bookStoreNode.xpath('.'))
# 选取此节点的所有子节点
bookNodeList = bookStoreNode.xpath('book')
print(bookNodeList)
# 选取当前节点的父节点
parentBookNode = bookNodeList[0].xpath('..')
print(parentBookNode)
# 选择所有的book下面的title中的lang属性的值
attrlist = tree.xpath("/bookstore/book/title/@lang")
print(attrlist)
# 选择所有的book下面的title的文本
textlist = tree.xpath("/bookstore/book/title/text()")
print(textlist)
# 选取所有 book 子元素,而不管它们在文档中的位置。
nodeList = tree.xpath('//book')
print(nodeList)
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
常见的谓语的一些路径表达式:
路径表达式 |
结果 |
/bookstore/book[1] |
选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] |
选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] |
选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<3] |
选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] |
选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang='eng'] |
选取所有 title 元素,要求这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] |
选取所有 bookstore 元素的 book 元素,要求book元素的子元素 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title |
选取所有 bookstore 元素中的 book 元素的 title 元素,要求book元素的子元素 price 元素的值须大于 35.00 |
示例:
# 选取属于 bookstore 子元素的第一个 book 元素。
firstBookNode = tree.xpath('/bookstore/book[1]')
print(firstBookNode)
# 选取属于 bookstore 子元素的最后一个 book 元素。
lastBookNode = tree.xpath('/bookstore/book[last()]')
print(lastBookNode)
# 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
positionBookNode = tree.xpath('/bookstore/book[position()<3]')
print(positionBookNode)
# 选取所有拥有名为 lang 的属性的 title 元素。
print(tree.xpath('//title[@lang]'))
# 选取所有 bookstore 元素的 book 元素,要求book元素的子元素 price 元素的值须大于 35.00。
print(tree.xpath('/bookstore/book[price>35.00]'))
# 选取所有 bookstore 元素中的 book 元素的 title 元素,要求book元素的子元素 price 元素的值须大于 35.00
print(tree.xpath('/bookstore/book[price>35.00]/title'))
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 |
描述 |
* |
匹配任何元素节点 |
@* |
匹配任何属性节点 |
node() |
匹配任何类型的节点 |
示例:
路径表达式 |
结果 |
/bookstore/* |
选取 bookstore 元素的所有子节点 |
//* |
选取文档中的所有元素 |
//title[@*] |
选取所有带有属性的 title 元素。 |
选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
示例:
路径表达式 |
结果 |
//book/title | //book/price |
选取所有 book 元素的 title 和 price 元素。 |
//title | //price |
选取所有文档中的 title 和 price 元素。 |
/bookstore/book/title|//price |
选取所有属于 bookstore 元素的 book 元素的 title 元素,以及文档中所有的 price 元素。 |
XPath 轴
轴可定义某个相对于当前节点的节点集。
轴名称 |
结果 |
ancestor |
选取当前节点的所有先辈(父、祖父等) |
ancestor-or-self |
选取当前节点的所有先辈(父、祖父等)以及当前节点本身 |
attribute |
选取当前节点的所有属性 |
child |
选取当前节点的所有子元素。 |
descendant |
选取当前节点的所有后代元素(子、孙等)。 |
descendant-or-self |
选取当前节点的所有后代元素(子、孙等)以及当前节点本身。 |
following |
选取文档中当前节点的结束标签之后的所有节点。 |
namespace |
选取当前节点的所有命名空间节点 |
parent |
选取当前节点的父节点。 |
preceding |
选取文档中当前节点的开始标签之前的所有节点。 |
preceding-sibling |
选取当前节点之前的所有同级节点。 |
self |
选取当前节点。 |
路径
(1)位置路径表达式
位置路径可以是绝对的,也可以是相对的。
绝对路径起始于正斜杠( / ),而相对路径不会这样。在两种情况中,位置路径均包括一个或多个步,每个步均被斜杠分割:
- 绝对位置路径:/step/step/...
- 相对位置路径:step/step/...
每个步均根据当前节点集之中的节点来进行计算。
(2)步(step)包括:
轴(axis):定义所选节点与当前节点之间的树关系
节点测试(node-test):识别某个轴内部的节点
零个或者更多谓语(predicate):更深入地提炼所选的节点集
步的语法:轴名称::节点测试[谓语]
示例:
例子 |
结果 |
child::book |
选取所有属于当前节点的子元素的 book 节点 |
attribute::lang |
选取当前节点的 lang 属性 |
child::* |
选取当前节点的所有子元素 |
attribute::* |
选取当前节点的所有属性 |
child::text() |
选取当前节点的所有文本子节点 |
child::node() |
选取当前节点的所有子节点 |
descendant::book |
选取当前节点的所有 book 后代 |
ancestor::book |
选择当前节点的所有 book 先辈 |
ancestor-or-self::book |
选取当前节点的所有book先辈以及当前节点(假如此节点是book节点的话) |
child::*/child::price |
选取当前节点的所有 price 孙。 |
XPath 运算符
运算符 |
描述 |
实例 |
返回值 |
| |
计算两个节点集 |
//book | //cd |
返回所有带有 book 和 ck 元素的节点集 |
+ |
加法 |
6 + 4 |
10 |
- |
减法 |
6 - 4 |
2 |
* |
乘法 |
6 * 4 |
24 |
div |
除法 |
8 div 4 |
2 |
= |
等于 |
price=9.80 |
如果 price 是 9.80,则返回 true。 如果 price 是 9.90,则返回 fasle。 |
!= |
不等于 |
price!=9.80 |
如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 fasle。 |
< |
小于 |
price<9.80 |
如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 fasle。 |
<= |
小于或等于 |
price<=9.80 |
如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 fasle。 |
> |
大于 |
price>9.80 |
如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 fasle。 |
>= |
大于或等于 |
price>=9.80 |
如果 price 是 9.90,则返回 true。 如果 price 是 9.70,则返回 fasle。 |
or |
或 |
price=9.80 or price=9.70 |
如果 price 是 9.80,则返回 true。 如果 price 是 9.50,则返回 fasle。 |
and |
与 |
price>9.00 and price<9.90 |
如果 price 是 9.80,则返回 true。 如果 price 是 8.50,则返回 fasle。 |
mod |
计算除法的余数 |
5 mod 2 |
1 |
常用函数
- starts-with() 获取以xxx开头的元素
tree.xpath('//title[starts-with(@lang,"ch")]')
- contains() 获取包含xxx的元素
tree.xpath('//title[contains(@lang,"chinese")]')
- and 并且 同时
tree.xpath("//title[@lang='chinese' and @lang='chinese']")
- or 或者
tree.xpath("//title[@lang='chinese' or @name='fuck']")
- not 非
tree.xpath("//title[@lang='chinese' and not(contains(@name,'fuck'))]")
- text()
tree.xpath('//title[contains(text(),"chinese")]')
tree.xpath('//title[@lang="chinese"]/text()')
正则表达式
正则表达式在网络爬虫中非常重要,可以在一大段文字中提取出有用的信息,虽然正则表达式不是最简单和高效的数据提取方式,但是是最直接的方式,Python中内置re模块来支持正则表达式。
正则表达式是由普通字符和元字符组成。
正则表达式中常见的特殊字符有以下几个:.+?*$[]()^{}\
如果要在正则表达式中表示这几个字符本身,就应该在字符前面加上\。
常用元字符
. 匹配除换行符以外的任意字符
\w 匹配数字、字母、下划线,汉字
\s 匹配任意的一个空白符,如空格,\t,\r,\n等
\d 匹配任意的一个数字,等价于[0-9]
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开始
$ 匹配字符串的结尾
\W 匹配非(字母、数字、下划线、汉字)
\D 匹配一个非数字,等价于[^\d],[^0-9]
\S 匹配非空白符
a|b 匹配a或b
() 匹配括号内的表达式,也表示一个组
[] 匹配字符组中的其中一个字符
[^] 匹配一个除了字符组中出现的字符
量词
想要一次性匹配多个字符,则需要使用到量词
* 表示左边的字符可重复零次或更多次
+ 表示左边的字符可重复一次或更多次
? 表示左边的字符可重复零次或一次
{n} n为整数,表示左边的字符必须且只能重复n次
{n,} 表示左边的字符最少重复n次或更多次
{n,m} 表示左边的字符至少重复n次,最多重复m次
惰性匹配和贪婪匹配
在量词中的?,*,+,{}都属于贪婪匹配,就是尽可能多的匹配到结果。
在使用.*后面如果加了?,这是尽可能的少匹配。表示惰性匹配。.?,.+,.{}类似
下面举个简单的例子来说明。
1.贪婪匹配是先看整个字符串是否匹配,如果不匹配,它会去掉字符串的最后一个字符,并再次尝试。如果还不匹配,那么再去掉当前最后一个,直到发现匹配或不剩任何字符。
var str='abcdabceba'
/.+b/ //匹配一个或多个任意字符后面跟一个字母b
执行str.match(/.+b/)
第一次(先看整个字符串是否是一个匹配) abcdabceba 不匹配,然后去掉最后一个字符a
第二次(去掉最后一个字符后再匹配) abcdabceb 匹配,返回abcdabceb。
2.惰性匹配是从左侧第一个字符开始向右匹配, 先看第一个字符是不是一个匹配, 如果不匹配就加入下一个字符再尝式匹配, 直到发现匹配...
执行str.match(/.+?b/)
第一次(读入左侧第一个字符) a 不匹配加一个再式
第二次 ab 匹配,返回ab
正则表达式中的函数
re.search(pattern, string, flags=0)
res = re.search("\d+", "我今年18岁了,体重为60kg")
# 输出子串及起止位置
print(res.group(), res.span()) # 18 (3, 5)
- 查找字符串中可以匹配成功的子串,匹配到第一个结果就返回。不会匹配出多个结果。
- 成功则返回一个匹配对象,否则返回None
content = '我的微博密码是:1234567,QQ密码是:33445566,银行卡密码是:888888,Github密码是:999abc999'
password_search = re.search('密码是:(.*?),', content)
password_search_not_find = re.search('xxx:(.*?),', content)
print(password_search)
print(password_search.group())
print(password_search.group(0))
print(password_search.group(1))
print(password_search_not_find)
re.findall(pattern, string, flags = 0)
list1 = re.findall("\d+", "我今年18岁了,体重为60kg")
print(list1) # ['18', '60']
- 查找字符串中所有和模式匹配的子串(不重叠)放入列表。一个子串都找不到就返回空表[]。
content = '我的微博密码是:1234567,QQ密码是:33445566,银行卡密码是:888888,Github密码是:999abc999'
password_list = re.findall(':(.*?),', content)
print('找到内容,返回:{}'.format(password_list))
re.match(pattern, string, flags = 0)
result = re.match("\d+", "18岁了,我喜欢5个明星")
print(result.group()) # 18,result.group()是匹配到的字符串
- 从字符串string的起始位置开始匹配一个模式pattern(即默认加了^),匹配到第一个结果就返回。 (一般用于匹配手机号,邮箱号)
- 成功则返回一个匹配对象,否则返回None
- flags 标志位,用于控制模式串的匹配方式,如:是否区分大小写,多行匹配等等。如: re.M | re.I表示忽略大小写,且多行匹配
re.finditer(pattern, string, flags = 0)
it = re.finditer("\d+", "我今年18岁了,体重为60kg")
for item in it:
print(item.group(), item.span()) # 18 (3, 5) 60 (11, 13)
- 查找字符串中所有和模式匹配的子串(不重叠),每个子串对应于一个匹配对象,返回匹配对象的迭代器。(一般用于爬虫)
obj = re.compile(r"今天吃了(?P<mian>\d+)碗面,又吃了(?P<xian>\d+)盘小咸菜")
result = obj.finditer("明天我要吃4碗面,喝上8碗汤。今天吃了5碗面,又吃了6盘小咸菜,昨天吃了1碗面条")
for item in result:
print(item.group("mian")) # 5
print(item.group("xian")) # 6
print(item.groupdict()) # {'mian': '5', 'xian': '6'}
其他操作
split分割
ret = re.split('[ab]', 'qwerafjbcd')
# 先按'a'分割,得到'qwer'和'fjbcd',再对'qwer'和'fjbcd'分别按'b'分割
print(ret) # ['qwer', 'fj', 'cd']
re.sub(pattern, repl, string, count=0, flags=0)
用于替换匹配的字符串,比str.replace功能更加强大
ret = re.sub('[a-z]+', 'sb', '武配齐是abc123', )
print(ret) # 武配齐是sb123
ret = re.sub('\d+', '|', 'alex22wupeiqi33oldboy55', count=2)
print(ret) # alex|wupeiqi|oldboy55
compiler
obj = re.compile(r'\d{3}') # 将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字
obj = re.compile(r'\d+') # 先加载这个正则,后面可以直接用这个正则去匹配内容
lst = obj.findall("我今天吃了3个馒头,喝了2碗粥")
print(lst) # ['3', '2']
爬虫必须会的重点
- ()括起来的内容是你最终想要的结果
- (?P正则) 把正则匹配到的内容直接放在name组里面,后面取数据的时候直接group(name)
obj = re.compile(r"今天吃了(?P<mian>\d+)碗面,又吃了(?P<xian>\d+)盘小咸菜")
result = obj.finditer("明天我要吃4碗面,喝上8碗汤。今天吃了5碗面,又吃了6盘小咸菜,昨天吃了1碗面条")
for item in result:
print(item.group("mian")) # 5
print(item.group("xian")) # 6
print(item.groupdict()) # {'mian': '5', 'xian': '6'}
参考: |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!