Python网络爬虫案例实战:解析网页:正则表达式解析网页
正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表达式的语言中,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的部分。
图5-1展示了使用正则表达式进行匹配的流程。
正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,那么这个过程会稍微有一些不同。
在提取网页中的数据时,可以先把源代码变成字符串,然后用正则表达式匹配想要的数据。使用正则表达式可以迅速地用极简单的方式实现字符串的复杂控制。
表5-1是常见的正则字符和含义。
下面介绍Python正则表达式的3种方法,分别是match、search 和findall。
5.2.1字符串匹配
本节利用 Python 中的re.match实现字符串匹配并找到匹配的位置。而re.match的意思是从字符串起始位置匹配一个模式,如果从起始位置匹配不了,match()就返回none。re.match的语法格式为:
re.match(string[,pos[,endpos]])| re.match(pattern,string[,flags])
match只找到一次可匹配的结果即返回。
这个方法将从string的 pos下标处开始尝试匹配 pattern;如果 pattern结束时仍可匹配,则返回一个match对象;如果匹配过程中 pattern无法匹配,或者匹配未结束就已到达endpos,则返回none。pos 和 endpos的默认值分别为0和len(string);re.match()无法指定这两个参数,参数 flags用于编译pattern时指定匹配模式。
注意:这个方法并不是完全匹配。当pattern结束时,若 string还有剩余字符,则仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符’$'。
[例5-1)使用两个字符串匹配并找到匹配的位置。
# encoding: UTF-8
import re
m=re.match('www','www.taobao.com')
print('匹配的结果:',m)
print('匹配的起始与终点:',m.span())
print('匹配的起始位置:',m.start())
print('匹配的终点位置:',m.end())
运行程序,输出如下:
匹配的结果: <re.Match object; span=(0, 3), match='www'>
匹配的起始与终点: (0, 3)
匹配的起始位置: 0
匹配的终点位置: 3
上面例子中的pattern只是一个字符串,也可以把 pattern改成正则表达式,从而匹配具有一定模式的字符串,例如:
# encoding: UTF-8
import re
line = 'Fat apples are smarter than bananas, is it right?'
# 使用 re.match 进行匹配
m = re.match(r'(\w+) (\w+) (?P<sign>.*)', line)
# 检查是否匹配成功
print('匹配的整句话:', m.group(0))
print('匹配的第一个结果:', m.group(1))
print('匹配的第二个结果:', m.group(2))
print('匹配的结果列表:', m.groups()) # 使用 groups() 获取所有捕获组
运行程序,输出如下:
![](/qrcode.jpg)
匹配的整句话: Fat apples are smarter than bananas, is it right?
匹配的第一个结果: Fat
匹配的第二个结果: apples
匹配的结果列表: ('Fat', 'apples', 'are smarter than bananas, is it right?')
为什么要在match的模式前加上r呢?
r’(\w+)(\w+)(?P.*)‘前面的r的意思是raw string,代表纯粹的字符串,使用它就不会对引号中的反斜杠八’进行特殊处理。因为在正则表达式中有一些类似’d’(匹配任何数字)的模式,所以模式中的单个反斜杠’'符号都要进行转义。
假如需要匹配文本中的字符"",使用编程语言表示的正则表达式里就需要4个反斜杠
“\\”,前两个反斜杠“\”和后两个反斜杠“\”各自在编程语言中转义成一个反斜杠“\”,
所以4个反斜杠“\\”就转义成了两个反斜杠“\”,这两个反斜杠“\”最终在正则表达式中转义成一个反斜杠“\”。
5.2.2起始位置匹配字符串
re.match只能从字符串的起始位置进行匹配,而re.search 扫描整个字符串并返回。re.search()方法扫描整个字符串,并返回第一个成功的匹配,如果匹配失败,则返回 None。
与re.match()方法不同,re.match()方法要求必须从字符串的开头进行匹配,如果字符串的开头不匹配,那么整个匹配就失败了;re.search()并不要求必须从字符串的开头进行匹配,也就是说,正则表达式可以是字符串的一部分。
re.search()的语法格式为:
re.search(pattern,string,flags = 0)
其中,pattern:正则中的模式字符串。string:要被查找替换的原始字符串。flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写、多行匹配等。
[例5-2]从起始位置匹配字符串演示实例。
import re
content = 'Hello 123456789 Word_This is just a test 666 Test'
result = re.search('(\d+).*?(\d+).*', content)
print(result)
print(result.group()) # print(result.group(0)) 同样效果字符串
print(result.groups())
print(result.group(1))
print(result.group(2))
运行程序,输出如下:
<re.Match object; span=(6, 49), match='123456789 Word_This is just a test 666 Test'>
123456789 Word_This is just a test 666 Test
('123456789', '666')
123456789
666
适当调用以下代码,可实现数字匹配。例如:
import re
content = 'Hello 123456789 Word_This is just a test 666 Test'
result = re.search('(\d+)', content)
print(result)
print(result.group()) # print(result.group(0)) 同样效果字符串
print(result.groups())
print(result.group(1))
运行程序,输出如下:
<re.Match object; span=(6, 15), match='123456789'>
123456789
('123456789',)
123456789
5.2.3所有子串匹配
re.findall()在字符串中找到正则表达式所匹配的所有子串,并返回一个列表;如果没有找到匹配的,则返回空列表。返回结果是列表类型,需要遍历一下才能依次获取每组内容。re.findall()的语法格式为:
findall(patern,string,flags = 0)
其中,pattern:正则中的模式字符串。string:要被查找替换的原始字符串。flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写、多行匹配等。
[例5-3]匹配所有子串演示。
import re
content = 'Hello 123456789 Word_This is just a test 666 Test'
results = re.findall('\d+', content)
print(results)
for result in results:
print(result)
运行程序,输出如下:
['123456789', '666']
123456789
666
findall与 match、search不同的是,findall能够找到所有匹配的结果,并且以列表的形式返回。
5.2.4Requests爬取猫眼电影排行
本节利用Requests库和正则表达式来爬取猫眼电影TOP100的相关内容。Requests比 urllib 使用更加方便,在此选用正则表达式来作为解析工具。
[例 5-4]利用 Requests 和正则表达式爬取猫眼电影排行信息。
#-*- coding: utf-8 -*-
import re
import os
import json
import requests
from multiprocessing import Pool
from requests.exceptions import RequestException
def get_one_page(url):
'''
获取网页html内容并返回
'''
try:
# 获取网页html内容
response = requests.get(url)
# 通过状态码判断是否获取成功
if response.status_code == 200:
return response.text
return None
except RequestException:
return None
def parse_one_page(html):
'''
解析HTML代码,提取有用信息并返回
'''
# 正则表达式进行解析
pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name">'
+ '<a.*?>(.*?)</a>.*?"star">(.*?)</p>.*?releasetime">(.*?)</p>'
+ '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)
# 匹配所有符合条件的内容
items = re.findall(pattern, html)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2],
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5] + item[6]
}
def write_to_file(content):
'''
将文本信息写入文件
'''
with open('result.txt', 'a', encoding='utf-8') as f:
f.write(json.dumps(content, ensure_ascii=False) + '\n')
f.close()
def save_image_file(url, path):
'''
保存电影封面
'''
ir = requests.get(url)
if ir.status_code == 200:
with open(path, 'wb') as f:
f.write(ir.content)
f.close()
def main(offset):
url = 'http://maoyan.com/board/4?offset=' + str(offset)
html = get_one_page(url)
# 封面文件夹不存在则创建
if not os.path.exists('covers'):
os.mkdir('covers')
for item in parse_one_page(html):
print(item)
write_to_file(item)
save_image_file(item['image'], 'covers/' + '%03d'%int(item['index']) + item['title'] + '.jpg')
if __name__ == '__main__':
# 使用多进程提高效率
pool = Pool()
pool.map(main, [i*10 for i in range(10)])