爬虫系列 | 第四讲 数据提取之正则表达式

1. 什么是数据提取?

  • 在前几讲中,我们发起HTTP请求拿到响应的数据是全部的网页内容,这些数据很庞大并且很混乱,其中大部分的数据并不是我们所关心的。因此我们需要根据我们的需要提取出想要的数据来,即进行数据提取,基本的手段就是过滤/匹配。
  • 对于文本数据的处理,进行数据的过滤或者规则的匹配,最强大的就是正则表达式,python 标准库中提供了 re 模块。

2. 什么是正则表达式?

  • 正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
  • 给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

(1)给定的字符串是否符合正则表达式的过滤逻辑(“匹配”);
(2)通过正则表达式,从文本字符串中获取我们想要的特定部分(“过滤”)。

  • 正则表达式匹配的基本原理
    在这里插入图片描述
  • 正则表达式匹配规则
    在这里插入图片描述

3. Python 标准库中的 re 模块

  • re 模块的一般使用步骤如下:

(1)使用 compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象;
(2)通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果,一个 Match 对象;
(3)最后使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作。

3.1 compile 函数

  • compile 函数用于将一个字符串形式的正则表达式编译生成一个 Pattern 对象,使用例子如下:
import re
pattern = re.compile(r'\d+') # 将正则表达式编译成 Pattern 对象
  • 在编译生成一个正则表达式的 Pattern 对象之后,我们就可以利用 Pattern 对象的一系列方法对文本进行匹配查找,以提取我们想要的数据。
  • Pattern 对象的一些常用的用于匹配查找的方法主要有如下几个:

match 方法:从起始位置开始查找,一次匹配
search 方法:从任何位置开始查找,一次匹配
findall 方法:全部匹配,返回列表
finditer 方法:全部匹配,返回迭代器
split 方法:分割字符串,返回列表
sub 方法:替换

3.2 match 方法

  • match 方法用于从待匹配字符串的头部开始进行查找(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。具体说明如下:
  • 方法原型: match(string[, pos[, endpos]])
  • 参数解释: 其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。因此,当你不指定 pos 和 endpos 时,match 方法默认从待匹配字符串的头部开始进行查找。
  • 返回值: 当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
import re
pattern = re.compile(r'\d+')
match_res1 = pattern.match('hello123welcome567')  # 从头部开始匹配,没有匹配
match_res2 = pattern.match('hello123welcome567',5, 10)  # 从下标5的位置开始匹配,刚好匹配上
print(match_res2, type(match_res2))

print(match_res2.group(0))   # 可省略 0
print(match_res2.start(0))
print(match_res2.end(0))
print(match_res2.span(0))
  • Match对象提供了一些常用的方法,用于获取匹配的位置,具体如下表
方法名描述
group([group1, …])用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0);
start([group])用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;
end([group])用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;
span([group])返回 (start(group), end(group))。
  • re.S 跨行匹配
  • re.I 大小写不敏感

3.3 search 方法

  • search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。
  • 方法原型: search(string[, pos[, endpos]])
  • **参数解释:**string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
  • **返回值:**当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
import re
pattern = re.compile(r'\d+')
match_res1 = pattern.search('hello123welcome567')  # 这里如果使用 match 方法则不匹配
print(match_res1.group())
match_res2  = pattern.search('hello123welcome567', 10, 30)  # 指定字符串区间
print(match_res2.group())

3.4 findall 方法

  • 上面的 match 和 search 方法都是一次匹配,只要找到了一个匹配的结果就返回。然而,在大多数时候,我们需要搜索整个字符串,获得所有匹配的结果。
  • 方法原型: findall(string[, pos[, endpos]])
  • **参数解释:**string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
  • 返回值: 以列表形式返回全部匹配到的子串,如果没有匹配,则返回一个空列表。
import re
pattern = re.compile(r'\d+')   # 查找数字
match_res1 = pattern.findall('hello123welcome456hi789')
match_res2 = pattern.findall('one1two2three3four4', 0, 10)
print(match_res1)
print(match_res2)
#findall 以列表形式,返回全部能匹配的子串给match_res1
for item in match_res1 :
    print item

3.5 finditer 方法

  • finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。
import re
pattern = re.compile(r'\d+')

result_iter1 = pattern.finditer('hello 123456 789')
result_iter2 = pattern.finditer('one1two2three3four4', 0, 10)

print(type(result_iter1))
print(type(result_iter2))


for m1 in result_iter1:   # m1 是 Match 对象
    print('matching string: {}, position: {}'.format(m1.group(), m1.span()))

for m2 in result_iter2:
    print('matching string: {}, position: {}'.format(m2.group(), m2.span()))

3.6 split 方法

  • split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
  • 方法原型: split(string[, maxsplit])
  • 参数解释: maxsplit 用于指定最大分割次数,不指定将全部分割。
  • 返回值: 分割后的列表
import re
p = re.compile(r'[\s\,\;]+')
print(p.split('a,b;; c   d'))

3.7 sub 方法

  • sub 方法用于替换。它的使用形式如下:
  • sub(repl, string[, count])
  • 参数解释:

repl 可以是字符串也可以是一个函数,(1)如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还可以使用 id 的形式来引用分组,但不能使用编号 0;(2)如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。

count 用于指定最多替换次数,不指定时全部替换。

import re
p = re.compile(r'(\w+) (\w+)') # \w = [A-Za-z0-9]
s = 'hello 123, hello 456'

print(p.sub(r'hello world', s))  # 使用 'hello world' 替换 'hello 123' 和 'hello 456'

def func(m):
    return 'hi' + ' ' + m.group(2)

print(p.sub(func, s))
print(p.sub(func, s, 1))         # 最多替换一次

3.8 贪婪模式与非贪婪模式

  • 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );
  • 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );
  • Python 里数量词默认是贪婪的。

4. 案例一:爬取猫眼电影

  • 如下例子,我们爬取猫眼电影网站榜单TOP100榜,其https://maoyan.com/board/4?offset=10
  • offset 表示用于控制分页的一个参数,表示从哪条数据记录开始显示
    在这里插入图片描述
import requests
import re

def get_maoyan_single_page(offset):
    headers = {
        'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0"
    }
    url = 'https://maoyan.com/board/4?offset={}'.format(offset)
    resp = requests.get(url, headers=headers)
    data_list = re.findall(
        '<p class="name">.*?>(.*?)</a>.*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>.*?<i class="integer">(.*?)</i><i class="fraction">(.*?)</i>',
        resp.text, re.S)
    return data_list


def get_maoyan_top():
    for offset in range(0,100,10):
        for idx, record in enumerate(get_maoyan_single_page(offset)):
            print(idx + offset, record[0], record[1].strip(), record[2], record[3] + record[4], sep='\t')

if __name__ == '__main__':
    get_maoyan_top()
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页