网络爬虫提取
[TOC]
1. Beautiful Soup库入门
bs库的安装
win平台下,cmd输入
pip install beautifulsoup4
beautifulsoup的基本结构
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'html.parser')
BeautifulSoup库的基本元素
对于bs库的理解
BeautifulSoup库是解析、遍历、维护”标签树“的功能库,什么是标签树呢,HTML语言是标记语言,通过不同的标签将内容分割为不同的结构,因此HTML的文本可以通过标签生成一颗标签树,bs库通过对这个树进行不同的操作。
一个标签,例如<p>..</p>``<a>..</a>
等是以一个标签:Tag的形式出现,p
a
是tag的名称Name,而后面跟的键值对是它的属性Attributes,一个HTML文档 = 一颗标签树 = 一个BeautifulSoup类实例 ,它对应的是HTML/XML文档的全部内容
bs库解析器
soup = BeautifulSoup('<html>data</html>','html.parser')
bs支持多种不同的解析器分别为
解析器 | 使用方法 | 条件 |
---|---|---|
bs4的HTML解析器 | BeautifulSoup('<html>data</html>','html.parser') | 安装bs4库 |
lxml的HTML解析器 | BeautifulSoup('<html>data</html>','lxml') | pip install lxml |
lxml的XML解析器 | BeautifulSoup('<html>data</html>','xml') | pip install lxml |
html5lib的解析器 | BeautifulSoup('<html>data</html>','html5lib') | pip install html5lib |
BeautifulSoup类的基本元素
基本元素 | 说明 |
---|---|
Tag | 标签,最基本信息组织单元<></> |
Name | 标签名字,比如p,a,格式<tag>.name |
Attributes | 标签属性,字典形式格式<tag>.attrs |
NavigableString | 标签内非属性的字符串<>和</>中间的字符串,格式:<tag>.string |
Comment | 标签内字符串的注释部分,特殊Comment类型 |
下面来说明一下这些都是怎么用的
-
Tag
因为不同的标签都有不同的名字所以任何存在与HTML语法中的标签都可以用soup.<tag>访问获得,如果存在多个<tag>对应内容时,返回第一个
比如,soup为经过处理的bs类
soup = BeautifulSoup(demo, 'html.parser')
如果需要访问title标签,输入
soup.title
如果需要访问a标签
soup.a tag = soup.a
这样就取得了一个Tag,后续可以对这个Tag做其他操作
-
Name
每个Tag都有自己的名字,通过<tag>.name获取,字符串类型
>>> soup.a.name 'a' >>> soup.a.parent.name #双亲的名字 'p' >>> soup.a.parent.parent.name 'body'
-
Attributes
一个<tag>可以有多个属性或者0个,字典类型,使用
<tag>.attrs
可以以字典形式显示所有属性,由于是字典类型,可以通过Dict['name']
取到对应的值>>> tag = soup.a >>> tag.attrs {'id':'link1','class':['py1'],'href':'xxx'} >>> tag.attrs['class'] ['py1'] >>> tag.attrs['href'] 'xxx' #注意是字符串形式
-
NavigableString
代表标签内非属性字符串,注意,NavigableString可以跨越多个层次,即,如果string外面有两个标签,取最外面的标签<tag>.string取到的是两个标签夹着的中间字符串。
-
Comment
Comment是一种特殊类型的String,代表注释
>>> type(soup.b.string) #b中间是Comment,<!--...--> <class 'bs4.element.Comment'>
Beautiful Soup库的3种遍历方式
对于标签树,一共有3种遍历方式,分别是:上行遍历,下行遍历,平行遍历
下行遍历
下行遍历有三种属性
属性 | 说明 |
---|---|
.contents | 子结点的列表,将<tag>所有儿子节点存入列表 |
.children | 子结点的迭代类型,与.contents类似,用于循环遍历儿子结点 |
.descendants | 子孙结点的迭代类型,包含所有子孙结点,用于循环遍历 |
BeautifulSoup类是根节点,<head></head>标签单独作为一例,中间包含title标签,<body></body>中间包含<p><a>标签等。
注:在.contents属性中,'\n'也是作为单独的一个儿子节点被存入
下行遍历的结构
-
遍历儿子节点
for child in soup.body.children: print(child)
-
遍历子孙节点
for child in soup.body.descendants: print(child)
上行遍历
属性 | 说明 |
---|---|
.parent | 结点的父亲标签 |
.parents | 结点先辈标签的迭代类型,用于循环遍历先辈结点 |
注:soup类本身的父结点为空,所以如果进行迭代,要进行判断
上行遍历的结构
for parent in soup.a.parents:
if parent is None: #判断是否为最顶结点
print(parent)
else:
print(parent.name)
平行遍历
属性 | 说明 |
---|---|
.next_sibling | 返回按照HTML文本顺序的下一个平行结点标签 |
.previous_sibling | 返回按照HTML文本顺序的上一个平行结点标签 |
.next_siblings | 迭代类型(迭代器),返回按照HTML文本顺序的后续平行结点 |
.previous_siblings | 迭代类型(迭代器),返回按照HTML文本顺序的前驱平行结点 |
注:平行遍历是发生在同一父结点下的各结点
平行遍历的结构
-
遍历后续结点
for sibling in soup.a.next_siblings: print(sibling)
-
遍历前驱结点
for sibling in soup.a.previous_siblings: print(sibling)
bs4的prettify方法
bs4库的prettify()方法为HTML文本<>及其内容增加换行符
prettify方法可用于标签或者BeautifulSoup类型,格式为
<tag>.prettify()
2. 信息组织与提取方法
信息标记的三种形式
XML JSON YAML
XML
XML全称为eXtensible Markup Language,是一种标记语言
<img src="china.jpg" size="10">...</img>
带名称和属性的标签,中间还有非标签元素
<img src="china.jpg" size="10" />
空元素书写形式
<!-- This is a comment-->
注释
JSON
JavaScript Object Notation 有类型的键值对 key:value
比如:
"name":"哈尔滨工业大学"
而一个关键字对应多个值时
"name":["哈尔滨工业大学","西大直街"]
如果出现键值对的嵌套
"name":{
"newName":"圣马家沟职业学院",
"oldName":"哈尔滨工业大学"
}
YAML
YAML Ain`t Markup Language 无类型键值对, key:value仅用字符串就可以表达关系
name:哈尔滨工业大学
利用缩进表达所属关系
name:
newName:圣马家沟职业学院
oldName:哈尔滨工业大学
-表达并列关系
name:
-哈尔滨工业大学
-西大直街
| 表达整块数据,如果数据很多的话,# 表示注释
text: | #introduction
balabalabala...
举个例子区分:
描述一个学生小明
XML:
<person>
<firstname>Ming</firstname>
<lastname>Xiao</lastname>
<address>
<street>西大直街</street>
<city>哈尔滨</city>
</address>
<prof>CS</prof><prof>SE</prof>
</person>
JSON
"person":{
"firstName":"Ming",
"lastName":"Xiao",
"address":{
"street":"西大直街"
"city":"哈尔滨"
},
"prof":["CS","SE"]
}
YAML
person:
firstname:Ming
lastName:Xiao
address:
street:西大直街
city:哈尔滨
prof:
-CS
-SE
三种信息标记比较:
XML 最早的标记语言,拓展性好,繁琐
JSON 信息有类型,适合程序处理,较XML简洁(什么叫有类型,JSON中每个关键字有""标记,”“叫做类型,”xxx“叫做键key
YAML 信息无类型,单纯为字符串,可读性好
信息提取的一般方法
方法一:完整解析信息的标记形式,在提取关键信息
需要标记解析器,例如bs4库的标签树,信息提取准确但是提取过程很繁琐,速度慢
方法二:无视标记形式,直接搜索关键信息
需要文本查找函数,提取过程简洁速度快,但是提取的结果准确性与信息内容有关
基于bs4库的HTML查找方法
<>.find_all方法
<>.find_all方法的实现为
<>.find_all(name, attrs, recursive, string, **kw)
函数返回的是一个列表LIST类型,存储查找的结果,注意:此时列表中的是Tag类型实例,其包含name、string等元素
- name:对标签名称的检索字符串
对一个标签搜索
>>> soup.find_all('a')
对多个标签搜索
>>> soup.find_all(['a','b'])
也可以输入参数True
和正则表达式,True返回的结果是所有的标签
>>> for tag in soup.find_all(True):
print(tag.name)
>>> import re
>>> for tag in soup.find_all(re.complie('b')):
print(tag.name)
body
b
-
attrs:对标签属性值的检索字符串,可标注属性检索
参数输入可以是单个的键值对中的值,也可以是键值对,也可以是正则表达式
>>> soup.find_all('p','course') #键值对中的值 >>> soup.find_all(id='link1') #键值对形式,注意此时name可以省略,如果attrs参数不是键值对,要加上name,否则函数会将attrs识别为name >>> soup.find_all(id=re.complie('link'))
-
recursive:是否对子孙全部检索,默认为True
通过
recursive=False``recursive=True
设置 -
string:对标签中字符串区域检索,检索结果为字符串
支持正则表达式
>>> soup.find_all(string='Python')
<>.find_all()的等价写法
<tag>(...) <--> <tag>.find_all(...)
soup(...) <--> soup.find_all(...)
主要是因为这个方法使用频率很高
<>.find_all的扩展方法
方法 | 说明 |
---|---|
<>.find() | 搜索值返回一个结果 |
<>.find_parents() | 在先辈节点中搜索,返回列表类型 |
<>.find_parent() | 在先辈节点中返回一个结果 |
<>.find_next_siblings() | 在后续平行节点中搜索,返回列表类型 |
<>.find_next_sibling() | 在后续平行节点中返回一个结果 |
<>.find_previous_siblings() | 在前序平行节点中搜索,返回列表 |
<>.find_previous_sibling() | 在前序平行节点中返回一个结果 |
至于为什么没有对于子孙节点的搜索,是因为find_all() 和 find()函数就是这样的功能
3. 正则表达式库入门(re)
RE,regular expression, regex,正则表达式是用来简洁表达一组字符串的表达式,是一种通用的字符串表达框架。
在re库中,首先使用正则表达式规则写出字符串,然后通过re库的编译是字符串转换为正则表达式特征,再将特征与字符串匹配。
正则表达式regex(string) => p = re.compile(regex)(编译) => 特征p
正则表达式的语法
正则表达式常用操作符
操作符 | 说明 | 实例 |
---|---|---|
. | 表示任何单个字符 | |
[ ] | 字符集,对单个字符给出取值范围 | [abc]表示abc,[a-z]表示a到z单个字符 |
[^ ] | 非字符集,对单个字符给出排除范围 | [^abc]表示非a或b或c的单个字符 |
* | 前一个字符0次或无限次扩展 | abc*表示ab、abc、abcccc |
+ | 前一个字符1次或无限次扩展 | abc+表示abc、abcc、abccc |
? | 前一个字符0次或1次扩展 | abc?表示ab、abc |
| | 左右表达式任意一个 | abc|edf表示abc、def |
{m} | 扩展前一个字符m次 | ab{2}表示abb |
{m,n} | 扩展前一个字符m至n次(含n) | ab{1,2}表示abc、abbc |
^ | 匹配字符串的开头 | ^abc表示abc且在一个字符串的开头 |
$ | 匹配字符串的结尾 | abc$表示abc且在一个字符串的结尾 |
( ) | 分组标记,内部只能使用|操作符 | (abc)表示abc,(abc|def)表示abc、def 没有或 |
\d | 数字,等价0-9 | |
\w | 单词字符,等价[A-Za-z0-9_] |
正则表达式的经典实例
由26个字母组成的字符串 ^[A-Za-z]+$
由26个字母和数字组成的字符串 ^[A-Za-z0-9]+$
整数形式的字符串 ^-?\d+$
正整数形式的字符串 ^[0-9]*****[1-9][0-9]*****$
邮政编码 [1-9]\d{5}
匹配中文字符 [\u4e00-\u9fa5]
匹配IP地址的正则表达式
0-99 :[1-9]?\d
100-199 :1\d{2}
200-249 :2[0-4]\d
250-255 :25[0-5]
表示为:(0-99|100-199|200-249|250-255.){3}(0-99|100-199|200-249|250-255)
Re库介绍
正则表达式使用raw string类型(原生字符串类型),表示为r'text'。当然也可以使用string类型表达,但是\
需要表示为\\
Re库的主要功能函数
函数 | 说明 |
---|---|
re.search() | 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match() | 从一个字符串的开始位置匹配正则表达式,返回match对象 |
re.findall() | 搜索字符串,列表类型返回全部匹配子串 |
re.split() | 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer() | 搜索字符串,返回匹配的迭代类型,每个迭代元素都是match对象 |
re.sub() | 在一个字符串中替换所有匹配子串,返回替换后字符串 |
-
re.search(pattern, string, flags=0)
pattern:正则表达式的字符串或原生字符串表示
string:待匹配字符串
flags:正则表达式使用时控制标记
标记有一下几种
常用标记 说明 re.I re.IGNORECASE h忽略正则表达式的大小写,[A-Z]能够匹配小写字符 re.M re.MULTILINE 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始 re.S re.DOTALL 正则表达式中的.操作符能够匹配所有字符,默认的匹配是除换行外的所有字符 re.match(pattern, string, flags=0)
re.findall(pattern, string, flags=0)
-
re.split(pattern, string, maxsplit=0, flags=0)
maxsplit:最大分割数,剩余部分作为最后一个元素输出
re.finditer(pattern, string, flags=0)
-
re.sub(pattern, repl, string, count=0, flags=0)
repl:替换匹配字符串的字符串
count:匹配最大替换次数
Re库的另一个等价用法
除了函数式用法外,还有一种面向对象的用法,即先编译出正则表达式对象,在对这个对象进行操作
其中生成编译对象的用法为
regex = re.complie(pattern, flags=0)
regex为编译后生成的正则表达式对象,pattern为正则字符串或原生字符串表示。flags为正则表达式使用时的控制标记。通过调用regex对象(也可以是其他名字)的方法实现搜索,具体方法的名字与上面功能函数相同。
上面很多的函数都返回了match对象,接下来简单介绍match对象
Match对象介绍
Match对象是一次匹配的结果,包含很多匹配的信息
Match对象的属性
属性 | 说明 |
---|---|
.string | 待匹配的文本 |
.re | 匹配时使用的pttern对象(正则表达式) |
.pos | 正则表达式搜索文本的开始位置 |
.endpos | 正则表达式的结束位置 |
Match对象的方法
方法 | 说明 |
---|---|
.group(0) | 获得匹配后的字符串 |
.start() | 匹配字符串在原始字符串的开始位置 |
.end() | 匹配字符串在原始字符串的结束位置 |
.span() | 返回(开始位置,结束位置) |
以"BIT100081 TSU10084"为例
>>> import re
>>> m = re.search(r'[1-9]\d{5}', "BIT100081 TSU100084")
>>> m.string
'BIT100081 TSU100084'
>>> m.re
re.complie('[1-9]\\d{5}') #正则表达式对象
>>> m.endpos
19
>>> m.group(0)
'100081'
>>> m.start()
3
>>> m.end()
9
>>> m.span()
(3, 9)
Re库的贪婪匹配和最小匹配
Re库默认采用贪婪匹配, 即输出匹配最长的子串,如果要输出最短的字符串,则采用最小匹配,在匹配符后加上?
最小匹配操作符
操作符 | 说明 |
---|---|
*? | 前一个字符0次或无限次扩展,最小匹配 |
+? | 前一个字符1次或无限次扩展,最小匹配 |
?? | 前一个字符0次或1次扩展,最小匹配 |
{m,n}? | 扩展前一个字符m至n次,最小匹配 |