整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

从0学爬虫-页面解析的几种方法

从0学爬虫-页面解析的几种方法

话不多说,直接进入正题。

一。首先是xpath。

1.官方的来讲就是能在xml树状结构中寻找节点 xpath用于在xml文档中通过元素和属性进行导航和定位。

2.解析网页,对于一些网页结构比较明显的我们一般都采用xpath去进行解析。

3.快速入门

from lxml import etree
# html网页源代码
html="""
        <div>
            <ul>
                 <li class="item-0"><a href="link1.html">first item</a></li>
                 <li class="item-1"><a href="link2.html">second item</a></li>
                 <li class="item-inactive"><a href="link3.html">third item</a></li>
                 <li class="item-1"><a href="link4.html">fourth item</a></li>
                 <li class="item-0"><a href="link5.html">fifth item</a>
             </ul>
         </div>
        """
# 将html转成xml
element=etree.HTML(html)
# print(element)
# 获取li标签下面的a标签的href属性值
links=element.xpath('//ul/li/a/@href')
print(links)  # 列表
# 获取li标签下面的a标签的文本数据
titles=element.xpath('//ul/li/a/text()')
print(titles)

结果如下:

二。BS4

BS4也是一种解析方式,原理基本类似xpath。两者相对比,xpath需要记住一些语法,bs4则只需要记住一些方法就可以了,掌握Beautifulsoup这个核心类就ok了

● 安装

○ pip install lxml

○ pip install bs4 bs4这个库对我们的lxml存在依赖

详细使用可以参考中文文档地址:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/

一般常用的find_all和find就能应对大部分情况。

import requests
from bs4 import BeautifulSoup
#解析
soup=BeautifulSoup(text,'html5lib') #这里一般采用 lxml ,也可以使用html5lib
#获取单个标签
aa=soup.find('div', id="container")
#获取标签下所有的下一级标签
list=aa.find_all('div', class_="box picblock col3")

三。urllib

保存图片很好用,其他情况使用不多。

获取请求示例:

import urllib.request
url='需要访问的网页地址'
headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 1 创建req请求对象并构建headers
req=urllib.request.Request(url,headers=headers)
print(req)  # <urllib.request.Request object at 0x0000022D35B9BB88>
# 2 获取响应数据
response=urllib.request.urlopen(req)
# 3 读取响应内容
html=response.read().decode('utf-8')
print(html)

保存图片示例:

是入门理论的最后一部分,完成了这一步就可以说是完成了一个简单的爬虫。用来解析响应中的Html文件的Python模块由很多,解析方式也有所不同。据我观察beautifulsoup的使用应该比lxml更加普及(我没有做过系统的调查,这个结果是根据查找到的资料数量来的),但是如何选择还要看使用者自己的喜好。

beautifulsoup(bs4)

beautifulsoup(下称bs4)使用Python编写,使用相较于lxml较容易,但是效率也较低。不过bs4支持很多解析器,lxml也是其中一种。

1 解析器

上文说到bs4支持很多解析器,我们在此列出:

-Python自带的标准库“html.parser”:因为是内置的库所以使用简单,但在Python2.7.3/3.2.2之前容错能力差

-lxml解析器“lxml”:速度快容错高,但是需要安装C语言库

-lxmlXML解析器“lxml-xml”/“xml”:速度快支持XML,也需要安装C语言库

-html5lib解析器“html5lib”:速度慢但会以浏览器的形式解析文档

至于bs4解析HTML文档只需要小小的一步:

from bs4 import BeautifulSoup
soup_html=BeautifulSoup(你的html文本,"解析器,也就是我刚刚介绍里的粗体部分,也可以不填让bs4自己选择")

2 四类对象

bs4会将html文本转换成一个树形结构,而结构里的对象有四种,分别是Tag(标签)、NavigableString(字符串)、BeautifulSoup和Comment(注释)。下面我们分别解释这四类对象在html文本中的含义。

-Tag:tag的意思就是标签,它与html文本中的标签同义。BeautifulSoup对象也可以被看做是一个Tag。Tag中属性的访问方法如同字典,比如访问Tag对象a的href属性的写法为a[‘href’]。

-NavigableString:html文本中所有的字符内容(就是除了标签之外被直接展示出来的文字)都是NavigableString。

-BeautifulSoup:它就是完整的一个html文本,可以被看做一个特殊的Tag。

-Comment:注释,也可以被当做NavigableString被.string访问到。

判断一个子节点对象类型的方法:type(soup_html)==bs4.element.对象名

3 筛选函数

在将html文本转换成对象树之后,bs4会提供一系列方法来查询它们。下面会列举出一些常用的方法。

-BeautifulSoup.prettify():得到格式化的标准缩进的html文本。

-Tag.Tagname:用.运算符会返回第一个标签名为Tagname的标签。

-Tag.name:返回此标签的名字(按照这个写法可能有点多余,但是如果是你不知道标签名的soup呢?)

-Tag.contents:获得所有子标签的列表。

-Tag.parents:获得所有父标签的列表。

-Tag.string:获得标签内部的文字。(只在标签内仅有一个NavigableString对象的时候返回值,否则为None)

-Tag.strings:获得所有文字列表。

-Tag.stripped_strings:获得去掉换行符和空格的文字列表。

-Tag.Tagname[‘Attrname’]:返回第一个Tagname标签的Attrname属性的值。

-Tag.find(attr=”attrname”[,”tagname”]):这个方法查找第一个符合条件的标签其属性attr值等于attrname。

-Tag.find_all(attr=”attrname”[,]):同上,不过返回的是所有符合条件的标签。对于find与find_all函数,当attr为class时需要写作class_,这是为了避开保留字。

lxml

相较于beautifulsoup而言lxml使用C语言编写(而且beautifulsoup一次要读取整个DOM),所以在运行效率和速度等方面lxml是当之无愧的优势。但是lxml采用XPath解析HTML和XML文件,所以如果要用lxml对HTML进行解析还要先学习XPath。

1 XPath

XPath全称为XML Path Language,也就是XML路径(解析)语言。顾名思义它最初被设计用于XML文件的搜索,但是现在也支持HTML。

学习XPath的成本(初步运用)应与学习正则表达式类似。(这是个人观点)

2 XPath基本常用语法

关于XPath的概念等我们限于篇幅就不展示了,请自行寻找资料学习。以下列出的是在爬虫领域较为常用的语法。

-nodename:选取nodename节点的所有子节点

-/:选取根节点

-//:从当前节点选取任意位置节点

-.:选取当前节点

-..:选取当前节点的父节点

-@:选取属性

-[]:XPath中的“谓语”,也就是查找条件

-*:匹配任何元素节点

-@*:匹配任何属性节点

-node():匹配任何节点

-text():获取文本内容

3 lxml解析html

from lxml import etree
html_lxml=etree.HTML(html) #将html文本变成lxml的对象
html_lxml.xpath("XPath表达式") #用XPath搜索对象

4 例 XPath的运用

以bs4官网教程里的一段html(爱丽丝文档)为例,我将写出这段文本里部分元素节点的XPath以供读者参考。你也可以使用chrome的开发者工具->copy->copyXPath获得页面中标签的XPath。

义上讲,爬虫只负责抓取,也就是下载网页。而实际上,爬虫还要负责从下载的网页中提取我们想要的数据,即对非结构化的数据(网页)进行解析提取出结构化的数据(有用数据)。

所以说,网页下载下来只是第一步,还有重要的一步就是数据提取。不同的爬虫想要的数据不一样,提取的数据也就不一样,但提取方法都是类似的。

最简单的提取数据的方法,就是使用正则表达式,此种方法简单,提取的逻辑也不能复杂,不然写出的正则表达式就晦涩难懂,甚至不能提取复杂的数据结构。

最终,老猿经过多年的使用经验,选择了lxml和xpath来解析网页提取结构化数据。顺便说一下 BeautifulSoup,它也是一个很棒的解析HTML的工具,可以使用多个解析器,比如Python标准库的parser,但是速度比较慢,也可以使用lxml作为解析器,但是它的使用方法、API跟lxml不太一样。使用下来,还是lxml的API更舒服。

lxml 对C语言库 libxml2和 libxslt进行绑定,提供了Pythonic的API,它有一些主要特点:

  • 支持标准的XML
  • 支持(损坏)的HTML
  • 非常快的解析速度
  • Pythonic的API更易于使用
  • 使用Python的unicode字符串
  • 内存安全(没有段错误)
  • 不需要手动管理内存

总结为一句话就是,C语言的速度和Python的简易相结合的神器。

lxml有两大部分,分别支持XML和HTML的解析:

  • lxml.etree 解析XML
  • lxml.html 解析html

lxml.etree可以用来解析RSS feed,它就是一个XML格式的文档。然而爬虫抓取的绝大部分都是html网页,所以,我们这里主要讲述lxml.html解析网页的方法。

lxml.html 从html字符串生成文档树结构

我们下载得到的网页就是一串html字符串,如何把它输入给lxml.html模块,从而生成html文档的树结构呢?

该模块提供了几种不同的方法:

  • parse(filename_url_or_file):
  • 输入的是一个文件名、URL或文件对象(有read()方法)。
  • document_fromstring(string):
  • 输入的是一个html的字符串,创建一个HTML文档树结构,它的根节点就是, 和 子节点。
  • fragment_fromstring(string, create_parent=False):
  • 返回输入字符串的HTML片段。这个片段壁纸只含有一个element(元素),也就是单一节点,除非给出了create_parent 参数,否则会报错。
  • fragments_fromstring(string):
  • 返回包含输入字符串中所有片段的列表。
  • fromstring(string):
  • 返回值依据输入字符串而定,如果输入看起来像是一个文档,则返回document_fromstring(string),如果是一个单一片段,则返回fragment_fromstring(string)。

下面我们通过具体示例来说明上面几个方法的不同。

document_fromstring 的使用方法

In [1]: import lxml.html as lh
In [2]: z=lh.document_fromstring('<span>abc</span><span>xyz</span>')
# 可以看到,它自动加了根节点<html>
In [3]: z
Out[3]: <Element html at 0x7fc410667b88>
In [4]: z.tag
Out[4]: 'html'
# 还加了<body>节点
In [5]: z.getchildren()
Out[5]: [<Element body at 0x7fc4101a3ae8>]
# 把字符串的两个节点放在了<body>里面
In [6]: z.getchildren()[0].getchildren()
Out[6]: [<Element span at 0x7fc410092bd8>, <Element span at 0x7fc410667c28>]

fragment_fromstring 的使用

In [11]: z=lh.fragment_fromstring(‘<div>abc</div><div>xyz</div>’)
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
<ipython-input-11-a11f9a0f71d1> in <module>()
----> 1 z=lh.fragment_fromstring(‘<div>abc</div><div>xyz</div>’)
~/.virtualenvs/py3.6/lib/python3.6/site-packages/lxml/html/__init__.py in fragment_fromstring(html, create_parent, base_url, parser, **kw)
 850 raise etree.ParserError(
 851 “Multiple elements found (%s)”
--> 852 % ‘, ‘.join([_element_name(e) for e in elements]))
 853 el=elements[0]
 854 if el.tail and el.tail.strip():
ParserError: Multiple elements found (div, div)
# 可以看到,输入是两个节点(element)时就会报错
# 如果加上 create_parent 参数,就没问题了
In [12]: z=lh.fragment_fromstring('<div>abc</div><div>xyz</div>', create_parent='p')
In [13]: z.tag
Out[13]: 'p'
In [14]: z.getchildren()
Out[14]: [<Element div at 0x7fc40a41a818>, <Element div at 0x7fc40a41aea8>]

fragments_fromstring 的使用

# 输入字符串含有一个节点,则返回包含这一个节点的列表
In [17]: lh.fragments_fromstring('<div>abc</div>')
Out[17]: [<Element div at 0x7fc40a124ea8>]
# 输入字符串含有多个节点,则返回包含这多个节点的列表
In [18]: lh.fragments_fromstring('<div>abc</div><div>xyz</div>')
Out[18]: [<Element div at 0x7fc40a124b88>, <Element div at 0x7fc40a124f98>]

fromstring 的使用

In [27]: z=lh.fromstring('<div>abc</div><div>xyz</div>')
In [28]: z
Out[28]: <Element div at 0x7fc40a0eb368>
In [29]: z.getchildren()
Out[29]: [<Element div at 0x7fc410135548>, <Element div at 0x7fc40a0eb2c8>]
In [30]: type(z)
Out[30]: lxml.html.HtmlElement

这里,fromstring输入的如果是多个节点,它会给加一个父节点并返回。但是像html网页都是从节点开始的,我们使用fromstring() 和 document_fromstring() 都可以得到完整的网页结构。

从上面代码中我们可以看到,那几个函数返回的都是HtmlElement对象,也就是说,我们已经学会了如何从html字符串得到HtmlElement的对象,下一节我们将学习如何操作HtmlElement对象,从中提取我们感兴趣的数据。