网页解析
4533 字约 15 分钟
2026-05-20
1,re正则
1、匹配单个字符与数字
| 匹配 | 说明 |
|---|---|
| . | 匹配除换行符以外的任意字符,当flags被设置为re.S时,可以匹配包含换行符以内的所有字符 |
| [] | 里面是字符集合,匹配[]里任意一个字符 |
| [0123456789] | 匹配任意一个数字字符 |
| [0-9] | 匹配任意一个数字字符 |
| [a-z] | 匹配任意一个小写英文字母字符 |
| [A-Z] | 匹配任意一个大写英文字母字符 |
| [A-Za-z] | 匹配任意一个英文字母字符 |
| [A-Za-z0-9] | 匹配任意一个数字或英文字母字符 |
| [^lucky] | []里的^称为脱字符,表示非,匹配不在[]内的任意一个字符 |
| 1 | 以[]中内的某一个字符作为开头 |
| 匹配任意一个数字字符,相当于[0-9] | |
匹配任意一个非数字字符,相当于[^0-9] | |
| 匹配字母、下划线、数字中的任意一个字符,相当于[0-9A-Za-z_] | |
匹配非字母、下划线、数字中的任意一个字符,相当于[^0-9A-Za-z_] | |
| 匹配空白符(空格、换页、换行、回车、制表),相当于[ | |
匹配非空白符(空格、换页、换行、回车、制表),相当于[^ \f\n\r\t] |
2、匹配锚字符
锚字符:用来判定是否按照规定开始或者结尾
| 匹配 | 说明 |
|---|---|
| ^ | 行首匹配,和[]里的^不是一个意思 |
| $ | 行尾匹配 |
3、限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
| 匹配 | 说明 |
|---|---|
| (xyz) | 匹配括号内的xyz,作为一个整体去匹配 一个单元 子存储 |
| x? | 匹配0个或者1个x,非贪婪匹配 |
| x* | 匹配0个或任意多个x |
| x+ | 匹配至少一个x |
| x | 确定匹配n个x,n是非负数 |
| x | 至少匹配n个x |
| x | 匹配至少n个最多m个x |
| x | y |
通用flags(修正符)
| 值 | 说明 |
|---|---|
| re.I | 是匹配对大小写不敏感 |
| re.S | 使.匹配包括换行符在内的所有字符 |
^a 匹配a开头的
2 匹配一个小写字母a并且a作为开头,等同于^a
[^a] 匹配一个小写字母a以外的任意字符
4,匹配符总结
1 []原子表
[a] 匹配一个小写字母a
[1] 匹配一个数字1[ab] 匹配一个小写字母a或者b
[a1] 匹配一个小写字母a或者数字1[123] 匹配一个数字1或者2或者3[a-z] 匹配任意一个小写字母
[A-Z] 匹配任意一个大写字母
[a-zA-Z] 匹配任意一个字母
[0-9] 匹配任意一个数字 0-9[a-zA-Z0-9] 匹配任意一个数字 0-9或者任意一个字母以上不管[]中有多少内容 只匹配一个
2 {m} 限定符 不能单独使用 限定前面那个正则的m次
a 匹配一个小写字母a
ab 匹配小写字母ab
aa 匹配2个小写字母aa{2} 匹配2个小写字母aab{2} 匹配小写字母abb
a{2}b{2} 匹配小写字母aabb
[a-zA-Z]{5} 匹配任意5个字母3 {m, n} 限定符 不能单独使用 限定前面那个正则的m-n次
[a-zA-Z]{3,5} 匹配任意3-5个字母4 {m,} 限定符 不能单独使用 限定前面那个正则的m次
[a-zA-Z]{3,} 至少匹配任意3个字母5 ^ 以...开始
abc 匹配abc三个字母
^abc 匹配以abc开头的三个字母
^[a] 匹配一个小写字母a并且a作为开头 等同于 ^a
[^a] 匹配一个小写字母a以外的任意字符
6 $ 以...结尾
abc 匹配abc三个字母并且作为结尾
^abc 匹配以abc开头的三个字母
abc$ 匹配abc三个字母并且作为结尾
7 ^$ 一般组合使用 (完全匹配)
匹配手机号
1[3-9][0-9]{9}
^1[3-9][0-9]{9}$
8 ? 匹配前面的正则0次或1次 相当于 {0,1}
-?[1-9] 匹配正负1-99 . 匹配换行符以外的任意字符
10 * 匹配任意次 {0,}
11 .* 匹配除换行符以外任意字符任意次 贪婪模式
12 .*? 匹配除换行符以外任意字符任意次 拒绝贪婪模式 (用的多)
13 + 匹配至少一次 相当于{1, }
14 .+ 匹配除换行符以外任意字符至少1次 贪婪模式
15 .+? 匹配除换行符以外任意字符至少1次 拒绝贪婪模式
16 () 子存储(会把括号里的值单独的保存下来) 一个单元 (ab)|(cd)
17 \w 匹配一位数字,字母,下划线 [a-zA-Z0-9_]
18 \W
19 \d 匹配一位数字 [0-9]
20 \D 和上面相反 [^0-9]
21 \s 匹配空白符
22 \S 和上面相反5、贪婪与非贪婪
#贪婪模式#贪婪概念:匹配尽可能多的字符# + .+ 匹配换行符以外的字符至少一次# + .* 匹配换行符以外的字符任意次res = re.search('<b>.+</b>', '<b></b><b>b标签</b>')
res = re.search('<b>.*</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>')
# .+? 匹配换行符以外的字符至少一次 拒绝贪婪# + .*? 匹配换行符以外的字符任意次 拒绝贪婪res = re.search('<b>.+?</b>', '<b>b标签</b><b>b标签</b>')
res = re.search('<b>.*?</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>')6,正则用到的方法
1,re.search
# re.search# 返回第一个匹配的结果res = re.search('a','abcdef343') # 返回一个对象 <re.Match object; span=(0, 1), match='a'># 匹配上才可以用group拿结果,不然的话匹配不上返回None,不能用group,要不然会报错print(res.group()) # a2,re.match
res = re.match('\d{2}','123')
print(res.group())
#match函数# match 必须第一位就开始匹配 否则匹配失败# 给当前匹配到的结果起别名s = '3G4HFD567'x = re.match("(?P<value>\d+)",s)
print(x.group(0)) # 3print(x.group('value')) # 33,re.findall
# findallstr = '<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>'res = re.findall('<br>.*?</br>',str) # ['<br>加粗1</br>', '<br>加粗2</br>', '<br>加粗3</br>', '<br></br>']res = re.findall('<br>.*</br>',str) # ['<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>']res = re.findall('<br>.+?</br>',str) # ['<br>加粗1</br>', '<br>加粗2</br>', '<br>加粗3</br>']res = re.findall('<br>.+</br>',str) # ['<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>']Str = '''<a href="http://www.baidu.com">百度</a><A href="https://www.taobao.com">淘宝</A><a href="https://www.sina.com">新浪</a>'''# 1,匹配出所有小写a的超链接print(re.findall('<a href=".*?">.*?</a>',Str))
# ['<a href="http://www.baidu.com">百度</a>']# .*? 匹配任意字符任意次,拒绝贪婪print(re.findall('<a href=".*?">.*?</a>',Str,flags=re.S))
# ['<a href="http://www.baidu.com">百度</a>', '<a href="https://www.sina.com">新\n浪</a>']# 2,匹配所有小写a或者大写A的超链接print(re.findall('<[aA] href=".*?">.*?</[aA]>',Str,flags=re.S))
# ['<a href="http://www.baidu.com">百度</a>', '<A href="https://www.taobao.com">淘宝</A>', '<a href="https://www.sina.com">新\n浪</a>']# 用 re.I 匹配大小写字符 re.S 可以匹配换行符print(re.findall('<a href=".*?">.*?</a>',Str,flags=re.S | re.I))
# ['<a href="http://www.baidu.com">百度</a>', '<A href="https://www.taobao.com">淘宝</A>', '<a href="https://www.sina.com">新\n浪</a>']# 3,获取网址和名称 () 给谁加括号,谁就返回print(re.findall('(<a href="(.*?)">(.*?)</a>)',Str,flags=re.S | re.I))
# [('<a href="http://www.baidu.com">百度</a>', 'http://www.baidu.com', '百度'), ('<A href="https://www.taobao.com">淘宝</A>', 'https://www.taobao.com', '淘宝'), ('<a href="https://www.sina.com">新\n浪</a>', 'https://www.sina.com', '新\n浪')]4,finditer
res = re.finditer('[a-z]','asdjgedksa43g')
print(res) # 返回一个迭代器 <callable_iterator object at 0x0000015D8995F790>print(next(res)) # <re.Match object; span=(0, 1), match='a'> 是一个对象,用group来取值print(next(res).group()) # sfor i in res:
print(i) # 返回一个迭代器 <callable_iterator object at 0x0000015D8995F790> print(i.group()) # 返回结果5,group 和 groups 区别
# group 取第一个匹配的print(re.search("<b>.*?</b>","<b>加粗</b>").group()) # <b>加粗</b>print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group()) # <b>加粗</b>print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(0)) # <b>加粗</b>print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(1)) # 加粗# print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(2)) # 报错print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group('val')) # 加粗# goups 返回所有括号中的值print(re.search("<a href='(.*?)'>(.*?)</a>","<a href='www.baidu.com'>百度</a>").groups()) # ('www.baidu.com', '百度')6,re.split 正则拆分
print(re.split('\d','fdas3fedsa5fd45fdsa34fg4')) # ['fdas', 'fedsa', 'fd', '', 'fdsa', '', 'fg', '']7,re.sub 正则替换
print(re.sub('\d','---','ab1fdsa456fdsa34fds65as35')) # ab---fdsa---------fdsa------fds------as------8,re.compile
# compile函数re_phone = re.compile(r"(0\d{2,3}-\d{7,8})")
s1 = "lucky's phone is 010-88888888"s2 = "kaige's phone is 010-99999999"ret1 = re_phone.search(s1)
ret2 = re_phone.search(s2)2,xpath解析
2-1 导入使用
#导入和使用from lxml import etree
html_tree = etree.HTML(html字符串)
html_tree.xpath()
# 使用xpath路径查询信息,返回一个列表from lxml import etree
# 第一种方式parse = etree.HTMLParser(encoding='UTF-8')
tree = etree.parse('./素材/豆瓣.html', parser=parse)
print(tree)
# 第二种 推荐data = open('./素材/豆瓣.html', 'r', encoding='UTF-8').read()
tree = etree.HTML(data)
print(tree)2-2 xpath 的使用
2-2-1 特定路径匹配
# 找登陆 获取当前路径下的所有匹配的aa_list = tree.xpath('/html/body/div/div/div/a')
for a in a_list:
print(a) # <Element a at 0x2bdd868bc00> print(a.text) # 登录 (获取文本) ## 想要把节点转换成看得懂的字符串标签数据 print(etree.tostring(a, encoding='UTF-8').decode('UTF-8'))
# <a href="https://www.douban.com/accounts/login?source=book" class="nav-login" rel="nofollow">登录</a>a_list = tree.xpath('/html/body/div/div/div/a')
a_list = tree.xpath('/html/body/div/div/div/div/p/text()')
a_list = tree.xpath('/html/body/div/div/div[1]/a[1]/text()')
a_list = tree.xpath('/html/body/div/div/div//a/text()')2-2-2 获取当前路径下的文本
# 第一种a_list = tree.xpath('/html/body/div/div/div/a/text()')
# 第二种a_list = tree.xpath('/html/body/div/div/div/a')
for a in a_list:
print(a.text) #(获取文本)2-2-3 //
# 不考虑当前所在位置# 我想获取当前对象里所有的aa_list = tree.xpath('//a')
a_list = tree.xpath('//a/text()')2-2-4 获取属性
# 获取img的src属性值img_src = tree.xpath('//ul/li/a/img/@src')
img_src = tree.xpath('//ul/li//a/img/@src')2-2-5 添加条件
# 添加class条件img_src = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')
img_src = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div[@class="detail-frame"]/h2/a/text()')2-2-6 位置查找
# 获取第一个lili = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[1]/div/h2/a/text()')
# 获取第二个lili = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[2]/div/h2/a/text()')
# 获取最后一个lili = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[last()]/div/h2/a/text()')
# 获取倒数第二个lili = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[last()-1]/div/h2/a/text()')
# 获取前俩个lili = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[position()<3]/div/h2/a/text()')
# 可以使用列表切片解决啊li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')[0]
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')[0:2]2-2-7 ./ .//
# 一个完整的xpath路径,但是可以拆分li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')
# 先匹配到li 再继续往下匹配li_list = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li')
for li in li_list:
print(li) # xpath 对象 # 从当前位置向下匹配 print(li.xpath('./div/h2/a/text()'))
print(li.xpath('.//div/h2/a/text()'))2-2-8 属性(一般用不到)
# 获取ul的class属性为cover-col-4 clearfix的ul下面的儿子lili_list = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li')
# 选取所有ul具有class属性的节点li_list = tree.xpath('//ul[@class]')
# 获取所有ul具有aa属性的节点li_list = tree.xpath('//ul[@aa]')2-2-9 多条件 and or |
# 多个条件 and orprint(tree.xpath('//div[@id="db-global-nav"]'))
print(tree.xpath('//div[@class="global-nav"]'))
# 获取同时满足id为db-global-nav class为global-nav 的ulprint(tree.xpath('//div[@class="global-nav" and @id="db-global-nav"]'))
# 获取满足id为db-global-nav 或 class为global-nav 的ulprint(tree.xpath('//div[@class="global-nav" or @id="db-global-nav"]'))
# |print(tree.xpath('//div[@id="db-global-nav"] | //div[@class="global-nav"]'))2-3 xpath语法
2-3-1 路径表达式
| 路径表达式 | 结果 |
|---|---|
| /ul/li[1] | 选取属于 ul子元素的第一个 li元素。 |
| /ul/li[last()] | 选取属于 ul子元素的最后一个 li元素。 |
| /ul/li[last()-1] | 选取属于 ul子元素的倒数第二个 li元素。 |
| //ul/li[position()❤️] | 选取最前面的两个属于 ul元素的子元素的 li元素。 |
| //a[@title] | 选取所有拥有名为 title的属性的 a元素。 |
| //a[@title='xx'] | 选取所有 a元素,且这些元素拥有值为 xx的 title属性。 |
2-3-2 选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
| 通配符 | 描述 |
|---|---|
| * | 匹配任何元素节点。 一般用于浏览器copy xpath会出现 |
| @* | 匹配任何属性节点。 |
| node() | 匹配任何类型的节点。 |
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
| 路径表达式 | 结果 |
|---|---|
| /ul/* | 选取 ul元素的所有子元素。 |
| //* | 选取文档中的所有元素。 |
| //title[@*] | 选取所有带有属性的 title 元素。 |
| //node() | 获取所有节点 |
选取未知节点
| 路径表达式 | 结果 |
|---|---|
| //book/title | //book/price |
| //title | //price |
| /bookstore/book/title | //price |
2-3-3 逻辑运算
查找所有id属性等于head并且class属性等于s_down的div标签
//div[@id="head" and @class="s_down"]选取文档中的所有 title 和 price 元素。
//title | //price注意: “|”两边必须是完整的xpath路径
2-3-4 属性查询
查找所有包含id属性的div节点
//div[@id]查找所有id属性等于maincontent的div标签
//div[@id="maincontent"]查找所有的class属性
//@class//@attrName
//li[@name="xx"]//text() # 获取li标签name为xx的里面的文本内容获取第几个标签 索引从1开始
tree.xpath('//li[1]/a/text()') # 获取第一个tree.xpath('//li[last()]/a/text()') # 获取最后一个tree.xpath('//li[last()-1]/a/text()') # 获取倒数第二个
2-3-5 内容查询
查找所有div标签下的直接子节点h1的内容
//div/h1/text()属性值获取
//div/a/@href 获取a里面的href属性值获取所有
//* #获取所有//*[@class="xx"] #获取所有class为xx的标签获取节点内容转换成字符串
c = tree.xpath('//li/a')[0] result=etree.tostring(c, encoding='utf-8') print(result.decode('UTF-8'))
3,bs4解析
1,导入和使用
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
# html进行美化print(soup.prettify())
# 可以传入一段字符串或一个文件句柄.from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html>data</html>", 'lxml')
# beautifulsoup lxmlfrom bs4 import BeautifulSoup
f = open('./素材/豆瓣.html', 'r', encoding='UTF-8')
data = f.read()
# 第一种方式 建议使用这种soup = BeautifulSoup(data, 'lxml')
# 第二种方式()soup = BeautifulSoup(open('./素材/豆瓣.html', 'r', encoding='UTF-8'), 'lxml')
print(soup)
print(type(soup))2,浏览器结构化数据
2-1 .语法 soup对象.标签名
## .标签和.find 只获取第一个soup.title # 获取标签title# <title>The Dormouse's story</title>soup.title.name # 获取标签名称# 'title'soup.title.string # 获取标签title内的内容# 'The Dormouse's story'soup.title.parent # 获取父级标签soup.title.parent.name # 获取父级标签名称# 'head'print(soup.title)
print(soup.div)
print(soup.a)
print(soup.img)
print(soup.abc) # 不存在则为None2-2 soup.find
# find 上面的.标签名 是当前find的简写 find可以给条件 .标签和.find 只获取第一个print(soup.find('title'))
print(soup.find('div'))
# 获取soup对象中的第一个img标签print(soup.img)
print(soup.find('img'))2-3 获取属性
.语法或者find都只获取第一个
print(soup.div.attrs) # {'id': 'db-global-nav', 'class': ['global-nav']}print(soup.div.attrs['id']) # db-global-navprint(soup.div.attrs['class']) # ['global-nav']print(soup.div['id']) # db-global-navprint(soup.div['class']) # ['global-nav']2-4 find 条件查找
print(soup.find('a', class_="cover")) # 查找第一个class为cover的a标签print(soup.find('p', class_="rating"))
print(soup.find('div', id="wrapper"))
print(soup.find('div', id="db-global-nav", class_="global-nav"))
## 可以用字典的形式查找满足多个属性的标签print(soup.find('div', attrs={'id': "db-global-nav", 'class': "global-nav"}))
# class为多个的中间空格隔开就行print(soup.find('ul', attrs={'class': "cover-col-4 clearfix"}))
print(soup.find('ul', attrs={'class': "caover-col-4 clearfix"}))2-5 find 和 .语法组合使用
print(soup.find('a', class_="cover"))
print(type(soup.find('a', class_="cover"))) # <class 'bs4.element.Tag'> bs4对象才可以用组合使用print(soup.find('a', class_="cover").find('img')) # 获取第一个a标签里的第一个imgprint(soup.find('a', class_="cover").img) # 获取第一个a标签里的第一个imgprint(soup.find('a', class_="cover").img.attrs) # {'src': 'https://img3.doubanio.com/mpic/s29535271.jpg'}print(soup.find('a', class_="cover").img.attrs['src']) # https://img3.doubanio.com/mpic/s29535271.jpgprint(soup.find('a', class_="cover").img['src']) # https://img3.doubanio.com/mpic/s29535271.jpg2-6 写入到文件
# 写入本地需要注意的点with open('img.html', 'w', encoding='UTF-8') as f:
f.write(str(soup.find('a', class_="cover").img)) ## 写入本地需要先转换为字符串,要不然会报错 # f.write(soup.title.string)2-7 获取文本
print(soup.title) # <title>新书速递</title>print(soup.title.string) # 新书速递print(type(soup.title.string)) # <class 'bs4.element.NavigableString'>print(soup.title.strings) # generator 对象print(list(soup.title.strings)) # ['新书速递']print(soup.title.text) # 新书速递print(soup.title.get_text()) # 新书速递print(soup.title.stripped_strings) # generator 对象print(list(soup.title.stripped_strings)) # ['新书速递']2-8 多层嵌套标签
## string 和 strings 区别print(soup.find('div', class_="detail-frame"))
print(soup.find('div', class_="detail-frame").string) # Noneprint(soup.find('div', class_="detail-frame").strings) # generator 对象print(list(soup.find('div', class_="detail-frame").strings)) # 获取子子孙孙的文本 生成器返回# text , get_text() , stripped_strings 区别print(soup.find('div', class_="detail-frame").text) # 返回所有文本字符串 包含非打印字符print(soup.find('div', class_="detail-frame").get_text()) #和text一样 返回所有文本字符串 包含非打印字符print(list(soup.find('div', class_="detail-frame").stripped_strings)) # 返回所有去除空白字符后的文本2-9 prettify 美化
print(soup.find('div', class_="detail-frame"))
print(soup.find('div', class_="detail-frame").prettify())2-10 find_all
# 查找所有 和find区别就是 查找所有 参数一样使用 find返回一个 find_all返回列表print(soup.find_all('img')) # 返回列表print(soup.find_all('div', id="db-global-nav", class_="global-nav"))
print(soup.find_all('div', attrs={'id': "db-global-nav", 'class': "global-nav"}))
print(soup.find_all('div', limit=2)) # 取几个值 没啥用(我们用切片就完事了)print(soup.find_all(['h2', 'img'])) # 获取h2和img标签2-11 select
# select 查找所有 条件是选择器print(soup.select('img'))
print(soup.select('.cover'))
print(soup.select('#db-global-nav'))
print(soup.select('.cover-col-4.clearfix'))
print(soup.select('.cover-col-4.clearfix#abc'))
print(soup.select('ul[class="cover-col-4 clearfix"]'))
print(soup.select('.cover-col-4.clearfix > li img'))