python爬虫(5):网页解析

103 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

网页解析

一,BeautifulSoup

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,解析器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAuTSWnO-1591103066862)(assets/1560061715348.png)]

解析器在大规模的爬取中时会影响整个爬虫系统的速度的,所以推荐使用 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 文档中选取节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uJy3fWXF-1591103066865)(assets/1560130239810.png)]

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)