杨记

碎片化学习令人焦虑,系统化学习使人进步

0%

数据提取

re正则解析,用于提取字符串

bs4 全名 BeautifulSoup,是编写 python 爬虫常用库之一,主要用来解析 html 标签

xpath解析

RE正则

re Regular Expression

反斜杠不仅可以把特殊符号变成普通符号,还可以把善通符号变成特殊符号。例如“n”只是一个善通的字母,但是“n”代表换行符; 在正则中是通配符,\ 就是普通的 *

findall

Python的正则表达式模块包含一个findall方法,它能够以列表的形式返回所有满足要求的字符串。

findall的函数原型为:

re.findall(pattern, string, flags=0)

pattern表示正则表达式,string表示原来的字符串,flags表示一些特殊功能的标志。

findall的结果是一个列表,包含了所有的匹配到的结果。如果没有匹配到结果,就会返回空列表

如果包含多个(.*? ),返回的仍然是一个列表,但是列表里面的元素变为了元组

flags参数。这个参数是可以省略的。当不省略的时候,具有一些辅助功能,例如忽略大小写、忽略换行符等。使用re.S作为flag来忽略换行符

search

search()的用法和findall()的用法一样,但是search()只会返回第1个满足要求的字符串。一旦找到符合要求的内容,它就会停止查找。对于从超级大的文本里面只找第1个数据特别有用,可以大大提高程序的运行效率。

search()的函数原型为:

re.search(pattern, string, flags=0)

对于结果,如果匹配成功,则是一个正则表达式的对象;如果没有匹配到任何数据,就是None。如果需要得到匹配到的结果,则需要通过.group()这个方法来获取里面的值

只有在.group()里面的参数为1的时候,才会把正则表达式里面的括号中的结果打印出来

group()的参数最大不能超过正则表达式里面括号的个数。参数为1表示读取第1个括号中的内容,参数为2表示读取第2个括号中的内容,以此类推

Python 3中正则表达式模块的源代码的入口文件为re.py。这个文件里面的注释就是学习Python正则表达式模块非常好的文档,它包含了正则表达式各种符号的简单说明和这个模块内部各个方法的使用。

re.py在Python 3安装文件夹下面的Lib文件夹中。

正则的语法: 使用元字符进行排列组合用来匹配字符串 在线测试正则表达式https://tool.oschina.net/regex/

元字符: 具有固定含义的特殊符号 常用元字符:(下面的[]{}在使用时注意转义问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.			匹配 除换行以外的任意字符
\w 匹配 字母、数字、下划线
\W 匹配 非字母或数字或下划线,\w取反
\s 匹配 任意的空白符,包括空格、制表符、换页符等等。等价于[ \f \n \r \t \v ]
\S 匹配 非空白符,\s补集
\d 匹配 数字
\D 匹配 非数字
\n 匹配 一个换行符
\t 匹配 一个制表符

^ 匹配 字符串的开始
$ 匹配 字符串的结尾
a|b 匹配 字符a或字符b
() 匹配 括号内的表达式,表示一个组
[] 匹配 字符组中的字符
[^...] 匹配除了字符组中的字符的所有字符

量词: 控制前面的元字符出现的次数

1
2
3
4
5
6
*			 重复>=0
+ 重复>=1
? 重复01
{n} 重复n次
{n,} 重复>=n次
{n,m} 重复n到m次
1
2
.*			贪婪匹配
.*? 惰性匹配(爬虫用的很多)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import re

# findall: 匹配字符串中所有的符合正则的内容,返回list
lst = re.findall(r"\d+", "我的电话号是:10086, 我女朋友的电话是:10010")
print(lst)

# finditer: 匹配字符串中所有的内容[返回的是迭代器], 从迭代器中拿到内容需要.group()
it = re.finditer(r"\d+", "我的电话号是:10086, 我女朋友的电话是:10010")
for i in it:
print(i.group())

# search, 找到一个结果就返回, 返回的结果是match对象. 拿数据需要.group()
s = re.search(r"\d+", "我的电话号是:10086, 我女朋友的电话是:10010")
print(s.group())


# match是从头开始匹配
s = re.match(r"\d+", "10086, 我女朋友的电话是:10010")
print(s.group())

# 预加载正则表达式 compile()
obj = re.compile(r"\d+")

ret = obj.finditer("我的电话号是:10086, 我女朋友的电话是:10010")
for it in ret:
print(it.group())

ret = obj.findall("呵呵哒, 我就不信你不换我1000000000")
print(ret)

s = """
<div class='jay'><span id='1'>郭麒麟</span></div>
<div class='jj'><span id='2'>宋铁</span></div>
<div class='jolin'><span id='3'>大聪明</span></div>
<div class='sylar'><span id='4'>范思哲</span></div>
<div class='tory'><span id='5'>胡说八道</span></div>
"""

# (?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取内容
obj = re.compile(r"<div class='.*?'><span id='(?P<id>\d+)'>(?P<wahaha>.*?)</span></div>", re.S) # re.S: 让.能匹配换行符

result = obj.finditer(s)
for it in result:
print(it.group("wahaha"))
print(it.group("id"))

beautifulsoup

简单使用

一点点HTML基础知识

1
2
3
<标签 属性="值" 属性="值">
被标记的内容
</标签>

安装bs4(清华源)

方法1:

1
pip install bs4 -i https://pypi.tuna.tsinghua.edu.cn/simple

方法2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 需要将pip源设置为国内源,阿里源、豆瓣源、网易源等
- windows
(1)打开文件资源管理器(文件夹地址栏中)
(2)地址栏上面输入 %appdata%
(3)在这里面新建一个文件夹 pip
(4)在pip文件夹里面新建一个文件叫做 pip.ini ,内容写如下即可
[global]
timeout = 6000
index-url = https://mirrors.aliyun.com/pypi/simple/
trusted-host = mirrors.aliyun.com
- linux
(1)cd ~
(2)mkdir ~/.pip
(3)vi ~/.pip/pip.conf
(4)编辑内容,和windows一模一样
- 需要安装:pip install bs4
bs4在使用时候需要一个第三方库,把这个库也安装一下
pip install lxml

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
使用流程:       
- 导包:from bs4 import BeautifulSoup
- 使用方式:可以将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容
(1)转化本地文件:
- soup = BeautifulSoup(open('本地文件'), 'lxml')
(2)转化网络文件:
- soup = BeautifulSoup('字符串类型或者字节类型', 'lxml')
(3)打印soup对象显示内容为html文件中的内容
基础巩固:
(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选择器返回永远是列表,需要通过下标提取指定的对象

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests
from bs4 import BeautifulSoup
#需求:爬取三国演义小说所有的章节标题和章节内容http://www.shicimingju.com/book/sanguoyanyi.html
if __name__ == "__main__":
#对首页的页面数据进行爬取
headers = {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}
url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=url, headers=headers).text

#在首页中解析出章节的标题和详情页的url
#1.实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
soup = BeautifulSoup(page_text, 'lxml')
#解析章节标题和详情页的url
li_list = soup.select('.book-mulu > ul > li')
fp = open('./sanguo.txt', 'w', encoding='utf-8')
for li in li_list:
title = li.a.string
detail_url = 'http://www.shicimingju.com' + li.a['href']
#对详情页发起请求,解析出章节内容
detail_page_text = requests.get(url=detail_url, headers=headers).text
#解析出详情页中相关的章节内容
detail_soup = BeautifulSoup(detail_page_text, 'lxml')
div_tag = detail_soup.find('div', class_='chapter_content')
#解析到了章节的内容
content = div_tag.text
fp.write(title + ':' + content + '\n')
print(title, '爬取成功!!!')

详细使用

【Python 库】bs4的使用 - 丹枫无迹 - 博客园 (cnblogs.com)

一、初始化

1
2
3
4
5
from bs4 import BeautifulSoup

soup = BeautifulSoup("<html>A Html Text</html>", "html.parser")

第一个参数是要解析的html文本,第二个参数是解析器
解析器 使用方法 优势
Python标准库 BeautifulSoup(html, “html.parser”) 1、Python的内置标准库
2、执行速度适中
3、文档容错能力强
lxml HTML BeautifulSoup(html, “lxml”) 1、速度快
2、文档容错能力强
lxml XML BeautifulSoup(html, [“lxml”, “xml”])
BeautifulSoup(html, “xml”)
1、速度快
2、唯一支持XML的解析器
html5lib BeautifulSoup(html, “html5lib”) 1、最好的容错性
2、以浏览器的方式解析文档
3、生成HTML5格式的文档

格式化输出

1
soup.prettify()  # prettify 有括号和没括号都可以

二、对象

  Beautfiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:tag,NavigableString,BeautifulSoup,Comment。

1、tag

  Tag对象与 xml 或 html 原生文档中的 tag 相同。

1
2
3
4
5
6
7
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')

tag = soup.b

type(tag)

# <class 'bs4.element.Tag'>

  如果不存在,则返回 None,如果存在多个,则返回第一个。

Name

  每个 tag 都有自己的名字

1
2
tag.name 
# 'b'

Attributes

  tag 的属性是一个字典

1
2
3
4
5
6
7
8
tag['class']
# 'boldest'

tag.attrs
# {'class': 'boldest'}

type(tag.attrs)
# <class 'dict'>

多值属性

  最常见的多值属性是class,多值属性的返回 list。

1
2
3
4
5
soup = BeautifulSoup('<p class="body strikeout"></p>')

print(soup.p['class']) # ['body', 'strikeout']

print(soup.p.attrs) # {'class': ['body', 'strikeout']}

  如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回。

1
2
soup = BeautifulSoup('<p id="my id"></p>', 'html.parser')
print(soup.p['id']) # 'my id'

Text

  text 属性返回 tag 的所有字符串连成的字符串。

其他方法

  tag.has_attr('id') # 返回 tag 是否包含 id 属性

  当然,以上代码还可以写成 ‘id’ in tag.attrs,之前说过,tag 的属性是一个字典。顺便提一下,has_key是老旧遗留的api,为了支持2.2之前的代码留下的。Python3已经删除了该函数。

2、NavigableString

  字符串常被包含在 tag 内,Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串。但是字符串中不能包含其他 tag。

1
2
3
4
5
6
7
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')

s = soup.b.string

print(s) # Extremely bold

print(type(s)) # <class 'bs4.element.NavigableString'>

3、BeautifulSoup

  BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象。但是 BeautifulSoup 对象并不是真正的 HTM L或 XML 的 tag,它没有attribute属性,name 属性是一个值为“[document]”的特殊属性。

4、Comment

  Comment 一般表示文档的注释部分。

1
2
3
4
5
6
7
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')

s = soup.b.string

print(s) # Extremely bold

print(type(s)) # <class 'bs4.element.NavigableString'>

三、遍历

1、子节点

contents 属性

  contents 属性返回所有子节点的列表,包括 NavigableString 类型节点。如果节点当中有换行符,会被当做是 NavigableString 类型节点而作为一个子节点。

  NavigableString 类型节点没有 contents 属性,因为没有子节点。

1
2
3
4
5
6
7
8
soup = BeautifulSoup("""<div>
<span>test</span>
</div>
""")

element = soup.div.contents

print(element) # ['\n', <span>test</span>, '\n']

children 属性

  children 属性跟 contents 属性基本一样,只不过返回的不是子节点列表,而是子节点的可迭代对象。

descendants 属性

  descendants 属性返回 tag 的所有子孙节点。

string 属性

  如果 tag 只有一个 NavigableString 类型子节点,那么这个 tag 可以使用 .string 得到子节点。

  如果一个 tag 仅有一个子节点,那么这个 tag 也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同。

  如果 tag 包含了多个子节点,tag 就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None。

1
2
3
4
5
6
7
8
9
10
soup = BeautifulSoup("""<div>
<p><span><b>test</b></span></p>
</div>
""")

element = soup.p.string

print(element) # test

print(type(element)) # <class 'bs4.element.NavigableString'>

  特别注意,为了清楚显示,一般我们会将 html 节点换行缩进显示,而在BeautifulSoup 中会被认为是一个 NavigableString 类型子节点,导致出错。上例中,如果改成 element = soup.div.string 就会出错。

strings 和 stripped_strings 属性

  如果 tag 中包含多个字符串,可以用 strings 属性来获取。如果返回结果中要去除空行,则可以用 stripped_strings 属性。

1
2
3
4
5
6
7
8
9
10
soup = BeautifulSoup("""<div>
<p> </p>
<p>test 1</p>
<p>test 2</p>
</div>
""", 'html.parser')

element = soup.div.stripped_strings

print(list(element)) # ['test 1', 'test 2']

2、父节点

parent 属性

  parent 属性返回某个元素(tag、NavigableString)的父节点,文档的顶层节点的父节点是 BeautifulSoup 对象,BeautifulSoup 对象的父节点是 None。

parents 属性

  parent 属性递归得到元素的所有父辈节点,包括 BeautifulSoup 对象。

3、兄弟节点

next_sibling 和 previous_sibling

  next_sibling 返回后一个兄弟节点,previous_sibling 返回前一个兄弟节点。直接看个例子,注意别被换行缩进搅了局。

1
2
3
4
5
6
7
8
9
soup = BeautifulSoup("""<div>
<p>test 1</p><b>test 2</b><h>test 3</h></div>
""", 'html.parser')

print(soup.b.next_sibling) # <h>test 3</h>

print(soup.b.previous_sibling) # <p>test 1</p>

print(soup.h.next_sibling) # None

next_siblings 和 previous_siblings

  next_siblings 返回后面的兄弟节点

  previous_siblings  返回前面的兄弟节点

4、回退和前进

  把html解析看成依次解析标签的一连串事件,BeautifulSoup 提供了重现解析器初始化过程的方法。

  next_element 属性指向解析过程中下一个被解析的对象(tag 或 NavigableString)。

  previous_element 属性指向解析过程中前一个被解析的对象。

  另外还有next_elements 和 previous_elements 属性,不赘述了。

四、搜索

1、过滤器

  介绍 find_all() 方法前,先介绍一下过滤器的类型,这些过滤器贯穿整个搜索的API。过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中。

示例使用的 html 文档如下:

1
2
3
4
5
6
7
8
9
10
11
html = """
<div>
<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></p>
</div>
"""

soup = BeautifulSoup(html, 'html.parser')

字符串

查找所有的标签

1
soup.find_all('b')  # [<b>The Dormouse's story</b>]

正则表达式

传入正则表达式作为参数,返回满足正则表达式的标签。下面例子中找出所有以b开头的标签。

1
soup.find_all(re.compile("^b"))  # [<b>The Dormouse's story</b>]

列表

传入列表参数,将返回与列表中任一元素匹配的内容。下面例子中找出所有\标签和\标签。

1
soup.find_all(["a", "b"])

True

True可以匹配任何值,下面的代码查找到所有的tag,但是不会返回字符串节点。

1
soup.find_all(True)

方法

如果没有合适过滤器,那么还可以自定义一个方法,方法只接受一个元素参数,如果这个方法返回True表示当前元素匹配被找到。下面示例返回所有包含 class 属性但不包含 id 属性的标签。

1
2
3
4
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')

print(soup.find_all(has_class_but_no_id))

返回结果:

1
2
3
4
[<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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a></p>]

这个结果乍一看不对,标签含有 id 属性,其实返回的 list 中只有2个元素,都是

标签,标签是

标签的子节点。

2、find 和 find_all

  搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件

语法:

  find(name=None, attrs={}, recursive=True, text=None, **kwargs)

  find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

参数:

  name:查找所有名字为 name 的 tag,字符串对象会被自动忽略掉。上面过滤器示例中的参数都是 name 参数。当然,其他参数中也可以使用过滤器。

  attrs:按属性名和值查找。传入字典,key 为属性名,value 为属性值。

  recursive:是否递归遍历所有子孙节点,默认 True。

  text:用于搜索字符串,会找到 .string 方法与 text 参数值相符的tag,通常配合正则表达式使用。也就是说,虽然参数名是 text,但实际上搜索的是 string 属性。

  limit:限定返回列表的最大个数。

  kwargs:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作 tag 的属性来搜索。这里注意,如果要按 class 属性搜索,因为 class 是 python 的保留字,需要写作 class_。

  Tag 的有些属性在搜索中不能作为 kwargs 参数使用,比如 html5 中的 data-* 属性。

1
2
3
4
5
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')

print(data_soup.find_all(data-foo="value"))

# SyntaxError: keyword can't be an expression

  但是可以通过 attrs 参数传递:

1
2
3
4
5
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')

print(data_soup.find_all(attrs={"data-foo": "value"}))

# [<div data-foo="value">foo!</div>]

  而按 class_ 查找时,只要一个CSS类名满足即可,如果写了多个CSS名称,那么顺序必须一致,而且不能跳跃。以下示例中,前三个可以查找到元素,后两个不可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
css_soup = BeautifulSoup('<p class="body bold strikeout"></p>')

print(css_soup.find_all("p", class_="strikeout"))

print(css_soup.find_all("p", class_="body"))

print(css_soup.find_all("p", class_="body bold strikeout"))

# [<p class="body strikeout"></p>]

print(css_soup.find_all("p", class_="body strikeout"))

print(css_soup.find_all("p", class_="strikeout body"))

# []

3、像调用find_all()一样调用tag

  find_all() 几乎是 BeautifulSoup 中最常用的搜索方法,所以我们定义了它的简写方法。BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:

1
2
soup.find_all('b')
soup('b')

4、其他搜索方法

find_parents()      返回所有祖先节点

find_parent()      返回直接父节点

find_next_siblings()   返回后面所有的兄弟节点

find_next_sibling()   返回后面的第一个兄弟节点

find_previous_siblings() 返回前面所有的兄弟节点

find_previous_sibling() 返回前面第一个兄弟节点

find_all_next()     返回节点后所有符合条件的节点

find_next()       返回节点后第一个符合条件的节点

find_all_previous()   返回节点前所有符合条件的节点

find_previous()     返回节点前所有符合条件的节点

五、CSS选择器

BeautifulSoup支持大部分的CSS选择器,这里直接用代码来演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from bs4 import BeautifulSoup

html = """
<html>
<head><title>标题</title></head>
<body>
<p class="title" name="dromouse"><b>标题</b></p>
<div name="divlink">
<p>
<a href="http://example.com/1" class="sister" id="link1">链接1</a>
<a href="http://example.com/2" class="sister" id="link2">链接2</a>
<a href="http://example.com/3" class="sister" id="link3">链接3</a>
</p>
</div>
<p></p>
<div name='dv2'></div>
</body>
</html>
"""

soup = BeautifulSoup(html, 'lxml')

# 通过tag查找
print(soup.select('title')) # [<title>标题</title>]

# 通过tag逐层查找
print(soup.select("html head title")) # [<title>标题</title>]

# 通过class查找
print(soup.select('.sister'))
# [<a class="sister" href="http://example.com/1" id="link1">链接1</a>,
# <a class="sister" href="http://example.com/2" id="link2">链接2</a>,
# <a class="sister" href="http://example.com/3" id="link3">链接3</a>]

# 通过id查找
print(soup.select('#link1, #link2'))
# [<a class="sister" href="http://example.com/1" id="link1">链接1</a>,
# <a class="sister" href="http://example.com/2" id="link2">链接2</a>]

# 组合查找
print(soup.select('p #link1'))    # [<a class="sister" href="http://example.com/1" id="link1">链接1</a>]

# 查找直接子标签
print(soup.select("head > title"))  # [<title>标题</title>]
print(soup.select("p > #link1"))   # [<a class="sister" href="http://example.com/1" id="link1">链接1</a>]

print(soup.select("p > a:nth-of-type(2)"))  # [<a class="sister" href="http://example.com/2" id="link2">链接2</a>]
# nth-of-type 是CSS选择器

# 查找兄弟节点(向后查找)
print(soup.select("#link1 ~ .sister"))
# [<a class="sister" href="http://example.com/2" id="link2">链接2</a>,
# <a class="sister" href="http://example.com/3" id="link3">链接3</a>]

print(soup.select("#link1 + .sister"))
# [<a class="sister" href="http://example.com/2" id="link2">链接2</a>]

# 通过属性查找
print(soup.select('a[href="http://example.com/1"]'))

# ^ 以XX开头
print(soup.select('a[href^="http://example.com/"]'))

# * 包含
print(soup.select('a[href*=".com/"]'))

# 查找包含指定属性的标签
print(soup.select('[name]'))

# 查找第一个元素
print(soup.select_one(".sister"))

Xpath

安装lxml模块

1
pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple

用法:

  • 将要解析的html内容构造出etree对象。
  • 使用etree对象的xpath()方法配合xpath表达式来完成对数据的提取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
属性定位:
#找到class属性值为song的div标签
//div[@class="song"]
层级&索引定位:
#找到class属性值为tangdiv的直系子标签ul下的第二个子标签li下的直系子标签a
//div[@class="tang"]/ul/li[2]/a
逻辑运算:
#找到href属性值为空且class属性值为dua标签
//a[@href="" and @class="du"]
模糊匹配:
//div[contains(@class, "ng")]
//div[starts-with(@class, "ta")]
取文本:
# /表示获取某个标签下的文本内容
# //表示获取某个标签下的文本内容和所有子标签下的文本内容
//div[@class="song"]/p[1]/text()
//div[@class="tang"]//text()
取属性:
//div[@class="tang"]//li[2]/a/@href

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from lxml import etree

xml = """
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="joy">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热热1</nick>
</div>
<span>
<nick>热热热热热2</nick>
</span>
</author>

<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>
"""

tree = etree.XML(xml)
result = tree.xpath("/book") # /表示层级关系. 第一个/是根节点
print(result)
result = tree.xpath("/book/name")
print(result)
result = tree.xpath("/book/name/text()") # text() 拿文本
print(result)
result = tree.xpath("/book/author//nick/text()") # // 后代
print(result)
result = tree.xpath("/book/author/*/nick/text()") # * 任意的节点. 通配符(会儿)
print(result)
result = tree.xpath("/book//nick/text()")
print(result)

# 输出
# [<Element book at 0x1ef1c91e2c0>]
# [<Element name at 0x1ef1c9c0e00>]
# ['野花遍地香']
# ['周大强', '周芷若', '周杰伦', '蔡依林', '热热热热热1', '热热热热热2']
# ['热热热热热1', '热热热热热2']
# ['臭豆腐', '周大强', '周芷若', '周杰伦', '蔡依林', '热热热热热1', '热热热热热2', '胖胖陈', '胖胖不陈']

b.html文件的内容,用于下个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉诚</div>
<div class="common">胡辣汤</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from lxml import etree

tree = etree.parse("b.html")
result = tree.xpath('/html')
result = tree.xpath("/html/body/ul/li/a/text()")
result = tree.xpath("/html/body/ul/li[1]/a/text()") # xpath的顺序是从1开始数的, []表示索引

result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()") # [@xxx=xxx] 属性的筛选

print(result)

ol_li_list = tree.xpath("/html/body/ol/li")

for li in ol_li_list:
# 从每一个li中提取到文字信息
result = li.xpath("./a/text()") # 在li中继续去寻找. 相对查找
print(result)
result2 = li.xpath("./a/@href") # 拿到属性值: @属性
print(result2)

print(tree.xpath("/html/body/ul/li/a/@href"))

print(tree.xpath('/html/body/div[1]/text()'))
print(tree.xpath('/html/body/ol/li/a/text()'))

# 输出
# ['大炮']
# ['飞机']
# ['feiji']
# ['大炮']
# ['dapao']
# ['火车']
# ['huoche']
# ['http://www.baidu.com', 'http://www.google.com', 'http://www.sogou.com']
# ['李嘉诚']
# ['飞机', '大炮', '火车']

注意:

xpath路径中不能有tbody

欢迎关注我的其它发布渠道