开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情
网页解析
一,BeautifulSoup
- 安装:
pip install beautifulsoup4 - 官方文档:www.crummy.com/software/Be…
1.1,简单的例子
In [1]: html_doc = """
...: <!doctype html>
...: <html>
...: <head>
...: <meta charset="UTF-8">
...: <title>这是一个网页标题</title>
...: </head>
...: <body>
...: <p class="p1", id="pp1">今天真是个好日子</p>
...: <p class="p2", id="pp2">明天真是个好日子</p>
...: <script></script>
...: </body>
...: </html>
...: """
In [2]: soup = BeautifulSoup(html_doc) # 创建一个beautifulesoup的对象
In [3]: soup.title # 取 title 标签
Out[3]: <title>这是一个网页标题</title>
In [4]: soup.title.name # 取 title 标签的名字
Out[4]: 'title'
In [5]: soup.title.string # 取 title 标签里面的文本的内容
Out[5]: '这是一个网页标题'
In [6]: soup.title.parent # 取 title 标签的父级标签
Out[6]:
<head>
<meta charset="utf-8"/><!--国际编码 万国码-->
<title>这是一个网页标题</title>
</head>
In [7]: soup.p # 取第一个 p 标签
Out[7]: <p class="p1" id="pp1">今天真是个好日子</p>
In [8]: soup.p['class'] # 取第一个 p 标签 里的类名
Out[8]: ['p1']
In [9]: soup.a # 取第一个 a 标签
Out[9]: 今天真是个好日子
In [10]: soup.find_all('a') # 取所有的 a 标签
Out[10]: [今天真是个好日子, 今天真是个好日子]
In [11]: soup.find(id='pp1') # 取 id 为 pp1 的标签
Out[11]: <p class="p1" id="pp1">今天真是个好日子</p>
1.2,解析器
解析器在大规模的爬取中时会影响整个爬虫系统的速度的,所以推荐使用 lxml 速度快很多
-
xml需要单独安装:
python -m pip install lxml
-Tag对象
Beautifulsoup里面最重要的对象
soup = BeautifulSoup(html_doc, 'lxml') # 生成一个beautifulsoup对象,解析器用 lxml
p = soup.p
print(type(p)) # 判断该标签的类型
--> <class 'bs4.element.Tag'>
print(p.name) # 拿到该标签的标签名字
--> p
print(p['class']) # 拿到单个 p 标签下的 class 名
--> ['p1']
print(p['id']) # 拿到单个 p 标签下的 id 名
--> pp1
print(p.get_text()) # 拿到单个 p 标签下的所有文本内容
--> 今天真是个好日子
body = soup.body
print(body.get_text()) # 获取页面下所有的文本内容
--> 今天真是个好日子
今天真是个好日子
-contents和children
两个方法都可以把指定对象的直接子节点清洗出来(只要儿子),children返回迭代器节省内存
soup = BeautifulSoup(html_doc, 'lxml') # 生成一个beautifulsoup对象,解析器用 lxml
# 获取 body 节点下的所有子辈节点(回车也算子节点)
print(soup.body.contents)
--> ['\n', <p class="p1" id="pp1"><a href="http://www.baidu.com">今天真是个好日 子</a></p>, '\n', <p class="p2"
id="pp2"><a href="">明天是个好日子</a></p>, '\n', <script></script>, '\n']
# 返回一个迭代器,大量数据可以一部分一部分迭代出来处理(节省内存)
print(soup.body.children)
--> <list_iterator object at 0x0000026A9B9EEEF0>
-descendants
返回指定对象下的所有直节点(儿子孙子都要)
soup = BeautifulSoup(html_doc, 'lxml')
print(list(soup.body.descendants))
--> ['\n', <p class="p1" id="pp1"><a href="http://www.baidu.com">今天真是个好日子</a></p>, <a href="http://www.baidu.com">今天真是个好日子</a>, '今天真是个好日子', '\n', <p class="p2" id="pp2"><a href="">明天是个好日子</a></p>, <a href="">明天是个好日子</a>, '明天是个好日子', '\n', <script></script>, '\n']
-string和strings及stripped_strings
string只能返回单个标签下的文本信息,strings则能返回所有信息
print(soup.p.string)
--> 今天真是个好日子
print(soup.body.string)
--> None
print(list(soup.body.strings))
--> ['\n', '今天真是个好日子', '\n', '明天是个好日子', '\n', '\n']
print(list(soup.body.stripped_strings))
--> ['今天真是个好日子', '明天是个好日子']
-next/previous_sibling
# 获取 p 标签的下一个兄弟节点
print(soup.p.next_sibling)
# 获取 p 标签的上一个兄弟节点
print(soup.p.previous_sibling)
# 获取 p 标签的下面的所有兄弟节点
print(soup.p.next_sibling)
# 获取 p 标签的上面的所有兄弟节点
print(soup.p.previous_sibling)
-find_all()
# 获取文档中所有的 p 标签
print(soup.find_all('p'))
# 获取文档中所有的 p, a 标签
print(soup.find_all(['p', 'a']))
# 返回所有属性为 p1 的标签
print(soup.find_all(attrs={'class': 'p1'}))
# 返回文本内容是 text 的标签
print(soup.find_all('a', text='今天真是个好日子'))
# 限制查找范围,只会在 soup 对象的子代里面取找
print(soup.find_all('p', recursive=False))
# 结合正则,查找出所有已 b 字母开头的标签
import re
tags = soup.find_all(re.compile("^b"))
# 通过选择器查找
print(soup.select('p')) # 拿到所有的 p 标签
print(soup.select('p > a')) # 拿到所有的 p 标签下的 a 标签
二,XPath
在 XPath 中使用路径表达在 HTML/XML 文档中选取节点
from lxml import etree
page = etree.HTML(html_doc) # 返回 html 的节点
print(page.xpath('/html/body')) # 拿到body标签
print(page.xpath('//p')) # 拿到文档下的所有的 p 标签
print(page.xpath('.')) # 拿到当前的节点
print(page.xpath('..')) # 拿到当前节点的父节点
print(page.xpath('//p/@class')) # 获取所有p标签的class属性
谓语:用来查找某些特定节点或包含某个指定值的节点
| 路径表达式 | 结果 |
|---|---|
page.xpath('//p/a[1]') | 选取所有 p 标签下的各自的 第一个 a 标签 |
page.xpath('//p/a[last()]') | 选取所有 p 标签下各自的 最后一个 a 标签 |
page.xpath('//p/a[last()-1]') | 选取所有 p 标签下各自的 倒数第二个 a 标签 |
page.xpath('//p/a[position()<2]') | 选取所有 p 标签下各自的位置小于 2 的 a 标签 |
page.xpath('//p[@class]' | 选取所有拥有 class 类名的 p 标签 |
page.xpath('//p[@class="p1"]') | 选取所有拥有 class 类名为 p1 的标签 |
page.xpath('//p[a>19]') | 选取 p 标签里的 a 标签文本值大于 19 的该 p 标签 |
page.xpath('//p[a>19]/a') | 选取 p 标签里的 a 标签文本值大于 19 的该 p 标签下的 a 标签 |
选取未知节点,XPath通配符(*)可以选取
| 通配符 | 路径表达 | 结果 |
|---|---|---|
| * | page.xpath('//p/*') | 选取所有 p 标签下的所有标签 |
| //* | page.xpath('//*') | 选取文档中的所有的标签 |
| @* | page.xpath('//p[@*]')) | 选取所有拥有属性的 p 标签 |
文本的选取
路径表达式 | 结果 | |
|---|---|---|
| `page.xpath('//a | //p')` | 选取所有的 a 标签和 p 标签 |
page.xpath('//a/text()') | 只能获取 a 标签下的文本(只包括儿子) | |
page.xpath('string(//p)') | 选取所有的 p 标签下的文本(后代也包括) |
模糊匹配
| 路径表达式 | 结果 |
|---|---|
//div[contains(@class, "only")] | 匹配出 class 属性中带有 only 的 div |
//div[starts-with(@class, "only")] | 匹配出 class 属性中以 only 开头的 div |
response.xpath('//div[@class="c" and @id]') | 匹配出带有class='c'并且有id的div标签 |
案例一:使用beautifulsoup提取网页
import requests
from bs4 import BeautifulSoup
page_url = 'http://www.quanshuwang.com/book/106/106281/27857005.html'
response = requests.get(page_url)
response.encoding = 'gbk'
html = response.text
soup = BeautifulSoup(html, 'lxml')
div = soup.find_all('div', attrs={'class': 'mainContenr', 'id': 'content'})[0]
context = ''.join([x.strip() for x in div.stripped_strings][1:-1])
print(context)