之前写了一个猫眼的爬虫的代码。是利用了request()和beautifulsoup()。但是之前的方法是利用了selector的搜索方法。最近学习了一点正则表达式,用正则表达式对豆瓣的top250进行了尝试。
豆瓣一直都是我比较喜欢的一个app,我也很喜欢看电影,你如果喜欢,在我的博客里有我的豆瓣链接,我们可以互粉一下聊聊电影hhhhhh,看得不多,但坚持再看并且写影评。
这次爬取下来top250的数据也非常高兴。
正则表达式的定义
正则表达式,又称规则表达式**。**(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
import re
python如果要使用正则表达式,首先需要引用re库
re库是一个python中有关于正则表达式的第三方库。因为我们在匹配过程中,也需要一些函数来剔除我们不需要的冗余字符。
正则表达式常用操作符(1)
操作符 | 说明 | 实例 |
---|---|---|
. | 表示任何单个字符 | 比如在非贪婪匹配中(.*?) |
[ ] | 字符集,对单个字符给出取值范围 | [abc]表示a/b/c,[a-z]表示a到z的单个字符 |
[^ ] | 非字符集,对单个字符给出排除范围 | [^abc]表示非a非b非c的单个字符 |
* | 前一个字符0次或无限次扩展 | abc*表示为ab/abc/abccc |
+ | 前一个字符1次或无限次扩展 | abc+表示abc/abcc/abccc |
? | 前一个字符0次或1次扩展 | abc?表示ab/abc |
| | 左右表达式的任意一个 | abc | def表示 abc、def |
正则表达式的常用操作符(2)
操作符 | 说明 | 实例 |
---|---|---|
{m} | 扩展前一个字符m次 | ab{2}c表示abbc |
{m,n} | 扩展前一个字符m至n次(含n) | ab{1,2}c表示abc、abbc |
^ | 匹配字符串开头 | ^abc表示abc且在一个字符串的开头 |
$ | 匹配字符串结尾 | abc$表示abc且在一个字符串的结尾 |
( ) | 分组标记,内部只能使用 | 操作符 | (abc)表示abc,(abc | def)表示abc、def |
\d | 数字,等价于[0-9] | |
\w | 单词字符,等价于[A-Za-z0-9_] |
常用标记 | 说明 |
---|---|
re.I re.IGNORECASE | 忽略正则表达式中的大小写,[A-Z]能够匹配小写字符 |
re.M re.MULTILINE | 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始 |
re.S re.DOTALL | 正则表达式中的.操作符能够匹配所有字符,默认匹配除了换行以外的所有字符 |
以上这些常用的操作符对于爬虫来说是足够的了,我们还需要的是怎么去使用去让其匹配。
正则表达式的练习我用的是anaconda中的jupyter进行练习。
几种常见的匹配方式
首先引用这个库,常规操作
import re
最常规匹配
content='Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result=re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}.*Demo$',content)
print(result)
print(result.group())#group()表示取全部匹配的字符串或者指定的组,返回结果是一个字符串
print(result.span())#span()表示返回一个元组包含匹配 (开始,结束) 的位置
输出结果
41
<re.Match object; span=(0, 41), match='Hello 123 4567 World_This is a Regex Demo'>
Hello 123 4567 World_This is a Regex Demo
(0, 41)
#这是最常规的匹配,将每一个字符都用操作符
#\s 是一个匹配空格 \d 匹配数字 \d{4} 匹配一串数字
#.*Demo$ 点用于匹配之前的 *作为前一个字符的匹配与'点'一起 最后用$作为一个匹配字符串的结尾
泛匹配
content = 'Hello 123 4567 World_This is a Regex Demo'
result = re.match('^Hello.*Demo$',content)
print(result)
print(result.group())
输出结果
<re.Match object; span=(0, 41), match='Hello 123 4567 World_This is a Regex Demo'>
Hello 123 4567 World_This is a Regex Demo
#泛匹配就比较省略,有前面和后面的字符就行,中间的不需要也可以
匹配目标
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld.*Demo$',content)
print(result)
print(result.group(1))#为了看看匹配的分组中是否是我所需要的
print(result.span())
输出结果
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567
(0, 40)
#(\d+) \d表示数字, + 表示一个或多个,括号就是将其作为一个分组
接着是两个比较关键的匹配方法
贪婪匹配
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$',content)
print(result)
print(result.group(1))
输出结果
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7
#Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符
#贪婪模式下字符串查找会直接走到字符串结尾去匹配,如果不相等就向前寻找,这一过程称为回溯。
#上面的例子可以看出 .* 这样的前后搭配匹配 可以前后尝试尽可能多的匹配字符
非贪婪匹配
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*Demo$',content)
print(result)
print(result.group(1))
输出结果
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567
#非贪婪则相反,总是尝试匹配尽可能少的字符。在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。
#非贪婪模式下会自左向右查找,一个一个匹配不会出现回溯的情况。
匹配模式
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo',content,re.S)
print(result.group(1))
匹配模式主要利用了re库中的re.S参数,这样就可以将字符串作为一个整体,在整体中进行匹配。
以上就是关于正则表达式的基本知识。看一下例子,知道大概就足够了。
豆瓣的正则表达式
首先进入豆瓣的top250排行榜,我们首先能看到的就是关于电影的内容。利用chrome浏览器右击检查打开审查元素,我们来观察一下这个网页的html大概构成。
我们将鼠标放到li标签上,可以看到左侧的一片区域都被标记了。说明这一块都是有关《肖申克的救赎》。
一步一步点开这个标签我们将鼠标放到每一个标签之上能看到,每一个标签都有一个与之对应的区域,这个时候我们就需要使用这些标签来构建正则表达式。
我们从每一个标签自上而下看下来。
依次是名次 片名 职员表 类型 星级 评价人数 热门短评这几个
比如名次去匹配
'<em class="">(\d+)</em>'
#因为名次是用数字表示的,先将一个标签内的数字用(\d+)来表示
'<em class="">(\d+)</em>.*?<span class=title">(.*?)</span>'
#当第一个标签完成之后,根据非贪婪匹配的规则 .*? 这样继续根据HTML标签的镶嵌继续向下搜寻。直到span标签结束。中间括号是我们需要匹配的内容
正则表达式的使用还是很简单的,只要找到我们需要的内容,利用非贪婪匹配进行前后选择就好了
下面的是一个已经匹配好的正则表达式。
'<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
代码获取内容
接下来的步骤就与我之前的一篇博文的内容很相似了
首先需要定义一个函数去获取页面
import requests
import re
import json
from requests.exceptions import RequestException
import time
#得到网页源代码
def get_one_page(url):
try:
res=resquest.get(url)
if res.status_code==200:
return res.text
return None
except RequestException:
return None
得到了网页源代码之后,需要利用正则表达式去匹配出我们需要的内容,并将它们放入到一个字典中。
def pares_one_html(html):
regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
pattern=re.compile(regex,re.S)#re.compile()编译正则表达式模式
items=re.findall(pattern,html)#返回的是一个list对象
for item in items:
content=""
for every_list in item[2].split():
#split()通过指定分隔符对字符串进行切片,因为职员表处有些标签需要我们进行处理
content=content+"".join(every_list)
content=re.sub(' ',' ',content)
content=re.sub('<br>',' ',content)
#删除空格和<br>这些多余的部分
#将获取到的list放到dict字典中
dict={
"index":item[0],
"name":item[1],
"describe":content,
"star":item[3],
"evaluate":item[4],
"title":item[5]
}
print(dict)
#此时先打印看一下,是否数据是正确的
#将数据写入文本中
with open('doubanMovie.txt','a',encoding='utf-8') as f:
f.write(json.dumps(dict,ensure_ascii=False)+'\n')
此时还有两个问题,我们此时只是能将第一个页面的数据爬取下来,这时候我们得分析一下这个url在切换页面时,它是怎么变化的。
https://movie.douban.com/top250这是豆瓣top250的第一页
转到第二页,第三页可以看到网页的变化
https://movie.douban.com/top250?start=25&filter= 第二页
https://movie.douban.com/top250?start=50&filter= 第三页
url中的参数start={} 这个数在不断的变化,而且规律是每次隔25个,说明这就是我们需要的。
if __name__ == '__main__':
urls=['https://movie.douban.com/top250?start={}&filter='.format(str(i)) for i in range(0,250,25)]
for url in urls:
html=get_one_page(url)
parse_one_html(html)
time.sleep(2)
最后将url放在一个列表中,利用for循环去将所有这些网页循环将数据爬取出来。
最后展示一下
python的学习中,爬虫只要有了基本的知识之后就可以慢慢尝试了,而且我觉得爬虫是最容易有自豪感的。
之前对猫眼的爬取用到了基本的Requests和beautifulsoup,接下来还会在写一篇关于Scrapy的基础知识,这个我认为是爬虫的总统山…但是我也还在摸索中
接着刷电影去了…(●’◡’●)