赞
踩
往期内容提要:
一般来讲对我们而言,需要抓取的是某个网站或者某个应用的内容,提取有用的价值。内容一般分为两部分,非结构化的数据 和 结构化的数据。
处理方式 | 非结构化数据 | 结构化数据 |
---|---|---|
正则表达式 | 文本、电话号码、邮箱地址、HTML 文件 | XML 文件 |
XPath | HTML 文件 | XML 文件 |
CSS选择器 | HTML 文件 | XML 文件 |
JSON Path | JSON 文件 | |
转化成Python类型 | JSON 文件(json类)、XML 文件(xmltodict) |
上一章节详细向大家介绍了正则表达式,有同学说,我正则用的不好,处理HTML文档很累,有没有其他的方法?有!那就是XPath,我们可以先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。
W3School官方文档:http://www.w3school.com.cn/xml/index.asp
数据格式 | 描述 | 设计目标 |
---|---|---|
XML | Extensible Markup Language (可扩展标记语言) | 被设计为传输和存储数据,其焦点是数据的内容。 |
HTML | HyperText Markup Language (超文本标记语言) | 显示数据以及如何更好显示数据。 |
HTML DOM | Document Object Model for HTML (文档对象模型) | 通过 HTML DOM,可以访问所有的 HTML 元素,连同它们所包含的文本和属性。可以对其中的内容进行修改和删除,同时也可以创建新的元素。 |
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book category="cooking"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> <price>30.00</price> </book> <book category="children"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> <book category="web"> <title lang="en">XQuery Kick Start</title> <author>James McGovern</author> <author>Per Bothner</author> <author>Kurt Cagle</author> <author>James Linn</author> <author>Vaidyanathan Nagarajan</author> <year>2003</year> <price>49.99</price> </book> <book category="web" cover="paperback"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> </bookstore>
HTML DOM 定义了访问和操作 HTML 文档的标准方法,以树结构方式表达 HTML 文档。
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
名称 | 含义 | 例子 |
---|---|---|
父(Parent) | 每个元素以及属性都有一个父 | book 元素是 title、author、year 以及 price 元素的父 |
子(Children) | 元素节点可有零个、一个或多个子 | title、author、year 以及 price 元素都是 book 元素的子 |
同胞(Sibling) | 拥有相同的父的节点 | title、author、year 以及 price 元素都是同胞 |
先辈(Ancestor) | 某节点的父、父的父,等等 | title 元素的先辈是 book 元素和 bookstore 元素 |
后代(Descendant) | 某个节点的子,子的子,等等 | bookstore 的后代是 book、title、author、year 及 price 元素 |
XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。
W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
这里以Chrome插件 XPath Helper为例,可以看到匹配到的标签会加载上class="xh-highlight"高光标签。初学者可以多加练习,结果会在右上方的黑色方框中回显,其中RESULTS 括号后的数字指匹配到的目标个数。
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
下面列出了最常用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号
中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang=’eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的值须大于 35.00的 book 元素的所有 title 元素。 |
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
html/node()/meta/@* | 选择html下面任意节点下的meta节点的所有属性 |
//title[@*] | 选取所有带有属性的 title 元素。 |
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
//title | //price | 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
下面列出了可用在 XPath 表达式中的运算符:
获取文本
a/text()
获取a下的文本a//text()
获取a下的所有标签的文本//a[text()='下一页']
选择文本为下一页三个字的a标签@符号
a/@href
获取a下的href ——>举一反三:a/@scr
获取a下的scr值//div[@id="detail-list"]
——>举一反三://*[@class="aa"]
定位任意class为aa的标签//
li//a
表示的是li下任何一个标签lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。
lxml python 官方文档:http://lxml.de/index.html
需要安装C语言库,可使用 pip 安装:
pip install lxml
(或通过wheel方式安装)
我们利用它来解析 HTML 代码,简单示例:
# lxml_test.py # 使用 lxml 的 etree 库 from lxml import etree text = ''' <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> #注意,此处缺少一个 </li> 闭合标签 </ul> </div> ''' #利用etree.HTML,将字符串解析为HTML文档 html = etree.HTML(text) # 按字符串序列化HTML文档 result = etree.tostring(html) print(result)
输出结果:
<html><body>
<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></li>
</ul>
</div>
</body></html>
lxml 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。
除了直接读取字符串,lxml还支持从文件里读取内容。我们新建一个hello.html文件:
<!-- hello.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"><span class="bold">third item</span></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></li>
</ul>
</div>
再利用 etree.parse() 方法来读取文件。
# lxml_parse.py
from lxml import etree
# 读取外部文件 hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
输出结果与之前相同:
<html><body>
<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></li>
</ul>
</div>
</body></html>
<!-- hello.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"><span class="bold">third item</span></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></li>
</ul>
</div>
<li>
标签# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
print type(html) # 显示etree.parse() 返回类型
result = html.xpath('//li')
print result # 打印<li>标签的元素集合
print len(result)
print type(result)
print type(result[0])
输出结果:
<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
5
<type 'list'>
<type 'lxml.etree._Element'>
hello.html
属性<!-- hello.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"><span class="bold">third item</span></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></li>
</ul>
</div>
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result1 = html.xpath('//li/@class') // 获取 <li> 标签的所有 class 属性 result2 = html.xpath('//li/a[@href="link1.html"]') //获取<li>标签下 href 为 link1.html 的 <a> 标签 result3 = html.xpath('//li//span') //获取<li> 标签下的所有 <span> 标签 (因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠) result4 = html.xpath('//li/a//@class') //获取 <li> 标签下的 <a> 标签里的所有 class result5 = html.xpath('//li[last()]/a/@href') //获取最后一个 <li> 的 <a> 的 href result6 = html.xpath('//li[last()-1]/a') //获取倒数第二个元素的内容 result7 = html.xpath('//*[@class="bold"]') //获取 class 值为 bold 的标签名 print result1 print result2 print result3 print result4 print result5 print result6 print result7
运行结果
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
[<Element a at 0x10ffaae18>]
[<Element span at 0x10d698e18>]
['blod']
['link5.html']
fourth item
span
现在我们用XPath来做一个简单的爬虫,我们尝试爬取某个贴吧里的所有帖子,并且将该这个帖子里每个楼层发布的图片下载到本地。
#coding=utf-8 import requests from lxml import etree import json class Tieba: def __init__(self,tieba_name): self.tieba_name = tieba_name #接收贴吧名 #设置为手机端的UA self.headers = {"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"} def get_total_url_list(self): '''获取所有的url list''' url = "https://tieba.baidu.com/f?kw="+self.tieba_name+"&ie=utf-8&pn={}&" url_list = [] for i in range(100): #通过循环拼接100个url url_list.append(url.format(i*50)) return url_list #返回100个url的url list def parse_url(self,url): '''一个发送请求,获取响应,同时etree处理html''' print("parsing url:",url) response = requests.get(url,headers=self.headers,timeout=10) #发送请求 html = response.content.decode() #获取html字符串 html = etree.HTML(html) #获取element 类型的html return html def get_title_href(self,url): '''获取一个页面的title和href''' html = self.parse_url(url) #返回elemet类型的html,具有xpath方法 li_temp_list = html.xpath("//li[@class='tl_shadow']") #分组,按照li标签分组 total_items = [] for i in li_temp_list: #遍历分组 # href = i.xpath("./a/@href")[0] if len(i.xpath("./a/@href"))>0 else None # if href is not None and not href.startswith("https:"): # href = "https:"+href href = "https:"+i.xpath("./a/@href")[0] if len(i.xpath("./a/@href"))>0 else None text = i.xpath("./a/div[1]/span[1]/text()") text = text[0] if len(text)>0 else None item = dict( #放入字典 href = href, text = text ) total_items.append(item) return total_items #返回一个页面所有的item def get_img(self,url): '''获取一个帖子里面的所有图片''' html = self.parse_url(url) #返回elemet类型的html,具有xpath方法 img_list = html.xpath('//div[@data-class="BDE_Image"]/@data-url') img_list = [i.split("src=")[-1] for i in img_list] #正则表达式提取图片的url img_list = [requests.utils.unquote(i) for i in img_list] #URL解码 return img_list def save_item(self,item): '''保存一个item''' with open("teibatupian.txt","a") as f: f.write(json.dumps(item,ensure_ascii=False,indent=2)) f.write("\n") def run(self): #1、找到了url规律,url list url_list = self.get_total_url_list() for url in url_list: #2、遍历urllist 发送请求,获得响应,etree处理html # 3、提取title,href total_item = self.get_title_href(url) for item in total_item: href = item["href"] img_list = self.get_img(href) #获取到了帖子的图片列表 item["img"] = img_list # 4、保存到本地 print(item) self.save_item(item) if __name__ == "__main__": tieba = Tieba("CSDN") tieba.run()
基本思路:在确定爬取对象后,开始运行run
方法,get_total_url_list
方法定义了每页链接的递归方法,首先结合parse_url
方法爬得全部数据,并通过etree将全部数去导入至lxml类库中,再通过get_title_href
方法和get_img
方法采用XPath形式提取有用数据,最后通过save_item
方法实现数据存储。
爬虫一共四个主要步骤:
步骤编号 | 爬虫步骤 | 对应操作 |
---|---|---|
1 | 明确目标 | Tieba(self,tieba_name) |
2 | 爬 | get_total_url_list;parse_url |
3 | 取 | get_title_href;get_img |
4 | 处理数据 | save_item |
除了 lxml 之外,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是解析和提取 HTML/XML 数据。lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml,故在此不再多述。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。使用 pip 安装即可:
pip install beautifulsoup4
抓取工具 | 速度 | 使用难度 | 安装难度 |
---|---|---|---|
正则 | 最快 | 困难 | 无(内置) |
BeautifulSoup | 慢 | 最简单 | 简单 |
lxml | 快 | 简单 | 一般 |
后期内容提要:
如果您有任何疑问或者好的建议,期待你的留言与评论!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。