CH2 网络爬虫提取

网络爬虫提取

[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库通过对这个树进行不同的操作。

​ 一个标签,例如

..

``..等是以一个标签:Tag的形式出现,p a是tag的名称Name,而后面跟的键值对是它的属性Attributes,一个HTML文档 = 一颗标签树 = 一个BeautifulSoup类实例 ,它对应的是HTML/XML文档的全部内容

bs库解析器

soup = BeautifulSoup('data','html.parser')

bs支持多种不同的解析器分别为

解析器 使用方法 条件
bs4的HTML解析器 BeautifulSoup('data','html.parser') 安装bs4库
lxml的HTML解析器 BeautifulSoup('data','lxml') pip install lxml
lxml的XML解析器 BeautifulSoup('data','xml') pip install lxml
html5lib的解析器 BeautifulSoup('data','html5lib') pip install html5lib

BeautifulSoup类的基本元素

基本元素 说明
Tag 标签,最基本信息组织单元<>
Name 标签名字,比如p,a,格式.name
Attributes 标签属性,字典形式格式.attrs
NavigableString 标签内非属性的字符串<>和中间的字符串,格式:.string
Comment 标签内字符串的注释部分,特殊Comment类型

下面来说明一下这些都是怎么用的

  • Tag

    ​ 因为不同的标签都有不同的名字所以任何存在与HTML语法中的标签都可以用soup.访问获得,如果存在多个对应内容时,返回第一个

    ​ 比如,soup为经过处理的bs类

    soup = BeautifulSoup(demo, 'html.parser')
    

    如果需要访问title标签,输入

    soup.title
    

    如果需要访问a标签

    soup.a
    tag = soup.a
    

    这样就取得了一个Tag,后续可以对这个Tag做其他操作

  • Name

    每个Tag都有自己的名字,通过.name获取,字符串类型

    >>> soup.a.name
    'a'
    >>> soup.a.parent.name    #双亲的名字
    'p'
    >>> soup.a.parent.parent.name
    'body'
    
  • Attributes

    ​ 一个可以有多个属性或者0个,字典类型,使用.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外面有两个标签,取最外面的标签.string取到的是两个标签夹着的中间字符串。

  • Comment

    Comment是一种特殊类型的String,代表注释

    >>> type(soup.b.string)    #b中间是Comment,
    
    

Beautiful Soup库的3种遍历方式

​ 对于标签树,一共有3种遍历方式,分别是:上行遍历,下行遍历,平行遍历

下行遍历

下行遍历有三种属性

属性 说明
.contents 子结点的列表,将所有儿子节点存入列表
.children 子结点的迭代类型,与.contents类似,用于循环遍历儿子结点
.descendants 子孙结点的迭代类型,包含所有子孙结点,用于循环遍历

BeautifulSoup类是根节点,标签单独作为一例,中间包含title标签,中间包含

标签等。

注:在.contents属性中,'\n'也是作为单独的一个儿子节点被存入

下行遍历的结构
  1. 遍历儿子节点

    for child in soup.body.children:
        print(child)
    
  1. 遍历子孙节点

    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文本顺序的前驱平行结点

注:平行遍历是发生在同一父结点下的各结点

平行遍历的结构
  1. 遍历后续结点

    for sibling in soup.a.next_siblings:
        print(sibling)
    
  1. 遍历前驱结点

    for sibling in soup.a.previous_siblings:
        print(sibling)
    

bs4的prettify方法

​ bs4库的prettify()方法为HTML文本<>及其内容增加换行符

prettify方法可用于标签或者BeautifulSoup类型,格式为

.prettify()    

2. 信息组织与提取方法

信息标记的三种形式

XML JSON YAML

XML

XML全称为eXtensible Markup Language,是一种标记语言

...

带名称和属性的标签,中间还有非标签元素



空元素书写形式



注释

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:


    Ming
    Xiao
    
西大直街 哈尔滨
CSSE

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()的等价写法

(...) <--> .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次,最小匹配

你可能感兴趣的:(CH2 网络爬虫提取)